From 23bdc65da38df5469dc1ae02f06cf5d1b59b8b5d Mon Sep 17 00:00:00 2001 From: not-elm Date: Sun, 10 Aug 2025 21:28:45 +0900 Subject: [PATCH] INIT --- .claude/commands/create-test.md | 39 + .claude/settings.json | 7 + .gitignore | 3 + CLAUDE.md | 102 + Cargo.lock | 6276 +++++++++++++++++ Cargo.toml | 56 + Makefile | 6 + assets/brp.html | 35 + assets/host_emit.html | 23 + assets/images/rustacean-flat-gesture.png | Bin 0 -> 49537 bytes assets/js_emit.html | 28 + assets/shaders/custom_material.wgsl | 20 + build.rs | 59 + crates/bevy_cef_core/Cargo.toml | 22 + crates/bevy_cef_core/src/browser_process.rs | 18 + .../bevy_cef_core/src/browser_process/app.rs | 85 + .../browser_process_handler.rs | 63 + .../src/browser_process/browsers.rs | 492 ++ .../browsers/devtool_render_handler.rs | 57 + .../src/browser_process/browsers/keyboard.rs | 979 +++ .../src/browser_process/client_handler.rs | 129 + .../client_handler/brp_handler.rs | 68 + .../client_handler/js_emit_event_handler.rs | 39 + .../browser_process/context_menu_handler.rs | 50 + .../src/browser_process/display_handler.rs | 165 + .../src/browser_process/localhost.rs | 281 + .../localhost/data_responser.rs | 236 + .../localhost/headers_responser.rs | 236 + .../src/browser_process/renderer_handler.rs | 167 + .../request_context_handler.rs | 50 + crates/bevy_cef_core/src/debug.rs | 52 + crates/bevy_cef_core/src/lib.rs | 15 + crates/bevy_cef_core/src/render_process.rs | 28 + .../bevy_cef_core/src/render_process/app.rs | 63 + .../bevy_cef_core/src/render_process/brp.rs | 181 + .../bevy_cef_core/src/render_process/emit.rs | 82 + .../src/render_process/listen.rs | 77 + .../render_process/render_process_handler.rs | 191 + crates/bevy_cef_core/src/util.rs | 140 + crates/bevy_cef_core/src/util/v8_accessor.rs | 48 + .../src/util/v8_handler_wrapper.rs | 70 + .../bevy_cef_core/src/util/v8_interceptor.rs | 48 + .../bevy_cef_debug_render_process/Cargo.toml | 12 + .../bevy_cef_debug_render_process/README.md | 12 + .../bevy_cef_debug_render_process/src/main.rs | 19 + docs/book.toml | 5 + docs/src/README.md | 91 + docs/src/SUMMARY.md | 12 + docs/src/basic-concepts.md | 274 + docs/src/core-components.md | 407 ++ docs/src/installation.md | 137 + docs/src/quick-start.md | 220 + examples/.DS_Store | Bin 0 -> 6148 bytes examples/brp.rs | 88 + examples/custom_material.rs | 57 + examples/demo/Cargo.lock | 5937 ++++++++++++++++ examples/demo/Cargo.toml | 14 + examples/demo/src/main.rs | 126 + examples/devtool.rs | 75 + examples/host_emit.rs | 58 + examples/js_emit.rs | 55 + examples/navigation.rs | 69 + examples/simple.rs | 40 + examples/sprite.rs | 27 + examples/zoom_level.rs | 54 + src/common.rs | 9 + src/common/components.rs | 80 + src/common/ipc.rs | 17 + src/common/ipc/host_emit.rs | 37 + src/common/ipc/js_emit.rs | 46 + src/common/localhost.rs | 15 + src/common/localhost/asset_loader.rs | 156 + src/common/localhost/responser.rs | 59 + src/common/message_loop.rs | 150 + src/cursor_icon.rs | 38 + src/keyboard.rs | 73 + src/lib.rs | 47 + src/mute.rs | 23 + src/navigation.rs | 30 + src/system_param.rs | 2 + src/system_param/mesh_aabb.rs | 53 + src/system_param/pointer.rs | 84 + src/webview.rs | 148 + src/webview/mesh.rs | 104 + src/webview/mesh/webview_extend_material.rs | 58 + .../mesh/webview_extend_standard_material.rs | 54 + .../webview_extend_standard_material.wgsl | 32 + src/webview/mesh/webview_material.rs | 55 + src/webview/mesh/webview_util.wgsl | 13 + src/webview/webview_sprite.rs | 143 + src/zoom.rs | 21 + 91 files changed, 20122 insertions(+) create mode 100644 .claude/commands/create-test.md create mode 100644 .claude/settings.json create mode 100644 .gitignore create mode 100644 CLAUDE.md create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Makefile create mode 100644 assets/brp.html create mode 100644 assets/host_emit.html create mode 100644 assets/images/rustacean-flat-gesture.png create mode 100644 assets/js_emit.html create mode 100644 assets/shaders/custom_material.wgsl create mode 100644 build.rs create mode 100644 crates/bevy_cef_core/Cargo.toml create mode 100644 crates/bevy_cef_core/src/browser_process.rs create mode 100644 crates/bevy_cef_core/src/browser_process/app.rs create mode 100644 crates/bevy_cef_core/src/browser_process/browser_process_handler.rs create mode 100644 crates/bevy_cef_core/src/browser_process/browsers.rs create mode 100644 crates/bevy_cef_core/src/browser_process/browsers/devtool_render_handler.rs create mode 100644 crates/bevy_cef_core/src/browser_process/browsers/keyboard.rs create mode 100644 crates/bevy_cef_core/src/browser_process/client_handler.rs create mode 100644 crates/bevy_cef_core/src/browser_process/client_handler/brp_handler.rs create mode 100644 crates/bevy_cef_core/src/browser_process/client_handler/js_emit_event_handler.rs create mode 100644 crates/bevy_cef_core/src/browser_process/context_menu_handler.rs create mode 100644 crates/bevy_cef_core/src/browser_process/display_handler.rs create mode 100644 crates/bevy_cef_core/src/browser_process/localhost.rs create mode 100644 crates/bevy_cef_core/src/browser_process/localhost/data_responser.rs create mode 100644 crates/bevy_cef_core/src/browser_process/localhost/headers_responser.rs create mode 100644 crates/bevy_cef_core/src/browser_process/renderer_handler.rs create mode 100644 crates/bevy_cef_core/src/browser_process/request_context_handler.rs create mode 100644 crates/bevy_cef_core/src/debug.rs create mode 100644 crates/bevy_cef_core/src/lib.rs create mode 100644 crates/bevy_cef_core/src/render_process.rs create mode 100644 crates/bevy_cef_core/src/render_process/app.rs create mode 100644 crates/bevy_cef_core/src/render_process/brp.rs create mode 100644 crates/bevy_cef_core/src/render_process/emit.rs create mode 100644 crates/bevy_cef_core/src/render_process/listen.rs create mode 100644 crates/bevy_cef_core/src/render_process/render_process_handler.rs create mode 100644 crates/bevy_cef_core/src/util.rs create mode 100644 crates/bevy_cef_core/src/util/v8_accessor.rs create mode 100644 crates/bevy_cef_core/src/util/v8_handler_wrapper.rs create mode 100644 crates/bevy_cef_core/src/util/v8_interceptor.rs create mode 100644 crates/bevy_cef_debug_render_process/Cargo.toml create mode 100644 crates/bevy_cef_debug_render_process/README.md create mode 100644 crates/bevy_cef_debug_render_process/src/main.rs create mode 100644 docs/book.toml create mode 100644 docs/src/README.md create mode 100644 docs/src/SUMMARY.md create mode 100644 docs/src/basic-concepts.md create mode 100644 docs/src/core-components.md create mode 100644 docs/src/installation.md create mode 100644 docs/src/quick-start.md create mode 100644 examples/.DS_Store create mode 100644 examples/brp.rs create mode 100644 examples/custom_material.rs create mode 100644 examples/demo/Cargo.lock create mode 100644 examples/demo/Cargo.toml create mode 100644 examples/demo/src/main.rs create mode 100644 examples/devtool.rs create mode 100644 examples/host_emit.rs create mode 100644 examples/js_emit.rs create mode 100644 examples/navigation.rs create mode 100644 examples/simple.rs create mode 100644 examples/sprite.rs create mode 100644 examples/zoom_level.rs create mode 100644 src/common.rs create mode 100644 src/common/components.rs create mode 100644 src/common/ipc.rs create mode 100644 src/common/ipc/host_emit.rs create mode 100644 src/common/ipc/js_emit.rs create mode 100644 src/common/localhost.rs create mode 100644 src/common/localhost/asset_loader.rs create mode 100644 src/common/localhost/responser.rs create mode 100644 src/common/message_loop.rs create mode 100644 src/cursor_icon.rs create mode 100644 src/keyboard.rs create mode 100644 src/lib.rs create mode 100644 src/mute.rs create mode 100644 src/navigation.rs create mode 100644 src/system_param.rs create mode 100644 src/system_param/mesh_aabb.rs create mode 100644 src/system_param/pointer.rs create mode 100644 src/webview.rs create mode 100644 src/webview/mesh.rs create mode 100644 src/webview/mesh/webview_extend_material.rs create mode 100644 src/webview/mesh/webview_extend_standard_material.rs create mode 100644 src/webview/mesh/webview_extend_standard_material.wgsl create mode 100644 src/webview/mesh/webview_material.rs create mode 100644 src/webview/mesh/webview_util.wgsl create mode 100644 src/webview/webview_sprite.rs create mode 100644 src/zoom.rs diff --git a/.claude/commands/create-test.md b/.claude/commands/create-test.md new file mode 100644 index 0000000..88a2b31 --- /dev/null +++ b/.claude/commands/create-test.md @@ -0,0 +1,39 @@ +--- +allowed-tools: Bash(cargo test:*), +description: Create new tests +--- + +1. Reading $ARGUMENTS and understanding the implementation details. +2. Creating new tests for $ARGUMENTS in `tests` module in the same file. +3. Run `cargo test --workspace --all-features` to ensure all tests pass. +4. If any tests fail, fix the issues and re-run the tests. + +## Contracts + +- You have to write the rust-doc that describes the test case for each test function. + + +以下の手順で問題を修正してください。 + +## 現在発生している問題 + +以下のようなHTMLにCEFのカスタムリソースハンドラを使って`cef://localhost/brp.html`経由でアクセスしています。 +このHTMLからvideoリソースを読み込むと、`cef://localhost/test.mov`というリクエストURLでローカルResourceHandlerのopenメソッドが呼ばれ、response_headers, readメソッドでヘッダーとレスポンスボディが返されることが期待されます。 +初回と2回目までのreadメソッドは呼ばれるのですが、3回目以降のreadメソッドが呼ばれず、何度もopenメソッドが呼ばれてしまっているため、正常にレスポンスが返却できるように修正してください。 + +```htm + + + + + +``` + +## 手順 + +1. [CEFのResourceHandler](https://cef-builds.spotifycdn.com/docs/122.0/classCefResourceHandler.html)の仕様を深く読み込み理解する +2. `crates/bevy_cef_core/src/browser_process/localhost.rs`以下にResourceHandlerを使ってローカルリソースを読み込むコードが書いています。深く読み込んで現状の実装を理解してください。 +3. openメソッドが複数回呼ばれ、readメソッドが3回目以降呼ばれない原因を調査する +4. 原因を特定し、修正する \ No newline at end of file diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..c7919fb --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,7 @@ + +{ + "permissions": { + "allow": ["WebFetch", "WebSearch"], + "deny": ["Read(./.env)", "Read(./secrets/**)"] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5535a2e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target/ +.idea/ +book/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5216bc6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,102 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is `bevy_cef`, a Bevy plugin that integrates the Chromium Embedded Framework (CEF) into Bevy applications, allowing webviews to be rendered as 3D objects in the game world or as UI overlays. + +## Architecture + +### Multi-Process Design +- **Browser Process**: Main application process running Bevy (`bevy_cef_core::browser_process`) +- **Render Process**: Separate CEF render process (`bevy_cef_core::render_process`) +- Communication through IPC channels and Bevy Remote Protocol (BRP) + +### Core Components +- `CefWebviewUri`: Component specifying webview URL (remote or local via `cef://localhost/`) +- `WebviewSize`: Controls webview rendering dimensions (default 800x800) +- `WebviewExtendStandardMaterial`: Material for rendering webviews on 3D meshes +- `HostWindow`: Optional parent window specification +- `ZoomLevel`: Webview zoom control +- `AudioMuted`: Audio muting control + +### Plugin Architecture +The main `CefPlugin` orchestrates several sub-plugins: +- `LocalHostPlugin`: Serves local assets via custom scheme +- `MessageLoopPlugin`: CEF message loop integration +- `WebviewCoreComponentsPlugin`: Core component registration +- `WebviewPlugin`: Main webview management +- `IpcPlugin`: Inter-process communication +- `KeyboardPlugin`, `NavigationPlugin`, `ZoomPlugin`, `AudioMutePlugin`: Feature-specific functionality + +### IPC System +Three communication patterns: +1. **JS Emit**: Webview → Bevy app via `JsEmitEventPlugin` +2. **Host Emit**: Bevy app → Webview via event emission +3. **BRP (Bevy Remote Protocol)**: Bidirectional RPC calls + +## Development Commands + +### Code Quality +```bash +# Fix and format code +make fix +# Which runs: +# cargo clippy --fix --allow-dirty --allow-staged --workspace --all --all-features +# cargo fmt --all +``` + +### Development Setup +The build system automatically handles CEF dependencies on macOS with debug feature: +- Installs `bevy_cef_debug_render_process` tool +- Installs `export-cef-dir` tool +- Downloads/extracts CEF framework to `$HOME/.local/share/cef` + +### Manual Installation +```bash +# Install debug render process tool +make install +# Or: cargo install --path ./crates/bevy_cef_debug_render_process --force +``` + +## Key Examples + +- `examples/simple.rs`: Basic webview on 3D plane +- `examples/js_emit.rs`: JavaScript to Bevy communication +- `examples/host_emit.rs`: Bevy to JavaScript communication +- `examples/brp.rs`: Bidirectional RPC with devtools +- `examples/navigation.rs`: Page navigation controls +- `examples/zoom_level.rs`: Zoom functionality +- `examples/sprite.rs`: Webview as 2D sprite +- `examples/devtool.rs`: Chrome DevTools integration + +## Local Asset Loading + +Local HTML/assets are served via the custom `cef://localhost/` scheme: +- Place assets in `assets/` directory +- Reference as `CefWebviewUri::local("filename.html")` +- Or manually: `cef://localhost/filename.html` + +## Testing + +No automated tests are present in this codebase. Testing is done through the example applications. + +### Manually Testing + +- Run tests with `cargo test --workspace --all-features` + +## Platform Notes + +- Currently focused on macOS development (see Cargo.toml target-specific dependencies) +- CEF framework must be available at `$HOME/.local/share/cef` +- Uses `objc` crate for macOS-specific window handling +- DYLD environment variables required for CEF library loading + +## Workspace Structure + +This is a Cargo workspace with: +- Root crate: `bevy_cef` (public API) +- `crates/bevy_cef_core`: Core CEF integration logic +- `crates/bevy_cef_debug_render_process`: Debug render process executable +- `examples/demo`: Standalone demo application \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1cdf910 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,6276 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "accesskit" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "becf0eb5215b6ecb0a739c31c21bd83c4f326524c9b46b7e882d77559b60a529" +dependencies = [ + "enumn", + "serde", +] + +[[package]] +name = "accesskit_consumer" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0bf66a7bf0b7ea4fd7742d50b64782a88f99217cf246b3f93b4162528dde520" +dependencies = [ + "accesskit", + "hashbrown", + "immutable-chunkmap", +] + +[[package]] +name = "accesskit_macos" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e230718177753b4e4ad9e1d9f6cfc2f4921212d4c1c480b253f526babb258d" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "accesskit_windows" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65178f3df98a51e4238e584fcb255cb1a4f9111820848eeddd37663be40a625f" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown", + "paste", + "static_assertions", + "windows 0.58.0", + "windows-core 0.58.0", +] + +[[package]] +name = "accesskit_winit" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34d941bb8c414caba6e206de669c7dc0dbeb305640ea890772ee422a40e6b89f" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_windows", + "raw-window-handle", + "winit", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alsa" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" +dependencies = [ + "alsa-sys", + "bitflags 2.9.1", + "cfg-if", + "libc", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "android-activity" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +dependencies = [ + "android-properties", + "bitflags 2.9.1", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk 0.9.0", + "ndk-context", + "ndk-sys 0.6.0+11769913", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android_log-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" + +[[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 = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.59.0", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[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 = "assert_type_match" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f548ad2c4031f2902e3edc1f29c29e835829437de49562d8eb5dc5584d3a1043" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f7e37c0ed80b2a977691c47dae8625cfb21e205827106c64f7c588766b2e50" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.0.8", + "slab", + "windows-sys 0.60.2", +] + +[[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-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "atomicow" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52e8890bb9844440d0c412fa74b67fd2f14e85248b6e00708059b6da9e5f8bf" +dependencies = [ + "portable-atomic", + "portable-atomic-util", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[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 = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bevy" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8369c16b7c017437021341521f8b4a0d98e1c70113fb358c3258ae7d661d79" +dependencies = [ + "bevy_internal", +] + +[[package]] +name = "bevy_a11y" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3561712cf49074d89e9989bfc2e6c6add5d33288f689db9a0c333300d2d004" +dependencies = [ + "accesskit", + "bevy_app", + "bevy_derive", + "bevy_ecs", + "bevy_reflect", + "serde", +] + +[[package]] +name = "bevy_animation" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49796627726d0b9a722ad9a0127719e7c1868f474d6575ec0f411e8299c4d7bb" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_derive", + "bevy_ecs", + "bevy_log", + "bevy_math", + "bevy_mesh", + "bevy_platform", + "bevy_reflect", + "bevy_render", + "bevy_time", + "bevy_transform", + "bevy_utils", + "blake3", + "derive_more", + "downcast-rs", + "either", + "petgraph", + "ron", + "serde", + "smallvec", + "thiserror 2.0.12", + "thread_local", + "tracing", + "uuid", +] + +[[package]] +name = "bevy_app" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4491cc4c718ae76b4c6883df58b94cc88b32dcd894ea8d5b603c7c7da72ca967" +dependencies = [ + "bevy_derive", + "bevy_ecs", + "bevy_platform", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "cfg-if", + "console_error_panic_hook", + "ctrlc", + "downcast-rs", + "log", + "thiserror 2.0.12", + "variadics_please", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "bevy_asset" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f56111d9b88d8649f331a667d9d72163fb26bd09518ca16476d238653823db1e" +dependencies = [ + "async-broadcast", + "async-fs", + "async-lock", + "atomicow", + "bevy_app", + "bevy_asset_macros", + "bevy_ecs", + "bevy_platform", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "bevy_window", + "bitflags 2.9.1", + "blake3", + "crossbeam-channel", + "derive_more", + "disqualified", + "downcast-rs", + "either", + "futures-io", + "futures-lite", + "js-sys", + "notify-debouncer-full", + "parking_lot", + "ron", + "serde", + "stackfuture", + "thiserror 2.0.12", + "tracing", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "bevy_asset_macros" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4cca3e67c0ec760d8889d42293d987ce5da92eaf9c592bf5d503728a63b276d" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bevy_audio" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b4f6f2a5c6c0e7c6825e791d2a061c76c2d6784f114c8f24382163fabbfaaa" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_derive", + "bevy_ecs", + "bevy_math", + "bevy_reflect", + "bevy_transform", + "cpal", + "rodio", + "tracing", +] + +[[package]] +name = "bevy_cef" +version = "0.1.0" +dependencies = [ + "async-channel", + "bevy", + "bevy_cef", + "bevy_cef_core", + "bevy_remote", + "cef", + "objc", + "raw-window-handle", + "serde", + "serde_json", +] + +[[package]] +name = "bevy_cef_core" +version = "0.1.0" +dependencies = [ + "async-channel", + "bevy", + "bevy_remote", + "cef", + "cef-dll-sys", + "raw-window-handle", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "bevy_cef_debug_render_process" +version = "0.1.0" +dependencies = [ + "bevy_cef_core", + "cef", + "libloading", +] + +[[package]] +name = "bevy_color" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c101cbe1e26b8d701eb77263b14346e2e0cbbd2a6e254b9b1aead814e5ca8d3" +dependencies = [ + "bevy_math", + "bevy_reflect", + "bytemuck", + "derive_more", + "encase", + "serde", + "thiserror 2.0.12", + "wgpu-types", +] + +[[package]] +name = "bevy_core_pipeline" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed46363cad80dc00f08254c3015232bd6f640738403961c6d63e7ecfc61625" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_derive", + "bevy_diagnostic", + "bevy_ecs", + "bevy_image", + "bevy_math", + "bevy_platform", + "bevy_reflect", + "bevy_render", + "bevy_transform", + "bevy_utils", + "bevy_window", + "bitflags 2.9.1", + "bytemuck", + "nonmax", + "radsort", + "serde", + "smallvec", + "thiserror 2.0.12", + "tracing", +] + +[[package]] +name = "bevy_derive" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b837bf6c51806b10ebfa9edf1844ad80a3a0760d6c5fac4e90761df91a8901a" +dependencies = [ + "bevy_macro_utils", + "quote", + "syn", +] + +[[package]] +name = "bevy_diagnostic" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48797366f312a8f31e237d08ce3ee70162591282d2bfe7c5ad8be196fb263e55" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_platform", + "bevy_tasks", + "bevy_time", + "bevy_utils", + "const-fnv1a-hash", + "log", + "serde", + "sysinfo", +] + +[[package]] +name = "bevy_ecs" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2bf6521aae57a0ec3487c4bfb59e36c4a378e834b626a4bea6a885af2fdfe7" +dependencies = [ + "arrayvec", + "bevy_ecs_macros", + "bevy_platform", + "bevy_ptr", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "bitflags 2.9.1", + "bumpalo", + "concurrent-queue", + "derive_more", + "disqualified", + "fixedbitset", + "indexmap", + "log", + "nonmax", + "serde", + "smallvec", + "thiserror 2.0.12", + "variadics_please", +] + +[[package]] +name = "bevy_ecs_macros" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38748d6f3339175c582d751f410fb60a93baf2286c3deb7efebb0878dce7f413" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bevy_encase_derive" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8148f4edee470a2ea5cad010184c492a4c94c36d7a7158ea28e134ea87f274ab" +dependencies = [ + "bevy_macro_utils", + "encase_derive_impl", +] + +[[package]] +name = "bevy_gilrs" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97efef87c631949e67d06bb5d7dfd2a5f936b3b379afb6b1485b08edbb219b87" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_input", + "bevy_platform", + "bevy_time", + "bevy_utils", + "gilrs", + "thiserror 2.0.12", + "tracing", +] + +[[package]] +name = "bevy_gizmos" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7823154a9682128c261d8bddb3a4d7192a188490075c527af04520c2f0f8aad6" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_core_pipeline", + "bevy_ecs", + "bevy_gizmos_macros", + "bevy_image", + "bevy_math", + "bevy_pbr", + "bevy_reflect", + "bevy_render", + "bevy_sprite", + "bevy_time", + "bevy_transform", + "bevy_utils", + "bytemuck", + "tracing", +] + +[[package]] +name = "bevy_gizmos_macros" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f378f3b513218ddc78254bbe76536d9de59c1429ebd0c14f5d8f2a25812131ad" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bevy_gltf" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a080237c0b8842ccc15a06d3379302c68580eeea4497b1c7387e470eda1f07" +dependencies = [ + "base64 0.22.1", + "bevy_animation", + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_core_pipeline", + "bevy_ecs", + "bevy_image", + "bevy_math", + "bevy_mesh", + "bevy_pbr", + "bevy_platform", + "bevy_reflect", + "bevy_render", + "bevy_scene", + "bevy_tasks", + "bevy_transform", + "bevy_utils", + "fixedbitset", + "gltf", + "itertools 0.14.0", + "percent-encoding", + "serde", + "serde_json", + "smallvec", + "thiserror 2.0.12", + "tracing", +] + +[[package]] +name = "bevy_image" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e6e900cfecadbc3149953169e36b9e26f922ed8b002d62339d8a9dc6129328" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_math", + "bevy_platform", + "bevy_reflect", + "bevy_utils", + "bitflags 2.9.1", + "bytemuck", + "futures-lite", + "guillotiere", + "half", + "image", + "ktx2", + "rectangle-pack", + "ruzstd", + "serde", + "thiserror 2.0.12", + "tracing", + "wgpu-types", +] + +[[package]] +name = "bevy_input" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d6b6516433f6f7d680f648d04eb1866bb3927a1782d52f74831b62042f3cd1" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_math", + "bevy_platform", + "bevy_reflect", + "bevy_utils", + "derive_more", + "log", + "serde", + "smol_str", + "thiserror 2.0.12", +] + +[[package]] +name = "bevy_input_focus" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e2d079fda74d1416e0a57dac29ea2b79ff77f420cd6b87f833d3aa29a46bc4d" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_input", + "bevy_math", + "bevy_reflect", + "bevy_window", + "log", + "thiserror 2.0.12", +] + +[[package]] +name = "bevy_internal" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "857da8785678fde537d02944cd20dec9cafb7d4c447efe15f898dc60e733cacd" +dependencies = [ + "bevy_a11y", + "bevy_animation", + "bevy_app", + "bevy_asset", + "bevy_audio", + "bevy_color", + "bevy_core_pipeline", + "bevy_derive", + "bevy_diagnostic", + "bevy_ecs", + "bevy_gilrs", + "bevy_gizmos", + "bevy_gltf", + "bevy_image", + "bevy_input", + "bevy_input_focus", + "bevy_log", + "bevy_math", + "bevy_pbr", + "bevy_picking", + "bevy_platform", + "bevy_ptr", + "bevy_reflect", + "bevy_render", + "bevy_scene", + "bevy_sprite", + "bevy_state", + "bevy_tasks", + "bevy_text", + "bevy_time", + "bevy_transform", + "bevy_ui", + "bevy_utils", + "bevy_window", + "bevy_winit", +] + +[[package]] +name = "bevy_log" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a61ee8aef17a974f5ca481dcedf0c2bd52670e231d4c4bc9ddef58328865f9" +dependencies = [ + "android_log-sys", + "bevy_app", + "bevy_ecs", + "bevy_utils", + "tracing", + "tracing-log", + "tracing-oslog", + "tracing-subscriber", + "tracing-wasm", +] + +[[package]] +name = "bevy_macro_utils" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052eeebcb8e7e072beea5031b227d9a290f8a7fbbb947573ab6ec81df0fb94be" +dependencies = [ + "parking_lot", + "proc-macro2", + "quote", + "syn", + "toml_edit", +] + +[[package]] +name = "bevy_math" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68553e0090fe9c3ba066c65629f636bd58e4ebd9444fdba097b91af6cd3e243f" +dependencies = [ + "approx", + "bevy_reflect", + "derive_more", + "glam", + "itertools 0.14.0", + "libm", + "rand", + "rand_distr", + "serde", + "smallvec", + "thiserror 2.0.12", + "variadics_please", +] + +[[package]] +name = "bevy_mesh" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b10399c7027001edbc0406d7d0198596b1f07206c1aae715274106ba5bdcac40" +dependencies = [ + "bevy_asset", + "bevy_derive", + "bevy_ecs", + "bevy_image", + "bevy_math", + "bevy_mikktspace", + "bevy_platform", + "bevy_reflect", + "bevy_transform", + "bevy_utils", + "bitflags 2.9.1", + "bytemuck", + "hexasphere", + "serde", + "thiserror 2.0.12", + "tracing", + "wgpu-types", +] + +[[package]] +name = "bevy_mikktspace" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb60c753b968a2de0fd279b76a3d19517695e771edb4c23575c7f92156315de" +dependencies = [ + "glam", +] + +[[package]] +name = "bevy_pbr" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5e0b4eb871f364a0d217f70f6c41d7fdc6f9f931fa1abbf222180c03d0ae410" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_core_pipeline", + "bevy_derive", + "bevy_diagnostic", + "bevy_ecs", + "bevy_image", + "bevy_math", + "bevy_platform", + "bevy_reflect", + "bevy_render", + "bevy_transform", + "bevy_utils", + "bevy_window", + "bitflags 2.9.1", + "bytemuck", + "derive_more", + "fixedbitset", + "nonmax", + "offset-allocator", + "radsort", + "smallvec", + "static_assertions", + "thiserror 2.0.12", + "tracing", +] + +[[package]] +name = "bevy_picking" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed04757938655ed8094ea1efb533f99063a8b22abffc22010c694d291522850" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_derive", + "bevy_ecs", + "bevy_input", + "bevy_math", + "bevy_mesh", + "bevy_platform", + "bevy_reflect", + "bevy_render", + "bevy_time", + "bevy_transform", + "bevy_utils", + "bevy_window", + "crossbeam-channel", + "tracing", + "uuid", +] + +[[package]] +name = "bevy_platform" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7573dc824a1b08b4c93fdbe421c53e1e8188e9ca1dd74a414455fe571facb47" +dependencies = [ + "cfg-if", + "critical-section", + "foldhash", + "getrandom 0.2.16", + "hashbrown", + "portable-atomic", + "portable-atomic-util", + "serde", + "spin", + "web-time", +] + +[[package]] +name = "bevy_ptr" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7370d0e46b60e071917711d0860721f5347bc958bf325975ae6913a5dfcf01" + +[[package]] +name = "bevy_reflect" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daeb91a63a1a4df00aa58da8cc4ddbd4b9f16ab8bb647c5553eb156ce36fa8c2" +dependencies = [ + "assert_type_match", + "bevy_platform", + "bevy_ptr", + "bevy_reflect_derive", + "bevy_utils", + "derive_more", + "disqualified", + "downcast-rs", + "erased-serde", + "foldhash", + "glam", + "petgraph", + "serde", + "smallvec", + "smol_str", + "thiserror 2.0.12", + "uuid", + "variadics_please", + "wgpu-types", +] + +[[package]] +name = "bevy_reflect_derive" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ddadc55fe16b45faaa54ab2f9cb00548013c74812e8b018aa172387103cce6" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", + "uuid", +] + +[[package]] +name = "bevy_remote" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7d073f6104c48d7c0468f682b3b2a00ee709001c3cf2778e0555afdb1305eb0" +dependencies = [ + "anyhow", + "async-channel", + "async-io", + "bevy_app", + "bevy_derive", + "bevy_ecs", + "bevy_platform", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "http-body-util", + "hyper", + "serde", + "serde_json", + "smol-hyper", +] + +[[package]] +name = "bevy_render" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef91fed1f09405769214b99ebe4390d69c1af5cdd27967deae9135c550eb1667" +dependencies = [ + "async-channel", + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_derive", + "bevy_diagnostic", + "bevy_ecs", + "bevy_encase_derive", + "bevy_image", + "bevy_math", + "bevy_mesh", + "bevy_platform", + "bevy_reflect", + "bevy_render_macros", + "bevy_tasks", + "bevy_time", + "bevy_transform", + "bevy_utils", + "bevy_window", + "bitflags 2.9.1", + "bytemuck", + "codespan-reporting", + "derive_more", + "downcast-rs", + "encase", + "fixedbitset", + "futures-lite", + "image", + "indexmap", + "js-sys", + "ktx2", + "naga", + "naga_oil", + "nonmax", + "offset-allocator", + "send_wrapper", + "serde", + "smallvec", + "thiserror 2.0.12", + "tracing", + "variadics_please", + "wasm-bindgen", + "web-sys", + "wgpu", +] + +[[package]] +name = "bevy_render_macros" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd42cf6c875bcf38da859f8e731e119a6aff190d41dd0a1b6000ad57cf2ed3d" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bevy_scene" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c52ca165200995fe8afd2a1a6c03e4ffee49198a1d4653d32240ea7f217d4ab" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_derive", + "bevy_ecs", + "bevy_platform", + "bevy_reflect", + "bevy_render", + "bevy_transform", + "bevy_utils", + "derive_more", + "serde", + "thiserror 2.0.12", + "uuid", +] + +[[package]] +name = "bevy_sprite" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ccae7bab2cb956fb0434004c359e432a3a1a074a6ef4eb471f1fb099f0b620b" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_core_pipeline", + "bevy_derive", + "bevy_ecs", + "bevy_image", + "bevy_math", + "bevy_picking", + "bevy_platform", + "bevy_reflect", + "bevy_render", + "bevy_transform", + "bevy_utils", + "bevy_window", + "bitflags 2.9.1", + "bytemuck", + "derive_more", + "fixedbitset", + "nonmax", + "radsort", + "tracing", +] + +[[package]] +name = "bevy_state" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "155d3cd97b900539008cdcaa702f88b724d94b08977b8e591a32536ce66faa8c" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_platform", + "bevy_reflect", + "bevy_state_macros", + "bevy_utils", + "log", + "variadics_please", +] + +[[package]] +name = "bevy_state_macros" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2481c1304fd2a1851a0d4cb63a1ce6421ae40f3f0117cbc9882963ee4c9bb609" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bevy_tasks" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b674242641cab680688fc3b850243b351c1af49d4f3417a576debd6cca8dcf5" +dependencies = [ + "async-channel", + "async-executor", + "async-task", + "atomic-waker", + "bevy_platform", + "cfg-if", + "concurrent-queue", + "crossbeam-queue", + "derive_more", + "futures-channel", + "futures-lite", + "heapless", + "pin-project", + "wasm-bindgen-futures", +] + +[[package]] +name = "bevy_text" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d76c85366159f5f54110f33321c76d8429cfd8f39638f26793a305dae568b60" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_derive", + "bevy_ecs", + "bevy_image", + "bevy_log", + "bevy_math", + "bevy_platform", + "bevy_reflect", + "bevy_render", + "bevy_sprite", + "bevy_transform", + "bevy_utils", + "bevy_window", + "cosmic-text", + "serde", + "smallvec", + "sys-locale", + "thiserror 2.0.12", + "tracing", + "unicode-bidi", +] + +[[package]] +name = "bevy_time" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc98eb356c75be04fbbc77bb3d8ffa24c8bacd99f76111cee23d444be6ac8c9c" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_platform", + "bevy_reflect", + "crossbeam-channel", + "log", + "serde", +] + +[[package]] +name = "bevy_transform" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df218e440bb9a19058e1b80a68a031c887bcf7bd3a145b55f361359a2fa3100d" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_log", + "bevy_math", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "derive_more", + "serde", + "thiserror 2.0.12", +] + +[[package]] +name = "bevy_ui" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea4a4d2ba51865bc3039af29a26b4f52c48b54cc758369f52004caf4b6f03770" +dependencies = [ + "accesskit", + "bevy_a11y", + "bevy_app", + "bevy_asset", + "bevy_color", + "bevy_core_pipeline", + "bevy_derive", + "bevy_ecs", + "bevy_image", + "bevy_input", + "bevy_math", + "bevy_picking", + "bevy_platform", + "bevy_reflect", + "bevy_render", + "bevy_sprite", + "bevy_text", + "bevy_transform", + "bevy_utils", + "bevy_window", + "bytemuck", + "derive_more", + "nonmax", + "serde", + "smallvec", + "taffy", + "thiserror 2.0.12", + "tracing", +] + +[[package]] +name = "bevy_utils" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f7a8905a125d2017e8561beefb7f2f5e67e93ff6324f072ad87c5fd6ec3b99" +dependencies = [ + "bevy_platform", + "thread_local", +] + +[[package]] +name = "bevy_window" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7e8ad0c17c3cc23ff5566ae2905c255e6986037fb041f74c446216f5c38431" +dependencies = [ + "android-activity", + "bevy_app", + "bevy_ecs", + "bevy_input", + "bevy_math", + "bevy_platform", + "bevy_reflect", + "bevy_utils", + "log", + "raw-window-handle", + "serde", + "smol_str", +] + +[[package]] +name = "bevy_winit" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a5e7f00c6b3b6823df5ec2a5e9067273607208919bc8c211773ebb9643c87f0" +dependencies = [ + "accesskit", + "accesskit_winit", + "approx", + "bevy_a11y", + "bevy_app", + "bevy_asset", + "bevy_derive", + "bevy_ecs", + "bevy_image", + "bevy_input", + "bevy_input_focus", + "bevy_log", + "bevy_math", + "bevy_platform", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "bevy_window", + "bytemuck", + "cfg-if", + "crossbeam-channel", + "raw-window-handle", + "serde", + "tracing", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "winit", +] + +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags 2.9.1", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn", +] + +[[package]] +name = "bindgen" +version = "0.72.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f" +dependencies = [ + "bitflags 2.9.1", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.1", + "shlex", + "syn", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec 0.6.3", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[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.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +dependencies = [ + "serde", +] + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytemuck" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "441473f2b4b0459a68628c744bc61d23e730fb00128b841d30fa4bb3972257e4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "bzip2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea8dcd42434048e4f7a304411d9273a411f647446c1234a65ce0554923f4cff" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.9.1", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "cc" +version = "1.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cef" +version = "138.8.0+138.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcab070ca301bdcc6e27d38f00840dafb241701270ddd4648c182cab0b0fbdca" +dependencies = [ + "cef-dll-sys", + "libloading", + "windows-sys 0.60.2", +] + +[[package]] +name = "cef-dll-sys" +version = "138.8.0+138.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76c169c1f879d08df993756c21f6f24f0b14795e92eedab57c13c94c727cdcbe" +dependencies = [ + "anyhow", + "cmake", + "download-cef", + "serde_json", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.5.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width 0.1.14", +] + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[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", + "portable-atomic", +] + +[[package]] +name = "console" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width 0.2.1", + "windows-sys 0.60.2", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-fnv1a-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b13ea120a812beba79e34316b3942a857c86ec1593cb34f27bb28272ce2cca" + +[[package]] +name = "const_panic" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98d1483e98c9d67f341ab4b3915cfdc54740bd6f5cccc9226ee0535d86aa8fb" + +[[package]] +name = "const_soft_float" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ca1caa64ef4ed453e68bb3db612e51cf1b2f5b871337f0fcab1c8f87cc3dff" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "constgebra" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1aaf9b65849a68662ac6c0810c8893a765c960b907dd7cfab9c4a50bf764fbc" +dependencies = [ + "const_soft_float", +] + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9" +dependencies = [ + "cookie", + "document-features", + "idna", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[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", + "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 = "coreaudio-rs" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace" +dependencies = [ + "bitflags 1.3.2", + "core-foundation-sys", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceec7a6067e62d6f931a2baf6f3a751f4a892595bcec1461a3c94ef9949864b6" +dependencies = [ + "bindgen 0.72.0", +] + +[[package]] +name = "cosmic-text" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e418dd4f5128c3e93eab12246391c54a20c496811131f85754dc8152ee207892" +dependencies = [ + "bitflags 2.9.1", + "fontdb", + "log", + "rangemap", + "rustc-hash 1.1.0", + "rustybuzz", + "self_cell", + "smol_str", + "swash", + "sys-locale", + "ttf-parser 0.21.1", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + +[[package]] +name = "cpal" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" +dependencies = [ + "alsa", + "core-foundation-sys", + "coreaudio-rs", + "dasp_sample", + "jni", + "js-sys", + "libc", + "mach2", + "ndk 0.8.0", + "ndk-context", + "oboe", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.54.0", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[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-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +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 = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "ctrlc" +version = "3.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" +dependencies = [ + "nix 0.30.1", + "windows-sys 0.59.0", +] + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "demo" +version = "0.1.0" +dependencies = [ + "bevy", + "bevy_cef", + "bevy_remote", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[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 = "disqualified" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd" + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea8a8b81cacc08888170eef4d13b775126db426d0b348bee9d18c2c1eaf123cf" + +[[package]] +name = "download-cef" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97f3e5e9cb37f5ccfbaaa66531a1b0e3bcaffa3e6c05e3412fffa2c25331531e" +dependencies = [ + "bzip2", + "clap", + "indicatif", + "regex", + "semver", + "serde", + "serde_json", + "sha1_smol", + "tar", + "thiserror 2.0.12", + "ureq", +] + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encase" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0a05902cf601ed11d564128448097b98ebe3c6574bd7b6a653a3d56d54aa020" +dependencies = [ + "const_panic", + "encase_derive", + "glam", + "thiserror 1.0.69", +] + +[[package]] +name = "encase_derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "181d475b694e2dd56ae919ce7699d344d1fd259292d590c723a50d1189a2ea85" +dependencies = [ + "encase_derive_impl", +] + +[[package]] +name = "encase_derive_impl" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f97b51c5cc57ef7c5f7a0c57c250251c49ee4c28f819f87ac32f4aceabc36792" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +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 = "erased-serde" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +dependencies = [ + "serde", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "euclid" +version = "0.22.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +dependencies = [ + "num-traits", +] + +[[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 = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "file-id" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc6a637b6dc58414714eddd9170ff187ecb0933d4c7024d1abbd23a3cc26e9" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "font-types" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02a596f5713680923a2080d86de50fe472fb290693cf0f701187a1c8b36996b7" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "fontconfig-parser" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser 0.20.0", +] + +[[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", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[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-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-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[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 = "gilrs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb2c998745a3c1ac90f64f4f7b3a54219fd3612d7705e7798212935641ed18f" +dependencies = [ + "fnv", + "gilrs-core", + "log", + "uuid", + "vec_map", +] + +[[package]] +name = "gilrs-core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d95ae10ce5aa99543a28cf74e41c11f3b9e3c14f0452bbde46024753cd683e" +dependencies = [ + "core-foundation 0.10.1", + "inotify", + "io-kit-sys", + "js-sys", + "libc", + "libudev-sys", + "log", + "nix 0.29.0", + "uuid", + "vec_map", + "wasm-bindgen", + "web-sys", + "windows 0.61.3", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[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 = "glam" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" +dependencies = [ + "bytemuck", + "libm", + "rand", + "serde", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[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 = "gltf" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ce1918195723ce6ac74e80542c5a96a40c2b26162c1957a5cd70799b8cacf7" +dependencies = [ + "byteorder", + "gltf-json", + "lazy_static", + "serde_json", +] + +[[package]] +name = "gltf-derive" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14070e711538afba5d6c807edb74bcb84e5dbb9211a3bf5dea0dfab5b24f4c51" +dependencies = [ + "inflections", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "gltf-json" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6176f9d60a7eab0a877e8e96548605dedbde9190a7ae1e80bbcc1c9af03ab14" +dependencies = [ + "gltf-derive", + "serde", + "serde_derive", + "serde_json", +] + +[[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 = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.9.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.9.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.9.1", + "gpu-descriptor-types", + "hashbrown", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "grid" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36119f3a540b086b4e436bb2b588cf98a68863470e0e880f4d0842f112a3183a" + +[[package]] +name = "guillotiere" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "equivalent", + "foldhash", + "serde", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "portable-atomic", + "stable_deref_trait", +] + +[[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 = "hexasphere" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c9e718d32b6e6b2b32354e1b0367025efdd0b11d6a740b905ddf5db1074679" +dependencies = [ + "constgebra", + "glam", + "tinyvec", +] + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[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 = "image" +version = "0.25.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" +dependencies = [ + "bytemuck", + "byteorder-lite", + "num-traits", + "png", +] + +[[package]] +name = "immutable-chunkmap" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f97096f508d54f8f8ab8957862eee2ccd628847b6217af1a335e1c44dee578" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "indicatif" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" +dependencies = [ + "console", + "portable-atomic", + "unicode-width 0.2.1", + "unit-prefix", + "web-time", +] + +[[package]] +name = "inflections" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" + +[[package]] +name = "inotify" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +dependencies = [ + "bitflags 2.9.1", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[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", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[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 = "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 = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + +[[package]] +name = "ktx2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87d65e08a9ec02e409d27a0139eaa6b9756b4d81fe7cde71f6941a83730ce838" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lewton" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" +dependencies = [ + "byteorder", + "ogg", + "tinyvec", +] + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775bf80d5878ab7c2b1080b5351a48b2f737d9f6f8b383574eebcc22be0dfccb" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[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.53.3", +] + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libredox" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +dependencies = [ + "bitflags 2.9.1", + "libc", + "redox_syscall 0.5.17", +] + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + +[[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 = "litrs" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "memmap2" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" +dependencies = [ + "libc", +] + +[[package]] +name = "metal" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" +dependencies = [ + "bitflags 2.9.1", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "log", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "naga" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e380993072e52eef724eddfcde0ed013b0c023c3f0417336ed041aa9f076994e" +dependencies = [ + "arrayvec", + "bit-set 0.8.0", + "bitflags 2.9.1", + "cfg_aliases", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "pp-rs", + "rustc-hash 1.1.0", + "spirv", + "strum", + "termcolor", + "thiserror 2.0.12", + "unicode-xid", +] + +[[package]] +name = "naga_oil" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2464f7395decfd16bb4c33fb0cb3b2c645cc60d051bc7fb652d3720bfb20f18" +dependencies = [ + "bit-set 0.5.3", + "codespan-reporting", + "data-encoding", + "indexmap", + "naga", + "once_cell", + "regex", + "regex-syntax 0.8.5", + "rustc-hash 1.1.0", + "thiserror 1.0.69", + "tracing", + "unicode-ident", +] + +[[package]] +name = "ndk" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +dependencies = [ + "bitflags 2.9.1", + "jni-sys", + "log", + "ndk-sys 0.5.0+25.2.9519653", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.9.1", + "jni-sys", + "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", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + +[[package]] +name = "notify" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +dependencies = [ + "bitflags 2.9.1", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-debouncer-full" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d88b1a7538054351c8258338df7c931a590513fb3745e8c15eb9ff4199b8d1" +dependencies = [ + "file-id", + "log", + "notify", + "notify-types", + "walkdir", +] + +[[package]] +name = "notify-types" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_enum" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[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-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.9.1", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.9.1", + "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 2.9.1", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + +[[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.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.9.1", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.9.1", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "oboe" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" +dependencies = [ + "jni", + "ndk 0.8.0", + "ndk-context", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d" +dependencies = [ + "cc", +] + +[[package]] +name = "offset-allocator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e234d535da3521eb95106f40f0b73483d80bfb3aacf27c40d7e2b72f1a3e00a2" +dependencies = [ + "log", + "nonmax", +] + +[[package]] +name = "ogg" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" +dependencies = [ + "byteorder", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "orbclient" +version = "0.3.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" +dependencies = [ + "libredox", +] + +[[package]] +name = "ordered-float" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" +dependencies = [ + "num-traits", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.17", + "smallvec", + "windows-targets 0.52.6", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", + "serde", + "serde_derive", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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 = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee9b2fa7a4517d2c91ff5bc6c297a427a96749d15f98fcdbb22c05571a4d4b7" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.0.8", + "windows-sys 0.60.2", +] + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "pp-rs" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb458bb7f6e250e6eb79d5026badc10a3ebb8f9a15d1fff0f13d17c71f4d6dee" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "prettyplease" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" +dependencies = [ + "proc-macro2", + "syn", +] + +[[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 = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" + +[[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.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "radsort" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "019b4b213425016d7d84a153c4c73afb0946fbb4840e4eece7ba8848b9d6da22" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "range-alloc" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" + +[[package]] +name = "rangemap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7e49bb0bf967717f7bd674458b3d6b0c5f48ec7e3038166026a69fc22223" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "read-fonts" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04ca636dac446b5664bd16c069c00a9621806895b8bb02c2dc68542b23b8f25d" +dependencies = [ + "bytemuck", + "font-types", +] + +[[package]] +name = "rectangle-pack" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d463f2884048e7153449a55166f91028d5b0ea53c79377099ce4e8cf0cf9bb" + +[[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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rodio" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ceb6607dd738c99bc8cb28eff249b7cd5c8ec88b9db96c0608c1480d140fb1" +dependencies = [ + "cpal", + "lewton", +] + +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.7", + "bitflags 2.9.1", + "serde", + "serde_derive", +] + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.60.2", +] + +[[package]] +name = "rustls" +version = "0.23.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "rustybuzz" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" +dependencies = [ + "bitflags 2.9.1", + "bytemuck", + "libm", + "smallvec", + "ttf-parser 0.21.1", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "ruzstd" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640bec8aad418d7d03c72ea2de10d5c646a598f9883c7babc160d91e3c1b26c" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[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 = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "self_cell" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + +[[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_json" +version = "1.0.142" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "skrifa" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbeb4ca4399663735553a09dd17ce7e49a0a0203f03b706b39628c4d913a8607" +dependencies = [ + "bytemuck", + "read-fonts", +] + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "smol-hyper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7428a49d323867702cd12b97b08a6b0104f39ec13b49117911f101271321bc1a" +dependencies = [ + "async-executor", + "async-io", + "futures-io", + "hyper", + "pin-project-lite", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "socks" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" +dependencies = [ + "byteorder", + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "portable-atomic", +] + +[[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.9.1", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stackfuture" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eae92052b72ef70dafa16eddbabffc77e5ca3574be2f7bc1127b36f0a7ad7f2" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "svg_fmt" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" + +[[package]] +name = "swash" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f745de914febc7c9ab4388dfaf94bbc87e69f57bb41133a9b0c84d4be49856f3" +dependencies = [ + "skrifa", + "yazi", + "zeno", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +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 = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + +[[package]] +name = "sysinfo" +version = "0.34.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b93974b3d3aeaa036504b8eefd4c039dced109171c1ae973f1dc63b2c7e4b2" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "windows 0.57.0", +] + +[[package]] +name = "taffy" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4f4d046dd956a47a7e1a2947083d7ac3e6aa3cfaaead36173ceaa5ab11878c" +dependencies = [ + "arrayvec", + "grid", + "serde", + "slotmap", +] + +[[package]] +name = "tar" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[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.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[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", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "io-uring", + "libc", + "mio", + "pin-project-lite", + "slab", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "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.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-oslog" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528bdd1f0e27b5dd9a4ededf154e824b0532731e4af73bb531de46276e0aab1e" +dependencies = [ + "bindgen 0.70.1", + "cc", + "cfg-if", + "once_cell", + "parking_lot", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tracing-wasm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" +dependencies = [ + "tracing", + "tracing-subscriber", + "wasm-bindgen", +] + +[[package]] +name = "ttf-parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" + +[[package]] +name = "twox-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b907da542cbced5261bd3256de1b3a1bf340a3d37f93425a07362a1d687de56" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" + +[[package]] +name = "unicode-ccc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "unicode-script" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unit-prefix" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "3.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f0fde9bc91026e381155f8c67cb354bcd35260b2f4a29bcc84639f762760c39" +dependencies = [ + "base64 0.22.1", + "cookie_store", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "socks", + "ureq-proto", + "utf-8", + "webpki-roots 0.26.11", +] + +[[package]] +name = "ureq-proto" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59db78ad1923f2b1be62b6da81fe80b173605ca0d57f85da2e005382adf693f7" +dependencies = [ + "base64 0.22.1", + "http", + "httparse", + "log", +] + +[[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", +] + +[[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 = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "variadics_please" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[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 = "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 = "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 = "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 = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.2", +] + +[[package]] +name = "webpki-roots" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "wgpu" +version = "24.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0b3436f0729f6cdf2e6e9201f3d39dc95813fad61d826c1ed07918b4539353" +dependencies = [ + "arrayvec", + "bitflags 2.9.1", + "cfg_aliases", + "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 = "24.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f0aa306497a238d169b9dc70659105b4a096859a34894544ca81719242e1499" +dependencies = [ + "arrayvec", + "bit-vec 0.8.0", + "bitflags 2.9.1", + "cfg_aliases", + "document-features", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.12", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-hal" +version = "24.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f112f464674ca69f3533248508ee30cb84c67cf06c25ff6800685f5e0294e259" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set 0.8.0", + "bitflags 2.9.1", + "block", + "bytemuck", + "cfg_aliases", + "core-graphics-types", + "glow", + "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", + "ordered-float", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.12", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "windows 0.58.0", + "windows-core 0.58.0", +] + +[[package]] +name = "wgpu-types" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" +dependencies = [ + "bitflags 2.9.1", + "js-sys", + "log", + "serde", + "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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[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.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core 0.54.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[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", + "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.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[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.0", + "windows-interface 0.59.1", + "windows-link", + "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", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[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", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[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", +] + +[[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", +] + +[[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.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.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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[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-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[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.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[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.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[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 = "winit" +version = "0.30.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" +dependencies = [ + "android-activity", + "atomic-waker", + "bitflags 2.9.1", + "block2", + "bytemuck", + "calloop", + "cfg_aliases", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "ndk 0.9.0", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +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 2.9.1", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 0.38.44", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + +[[package]] +name = "xattr" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" +dependencies = [ + "libc", + "rustix 1.0.8", +] + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.9.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.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" + +[[package]] +name = "yazi" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" + +[[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 = "zeno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +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 = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[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", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a336f80 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "bevy_cef" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true + +[workspace] +resolver = "2" +members = [ + "crates/*", + "examples/demo", +] + +[workspace.package] +version = "0.1.0" +edition = "2024" +license = "Apache-2.0 OR MIT" +authors = ["notelm"] +repository = "https://github.com/not-elm/bevy_cef" + +[workspace.dependencies] +bevy = { version = "0.16" } +bevy_remote = "0.16" +cef = { version = "138" } +cef-dll-sys = { version = "138", features = ["sandbox"] } +download-cef = { version = "2" } +bevy_cef = { path = "." } +bevy_cef_core = { path = "crates/bevy_cef_core" } +async-channel = { version = "2.5" } +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1" } +raw-window-handle = "0.6" + +[dependencies] +bevy = { workspace = true } +bevy_remote = { workspace = true } +cef = { workspace = true } +bevy_cef_core = { workspace = true } +async-channel = { version = "2.5" } +serde = { workspace = true } +serde_json = { workspace = true } +raw-window-handle = "0.6" + +[dev-dependencies] +bevy = { workspace = true, features = ["file_watcher"]} +bevy_cef = { workspace = true, features = ["debug"] } + +[target.'cfg(target_os = "macos")'.dependencies] +objc = { version = "0.2" } + +[features] +default = [] +serialize = ["bevy/serialize"] +debug = ["bevy_cef_core/debug"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2964840 --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +fix: + cargo clippy --fix --allow-dirty --allow-staged --workspace --all --all-features + cargo fmt --all + +install: + cargo install --path ./crates/bevy_cef_debug_render_process --force \ No newline at end of file diff --git a/assets/brp.html b/assets/brp.html new file mode 100644 index 0000000..e6ffa7c --- /dev/null +++ b/assets/brp.html @@ -0,0 +1,35 @@ + + + + + Title + + +

HTML Path: assets/brp.html

+

+ + +

+ + + \ No newline at end of file diff --git a/assets/host_emit.html b/assets/host_emit.html new file mode 100644 index 0000000..3607ecd --- /dev/null +++ b/assets/host_emit.html @@ -0,0 +1,23 @@ + + + + + Host Emit + + +

HTML Path: assets/host_emit.html

+

+ This example demonstrates how to receive events from the application. +

+

+ The application emits a count value every second, which is then displayed in the HTML. +

+

0

+ + + \ No newline at end of file diff --git a/assets/images/rustacean-flat-gesture.png b/assets/images/rustacean-flat-gesture.png new file mode 100644 index 0000000000000000000000000000000000000000..866412dc60dda68a204978cb15726b22c28567d7 GIT binary patch literal 49537 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 F{{ZTl;J^R? literal 0 HcmV?d00001 diff --git a/assets/js_emit.html b/assets/js_emit.html new file mode 100644 index 0000000..081125f --- /dev/null +++ b/assets/js_emit.html @@ -0,0 +1,28 @@ + + + + + JS Emit + + +

HTML Path: assets/js_emit.html

+ +

The example sends events from JavaScript to the application.

+

+ It emits a count value every second, which is then sent to the application and logged in the console. +

+

0

+ + + \ No newline at end of file diff --git a/assets/shaders/custom_material.wgsl b/assets/shaders/custom_material.wgsl new file mode 100644 index 0000000..4488d28 --- /dev/null +++ b/assets/shaders/custom_material.wgsl @@ -0,0 +1,20 @@ +#import bevy_pbr::{ + forward_io::VertexOutput, +} +#import webview::util::{ + surface_color, +} + +@group(2) @binding(0) var mask_texture: texture_2d; +@group(2) @binding(1) var mask_sampler: sampler; + +@fragment +fn fragment( + in: VertexOutput, +) -> @location(0) vec4 { + // You can obtain the surface color. + var color = surface_color(in.uv); + // Blend the color with the mask texture. + color *= (textureSample(mask_texture, mask_sampler, in.uv) * vec4(vec3(1.0), 0.3)); + return color; +} diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..6829e30 --- /dev/null +++ b/build.rs @@ -0,0 +1,59 @@ +use std::env::home_dir; +use std::process::Command; + +fn main() -> std::io::Result<()> { + println!("cargo::rerun-if-changed=build.rs"); + #[cfg(all(target_os = "macos", feature = "debug"))] + { + install_bevy_cef_debug_render_process()?; + install_export_cef_dir()?; + export_cef_dir()?; + } + Ok(()) +} + +fn install_bevy_cef_debug_render_process() -> std::io::Result<()> { + let bevy_cef_render_process_path = home_dir() + .unwrap() + .join(".cargo") + .join("bin") + .join("bevy_cef_debug_render_process"); + if !bevy_cef_render_process_path.exists() { + Command::new("cargo") + .args(["install", "bevy_cef_debug_render_process"]) + .spawn()?; + } + Ok(()) +} + +fn install_export_cef_dir() -> std::io::Result<()> { + let export_cef_dir_path = home_dir() + .unwrap() + .join(".cargo") + .join("bin") + .join("export-cef-dir"); + if !export_cef_dir_path.exists() { + Command::new("cargo") + .args(["install", "export-cef-dir"]) + .spawn()?; + } + Ok(()) +} + +fn export_cef_dir() -> std::io::Result<()> { + let cef_dir = home_dir().unwrap().join(".local").join("share").join("cef"); + if cef_dir.exists() { + return Ok(()); + } + let export_cef_dir_path = home_dir() + .unwrap() + .join(".cargo") + .join("bin") + .join("export-cef-dir"); + Command::new(export_cef_dir_path) + .arg("--force") + .arg(cef_dir) + .spawn()? + .wait()?; + Ok(()) +} diff --git a/crates/bevy_cef_core/Cargo.toml b/crates/bevy_cef_core/Cargo.toml new file mode 100644 index 0000000..3d67b7a --- /dev/null +++ b/crates/bevy_cef_core/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "bevy_cef_core" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true + +[dependencies] +bevy = { workspace = true } +bevy_remote = { workspace = true } +uuid = { version = "1" } +cef = { workspace = true } +cef-dll-sys = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +async-channel = { workspace = true } +raw-window-handle = { workspace = true } + +[features] +default = [] +debug = [] \ No newline at end of file diff --git a/crates/bevy_cef_core/src/browser_process.rs b/crates/bevy_cef_core/src/browser_process.rs new file mode 100644 index 0000000..0628f38 --- /dev/null +++ b/crates/bevy_cef_core/src/browser_process.rs @@ -0,0 +1,18 @@ +mod app; +mod browser_process_handler; +mod browsers; +mod client_handler; +mod context_menu_handler; +mod display_handler; +mod localhost; +mod renderer_handler; +mod request_context_handler; + +pub use app::*; +pub use browser_process_handler::*; +pub use browsers::*; +pub use client_handler::*; +pub use context_menu_handler::*; +pub use localhost::*; +pub use renderer_handler::*; +pub use request_context_handler::*; diff --git a/crates/bevy_cef_core/src/browser_process/app.rs b/crates/bevy_cef_core/src/browser_process/app.rs new file mode 100644 index 0000000..21d02bc --- /dev/null +++ b/crates/bevy_cef_core/src/browser_process/app.rs @@ -0,0 +1,85 @@ +use crate::browser_process::browser_process_handler::BrowserProcessHandlerBuilder; +use crate::util::{SCHEME_CEF, cef_scheme_flags}; +use cef::rc::{Rc, RcImpl}; +use cef::{ + BrowserProcessHandler, CefString, CommandLine, ImplApp, ImplCommandLine, ImplSchemeRegistrar, + SchemeRegistrar, WrapApp, +}; +use cef_dll_sys::{_cef_app_t, cef_base_ref_counted_t}; + +/// ## Reference +/// +/// - [`CefApp Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefApp.html) +#[derive(Default)] +pub struct BrowserProcessAppBuilder { + object: *mut RcImpl<_cef_app_t, Self>, +} + +impl BrowserProcessAppBuilder { + pub fn build() -> cef::App { + cef::App::new(Self { + object: core::ptr::null_mut(), + }) + } +} + +impl Clone for BrowserProcessAppBuilder { + fn clone(&self) -> Self { + let object = unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + self.object + }; + Self { object } + } +} + +impl Rc for BrowserProcessAppBuilder { + fn as_base(&self) -> &cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl ImplApp for BrowserProcessAppBuilder { + fn on_before_command_line_processing( + &self, + _: Option<&CefString>, + command_line: Option<&mut CommandLine>, + ) { + let Some(command_line) = command_line else { + return; + }; + //TODO: フラグで切り替えるようにする + command_line.append_switch(Some(&"use-mock-keychain".into())); + #[cfg(feature = "debug")] + { + command_line.append_switch(Some(&"disable-gpu".into())); + command_line.append_switch(Some(&"disable-gpu-compositing".into())); + command_line.append_switch(Some(&"disable-software-rasterizer".into())); + } + } + + fn browser_process_handler(&self) -> Option { + Some(BrowserProcessHandlerBuilder::build()) + } + + fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) { + if let Some(registrar) = registrar { + registrar.add_custom_scheme(Some(&SCHEME_CEF.into()), cef_scheme_flags() as _); + } + } + + #[inline] + fn get_raw(&self) -> *mut _cef_app_t { + self.object as *mut cef::sys::_cef_app_t + } +} + +impl WrapApp for BrowserProcessAppBuilder { + fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) { + self.object = object; + } +} diff --git a/crates/bevy_cef_core/src/browser_process/browser_process_handler.rs b/crates/bevy_cef_core/src/browser_process/browser_process_handler.rs new file mode 100644 index 0000000..c48598f --- /dev/null +++ b/crates/bevy_cef_core/src/browser_process/browser_process_handler.rs @@ -0,0 +1,63 @@ +use cef::rc::{Rc, RcImpl}; +use cef::*; + +/// ## Reference +/// +/// - [`CefBrowserProcessHandler Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefBrowserProcessHandler.html) +pub struct BrowserProcessHandlerBuilder { + object: *mut RcImpl, +} + +impl BrowserProcessHandlerBuilder { + pub fn build() -> BrowserProcessHandler { + BrowserProcessHandler::new(Self { + object: core::ptr::null_mut(), + }) + } +} + +impl Rc for BrowserProcessHandlerBuilder { + fn as_base(&self) -> &cef_dll_sys::cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl WrapBrowserProcessHandler for BrowserProcessHandlerBuilder { + fn wrap_rc(&mut self, object: *mut RcImpl) { + self.object = object; + } +} + +impl Clone for BrowserProcessHandlerBuilder { + fn clone(&self) -> Self { + let object = unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + rc_impl + }; + + Self { object } + } +} + +impl ImplBrowserProcessHandler for BrowserProcessHandlerBuilder { + fn on_before_child_process_launch(&self, command_line: Option<&mut CommandLine>) { + let Some(command_line) = command_line else { + return; + }; + + command_line.append_switch(Some(&"disable-web-security".into())); + command_line.append_switch(Some(&"allow-running-insecure-content".into())); + command_line.append_switch(Some(&"disable-session-crashed-bubble".into())); + command_line.append_switch(Some(&"ignore-certificate-errors".into())); + command_line.append_switch(Some(&"ignore-ssl-errors".into())); + command_line.append_switch(Some(&"enable-logging=stderr".into())); + } + #[inline] + fn get_raw(&self) -> *mut cef_dll_sys::_cef_browser_process_handler_t { + self.object.cast() + } +} diff --git a/crates/bevy_cef_core/src/browser_process/browsers.rs b/crates/bevy_cef_core/src/browser_process/browsers.rs new file mode 100644 index 0000000..7569392 --- /dev/null +++ b/crates/bevy_cef_core/src/browser_process/browsers.rs @@ -0,0 +1,492 @@ +use crate::browser_process::BrpHandler; +use crate::browser_process::ClientHandlerBuilder; +use crate::browser_process::client_handler::{IpcEventRaw, JsEmitEventHandler}; +use crate::prelude::IntoString; +use crate::prelude::*; +use async_channel::{Sender, TryRecvError}; +use bevy::platform::collections::HashMap; +use bevy::prelude::*; +use bevy_remote::BrpMessage; +use cef::{ + Browser, BrowserHost, BrowserSettings, Client, CompositionUnderline, ImplBrowser, + ImplBrowserHost, ImplFrame, ImplListValue, ImplProcessMessage, ImplRequestContext, + MouseButtonType, ProcessId, Range, RequestContext, RequestContextSettings, WindowInfo, + browser_host_create_browser_sync, process_message_create, +}; +use cef_dll_sys::{cef_event_flags_t, cef_mouse_button_type_t}; +#[allow(deprecated)] +use raw_window_handle::RawWindowHandle; +use std::cell::Cell; +use std::rc::Rc; + +mod devtool_render_handler; +mod keyboard; + +use crate::browser_process::browsers::devtool_render_handler::DevToolRenderHandlerBuilder; +use crate::browser_process::display_handler::{DisplayHandlerBuilder, SystemCursorIconSenderInner}; +pub use keyboard::*; + +pub struct WebviewBrowser { + pub client: Browser, + pub host: BrowserHost, + pub size: SharedViewSize, +} + +pub struct Browsers { + browsers: HashMap, + sender: TextureSender, + receiver: TextureReceiver, + ime_caret: SharedImeCaret, +} + +impl Default for Browsers { + fn default() -> Self { + let (sender, receiver) = async_channel::unbounded::(); + Browsers { + browsers: HashMap::default(), + sender, + receiver, + ime_caret: Rc::new(Cell::new(0)), + } + } +} + +impl Browsers { + #[allow(clippy::too_many_arguments)] + pub fn create_browser( + &mut self, + webview: Entity, + uri: &str, + webview_size: Vec2, + requester: Requester, + ipc_event_sender: Sender, + brp_sender: Sender, + system_cursor_icon_sender: SystemCursorIconSenderInner, + window_handle: Option, + ) { + let mut context = Self::request_context(requester); + let size = Rc::new(Cell::new(webview_size)); + let browser = browser_host_create_browser_sync( + Some(&WindowInfo { + windowless_rendering_enabled: true as _, + external_begin_frame_enabled: true as _, + parent_view: match window_handle { + Some(RawWindowHandle::AppKit(handle)) => handle.ns_view.as_ptr(), + Some(RawWindowHandle::Win32(handle)) => handle.hwnd.get() as _, + Some(RawWindowHandle::Xlib(handle)) => handle.window as _, + Some(RawWindowHandle::Wayland(handle)) => handle.surface.as_ptr(), + _ => std::ptr::null_mut(), + }, + // shared_texture_enabled: true as _, + ..Default::default() + }), + Some(&mut self.client_handler( + webview, + size.clone(), + ipc_event_sender, + brp_sender, + system_cursor_icon_sender, + )), + Some(&uri.into()), + Some(&BrowserSettings { + windowless_frame_rate: 60, + ..Default::default() + }), + None, + context.as_mut(), + ) + .expect("Failed to create browser"); + self.browsers.insert( + webview, + WebviewBrowser { + host: browser.host().expect("Failed to get browser host"), + client: browser, + size, + }, + ); + } + + pub fn send_external_begin_frame(&mut self) { + for browser in self.browsers.values_mut() { + browser.host.send_external_begin_frame(); + } + } + + pub fn send_mouse_move<'a>( + &self, + webview: &Entity, + buttons: impl IntoIterator, + position: Vec2, + mouse_leave: bool, + ) { + if let Some(browser) = self.get_focused_browser(webview) { + let mouse_event = cef::MouseEvent { + x: position.x as i32, + y: position.y as i32, + modifiers: modifiers_from_mouse_buttons(buttons), + }; + browser + .host + .send_mouse_move_event(Some(&mouse_event), mouse_leave as _); + } + } + + pub fn send_mouse_click( + &self, + webview: &Entity, + position: Vec2, + button: PointerButton, + mouse_up: bool, + ) { + if let Some(browser) = self.get_focused_browser(webview) { + let mouse_event = cef::MouseEvent { + x: position.x as i32, + y: position.y as i32, + modifiers: match button { + PointerButton::Primary => cef_event_flags_t::EVENTFLAG_LEFT_MOUSE_BUTTON, + PointerButton::Secondary => cef_event_flags_t::EVENTFLAG_RIGHT_MOUSE_BUTTON, + PointerButton::Middle => cef_event_flags_t::EVENTFLAG_MIDDLE_MOUSE_BUTTON, + } as _, // No modifiers for simplicity + }; + let mouse_button = match button { + PointerButton::Secondary => cef_mouse_button_type_t::MBT_RIGHT, + PointerButton::Middle => cef_mouse_button_type_t::MBT_MIDDLE, + _ => cef_mouse_button_type_t::MBT_LEFT, + }; + browser.host.set_focus(true as _); + browser.host.send_mouse_click_event( + Some(&mouse_event), + MouseButtonType::from(mouse_button), + mouse_up as _, + 1, + ); + } + } + + /// [`SendMouseWheelEvent`](https://cef-builds.spotifycdn.com/docs/106.1/classCefBrowserHost.html#acd5d057bd5230baa9a94b7853ba755f7) + pub fn send_mouse_wheel(&self, webview: &Entity, position: Vec2, delta: Vec2) { + if let Some(browser) = self.get_focused_browser(webview) { + let mouse_event = cef::MouseEvent { + x: position.x as i32, + y: position.y as i32, + modifiers: 0, + }; + browser + .host + .send_mouse_wheel_event(Some(&mouse_event), delta.x as _, delta.y as _); + } + } + + #[inline] + pub fn send_key(&self, webview: &Entity, event: cef::KeyEvent) { + if let Some(browser) = self.get_focused_browser(webview) { + browser.host.send_key_event(Some(&event)); + } + } + + pub fn emit_event(&self, webview: &Entity, id: impl Into, event: &serde_json::Value) { + if let Some(mut process_message) = + process_message_create(Some(&PROCESS_MESSAGE_HOST_EMIT.into())) + && let Some(argument_list) = process_message.argument_list() + && let Some(browser) = self.browsers.get(webview) + && let Some(frame) = browser.client.main_frame() + { + argument_list.set_string(0, Some(&id.into().as_str().into())); + argument_list.set_string(1, Some(&event.to_string().as_str().into())); + frame.send_process_message( + ProcessId::from(cef_dll_sys::cef_process_id_t::PID_RENDERER), + Some(&mut process_message), + ); + }; + } + + pub fn resize(&self, webview: &Entity, size: Vec2) { + if let Some(browser) = self.browsers.get(webview) { + browser.size.set(size); + browser.host.was_resized(); + } + } + + /// Closes the browser associated with the given webview entity. + /// + /// The browser will be removed from the hash map after closing. + pub fn close(&mut self, webview: &Entity) { + if let Some(browser) = self.browsers.remove(webview) { + browser.host.close_browser(true as _); + debug!("Closed browser with webview: {:?}", webview); + } + } + + #[inline] + pub fn try_receive_texture(&self) -> core::result::Result { + self.receiver.try_recv() + } + + /// Shows the DevTools for the specified webview. + pub fn show_devtool(&self, webview: &Entity) { + let Some(browser) = self.browsers.get(webview) else { + return; + }; + browser.host.show_dev_tools( + Some(&WindowInfo::default()), + Some(&mut ClientHandlerBuilder::new(DevToolRenderHandlerBuilder::build()).build()), + Some(&BrowserSettings::default()), + None, + ); + } + + /// Closes the DevTools for the specified webview. + pub fn close_devtools(&self, webview: &Entity) { + if let Some(browser) = self.browsers.get(webview) { + browser.host.close_dev_tools(); + } + } + + /// Navigate backwards. + /// + /// ## Reference + /// + /// - [`GoBack`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowser.html#a85b02760885c070e4ad2a2705cea56cb) + pub fn go_back(&self, webview: &Entity) { + if let Some(browser) = self.browsers.get(webview) + && browser.client.can_go_back() == 1 + { + browser.client.go_back(); + } + } + + /// Navigate forwards. + /// + /// ## Reference + /// + /// - [`GoForward`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowser.html#aa8e97fc210ee0e73f16b2d98482419d0) + pub fn go_forward(&self, webview: &Entity) { + if let Some(browser) = self.browsers.get(webview) + && browser.client.can_go_forward() == 1 + { + browser.client.go_forward(); + } + } + + /// Returns the current zoom level for the specified webview. + /// + /// ## Reference + /// + /// - [`GetZoomLevel`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowserHost.html#a524d4a358287dab284c0dfec6d6d229e) + pub fn zoom_level(&self, webview: &Entity) -> Option { + self.browsers + .get(webview) + .map(|browser| browser.host.zoom_level()) + } + + /// Sets the zoom level for the specified webview. + /// + /// ## Reference + /// + /// - [`SetZoomLevel`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowserHost.html#af2b7bf250ac78345117cd575190f2f7b) + pub fn set_zoom_level(&self, webview: &Entity, zoom_level: f64) { + if let Some(browser) = self.browsers.get(webview) { + browser.host.set_zoom_level(zoom_level); + } + } + + /// Sets whether the audio is muted for the specified webview. + /// + /// ## Reference + /// + /// - [`SetAudioMuted`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowserHost.html#a153d179c9ff202c8bb8869d2e9a820a2) + pub fn set_audio_muted(&self, webview: &Entity, muted: bool) { + if let Some(browser) = self.browsers.get(webview) { + browser.host.set_audio_muted(muted as _); + } + } + + #[inline] + pub fn reload(&self) { + for browser in self.browsers.values() { + if let Some(frame) = browser.client.main_frame() { + let url = frame.url().into_string(); + info!("Reloading browser with URL: {}", url); + frame.load_url(Some(&url.as_str().into())); + } + } + } + + /// ## Reference + /// + /// - [`ImeSetComposition`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowserHost.html#a567b41fb2d3917843ece3b57adc21ebe) + pub fn set_ime_composition(&self, text: &str, cursor_utf16: Option) { + let underlines = make_underlines_for(text, cursor_utf16.map(|i| (i, i))); + let i = text.encode_utf16().count(); + let selection_range = Range { + from: i as _, + to: i as _, + }; + let replacement_range = self.ime_caret_range(); + for browser in self + .browsers + .values() + .filter(|b| b.client.focused_frame().is_some()) + { + browser.host.ime_set_composition( + Some(&text.into()), + underlines.len(), + Some(&underlines[0]), + Some(&replacement_range), + Some(&selection_range), + ); + } + } + + /// ## Reference + /// + /// [`ImeSetComposition`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowserHost.html#a567b41fb2d3917843ece3b57adc21ebe) + pub fn ime_finish_composition(&self, keep_selection: bool) { + for browser in self + .browsers + .values() + .filter(|b| b.client.focused_frame().is_some()) + { + browser.host.ime_finish_composing_text(keep_selection as _); + } + } + + pub fn set_ime_commit_text(&self, text: &str) { + let replacement_range = self.ime_caret_range(); + for browser in self + .browsers + .values() + .filter(|b| b.client.focused_frame().is_some()) + { + browser + .host + .ime_commit_text(Some(&text.into()), Some(&replacement_range), 0) + } + } + + fn request_context(requester: Requester) -> Option { + let mut context = cef::request_context_create_context( + Some(&RequestContextSettings::default()), + Some(&mut RequestContextHandlerBuilder::build()), + ); + if let Some(context) = context.as_mut() { + context.register_scheme_handler_factory( + Some(&SCHEME_CEF.into()), + Some(&HOST_CEF.into()), + Some(&mut LocalSchemaHandlerBuilder::build(requester)), + ); + } + context + } + + fn client_handler( + &self, + webview: Entity, + size: SharedViewSize, + ipc_event_sender: Sender, + brp_sender: Sender, + system_cursor_icon_sender: SystemCursorIconSenderInner, + ) -> Client { + ClientHandlerBuilder::new(RenderHandlerBuilder::build( + webview, + self.sender.clone(), + size.clone(), + self.ime_caret.clone(), + )) + .with_display_handler(DisplayHandlerBuilder::build(system_cursor_icon_sender)) + .with_message_handler(JsEmitEventHandler::new(webview, ipc_event_sender)) + .with_message_handler(BrpHandler::new(brp_sender)) + .build() + } + + #[inline] + fn ime_caret_range(&self) -> Range { + let caret = self.ime_caret.get(); + Range { + from: caret, + to: caret, + } + } + + #[inline] + fn get_focused_browser(&self, webview: &Entity) -> Option<&WebviewBrowser> { + self.browsers + .get(webview) + .and_then(|b| b.client.focused_frame().is_some().then_some(b)) + } +} + +pub fn modifiers_from_mouse_buttons<'a>(buttons: impl IntoIterator) -> u32 { + let mut modifiers = cef_event_flags_t::EVENTFLAG_NONE as u32; + for button in buttons { + match button { + MouseButton::Left => modifiers |= cef_event_flags_t::EVENTFLAG_LEFT_MOUSE_BUTTON as u32, + MouseButton::Right => { + modifiers |= cef_event_flags_t::EVENTFLAG_RIGHT_MOUSE_BUTTON as u32 + } + MouseButton::Middle => { + modifiers |= cef_event_flags_t::EVENTFLAG_MIDDLE_MOUSE_BUTTON as u32 + } + _ => {} + } + } + modifiers +} + +pub fn make_underlines_for( + text: &str, + selection_utf16: Option<(u32, u32)>, +) -> Vec { + let len16 = utf16_len(text); + + let base = CompositionUnderline { + size: size_of::(), + range: Range { from: 0, to: len16 }, + color: 0, + background_color: 0, + thick: 0, + style: Default::default(), + }; + + if let Some((from, to)) = selection_utf16 + && from < to + { + let sel = CompositionUnderline { + size: size_of::(), + range: Range { from, to }, + color: 0, + background_color: 0, + thick: 1, + style: Default::default(), + }; + return vec![base, sel]; + } + vec![base] +} + +#[inline] +fn utf16_len(s: &str) -> u32 { + s.encode_utf16().count() as u32 +} + +#[allow(dead_code)] +fn utf16_index_from_byte(s: &str, byte_idx: usize) -> u32 { + s[..byte_idx].encode_utf16().count() as u32 +} + +#[cfg(test)] +mod tests { + use crate::prelude::modifiers_from_mouse_buttons; + use bevy::prelude::*; + + #[test] + fn test_modifiers_from_mouse_buttons() { + let buttons = vec![&MouseButton::Left, &MouseButton::Right]; + let modifiers = modifiers_from_mouse_buttons(buttons); + assert_eq!( + modifiers, + cef_dll_sys::cef_event_flags_t::EVENTFLAG_LEFT_MOUSE_BUTTON as u32 + | cef_dll_sys::cef_event_flags_t::EVENTFLAG_RIGHT_MOUSE_BUTTON as u32 + ); + } +} diff --git a/crates/bevy_cef_core/src/browser_process/browsers/devtool_render_handler.rs b/crates/bevy_cef_core/src/browser_process/browsers/devtool_render_handler.rs new file mode 100644 index 0000000..054e325 --- /dev/null +++ b/crates/bevy_cef_core/src/browser_process/browsers/devtool_render_handler.rs @@ -0,0 +1,57 @@ +use cef::rc::{Rc, RcImpl}; +use cef::{Browser, ImplRenderHandler, Rect, RenderHandler, WrapRenderHandler, sys}; + +/// ## Reference +/// +/// - [`CefRenderHandler Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefRenderHandler.html) +pub struct DevToolRenderHandlerBuilder { + object: *mut RcImpl, +} + +impl DevToolRenderHandlerBuilder { + pub fn build() -> RenderHandler { + RenderHandler::new(Self { + object: std::ptr::null_mut(), + }) + } +} + +impl Rc for DevToolRenderHandlerBuilder { + fn as_base(&self) -> &sys::cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl WrapRenderHandler for DevToolRenderHandlerBuilder { + fn wrap_rc(&mut self, object: *mut RcImpl) { + self.object = object; + } +} + +impl Clone for DevToolRenderHandlerBuilder { + fn clone(&self) -> Self { + let object = unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + rc_impl + }; + Self { object } + } +} + +impl ImplRenderHandler for DevToolRenderHandlerBuilder { + fn view_rect(&self, _browser: Option<&mut Browser>, rect: Option<&mut Rect>) { + if let Some(rect) = rect { + rect.width = 800; + rect.height = 800; + } + } + + #[inline] + fn get_raw(&self) -> *mut sys::_cef_render_handler_t { + self.object.cast() + } +} diff --git a/crates/bevy_cef_core/src/browser_process/browsers/keyboard.rs b/crates/bevy_cef_core/src/browser_process/browsers/keyboard.rs new file mode 100644 index 0000000..90c1fe1 --- /dev/null +++ b/crates/bevy_cef_core/src/browser_process/browsers/keyboard.rs @@ -0,0 +1,979 @@ +//! ## Reference +//! +//! - [`cef_key_event_t`](https://cef-builds.spotifycdn.com/docs/106.1/structcef__key__event__t.html) +//! - [KeyboardCodes](https://chromium.googlesource.com/external/Webkit/+/safari-4-branch/WebCore/platform/KeyboardCodes.h) + +use bevy::input::ButtonState; +use bevy::input::keyboard::KeyboardInput; +use bevy::prelude::{ButtonInput, KeyCode}; +use cef_dll_sys::{cef_event_flags_t, cef_key_event_t, cef_key_event_type_t}; + +pub fn keyboard_modifiers(input: &ButtonInput) -> u32 { + let mut flags = 0u32; + + if input.pressed(KeyCode::ControlLeft) || input.pressed(KeyCode::ControlRight) { + flags |= cef_event_flags_t::EVENTFLAG_CONTROL_DOWN as u32; + } + if input.pressed(KeyCode::AltLeft) || input.pressed(KeyCode::AltRight) { + flags |= cef_event_flags_t::EVENTFLAG_ALT_DOWN as u32; + } + if input.pressed(KeyCode::ShiftLeft) || input.pressed(KeyCode::ShiftRight) { + flags |= cef_event_flags_t::EVENTFLAG_SHIFT_DOWN as u32; + } + if input.pressed(KeyCode::SuperLeft) || input.pressed(KeyCode::SuperRight) { + flags |= cef_event_flags_t::EVENTFLAG_COMMAND_DOWN as u32; + } + if input.pressed(KeyCode::CapsLock) { + flags |= cef_event_flags_t::EVENTFLAG_CAPS_LOCK_ON as u32; + } + if input.pressed(KeyCode::NumLock) { + flags |= cef_event_flags_t::EVENTFLAG_NUM_LOCK_ON as u32; + } + + flags +} + +pub fn create_cef_key_event( + modifiers: u32, + _input: &ButtonInput, + key_event: &KeyboardInput, +) -> Option { + let key_type = match key_event.state { + // ButtonState::Pressed if input.just_pressed(key_event.key_code) => { + // cef_key_event_type_t::KEYEVENT_RAWKEYDOWN + // } + ButtonState::Pressed => cef_key_event_type_t::KEYEVENT_CHAR, + ButtonState::Released => cef_key_event_type_t::KEYEVENT_KEYUP, + }; + let windows_key_code = keycode_to_windows_vk(key_event.key_code); + + let character = key_event + .text + .as_ref() + .and_then(|text| text.chars().next()) + .unwrap_or('\0') as u16; + + Some(cef::KeyEvent::from(cef_key_event_t { + size: core::mem::size_of::(), + type_: key_type, + modifiers, + windows_key_code, + native_key_code: to_native_key_code(&key_event.key_code) as _, + character, + unmodified_character: character, + is_system_key: false as _, + focus_on_editable_field: false as _, + })) +} + +// fn is_not_character_key_code(keycode: &KeyCode) -> bool { +// match keycode { +// // Function keys are not character keys +// KeyCode::F1 +// | KeyCode::F2 +// | KeyCode::F3 +// | KeyCode::F4 +// | KeyCode::F5 +// | KeyCode::F6 +// | KeyCode::F7 +// | KeyCode::F8 +// | KeyCode::F9 +// | KeyCode::F10 +// | KeyCode::F11 +// | KeyCode::F12 => true, +// +// // Navigation keys are not character keys +// KeyCode::ArrowLeft +// | KeyCode::ArrowUp +// | KeyCode::ArrowRight +// | KeyCode::ArrowDown +// | KeyCode::Home +// | KeyCode::End +// | KeyCode::PageUp +// | KeyCode::PageDown => true, +// +// // Modifier keys are not character keys +// KeyCode::ShiftLeft +// | KeyCode::ShiftRight +// | KeyCode::ControlLeft +// | KeyCode::ControlRight +// | KeyCode::AltLeft +// | KeyCode::AltRight +// | KeyCode::SuperLeft +// | KeyCode::SuperRight => true, +// +// // Lock keys are not character keys +// KeyCode::CapsLock | KeyCode::NumLock | KeyCode::ScrollLock => true, +// +// // Special control keys are not character keys +// KeyCode::Escape +// | KeyCode::Tab +// | KeyCode::Enter +// | KeyCode::Backspace +// | KeyCode::Delete +// | KeyCode::Insert => true, +// +// // All other keys (letters, numbers, punctuation, space, numpad) are character keys +// _ => false, +// } +// } + +fn keycode_to_windows_vk(keycode: KeyCode) -> i32 { + match keycode { + // Letters + KeyCode::KeyA => 0x41, + KeyCode::KeyB => 0x42, + KeyCode::KeyC => 0x43, + KeyCode::KeyD => 0x44, + KeyCode::KeyE => 0x45, + KeyCode::KeyF => 0x46, + KeyCode::KeyG => 0x47, + KeyCode::KeyH => 0x48, + KeyCode::KeyI => 0x49, + KeyCode::KeyJ => 0x4A, + KeyCode::KeyK => 0x4B, + KeyCode::KeyL => 0x4C, + KeyCode::KeyM => 0x4D, + KeyCode::KeyN => 0x4E, + KeyCode::KeyO => 0x4F, + KeyCode::KeyP => 0x50, + KeyCode::KeyQ => 0x51, + KeyCode::KeyR => 0x52, + KeyCode::KeyS => 0x53, + KeyCode::KeyT => 0x54, + KeyCode::KeyU => 0x55, + KeyCode::KeyV => 0x56, + KeyCode::KeyW => 0x57, + KeyCode::KeyX => 0x58, + KeyCode::KeyY => 0x59, + KeyCode::KeyZ => 0x5A, + + // Numbers + KeyCode::Digit0 => 0x30, + KeyCode::Digit1 => 0x31, + KeyCode::Digit2 => 0x32, + KeyCode::Digit3 => 0x33, + KeyCode::Digit4 => 0x34, + KeyCode::Digit5 => 0x35, + KeyCode::Digit6 => 0x36, + KeyCode::Digit7 => 0x37, + KeyCode::Digit8 => 0x38, + KeyCode::Digit9 => 0x39, + + // Function keys + KeyCode::F1 => 0x70, + KeyCode::F2 => 0x71, + KeyCode::F3 => 0x72, + KeyCode::F4 => 0x73, + KeyCode::F5 => 0x74, + KeyCode::F6 => 0x75, + KeyCode::F7 => 0x76, + KeyCode::F8 => 0x77, + KeyCode::F9 => 0x78, + KeyCode::F10 => 0x79, + KeyCode::F11 => 0x7A, + KeyCode::F12 => 0x7B, + + // Special keys + KeyCode::Enter => 0x0D, + KeyCode::Space => 0x20, + KeyCode::Backspace => 0x08, + KeyCode::Delete => 0x2E, + KeyCode::Tab => 0x09, + KeyCode::Escape => 0x1B, + KeyCode::Insert => 0x2D, + KeyCode::Home => 0x24, + KeyCode::End => 0x23, + KeyCode::PageUp => 0x21, + KeyCode::PageDown => 0x22, + + // Arrow keys + KeyCode::ArrowLeft => 0x25, + KeyCode::ArrowUp => 0x26, + KeyCode::ArrowRight => 0x27, + KeyCode::ArrowDown => 0x28, + + // Modifier keys + KeyCode::ShiftLeft | KeyCode::ShiftRight => 0x10, + KeyCode::ControlLeft | KeyCode::ControlRight => 0x11, + KeyCode::AltLeft | KeyCode::AltRight => 0x12, + KeyCode::SuperLeft => 0x5B, // Left Windows key + KeyCode::SuperRight => 0x5C, // Right Windows key + + // Lock keys + KeyCode::CapsLock => 0x14, + KeyCode::NumLock => 0x90, + KeyCode::ScrollLock => 0x91, + + // Punctuation + KeyCode::Semicolon => 0xBA, + KeyCode::Equal => 0xBB, + KeyCode::Comma => 0xBC, + KeyCode::Minus => 0xBD, + KeyCode::Period => 0xBE, + KeyCode::Slash => 0xBF, + KeyCode::Backquote => 0xC0, + KeyCode::BracketLeft => 0xDB, + KeyCode::Backslash => 0xDC, + KeyCode::BracketRight => 0xDD, + KeyCode::Quote => 0xDE, + + // Numpad + KeyCode::Numpad0 => 0x60, + KeyCode::Numpad1 => 0x61, + KeyCode::Numpad2 => 0x62, + KeyCode::Numpad3 => 0x63, + KeyCode::Numpad4 => 0x64, + KeyCode::Numpad5 => 0x65, + KeyCode::Numpad6 => 0x66, + KeyCode::Numpad7 => 0x67, + KeyCode::Numpad8 => 0x68, + KeyCode::Numpad9 => 0x69, + KeyCode::NumpadMultiply => 0x6A, + KeyCode::NumpadAdd => 0x6B, + KeyCode::NumpadSubtract => 0x6D, + KeyCode::NumpadDecimal => 0x6E, + KeyCode::NumpadDivide => 0x6F, + + // Default case for unhandled keys + _ => 0, + } +} + +// fn is_special_key(keycode: &KeyCode) -> bool { +// matches!( +// keycode, +// KeyCode::Enter +// | KeyCode::Space +// | KeyCode::Backspace +// | KeyCode::Delete +// | KeyCode::Tab +// | KeyCode::Escape +// | KeyCode::Insert +// | KeyCode::Home +// | KeyCode::End +// | KeyCode::PageUp +// | KeyCode::PageDown +// ) +// } + +/// Native key codes for different platforms based on MDN documentation +/// [`Keyboard_event_key_values`](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) +fn to_native_key_code(keycode: &KeyCode) -> u32 { + match keycode { + // Letters - Platform specific native codes + KeyCode::KeyA => { + if cfg!(target_os = "macos") { + 0x00 + } else { + 0x41 + } // Linux/default + } + KeyCode::KeyB => { + if cfg!(target_os = "macos") { + 0x0B + } else { + 0x42 + } + } + KeyCode::KeyC => { + if cfg!(target_os = "macos") { + 0x08 + } else { + 0x43 + } + } + KeyCode::KeyD => { + if cfg!(target_os = "macos") { + 0x02 + } else { + 0x44 + } + } + KeyCode::KeyE => { + if cfg!(target_os = "macos") { + 0x0E + } else { + 0x45 + } + } + KeyCode::KeyF => { + if cfg!(target_os = "macos") { + 0x03 + } else { + 0x46 + } + } + KeyCode::KeyG => { + if cfg!(target_os = "macos") { + 0x05 + } else { + 0x47 + } + } + KeyCode::KeyH => { + if cfg!(target_os = "macos") { + 0x04 + } else { + 0x48 + } + } + KeyCode::KeyI => { + if cfg!(target_os = "macos") { + 0x22 + } else { + 0x49 + } + } + KeyCode::KeyJ => { + if cfg!(target_os = "macos") { + 0x26 + } else { + 0x4A + } + } + KeyCode::KeyK => { + if cfg!(target_os = "macos") { + 0x28 + } else { + 0x4B + } + } + KeyCode::KeyL => { + if cfg!(target_os = "macos") { + 0x25 + } else { + 0x4C + } + } + KeyCode::KeyM => { + if cfg!(target_os = "macos") { + 0x2E + } else { + 0x4D + } + } + KeyCode::KeyN => { + if cfg!(target_os = "macos") { + 0x2D + } else { + 0x4E + } + } + KeyCode::KeyO => { + if cfg!(target_os = "macos") { + 0x1F + } else { + 0x4F + } + } + KeyCode::KeyP => { + if cfg!(target_os = "macos") { + 0x23 + } else { + 0x50 + } + } + KeyCode::KeyQ => { + if cfg!(target_os = "macos") { + 0x0C + } else { + 0x51 + } + } + KeyCode::KeyR => { + if cfg!(target_os = "macos") { + 0x0F + } else { + 0x52 + } + } + KeyCode::KeyS => { + if cfg!(target_os = "macos") { + 0x01 + } else { + 0x53 + } + } + KeyCode::KeyT => { + if cfg!(target_os = "macos") { + 0x11 + } else { + 0x54 + } + } + KeyCode::KeyU => { + if cfg!(target_os = "macos") { + 0x20 + } else { + 0x55 + } + } + KeyCode::KeyV => { + if cfg!(target_os = "macos") { + 0x09 + } else { + 0x56 + } + } + KeyCode::KeyW => { + if cfg!(target_os = "macos") { + 0x0D + } else { + 0x57 + } + } + KeyCode::KeyX => { + if cfg!(target_os = "macos") { + 0x07 + } else { + 0x58 + } + } + KeyCode::KeyY => { + if cfg!(target_os = "macos") { + 0x10 + } else { + 0x59 + } + } + KeyCode::KeyZ => { + if cfg!(target_os = "macos") { + 0x06 + } else { + 0x5A + } + } + + // Numbers + KeyCode::Digit0 => { + if cfg!(target_os = "macos") { + 0x1D + } else { + 0x30 + } + } + KeyCode::Digit1 => { + if cfg!(target_os = "macos") { + 0x12 + } else { + 0x31 + } + } + KeyCode::Digit2 => { + if cfg!(target_os = "macos") { + 0x13 + } else { + 0x32 + } + } + KeyCode::Digit3 => { + if cfg!(target_os = "macos") { + 0x14 + } else { + 0x33 + } + } + KeyCode::Digit4 => { + if cfg!(target_os = "macos") { + 0x15 + } else { + 0x34 + } + } + KeyCode::Digit5 => { + if cfg!(target_os = "macos") { + 0x17 + } else { + 0x35 + } + } + KeyCode::Digit6 => { + if cfg!(target_os = "macos") { + 0x16 + } else { + 0x36 + } + } + KeyCode::Digit7 => { + if cfg!(target_os = "macos") { + 0x1A + } else { + 0x37 + } + } + KeyCode::Digit8 => { + if cfg!(target_os = "macos") { + 0x1C + } else { + 0x38 + } + } + KeyCode::Digit9 => { + if cfg!(target_os = "macos") { + 0x19 + } else { + 0x39 + } + } + + // Function keys + KeyCode::F1 => { + if cfg!(target_os = "macos") { + 0x7A + } else { + 0x70 + } + } + KeyCode::F2 => { + if cfg!(target_os = "macos") { + 0x78 + } else { + 0x71 + } + } + KeyCode::F3 => { + if cfg!(target_os = "macos") { + 0x63 + } else { + 0x72 + } + } + KeyCode::F4 => { + if cfg!(target_os = "macos") { + 0x76 + } else { + 0x73 + } + } + KeyCode::F5 => { + if cfg!(target_os = "macos") { + 0x60 + } else { + 0x74 + } + } + KeyCode::F6 => { + if cfg!(target_os = "macos") { + 0x61 + } else { + 0x75 + } + } + KeyCode::F7 => { + if cfg!(target_os = "macos") { + 0x62 + } else { + 0x76 + } + } + KeyCode::F8 => { + if cfg!(target_os = "macos") { + 0x64 + } else { + 0x77 + } + } + KeyCode::F9 => { + if cfg!(target_os = "macos") { + 0x65 + } else { + 0x78 + } + } + KeyCode::F10 => { + if cfg!(target_os = "macos") { + 0x6D + } else { + 0x79 + } + } + KeyCode::F11 => { + if cfg!(target_os = "macos") { + 0x67 + } else { + 0x7A + } + } + KeyCode::F12 => { + if cfg!(target_os = "macos") { + 0x6F + } else { + 0x7B + } + } + + // Special keys + KeyCode::Enter => { + if cfg!(target_os = "macos") { + 0x24 + } else { + 0x0D + } + } + KeyCode::Space => { + if cfg!(target_os = "macos") { + 0x31 + } else { + 0x20 + } + } + KeyCode::Backspace => { + if cfg!(target_os = "macos") { + 0x33 + } else { + 0x08 + } + } + KeyCode::Delete => { + if cfg!(target_os = "macos") { + 0x75 + } else { + 0x2E + } + } + KeyCode::Tab => { + if cfg!(target_os = "macos") { + 0x30 + } else { + 0x09 + } + } + KeyCode::Escape => { + if cfg!(target_os = "macos") { + 0x35 + } else { + 0x1B + } + } + KeyCode::Insert => { + if cfg!(target_os = "macos") { + 0x72 + } else { + 0x2D + } + } + KeyCode::Home => { + if cfg!(target_os = "macos") { + 0x73 + } else { + 0x24 + } + } + KeyCode::End => { + if cfg!(target_os = "macos") { + 0x77 + } else { + 0x23 + } + } + KeyCode::PageUp => { + if cfg!(target_os = "macos") { + 0x74 + } else { + 0x21 + } + } + KeyCode::PageDown => { + if cfg!(target_os = "macos") { + 0x79 + } else { + 0x22 + } + } + + // Arrow keys + KeyCode::ArrowLeft => { + if cfg!(target_os = "macos") { + 0x7B + } else { + 0x25 + } + } + KeyCode::ArrowUp => { + if cfg!(target_os = "macos") { + 0x7E + } else { + 0x26 + } + } + KeyCode::ArrowRight => { + if cfg!(target_os = "macos") { + 0x7C + } else { + 0x27 + } + } + KeyCode::ArrowDown => { + if cfg!(target_os = "macos") { + 0x7D + } else { + 0x28 + } + } + + // Modifier keys + KeyCode::ShiftLeft => { + if cfg!(target_os = "macos") { + 0x38 + } else { + 0xA0 + } + } + KeyCode::ShiftRight => { + if cfg!(target_os = "macos") { + 0x3C + } else { + 0xA1 + } + } + KeyCode::ControlLeft => { + if cfg!(target_os = "macos") { + 0x3B + } else { + 0xA2 + } + } + KeyCode::ControlRight => { + if cfg!(target_os = "macos") { + 0x3E + } else { + 0xA3 + } + } + KeyCode::AltLeft => { + if cfg!(target_os = "macos") { + 0x3A + } else { + 0xA4 + } + } + KeyCode::AltRight => { + if cfg!(target_os = "macos") { + 0x3D + } else { + 0xA5 + } + } + KeyCode::SuperLeft => { + if cfg!(target_os = "macos") { + 0x37 + } else { + 0x5B + } + } + KeyCode::SuperRight => { + if cfg!(target_os = "macos") { + 0x36 + } else { + 0x5C + } + } + + // Lock keys + KeyCode::CapsLock => { + if cfg!(target_os = "macos") { + 0x39 + } else { + 0x14 + } + } + KeyCode::NumLock => { + if cfg!(target_os = "macos") { + 0x47 + } else { + 0x90 + } + } + KeyCode::ScrollLock => 0x91, + + // Punctuation + KeyCode::Semicolon => { + if cfg!(target_os = "macos") { + 0x29 + } else { + 0xBA + } + } + KeyCode::Equal => { + if cfg!(target_os = "macos") { + 0x18 + } else { + 0xBB + } + } + KeyCode::Comma => { + if cfg!(target_os = "macos") { + 0x2B + } else { + 0xBC + } + } + KeyCode::Minus => { + if cfg!(target_os = "macos") { + 0x1B + } else { + 0xBD + } + } + KeyCode::Period => { + if cfg!(target_os = "macos") { + 0x2F + } else { + 0xBE + } + } + KeyCode::Slash => { + if cfg!(target_os = "macos") { + 0x2C + } else { + 0xBF + } + } + KeyCode::Backquote => { + if cfg!(target_os = "macos") { + 0x32 + } else { + 0xC0 + } + } + KeyCode::BracketLeft => { + if cfg!(target_os = "macos") { + 0x21 + } else { + 0xDB + } + } + KeyCode::Backslash => { + if cfg!(target_os = "macos") { + 0x2A + } else { + 0xDC + } + } + KeyCode::BracketRight => { + if cfg!(target_os = "macos") { + 0x1E + } else { + 0xDD + } + } + KeyCode::Quote => { + if cfg!(target_os = "macos") { + 0x27 + } else { + 0xDE + } + } + + // Numpad + KeyCode::Numpad0 => { + if cfg!(target_os = "macos") { + 0x52 + } else { + 0x60 + } + } + KeyCode::Numpad1 => { + if cfg!(target_os = "macos") { + 0x53 + } else { + 0x61 + } + } + KeyCode::Numpad2 => { + if cfg!(target_os = "macos") { + 0x54 + } else { + 0x62 + } + } + KeyCode::Numpad3 => { + if cfg!(target_os = "macos") { + 0x55 + } else { + 0x63 + } + } + KeyCode::Numpad4 => { + if cfg!(target_os = "macos") { + 0x56 + } else { + 0x64 + } + } + KeyCode::Numpad5 => { + if cfg!(target_os = "macos") { + 0x57 + } else { + 0x65 + } + } + KeyCode::Numpad6 => { + if cfg!(target_os = "macos") { + 0x58 + } else { + 0x66 + } + } + KeyCode::Numpad7 => { + if cfg!(target_os = "macos") { + 0x59 + } else { + 0x67 + } + } + KeyCode::Numpad8 => { + if cfg!(target_os = "macos") { + 0x5B + } else { + 0x68 + } + } + KeyCode::Numpad9 => { + if cfg!(target_os = "macos") { + 0x5C + } else { + 0x69 + } + } + KeyCode::NumpadMultiply => { + if cfg!(target_os = "macos") { + 0x43 + } else { + 0x6A + } + } + KeyCode::NumpadAdd => { + if cfg!(target_os = "macos") { + 0x45 + } else { + 0x6B + } + } + KeyCode::NumpadSubtract => { + if cfg!(target_os = "macos") { + 0x4E + } else { + 0x6D + } + } + KeyCode::NumpadDecimal => { + if cfg!(target_os = "macos") { + 0x41 + } else { + 0x6E + } + } + KeyCode::NumpadDivide => { + if cfg!(target_os = "macos") { + 0x4B + } else { + 0x6F + } + } + + // Default case for unhandled keys + _ => 0, + } +} diff --git a/crates/bevy_cef_core/src/browser_process/client_handler.rs b/crates/bevy_cef_core/src/browser_process/client_handler.rs new file mode 100644 index 0000000..3c9a377 --- /dev/null +++ b/crates/bevy_cef_core/src/browser_process/client_handler.rs @@ -0,0 +1,129 @@ +mod brp_handler; +mod js_emit_event_handler; + +use crate::browser_process::ContextMenuHandlerBuilder; +use crate::prelude::IntoString; +use cef::rc::{Rc, RcImpl}; +use cef::{ + Browser, Client, ContextMenuHandler, DisplayHandler, Frame, ImplClient, ImplProcessMessage, + ListValue, ProcessId, ProcessMessage, RenderHandler, WrapClient, sys, +}; +use std::os::raw::c_int; + +pub use brp_handler::BrpHandler; +pub use js_emit_event_handler::{IpcEventRaw, JsEmitEventHandler}; + +pub trait ProcessMessageHandler { + fn process_name(&self) -> &'static str; + + fn handle_message(&self, browser: &mut Browser, frame: &mut Frame, args: Option); +} + +/// ## Reference +/// +/// - [`CefBrowser Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefBrowser.html) +pub struct ClientHandlerBuilder { + object: *mut RcImpl, + render_handler: RenderHandler, + context_menu_handler: ContextMenuHandler, + message_handlers: Vec>, + display_handler: Option, +} + +impl ClientHandlerBuilder { + pub fn new(render_handler: RenderHandler) -> Self { + Self { + object: std::ptr::null_mut(), + render_handler, + context_menu_handler: ContextMenuHandlerBuilder::build(), + message_handlers: Vec::new(), + display_handler: None, + } + } + + pub fn with_display_handler(mut self, display_handler: DisplayHandler) -> Self { + self.display_handler = Some(display_handler); + self + } + + pub fn with_message_handler(mut self, handler: impl ProcessMessageHandler + 'static) -> Self { + self.message_handlers.push(std::rc::Rc::new(handler)); + self + } + + pub fn build(self) -> Client { + Client::new(self) + } +} + +impl Rc for ClientHandlerBuilder { + fn as_base(&self) -> &sys::cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl WrapClient for ClientHandlerBuilder { + fn wrap_rc(&mut self, object: *mut RcImpl) { + self.object = object; + } +} + +impl Clone for ClientHandlerBuilder { + fn clone(&self) -> Self { + let object = unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + rc_impl + }; + + Self { + object, + render_handler: self.render_handler.clone(), + context_menu_handler: self.context_menu_handler.clone(), + message_handlers: self.message_handlers.clone(), + display_handler: self.display_handler.clone(), + } + } +} + +impl ImplClient for ClientHandlerBuilder { + fn render_handler(&self) -> Option { + Some(self.render_handler.clone()) + } + + fn display_handler(&self) -> Option { + self.display_handler.clone() + } + + fn on_process_message_received( + &self, + browser: Option<&mut Browser>, + frame: Option<&mut Frame>, + _: ProcessId, + message: Option<&mut ProcessMessage>, + ) -> c_int { + if let Some(message) = message + && let Some(browser) = browser + && let Some(frame) = frame + && let Some(name) = Some(message.name().into_string()) + && let Some(handler) = self + .message_handlers + .iter() + .find(|h| h.process_name() == name.as_str()) + { + { + let args = message.argument_list(); + handler.handle_message(browser, frame, args); + } + }; + 1 + } + + #[inline] + fn get_raw(&self) -> *mut sys::_cef_client_t { + self.object.cast() + } +} diff --git a/crates/bevy_cef_core/src/browser_process/client_handler/brp_handler.rs b/crates/bevy_cef_core/src/browser_process/client_handler/brp_handler.rs new file mode 100644 index 0000000..06f2ea7 --- /dev/null +++ b/crates/bevy_cef_core/src/browser_process/client_handler/brp_handler.rs @@ -0,0 +1,68 @@ +use crate::browser_process::client_handler::ProcessMessageHandler; +use crate::prelude::PROCESS_MESSAGE_BRP; +use crate::util::IntoString; +use async_channel::Sender; +use bevy::tasks::IoTaskPool; +use bevy_remote::{BrpMessage, BrpRequest}; +use cef::{ + Browser, Frame, ImplFrame, ImplListValue, ImplProcessMessage, ListValue, ProcessId, + process_message_create, +}; +use cef_dll_sys::cef_process_id_t; + +pub struct BrpHandler { + sender: Sender, +} + +impl BrpHandler { + pub const fn new(sender: Sender) -> Self { + Self { sender } + } +} + +impl ProcessMessageHandler for BrpHandler { + fn process_name(&self) -> &'static str { + PROCESS_MESSAGE_BRP + } + + fn handle_message(&self, _browser: &mut Browser, frame: &mut Frame, args: Option) { + if let Some(args) = args + && let Ok(request) = serde_json::from_str::(&args.string(1).into_string()) + { + let id = args.string(0).into_string(); + let frame = frame.clone(); + let brp_sender = self.sender.clone(); + IoTaskPool::get() + .spawn(async move { + let (tx, rx) = async_channel::unbounded(); + if brp_sender + .send(BrpMessage { + method: request.method, + params: request.params, + sender: tx, + }) + .await + .is_err() + { + return; + } + if let Ok(result) = rx.recv().await + && let Some(mut message) = + process_message_create(Some(&PROCESS_MESSAGE_BRP.into())) + && let Some(argument_list) = message.argument_list() + { + argument_list.set_string(0, Some(&id.as_str().into())); + argument_list.set_string( + 1, + Some(&serde_json::to_string(&result).unwrap().as_str().into()), + ); + frame.send_process_message( + ProcessId::from(cef_process_id_t::PID_RENDERER), + Some(&mut message), + ); + } + }) + .detach(); + } + } +} diff --git a/crates/bevy_cef_core/src/browser_process/client_handler/js_emit_event_handler.rs b/crates/bevy_cef_core/src/browser_process/client_handler/js_emit_event_handler.rs new file mode 100644 index 0000000..dbf36b2 --- /dev/null +++ b/crates/bevy_cef_core/src/browser_process/client_handler/js_emit_event_handler.rs @@ -0,0 +1,39 @@ +use crate::browser_process::client_handler::ProcessMessageHandler; +use crate::prelude::{IntoString, PROCESS_MESSAGE_JS_EMIT}; +use async_channel::Sender; +use bevy::prelude::Entity; +use cef::{Browser, Frame, ImplListValue, ListValue}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct IpcEventRaw { + pub webview: Entity, + pub payload: String, +} + +pub struct JsEmitEventHandler { + webview: Entity, + sender: Sender, +} + +impl JsEmitEventHandler { + pub const fn new(webview: Entity, sender: Sender) -> Self { + Self { sender, webview } + } +} + +impl ProcessMessageHandler for JsEmitEventHandler { + fn process_name(&self) -> &'static str { + PROCESS_MESSAGE_JS_EMIT + } + + fn handle_message(&self, _browser: &mut Browser, _frame: &mut Frame, args: Option) { + if let Some(args) = args { + let event = IpcEventRaw { + webview: self.webview, // Placeholder, should be set correctly + payload: args.string(0).into_string(), + }; + let _ = self.sender.send_blocking(event); + } + } +} diff --git a/crates/bevy_cef_core/src/browser_process/context_menu_handler.rs b/crates/bevy_cef_core/src/browser_process/context_menu_handler.rs new file mode 100644 index 0000000..eda1363 --- /dev/null +++ b/crates/bevy_cef_core/src/browser_process/context_menu_handler.rs @@ -0,0 +1,50 @@ +use cef::rc::{Rc, RcImpl}; +use cef::{ImplContextMenuHandler, WrapContextMenuHandler, sys}; + +/// ## Reference +/// +/// - [`CefContextMenuHandler Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefContextMenuHandler.html) +pub struct ContextMenuHandlerBuilder { + object: *mut RcImpl, +} + +impl ContextMenuHandlerBuilder { + pub fn build() -> cef::ContextMenuHandler { + cef::ContextMenuHandler::new(Self { + object: core::ptr::null_mut(), + }) + } +} + +impl WrapContextMenuHandler for ContextMenuHandlerBuilder { + fn wrap_rc(&mut self, object: *mut RcImpl) { + self.object = object; + } +} + +impl Rc for ContextMenuHandlerBuilder { + fn as_base(&self) -> &sys::cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + core::mem::transmute(&base.cef_object) + } + } +} + +impl Clone for ContextMenuHandlerBuilder { + fn clone(&self) -> Self { + let object = unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + rc_impl + }; + Self { object } + } +} + +impl ImplContextMenuHandler for ContextMenuHandlerBuilder { + #[inline] + fn get_raw(&self) -> *mut sys::cef_context_menu_handler_t { + self.object as *mut sys::cef_context_menu_handler_t + } +} diff --git a/crates/bevy_cef_core/src/browser_process/display_handler.rs b/crates/bevy_cef_core/src/browser_process/display_handler.rs new file mode 100644 index 0000000..1969dec --- /dev/null +++ b/crates/bevy_cef_core/src/browser_process/display_handler.rs @@ -0,0 +1,165 @@ +use async_channel::Sender; +use bevy::log::{error, info, trace, warn}; +use bevy::window::SystemCursorIcon; +use cef::rc::{ConvertParam, Rc, RcImpl}; +use cef::{ + Browser, CefString, CursorInfo, CursorType, ImplDisplayHandler, LogSeverity, + WrapDisplayHandler, sys, +}; +use cef_dll_sys::{cef_cursor_type_t, cef_log_severity_t}; +use std::os::raw::c_int; + +pub type SystemCursorIconSenderInner = Sender; + +/// ## Reference +/// +/// - [`CefDisplayHandler Class Reference`](https://cef-builds.spotifycdn.com/docs/112.3/classCefDisplayHandler.html#af1cc8410a0b1a97166923428d3794636) +pub struct DisplayHandlerBuilder { + object: *mut RcImpl, + cursor_icon: SystemCursorIconSenderInner, +} + +impl DisplayHandlerBuilder { + pub fn build(cursor_icon: SystemCursorIconSenderInner) -> cef::DisplayHandler { + cef::DisplayHandler::new(Self { + object: core::ptr::null_mut(), + cursor_icon, + }) + } +} + +impl Rc for DisplayHandlerBuilder { + fn as_base(&self) -> &sys::cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + core::mem::transmute(&base.cef_object) + } + } +} + +impl Clone for DisplayHandlerBuilder { + fn clone(&self) -> Self { + let object = unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + rc_impl + }; + Self { + object, + cursor_icon: self.cursor_icon.clone(), + } + } +} + +impl WrapDisplayHandler for DisplayHandlerBuilder { + fn wrap_rc(&mut self, object: *mut RcImpl) { + self.object = object; + } +} + +impl ImplDisplayHandler for DisplayHandlerBuilder { + fn on_console_message( + &self, + _: Option<&mut Browser>, + level: LogSeverity, + message: Option<&CefString>, + source: Option<&CefString>, + line: c_int, + ) -> c_int { + let message = format!( + "{}\nline:{line}\n{}", + source.map(|s| s.to_string()).unwrap_or_default(), + message.map(|m| m.to_string()).unwrap_or_default() + ); + match level.into_raw() { + cef_log_severity_t::LOGSEVERITY_ERROR => { + error!("{message}"); + } + cef_log_severity_t::LOGSEVERITY_WARNING => { + warn!("{message}"); + } + cef_log_severity_t::LOGSEVERITY_VERBOSE => { + trace!("{message}"); + } + _ => { + info!("{message}"); + } + } + 1 + } + + fn on_cursor_change( + &self, + _browser: Option<&mut Browser>, + _cursor: *mut u8, + type_: CursorType, + _: Option<&CursorInfo>, + ) -> c_int { + let _ = self + .cursor_icon + .send_blocking(to_system_cursor_icon(type_.into_raw())); + 1 + } + + #[inline] + fn get_raw(&self) -> *mut sys::cef_display_handler_t { + self.object.cast() + } +} + +pub fn to_system_cursor_icon(cursor_type: cef_dll_sys::cef_cursor_type_t) -> SystemCursorIcon { + match cursor_type { + cef_cursor_type_t::CT_POINTER => SystemCursorIcon::Default, + cef_cursor_type_t::CT_CROSS => SystemCursorIcon::Crosshair, + cef_cursor_type_t::CT_HAND => SystemCursorIcon::Pointer, + cef_cursor_type_t::CT_IBEAM => SystemCursorIcon::Text, + cef_cursor_type_t::CT_WAIT => SystemCursorIcon::Wait, + cef_cursor_type_t::CT_HELP => SystemCursorIcon::Help, + cef_cursor_type_t::CT_EASTRESIZE => SystemCursorIcon::EResize, + cef_cursor_type_t::CT_NORTHRESIZE => SystemCursorIcon::NResize, + cef_cursor_type_t::CT_NORTHEASTRESIZE => SystemCursorIcon::NeResize, + cef_cursor_type_t::CT_NORTHWESTRESIZE => SystemCursorIcon::NwResize, + cef_cursor_type_t::CT_SOUTHRESIZE => SystemCursorIcon::SResize, + cef_cursor_type_t::CT_SOUTHEASTRESIZE => SystemCursorIcon::SeResize, + cef_cursor_type_t::CT_SOUTHWESTRESIZE => SystemCursorIcon::SwResize, + cef_cursor_type_t::CT_WESTRESIZE => SystemCursorIcon::WResize, + cef_cursor_type_t::CT_NORTHSOUTHRESIZE => SystemCursorIcon::NsResize, + cef_cursor_type_t::CT_EASTWESTRESIZE => SystemCursorIcon::EwResize, + cef_cursor_type_t::CT_NORTHEASTSOUTHWESTRESIZE => SystemCursorIcon::NeswResize, + cef_cursor_type_t::CT_NORTHWESTSOUTHEASTRESIZE => SystemCursorIcon::NwseResize, + cef_cursor_type_t::CT_COLUMNRESIZE => SystemCursorIcon::ColResize, + cef_cursor_type_t::CT_ROWRESIZE => SystemCursorIcon::RowResize, + cef_cursor_type_t::CT_MIDDLEPANNING => SystemCursorIcon::AllScroll, + cef_cursor_type_t::CT_EASTPANNING => SystemCursorIcon::AllScroll, + cef_cursor_type_t::CT_NORTHPANNING => SystemCursorIcon::AllScroll, + cef_cursor_type_t::CT_NORTHEASTPANNING => SystemCursorIcon::AllScroll, + cef_cursor_type_t::CT_NORTHWESTPANNING => SystemCursorIcon::AllScroll, + cef_cursor_type_t::CT_SOUTHPANNING => SystemCursorIcon::AllScroll, + cef_cursor_type_t::CT_SOUTHEASTPANNING => SystemCursorIcon::AllScroll, + cef_cursor_type_t::CT_SOUTHWESTPANNING => SystemCursorIcon::AllScroll, + cef_cursor_type_t::CT_WESTPANNING => SystemCursorIcon::AllScroll, + cef_cursor_type_t::CT_MOVE => SystemCursorIcon::Move, + cef_cursor_type_t::CT_VERTICALTEXT => SystemCursorIcon::VerticalText, + cef_cursor_type_t::CT_CELL => SystemCursorIcon::Cell, + cef_cursor_type_t::CT_CONTEXTMENU => SystemCursorIcon::ContextMenu, + cef_cursor_type_t::CT_ALIAS => SystemCursorIcon::Alias, + cef_cursor_type_t::CT_PROGRESS => SystemCursorIcon::Progress, + cef_cursor_type_t::CT_NODROP => SystemCursorIcon::NoDrop, + cef_cursor_type_t::CT_COPY => SystemCursorIcon::Copy, + cef_cursor_type_t::CT_NONE => SystemCursorIcon::Default, + cef_cursor_type_t::CT_NOTALLOWED => SystemCursorIcon::NotAllowed, + cef_cursor_type_t::CT_ZOOMIN => SystemCursorIcon::ZoomIn, + cef_cursor_type_t::CT_ZOOMOUT => SystemCursorIcon::ZoomOut, + cef_cursor_type_t::CT_GRAB => SystemCursorIcon::Grab, + cef_cursor_type_t::CT_GRABBING => SystemCursorIcon::Grabbing, + cef_cursor_type_t::CT_MIDDLE_PANNING_VERTICAL => SystemCursorIcon::AllScroll, + cef_cursor_type_t::CT_MIDDLE_PANNING_HORIZONTAL => SystemCursorIcon::AllScroll, + cef_cursor_type_t::CT_CUSTOM => SystemCursorIcon::Default, + cef_cursor_type_t::CT_DND_NONE => SystemCursorIcon::Default, + cef_cursor_type_t::CT_DND_MOVE => SystemCursorIcon::Move, + cef_cursor_type_t::CT_DND_COPY => SystemCursorIcon::Copy, + cef_cursor_type_t::CT_DND_LINK => SystemCursorIcon::Alias, + cef_cursor_type_t::CT_NUM_VALUES => SystemCursorIcon::Default, + _ => SystemCursorIcon::Default, + } +} diff --git a/crates/bevy_cef_core/src/browser_process/localhost.rs b/crates/bevy_cef_core/src/browser_process/localhost.rs new file mode 100644 index 0000000..8a03e1e --- /dev/null +++ b/crates/bevy_cef_core/src/browser_process/localhost.rs @@ -0,0 +1,281 @@ +mod data_responser; +mod headers_responser; + +use crate::browser_process::localhost::data_responser::{DataResponser, parse_bytes_single_range}; +use crate::browser_process::localhost::headers_responser::HeadersResponser; +use crate::prelude::IntoString; +use async_channel::{Receiver, Sender}; +use bevy::asset::Asset; +use bevy::prelude::*; +use bevy::tasks::IoTaskPool; +use cef::rc::{Rc, RcImpl}; +use cef::{ + Browser, Callback, CefString, Frame, ImplCallback, ImplRequest, ImplResourceHandler, + ImplResponse, ImplSchemeHandlerFactory, Request, ResourceHandler, ResourceReadCallback, + Response, SchemeHandlerFactory, WrapResourceHandler, WrapSchemeHandlerFactory, sys, +}; +use cef_dll_sys::{_cef_resource_handler_t, cef_base_ref_counted_t}; +use serde::{Deserialize, Serialize}; +use std::os::raw::c_int; +use std::sync::{Arc, Mutex}; + +/// `cef://` scheme response asset. +#[derive(Asset, Reflect, Debug, Clone, Serialize, Deserialize)] +#[reflect(Debug, Serialize, Deserialize)] +pub struct CefResponse { + /// The media type. + pub mime_type: String, + /// The status code of the response, e.g., 200 for OK, 404 for Not Found. + pub status_code: u32, + /// The response data, typically HTML or other content. + pub data: Vec, +} + +impl Default for CefResponse { + fn default() -> Self { + Self { + mime_type: "text/html".to_string(), + status_code: 404, + data: b"

404 Not Found

".to_vec(), + } + } +} + +#[derive(Debug, Clone, Component)] +pub struct Responser(pub Sender); + +#[derive(Resource, Debug, Clone, Deref)] +pub struct Requester(pub Sender); + +#[derive(Resource, Debug, Clone)] +pub struct RequesterReceiver(pub Receiver); + +#[derive(Debug, Clone)] +pub struct CefRequest { + pub uri: String, + pub responser: Responser, +} + +/// Use to register a local schema handler for the CEF browser. +/// +/// ## Reference +/// +/// - [`CefSchemeHandlerFactory Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefSchemeHandlerFactory.html) +pub struct LocalSchemaHandlerBuilder { + object: *mut RcImpl, + requester: Requester, +} + +impl LocalSchemaHandlerBuilder { + pub fn build(requester: Requester) -> SchemeHandlerFactory { + SchemeHandlerFactory::new(Self { + object: std::ptr::null_mut(), + requester, + }) + } +} + +impl Rc for LocalSchemaHandlerBuilder { + fn as_base(&self) -> &sys::cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl WrapSchemeHandlerFactory for LocalSchemaHandlerBuilder { + fn wrap_rc(&mut self, object: *mut RcImpl) { + self.object = object; + } +} + +impl Clone for LocalSchemaHandlerBuilder { + fn clone(&self) -> Self { + let object = unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + rc_impl + }; + Self { + object, + requester: self.requester.clone(), + } + } +} + +impl ImplSchemeHandlerFactory for LocalSchemaHandlerBuilder { + fn create( + &self, + _browser: Option<&mut Browser>, + _frame: Option<&mut Frame>, + _scheme_name: Option<&CefString>, + _request: Option<&mut Request>, + ) -> Option { + Some(LocalResourceHandlerBuilder::build(self.requester.clone())) + } + + #[inline] + fn get_raw(&self) -> *mut sys::_cef_scheme_handler_factory_t { + self.object.cast() + } +} + +struct LocalResourceHandlerBuilder { + object: *mut RcImpl<_cef_resource_handler_t, Self>, + requester: Requester, + headers: Arc>, + data: Arc>, +} + +impl LocalResourceHandlerBuilder { + fn build(requester: Requester) -> ResourceHandler { + ResourceHandler::new(Self { + object: std::ptr::null_mut(), + requester, + headers: Arc::new(Mutex::new(HeadersResponser::default())), + data: Arc::new(Mutex::new(DataResponser::default())), + }) + } +} + +impl WrapResourceHandler for LocalResourceHandlerBuilder { + fn wrap_rc(&mut self, object: *mut RcImpl) { + self.object = object; + } +} + +impl Clone for LocalResourceHandlerBuilder { + fn clone(&self) -> Self { + let object = unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + rc_impl + }; + Self { + object, + requester: self.requester.clone(), + headers: self.headers.clone(), + data: self.data.clone(), + } + } +} + +impl Rc for LocalResourceHandlerBuilder { + fn as_base(&self) -> &cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl ImplResourceHandler for LocalResourceHandlerBuilder { + fn open( + &self, + request: Option<&mut Request>, + handle_request: Option<&mut c_int>, + callback: Option<&mut Callback>, + ) -> c_int { + let Some(request) = request else { + // Cancel the request if no request is provided + return 0; + }; + let range_header_value = request.header_by_name(Some(&"Range".into())).into_string(); + let range = parse_bytes_single_range(&range_header_value); + let Some(callback) = callback.cloned() else { + // If no callback is provided, we cannot handle the request + return 0; + }; + if let Some(handle_request) = handle_request { + *handle_request = 0; + } + let url = request.url().into_string(); + let requester = self.requester.clone(); + let headers_responser = self.headers.clone(); + let data_responser = self.data.clone(); + IoTaskPool::get() + .spawn(async move { + let (tx, rx) = async_channel::bounded(1); + let _ = requester + .send(CefRequest { + uri: url + .strip_prefix("cef://localhost/") + .unwrap_or_default() + .to_string(), + responser: Responser(tx), + }) + .await; + let response = rx.recv().await.unwrap_or_default(); + headers_responser.lock().unwrap().prepare(&response, &range); + data_responser + .lock() + .unwrap() + .prepare(response.data, &range); + callback.cont(); + }) + .detach(); + 1 + } + + fn response_headers( + &self, + response: Option<&mut Response>, + response_length: Option<&mut i64>, + _redirect_url: Option<&mut CefString>, + ) { + let Ok(responser) = self.headers.lock() else { + return; + }; + if let Some(response) = response { + response.set_mime_type(Some(&responser.mime_type.as_str().into())); + response.set_status(responser.status_code as _); + for (name, value) in &responser.headers { + response.set_header_by_name( + Some(&name.as_str().into()), + Some(&value.as_str().into()), + false as _, + ); + } + } + if let Some(response_length) = response_length { + *response_length = responser.response_length as _; + } + } + + #[allow(clippy::not_unsafe_ptr_arg_deref)] + fn read( + &self, + data_out: *mut u8, + bytes_to_read: c_int, + bytes_read: Option<&mut c_int>, + _: Option<&mut ResourceReadCallback>, + ) -> c_int { + let Some(bytes_read) = bytes_read else { + // If no bytes_read is provided, we cannot read data + return 0; + }; + let Ok(mut responser) = self.data.lock() else { + return 0; + }; + match responser.read(bytes_to_read as _) { + Some(data) if !data.is_empty() => { + let n = data.len(); + unsafe { + std::ptr::copy_nonoverlapping(data.as_ptr(), data_out, n); + } + *bytes_read = n as i32; + 1 + } + _ => { + *bytes_read = 0; + 0 + } + } + } + + #[inline] + fn get_raw(&self) -> *mut _cef_resource_handler_t { + self.object.cast() + } +} diff --git a/crates/bevy_cef_core/src/browser_process/localhost/data_responser.rs b/crates/bevy_cef_core/src/browser_process/localhost/data_responser.rs new file mode 100644 index 0000000..cf6e1e2 --- /dev/null +++ b/crates/bevy_cef_core/src/browser_process/localhost/data_responser.rs @@ -0,0 +1,236 @@ +use bevy::prelude::*; + +#[derive(Default, Clone)] +pub struct DataResponser { + data: Vec, + offset: usize, + end_offset: usize, +} + +impl DataResponser { + /// Prepares the data and headers for the response. + /// + /// The range header values only support the `bytes` range unit type and single range. + /// TODO: Support multiple ranges. + pub fn prepare(&mut self, data: Vec, range: &Option<(usize, Option)>) { + if let Some((start, end)) = range { + self.offset = *start; + self.end_offset = end.unwrap_or(data.len() - 1) + 1; + self.data = data; + } else { + self.offset = 0; + self.end_offset = data.len(); + self.data = data; + } + } + + pub fn read(&mut self, bytes_to_read: isize) -> Option<&[u8]> { + if self.offset >= self.data.len() { + return None; + } + let start = self.offset; + let end = if bytes_to_read < 0 { + self.data.len() + } else { + (self.offset as isize + bytes_to_read) as usize + }; + let end = end.min(self.end_offset); + + if start >= end || start >= self.data.len() { + return None; + } + + let slice = &self.data[start..end.min(self.data.len())]; + self.offset += slice.len(); + Some(slice) + } +} + +pub fn parse_bytes_single_range(range_header_value: &str) -> Option<(usize, Option)> { + let ranges = parse_bytes_range(range_header_value)?; + ranges.first().cloned() +} + +/// Parses the `Range` header value from a request and returns the start of the range. +/// +/// ## Reference +/// +/// - [`Range_requests`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Range_requests) +fn parse_bytes_range(range_header_value: &str) -> Option)>> { + if !range_header_value.starts_with("bytes=") { + return None; + } + let mut ranges = Vec::new(); + let value = range_header_value.trim_start_matches("bytes="); + // bytes=100-200,300-400 => ["100-200", "300-400"] + let byte_ranges = value.split(","); + for range in byte_ranges { + // 100-200 => ["100", "200"] + let mut split = range.split("-"); + let start = split.next()?; + let end = split.next(); + let start = start.parse::().ok()?; + let end = end.and_then(|e| e.parse::().ok()); + ranges.push((start, end)); + } + Some(ranges) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn range_start_is_none_if_empty() { + assert_eq!(parse_bytes_range(""), None); + } + + #[test] + fn range_only_start_offset() { + assert_eq!(parse_bytes_range("bytes=100-"), Some(vec![(100, None)])); + } + + #[test] + fn range_one_bytes() { + assert_eq!( + parse_bytes_range("bytes=100-200"), + Some(vec![(100, Some(200))]) + ); + } + + #[test] + fn range_multiple_ranges() { + assert_eq!( + parse_bytes_range("bytes=100-200,300-400"), + Some(vec![(100, Some(200)), (300, Some(400))]) + ); + } + + #[test] + fn data_responser_new_with_start_and_end() { + let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let mut responser = DataResponser::default(); + responser.prepare(data.clone(), &Some((2, Some(7)))); + assert_eq!(responser.data, data); + assert_eq!(responser.offset, 2); + assert_eq!(responser.end_offset, 7); + } + + #[test] + fn data_responser_new_with_start_only() { + let data = vec![1, 2, 3, 4, 5]; + let mut responser = DataResponser::default(); + responser.prepare(data.clone(), &Some((3, None))); + + assert_eq!(responser.data, data); + assert_eq!(responser.offset, 3); + assert_eq!(responser.end_offset, 5); + } + + #[test] + fn data_responser_new_with_zero_start() { + let data = vec![1, 2, 3]; + let mut responser = DataResponser::default(); + responser.prepare(data.clone(), &Some((0, None))); + + assert_eq!(responser.data, data); + assert_eq!(responser.offset, 0); + assert_eq!(responser.end_offset, 2); + } + + #[test] + fn data_responser_new_with_empty_data() { + let data = vec![]; + let mut responser = DataResponser::default(); + responser.prepare(data.clone(), &Some((0, None))); + + assert_eq!(responser.data, data); + assert_eq!(responser.offset, 0); + assert_eq!(responser.end_offset, 0); + } + + #[test] + fn data_responser_new_with_start_beyond_data_length() { + let data = vec![1, 2, 3]; + let mut responser = DataResponser::default(); + responser.prepare(data.clone(), &Some((5, None))); + + assert_eq!(responser.data, data); + assert_eq!(responser.offset, 5); + assert_eq!(responser.end_offset, 3); + } + + #[test] + fn data_responser_new_with_end_beyond_data_length() { + let data = vec![1, 2, 3]; + let mut responser = DataResponser::default(); + responser.prepare(data.clone(), &Some((1, Some(10)))); + + assert_eq!(responser.data, data); + assert_eq!(responser.offset, 1); + assert_eq!(responser.end_offset, 10); + } + + #[test] + fn data_responser_read_no_end_data_smaller_than_bytes_to_read() { + let data = vec![1, 2, 3, 4, 5]; + let mut responser = DataResponser::default(); + responser.prepare(data, &Some((2, None))); + + let result = responser.read(10); + assert_eq!(result, Some(&[3, 4, 5][..])); + } + + #[test] + fn data_responser_read_no_end_data_larger_than_bytes_to_read() { + let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let mut responser = DataResponser::default(); + responser.prepare(data, &Some((2, None))); + + let result1 = responser.read(3); + assert_eq!(result1, Some(&[3, 4, 5][..])); + + let result2 = responser.read(3); + assert_eq!(result2, Some(&[6, 7, 8][..])); + } + + #[test] + fn data_responser_read_with_end_data_smaller_than_bytes_to_read() { + let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let mut responser = DataResponser::default(); + responser.prepare(data, &Some((2, Some(6)))); + + let result = responser.read(10); + assert_eq!(result, Some(&[3, 4, 5, 6][..])); + } + + #[test] + fn data_responser_read_with_end_data_larger_than_bytes_to_read() { + let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let mut responser = DataResponser::default(); + responser.prepare(data, &Some((1, Some(7)))); + + let result1 = responser.read(3); + assert_eq!(result1, Some(&[2, 3, 4][..])); + + let result2 = responser.read(3); + assert_eq!(result2, Some(&[5, 6, 7][..])); + } + #[test] + fn data_responser_read_consecutive_calls_until_end() { + let data = vec![1, 2, 3, 4, 5, 6, 7, 8]; + let mut responser = DataResponser::default(); + responser.prepare(data, &Some((1, Some(6)))); + + let result1 = responser.read(2); + assert_eq!(result1, Some(&[2, 3][..])); + + let result2 = responser.read(2); + assert_eq!(result2, Some(&[4, 5][..])); + + let result3 = responser.read(2); + assert_eq!(result3, Some(&[6][..])); + + let result4 = responser.read(2); + assert_eq!(result4, None); + } +} diff --git a/crates/bevy_cef_core/src/browser_process/localhost/headers_responser.rs b/crates/bevy_cef_core/src/browser_process/localhost/headers_responser.rs new file mode 100644 index 0000000..46d3ef7 --- /dev/null +++ b/crates/bevy_cef_core/src/browser_process/localhost/headers_responser.rs @@ -0,0 +1,236 @@ +use crate::prelude::CefResponse; + +#[derive(Clone, Default, Debug)] +pub struct HeadersResponser { + pub mime_type: String, + pub status_code: u32, + pub headers: Vec<(String, String)>, + pub response_length: usize, +} + +impl HeadersResponser { + pub fn prepare(&mut self, cef_response: &CefResponse, range: &Option<(usize, Option)>) { + self.mime_type = cef_response.mime_type.clone(); + self.status_code = if range.is_some() { + 206 // Partial Content + } else { + cef_response.status_code + }; + self.headers.clear(); + self.response_length = obtain_response_length(&cef_response.data, range); + if let Some(content_range) = content_range_header_value(&cef_response.data, range) { + self.headers + .push(("Content-Range".to_string(), content_range)); + self.headers + .push(("Accept-Ranges".to_string(), "bytes".to_string())); + } + } +} + +/// Create a `Content-Range` header value based on the provided data and range. +/// +/// If the range is `None`, since the request type is not a range request, it returns `None`. +/// +/// ## Reference +/// +/// - [206 Partial Content](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/206) +fn content_range_header_value( + data: &[u8], + range: &Option<(usize, Option)>, +) -> Option { + let (start, end) = range.as_ref()?; + Some(format!( + "bytes {}-{}/{}", + start, + end.unwrap_or(data.len() - 1), + data.len() + )) +} + +fn obtain_response_length(data: &[u8], range: &Option<(usize, Option)>) -> usize { + match range { + Some((start, end)) => end.unwrap_or(data.len() - 1) - start + 1, + None => data.len(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bevy::utils::default; + + #[test] + fn test_obtain_response_length_no_range() { + let data = b"Hello, World!"; + let result = obtain_response_length(data, &None); + assert_eq!(result, 13); + } + + #[test] + fn test_obtain_response_length_empty_data_no_range() { + let data = b""; + let result = obtain_response_length(data, &None); + assert_eq!(result, 0); + } + + #[test] + fn test_obtain_response_length_range_with_end() { + let data = b"Hello, World!"; + let result = obtain_response_length(data, &Some((0, Some(5)))); + assert_eq!(result, 5); + } + + #[test] + fn test_obtain_response_length_range_partial() { + let data = b"Hello, World!"; + let result = obtain_response_length(data, &Some((7, Some(12)))); + assert_eq!(result, 5); + } + + #[test] + fn test_obtain_response_length_range_without_end() { + let data = b"Hello, World!"; + let result = obtain_response_length(data, &Some((7, None))); + assert_eq!(result, 6); + } + + #[test] + fn test_obtain_response_length_range_from_start() { + let data = b"Hello, World!"; + let result = obtain_response_length(data, &Some((0, None))); + assert_eq!(result, 13); + } + + #[test] + fn test_obtain_response_length_range_zero_length() { + let data = b"Hello, World!"; + let result = obtain_response_length(data, &Some((5, Some(5)))); + assert_eq!(result, 0); + } + + #[test] + fn test_obtain_response_length_range_end_equals_data_len() { + let data = b"Hello, World!"; + let result = obtain_response_length(data, &Some((0, Some(13)))); + assert_eq!(result, 13); + } + + #[test] + fn test_obtain_response_length_empty_data_with_range() { + let data = b""; + let result = obtain_response_length(data, &Some((0, None))); + assert_eq!(result, 0); + } + + #[test] + fn test_obtain_response_length_large_data() { + let data = vec![0u8; 1024]; + let result = obtain_response_length(&data, &None); + assert_eq!(result, 1024); + } + + #[test] + fn test_obtain_response_length_large_data_with_range() { + let data = vec![0u8; 1024]; + let result = obtain_response_length(&data, &Some((100, Some(200)))); + assert_eq!(result, 100); + } + + #[test] + fn test_content_range_header_value_no_range() { + let data = b"Hello, World!"; + let result = content_range_header_value(data, &None); + assert_eq!(result, None); + } + + #[test] + fn test_content_range_header_value_range_with_end() { + let data = b"Hello, World!"; + let result = content_range_header_value(data, &Some((0, Some(5)))); + assert_eq!(result, Some("bytes 0-4/13".to_string())); + } + + #[test] + fn test_content_range_header_value_range_without_end() { + let data = b"Hello, World!"; + let result = content_range_header_value(data, &Some((7, None))); + assert_eq!(result, Some("bytes 7-12/13".to_string())); + } + + #[test] + fn test_content_range_header_value_range_from_start() { + let data = b"Hello, World!"; + let result = content_range_header_value(data, &Some((0, None))); + assert_eq!(result, Some("bytes 0-12/13".to_string())); + } + + #[test] + fn test_content_range_header_value_range_partial() { + let data = b"Hello, World!"; + let result = content_range_header_value(data, &Some((7, Some(12)))); + assert_eq!(result, Some("bytes 7-11/13".to_string())); + } + + #[test] + fn test_content_range_header_value_range_single_byte() { + let data = b"Hello, World!"; + let result = content_range_header_value(data, &Some((5, Some(6)))); + assert_eq!(result, Some("bytes 5-5/13".to_string())); + } + + #[test] + fn test_content_range_header_value_range_last_byte() { + let data = b"Hello, World!"; + let result = content_range_header_value(data, &Some((12, Some(13)))); + assert_eq!(result, Some("bytes 12-12/13".to_string())); + } + + #[test] + fn test_content_range_header_value_single_byte_data() { + let data = b"a"; + let result = content_range_header_value(data, &Some((0, None))); + assert_eq!(result, Some("bytes 0-0/1".to_string())); + } + + #[test] + fn test_content_range_header_value_large_data() { + let data = vec![0u8; 1024]; + let result = content_range_header_value(&data, &Some((100, Some(200)))); + assert_eq!(result, Some("bytes 100-199/1024".to_string())); + } + + #[test] + fn test_content_range_header_value_large_data_no_end() { + let data = vec![0u8; 1024]; + let result = content_range_header_value(&data, &Some((500, None))); + assert_eq!(result, Some("bytes 500-1023/1024".to_string())); + } + + #[test] + fn test_content_range_header_value_zero_start() { + let data = b"test"; + let result = content_range_header_value(data, &Some((0, Some(2)))); + assert_eq!(result, Some("bytes 0-1/4".to_string())); + } + + #[test] + fn test_content_range_header_value_range_end_equals_data_len() { + let data = b"Hello, World!"; + let result = content_range_header_value(data, &Some((0, Some(13)))); + assert_eq!(result, Some("bytes 0-12/13".to_string())); + } + + #[test] + fn status_code_is_206_for_partial_content() { + let data = b"Hello, World!"; + let mut headers_responser = HeadersResponser::default(); + headers_responser.prepare( + &CefResponse { + data: data.to_vec(), + ..default() + }, + &Some((0, Some(5))), + ); + assert_eq!(headers_responser.status_code, 206); + } +} diff --git a/crates/bevy_cef_core/src/browser_process/renderer_handler.rs b/crates/bevy_cef_core/src/browser_process/renderer_handler.rs new file mode 100644 index 0000000..56c0702 --- /dev/null +++ b/crates/bevy_cef_core/src/browser_process/renderer_handler.rs @@ -0,0 +1,167 @@ +use async_channel::{Receiver, Sender}; +use bevy::prelude::{Entity, Event}; +use cef::rc::{Rc, RcImpl}; +use cef::{ + Browser, CefString, ImplRenderHandler, PaintElementType, Range, Rect, RenderHandler, + WrapRenderHandler, sys, +}; +use cef_dll_sys::cef_paint_element_type_t; +use std::cell::Cell; +use std::os::raw::c_int; + +pub type TextureSender = Sender; + +pub type TextureReceiver = Receiver; + +/// The texture structure passed from [`CefRenderHandler::OnPaint`](https://cef-builds.spotifycdn.com/docs/106.1/classCefRenderHandler.html#a6547d5c9dd472e6b84706dc81d3f1741). +#[derive(Debug, Clone, PartialEq, Event)] +pub struct RenderTexture { + /// The entity of target rendering webview. + pub webview: Entity, + /// The type of the paint element. + pub ty: RenderPaintElementType, + /// The width of the texture. + pub width: u32, + /// The height of the texture. + pub height: u32, + /// This buffer will be `width` *`height` * 4 bytes in size and represents a BGRA image with an upper-left origin + pub buffer: Vec, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum RenderPaintElementType { + /// The main frame of the browser. + View, + /// The popup frame of the browser. + Popup, +} + +pub type SharedViewSize = std::rc::Rc>; +pub type SharedImeCaret = std::rc::Rc>; + +/// ## Reference +/// +/// - [`CefRenderHandler Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefRenderHandler.html) +pub struct RenderHandlerBuilder { + object: *mut RcImpl, + webview: Entity, + texture_sender: TextureSender, + size: SharedViewSize, + ime_caret: SharedImeCaret, +} + +impl RenderHandlerBuilder { + pub fn build( + webview: Entity, + texture_sender: TextureSender, + size: SharedViewSize, + ime_caret: SharedImeCaret, + ) -> RenderHandler { + RenderHandler::new(Self { + object: std::ptr::null_mut(), + webview, + texture_sender, + size, + ime_caret, + }) + } +} + +impl Rc for RenderHandlerBuilder { + fn as_base(&self) -> &sys::cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl WrapRenderHandler for RenderHandlerBuilder { + fn wrap_rc(&mut self, object: *mut RcImpl) { + self.object = object; + } +} + +impl Clone for RenderHandlerBuilder { + fn clone(&self) -> Self { + let object = unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + rc_impl + }; + Self { + object, + webview: self.webview, + texture_sender: self.texture_sender.clone(), + size: self.size.clone(), + ime_caret: self.ime_caret.clone(), + } + } +} + +impl ImplRenderHandler for RenderHandlerBuilder { + fn view_rect(&self, _browser: Option<&mut Browser>, rect: Option<&mut Rect>) { + if let Some(rect) = rect { + let size = self.size.get(); + rect.width = size.x as _; + rect.height = size.y as _; + } + } + + fn on_text_selection_changed( + &self, + _browser: Option<&mut Browser>, + _: Option<&CefString>, + selected_range: Option<&Range>, + ) { + if let Some(selected_range) = selected_range { + self.ime_caret.set(selected_range.to); + } + } + + #[allow(clippy::not_unsafe_ptr_arg_deref)] + fn on_paint( + &self, + _browser: Option<&mut Browser>, + type_: PaintElementType, + _dirty_rects_count: usize, + _dirty_rects: Option<&Rect>, + buffer: *const u8, + width: c_int, + height: c_int, + ) { + let ty = match type_.as_ref() { + cef_paint_element_type_t::PET_POPUP => RenderPaintElementType::Popup, + _ => RenderPaintElementType::View, + }; + let texture = RenderTexture { + webview: self.webview, + ty, + width: width as u32, + height: height as u32, + buffer: unsafe { + std::slice::from_raw_parts(buffer, (width * height * 4) as usize).to_vec() + }, + }; + let _ = self.texture_sender.send_blocking(texture); + } + + /// MEMO: This method only supports on Windows + /// + /// In Windows, this method is more performant than `on_paint`? + #[cfg(target_os = "windows")] + fn on_accelerated_paint( + &self, + _browser: Option<&mut Browser>, + _type_: PaintElementType, + _dirty_rects_count: usize, + _dirty_rects: Option<&Rect>, + _: Option<&AcceleratedPaintInfo>, + ) { + } + + #[inline] + fn get_raw(&self) -> *mut sys::_cef_render_handler_t { + self.object.cast() + } +} diff --git a/crates/bevy_cef_core/src/browser_process/request_context_handler.rs b/crates/bevy_cef_core/src/browser_process/request_context_handler.rs new file mode 100644 index 0000000..78ab745 --- /dev/null +++ b/crates/bevy_cef_core/src/browser_process/request_context_handler.rs @@ -0,0 +1,50 @@ +use cef::rc::{Rc, RcImpl}; +use cef::{ImplRequestContextHandler, WrapRequestContextHandler, sys}; + +/// ## Reference +/// +/// - [`CefRequestContextHandler Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefRequestContextHandler.html) +pub struct RequestContextHandlerBuilder { + object: *mut RcImpl, +} + +impl RequestContextHandlerBuilder { + pub fn build() -> cef::RequestContextHandler { + cef::RequestContextHandler::new(Self { + object: core::ptr::null_mut(), + }) + } +} + +impl WrapRequestContextHandler for RequestContextHandlerBuilder { + fn wrap_rc(&mut self, object: *mut RcImpl) { + self.object = object; + } +} + +impl Rc for RequestContextHandlerBuilder { + fn as_base(&self) -> &sys::cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + core::mem::transmute(&base.cef_object) + } + } +} + +impl Clone for RequestContextHandlerBuilder { + fn clone(&self) -> Self { + let object = unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + rc_impl + }; + Self { object } + } +} + +impl ImplRequestContextHandler for RequestContextHandlerBuilder { + #[inline] + fn get_raw(&self) -> *mut sys::_cef_request_context_handler_t { + self.object.cast() + } +} diff --git a/crates/bevy_cef_core/src/debug.rs b/crates/bevy_cef_core/src/debug.rs new file mode 100644 index 0000000..1e97dbd --- /dev/null +++ b/crates/bevy_cef_core/src/debug.rs @@ -0,0 +1,52 @@ +use cef::{load_library, unload_library}; +use std::env::home_dir; + +/// This loader is a modified version of [LibraryLoader](cef::library_loader::LibraryLoader) that can load the framework located in the home directory. +pub struct DebugLibraryLoader { + path: std::path::PathBuf, +} + +impl Default for DebugLibraryLoader { + fn default() -> Self { + Self::new() + } +} + +impl DebugLibraryLoader { + const FRAMEWORK_PATH: &'static str = + "Chromium Embedded Framework.framework/Chromium Embedded Framework"; + + pub fn new() -> Self { + let path = home_dir() + .unwrap() + .join(".local") + .join("share") + .join("cef") + .join(Self::FRAMEWORK_PATH) + .canonicalize() + .unwrap(); + + Self { path } + } + + // See [cef_load_library] for more documentation. + pub fn load(&self) -> bool { + Self::load_library(&self.path) + } + + fn load_library(name: &std::path::Path) -> bool { + use std::os::unix::ffi::OsStrExt; + let Ok(name) = std::ffi::CString::new(name.as_os_str().as_bytes()) else { + return false; + }; + unsafe { load_library(Some(&*name.as_ptr().cast())) == 1 } + } +} + +impl Drop for DebugLibraryLoader { + fn drop(&mut self) { + if unload_library() != 1 { + eprintln!("cannot unload framework {}", self.path.display()); + } + } +} diff --git a/crates/bevy_cef_core/src/lib.rs b/crates/bevy_cef_core/src/lib.rs new file mode 100644 index 0000000..a50b869 --- /dev/null +++ b/crates/bevy_cef_core/src/lib.rs @@ -0,0 +1,15 @@ +mod browser_process; +mod debug; +mod render_process; +mod util; + +pub mod prelude { + pub use crate::browser_process::*; + pub use crate::debug::*; + pub use crate::render_process::app::*; + pub use crate::render_process::brp::*; + pub use crate::render_process::emit::*; + pub use crate::render_process::execute_render_process; + pub use crate::render_process::render_process_handler::*; + pub use crate::util::*; +} diff --git a/crates/bevy_cef_core/src/render_process.rs b/crates/bevy_cef_core/src/render_process.rs new file mode 100644 index 0000000..bebbef5 --- /dev/null +++ b/crates/bevy_cef_core/src/render_process.rs @@ -0,0 +1,28 @@ +use crate::prelude::RenderProcessAppBuilder; +use cef::args::Args; +use cef::{api_hash, execute_process, sys}; + +pub mod app; +pub mod brp; +pub mod emit; +pub mod listen; +pub mod render_process_handler; + +/// Execute the CEF render process. +pub fn execute_render_process() { + let args = Args::new(); + #[cfg(target_os = "macos")] + let _loader = { + let loader = + cef::library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), true); + assert!(loader.load()); + loader + }; + let _ = api_hash(sys::CEF_API_VERSION_LAST, 0); + let mut app = RenderProcessAppBuilder::build(); + execute_process( + Some(args.as_main_args()), + Some(&mut app), + std::ptr::null_mut(), + ); +} diff --git a/crates/bevy_cef_core/src/render_process/app.rs b/crates/bevy_cef_core/src/render_process/app.rs new file mode 100644 index 0000000..8cd450c --- /dev/null +++ b/crates/bevy_cef_core/src/render_process/app.rs @@ -0,0 +1,63 @@ +use crate::prelude::RenderProcessHandlerBuilder; +use crate::util::{SCHEME_CEF, cef_scheme_flags}; +use cef::rc::{Rc, RcImpl}; +use cef::{ImplApp, ImplSchemeRegistrar, RenderProcessHandler, SchemeRegistrar, WrapApp}; +use cef_dll_sys::{_cef_app_t, cef_base_ref_counted_t}; + +#[derive(Default)] +pub struct RenderProcessAppBuilder { + object: *mut RcImpl<_cef_app_t, Self>, +} + +impl RenderProcessAppBuilder { + pub fn build() -> cef::App { + cef::App::new(Self { + object: core::ptr::null_mut(), + }) + } +} + +impl Clone for RenderProcessAppBuilder { + fn clone(&self) -> Self { + let object = unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + self.object + }; + Self { object } + } +} + +impl Rc for RenderProcessAppBuilder { + fn as_base(&self) -> &cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl ImplApp for RenderProcessAppBuilder { + fn render_process_handler(&self) -> Option { + Some(RenderProcessHandler::new( + RenderProcessHandlerBuilder::build(), + )) + } + + fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) { + if let Some(registrar) = registrar { + registrar.add_custom_scheme(Some(&SCHEME_CEF.into()), cef_scheme_flags() as _); + } + } + + #[inline] + fn get_raw(&self) -> *mut _cef_app_t { + self.object as *mut _cef_app_t + } +} + +impl WrapApp for RenderProcessAppBuilder { + fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) { + self.object = object; + } +} diff --git a/crates/bevy_cef_core/src/render_process/brp.rs b/crates/bevy_cef_core/src/render_process/brp.rs new file mode 100644 index 0000000..c310cf4 --- /dev/null +++ b/crates/bevy_cef_core/src/render_process/brp.rs @@ -0,0 +1,181 @@ +use crate::prelude::{BRP_PROMISES, PROCESS_MESSAGE_BRP, v8_value_to_json}; +use cef::rc::{ConvertParam, ConvertReturnValue, Rc, RcImpl}; +use cef::{ + CefString, Frame, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Handler, ImplV8Value, + ProcessId, V8Handler, V8Value, WrapV8Handler, process_message_create, sys, + v8_value_create_promise, v8_value_create_string, +}; +use cef_dll_sys::{_cef_v8_handler_t, _cef_v8_value_t, cef_process_id_t, cef_string_t}; +use std::os::raw::c_int; +use std::ptr::write; + +/// Implements the `window.brp` function in JavaScript. +/// +/// The function definition is `async (request: BrpRequest) -> Promise`. +/// +/// The flow from the execution of the function to the return of the result to the Javascript side is as follows: +/// 1. Send `BrpRequest` to the browser process. +/// 2. The browser process receives `BrpResult` and sends it to the render process. +/// 3. The render process receives the result in `on_process_message_received`. +/// 4. The render process resolves the result to the `Promise` of `window.brp`. +pub struct BrpBuilder { + object: *mut RcImpl<_cef_v8_handler_t, Self>, + frame: Frame, +} + +impl BrpBuilder { + pub fn build(frame: Frame) -> V8Handler { + V8Handler::new(Self { + object: core::ptr::null_mut(), + frame, + }) + } +} + +impl Rc for BrpBuilder { + fn as_base(&self) -> &sys::cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl WrapV8Handler for BrpBuilder { + fn wrap_rc(&mut self, object: *mut RcImpl<_cef_v8_handler_t, Self>) { + self.object = object; + } +} + +impl Clone for BrpBuilder { + fn clone(&self) -> Self { + let object = unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + rc_impl + }; + Self { + object, + frame: self.frame.clone(), + } + } +} + +impl ImplV8Handler for BrpBuilder { + fn execute( + &self, + _: Option<&CefString>, + _: Option<&mut V8Value>, + arguments: Option<&[Option]>, + ret: Option<&mut Option>, + _: Option<&mut CefString>, + ) -> c_int { + if let Some(mut process) = process_message_create(Some(&PROCESS_MESSAGE_BRP.into())) + && let Some(promise) = v8_value_create_promise() + { + if let Some(arguments_list) = process.argument_list() + && let Some(arguments) = arguments + && let Some(Some(arg)) = arguments.first() + && let Some(brp_request) = v8_value_to_json(arg) + && let Ok(brp_request) = serde_json::to_string(&brp_request) + && let Some(ret) = ret + { + let id = uuid::Uuid::new_v4().to_string(); + arguments_list.set_string(0, Some(&id.as_str().into())); + arguments_list.set_string(1, Some(&brp_request.as_str().into())); + self.frame.send_process_message( + ProcessId::from(cef_process_id_t::PID_BROWSER), + Some(&mut process), + ); + ret.replace(promise.clone()); + let mut promises = BRP_PROMISES.lock().unwrap(); + promises.insert(id, promise); + } else { + let mut exception = + v8_value_create_string(Some(&"Failed to execute BRP request".into())); + promise.resolve_promise(exception.as_mut()); + } + } + 1 + } + + fn init_methods(object: &mut _cef_v8_handler_t) { + init_methods::(object); + } + + fn get_raw(&self) -> *mut _cef_v8_handler_t { + self.object.cast() + } +} + +fn init_methods(object: &mut _cef_v8_handler_t) { + object.execute = Some(execute::); +} + +extern "C" fn execute( + self_: *mut _cef_v8_handler_t, + name: *const cef_string_t, + object: *mut _cef_v8_value_t, + arguments_count: usize, + arguments: *const *mut _cef_v8_value_t, + retval: *mut *mut _cef_v8_value_t, + exception: *mut cef_string_t, +) -> c_int { + let (arg_self_, arg_name, arg_object, arg_arguments_count, arg_arguments, _, arg_exception) = ( + self_, + name, + object, + arguments_count, + arguments, + retval, + exception, + ); + let arg_self_: &RcImpl<_, I> = RcImpl::get(arg_self_); + let arg_name = if arg_name.is_null() { + None + } else { + Some(arg_name.into()) + }; + let arg_name = arg_name.as_ref(); + let mut arg_object = + unsafe { arg_object.as_mut() }.map(|arg| (arg as *mut _cef_v8_value_t).wrap_result()); + let arg_object = arg_object.as_mut(); + let vec_arguments = unsafe { arg_arguments.as_ref() }.map(|arg| { + let arg = + unsafe { std::slice::from_raw_parts(std::ptr::from_ref(arg), arg_arguments_count) }; + arg.iter() + .map(|arg| { + if arg.is_null() { + None + } else { + Some((*arg).wrap_result()) + } + }) + .collect::>() + }); + let arg_arguments = vec_arguments.as_deref(); + let mut arg_retval: Option = None; + let arg = Some(&mut arg_retval); + let mut arg_exception = if arg_exception.is_null() { + None + } else { + Some(arg_exception.into()) + }; + let arg_exception = arg_exception.as_mut(); + let r = ImplV8Handler::execute( + &arg_self_.interface, + arg_name, + arg_object, + arg_arguments, + arg, + arg_exception, + ); + if let Some(ret) = arg_retval { + // When the result is received, the pointer should be updated here + // and the exception should also be updated. + unsafe { + write(retval, ret.into_raw()); + } + } + r +} diff --git a/crates/bevy_cef_core/src/render_process/emit.rs b/crates/bevy_cef_core/src/render_process/emit.rs new file mode 100644 index 0000000..6f55f05 --- /dev/null +++ b/crates/bevy_cef_core/src/render_process/emit.rs @@ -0,0 +1,82 @@ +use crate::prelude::PROCESS_MESSAGE_JS_EMIT; +use crate::util::v8_value_to_json; +use cef::rc::{Rc, RcImpl}; +use cef::{ + CefString, Frame, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Handler, ProcessId, + V8Handler, V8Value, WrapV8Handler, process_message_create, sys, +}; +use cef_dll_sys::cef_process_id_t; +use std::os::raw::c_int; + +pub struct EmitBuilder { + object: *mut RcImpl, + frame: Frame, +} + +impl EmitBuilder { + pub fn build(frame: Frame) -> V8Handler { + V8Handler::new(Self { + object: core::ptr::null_mut(), + frame, + }) + } +} + +impl Rc for EmitBuilder { + fn as_base(&self) -> &sys::cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl WrapV8Handler for EmitBuilder { + fn wrap_rc(&mut self, object: *mut RcImpl) { + self.object = object; + } +} + +impl Clone for EmitBuilder { + fn clone(&self) -> Self { + let object = unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + rc_impl + }; + Self { + object, + frame: self.frame.clone(), + } + } +} + +impl ImplV8Handler for EmitBuilder { + fn execute( + &self, + _: Option<&CefString>, + _: Option<&mut V8Value>, + arguments: Option<&[Option]>, + _: Option<&mut Option>, + _: Option<&mut CefString>, + ) -> c_int { + if let Some(mut process) = process_message_create(Some(&PROCESS_MESSAGE_JS_EMIT.into())) + && let Some(arguments_list) = process.argument_list() + && let Some(arguments) = arguments + && let Some(Some(arg)) = arguments.first() + && let Some(arg) = v8_value_to_json(arg) + && let Ok(arg) = serde_json::to_string(&arg) + { + arguments_list.set_string(0, Some(&arg.as_str().into())); + self.frame.send_process_message( + ProcessId::from(cef_process_id_t::PID_BROWSER), + Some(&mut process), + ); + } + 1 + } + + fn get_raw(&self) -> *mut sys::_cef_v8_handler_t { + self.object.cast() + } +} diff --git a/crates/bevy_cef_core/src/render_process/listen.rs b/crates/bevy_cef_core/src/render_process/listen.rs new file mode 100644 index 0000000..d744478 --- /dev/null +++ b/crates/bevy_cef_core/src/render_process/listen.rs @@ -0,0 +1,77 @@ +use crate::prelude::LISTEN_EVENTS; +use crate::util::IntoString; +use cef::rc::{Rc, RcImpl}; +use cef::{CefString, Frame, ImplV8Handler, ImplV8Value, V8Handler, V8Value, WrapV8Handler, sys}; +use std::os::raw::c_int; + +pub struct ListenBuilder { + object: *mut RcImpl, + frame: Frame, +} + +impl ListenBuilder { + pub fn build(frame: Frame) -> V8Handler { + V8Handler::new(Self { + object: core::ptr::null_mut(), + frame, + }) + } +} + +impl Rc for ListenBuilder { + fn as_base(&self) -> &sys::cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl WrapV8Handler for ListenBuilder { + fn wrap_rc(&mut self, object: *mut RcImpl) { + self.object = object; + } +} + +impl Clone for ListenBuilder { + fn clone(&self) -> Self { + let object = unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + rc_impl + }; + Self { + object, + frame: self.frame.clone(), + } + } +} + +impl ImplV8Handler for ListenBuilder { + fn execute( + &self, + _: Option<&CefString>, + _: Option<&mut V8Value>, + arguments: Option<&[Option]>, + _: Option<&mut Option>, + _: Option<&mut CefString>, + ) -> c_int { + if let Some(arguments) = arguments + && let Some(Some(id)) = arguments.first() + && 0 < id.is_string() + && let Some(Some(callback)) = arguments.get(1) + && 0 < callback.is_function() + { + LISTEN_EVENTS + .lock() + .unwrap() + .insert(id.string_value().into_string(), callback.clone()); + } + 1 + } + + #[inline] + fn get_raw(&self) -> *mut sys::_cef_v8_handler_t { + self.object.cast() + } +} diff --git a/crates/bevy_cef_core/src/render_process/render_process_handler.rs b/crates/bevy_cef_core/src/render_process/render_process_handler.rs new file mode 100644 index 0000000..2ed4cb4 --- /dev/null +++ b/crates/bevy_cef_core/src/render_process/render_process_handler.rs @@ -0,0 +1,191 @@ +use crate::prelude::{EmitBuilder, IntoString}; +use crate::render_process::brp::BrpBuilder; +use crate::render_process::listen::ListenBuilder; +use crate::util::json_to_v8; +use crate::util::v8_accessor::V8DefaultAccessorBuilder; +use crate::util::v8_interceptor::V8DefaultInterceptorBuilder; +use bevy::platform::collections::HashMap; +use bevy_remote::BrpResult; +use cef::rc::{Rc, RcImpl}; +use cef::{ + Browser, Frame, ImplFrame, ImplListValue, ImplProcessMessage, ImplRenderProcessHandler, + ImplV8Context, ImplV8Value, ProcessId, ProcessMessage, V8Context, V8Propertyattribute, V8Value, + WrapRenderProcessHandler, sys, v8_value_create_function, v8_value_create_object, +}; +use std::os::raw::c_int; +use std::sync::Mutex; + +pub(crate) static BRP_PROMISES: Mutex> = Mutex::new(HashMap::new()); +pub(crate) static LISTEN_EVENTS: Mutex> = Mutex::new(HashMap::new()); + +pub const PROCESS_MESSAGE_BRP: &str = "brp"; +pub const PROCESS_MESSAGE_HOST_EMIT: &str = "host-emit"; +pub const PROCESS_MESSAGE_JS_EMIT: &str = "js-emit"; + +pub struct RenderProcessHandlerBuilder { + object: *mut RcImpl, +} + +impl RenderProcessHandlerBuilder { + pub fn build() -> RenderProcessHandlerBuilder { + RenderProcessHandlerBuilder { + object: core::ptr::null_mut(), + } + } +} + +impl WrapRenderProcessHandler for RenderProcessHandlerBuilder { + fn wrap_rc(&mut self, object: *mut RcImpl) { + self.object = object; + } +} + +impl Rc for RenderProcessHandlerBuilder { + fn as_base(&self) -> &sys::cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl Clone for RenderProcessHandlerBuilder { + fn clone(&self) -> Self { + let object = unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + rc_impl + }; + Self { object } + } +} + +impl ImplRenderProcessHandler for RenderProcessHandlerBuilder { + fn on_context_created( + &self, + _browser: Option<&mut Browser>, + frame: Option<&mut Frame>, + context: Option<&mut V8Context>, + ) { + if let Some(g) = context.and_then(|c| c.global()) + && let Some(frame) = frame + && let Some(mut cef) = v8_value_create_object( + Some(&mut V8DefaultAccessorBuilder::build()), + Some(&mut V8DefaultInterceptorBuilder::build()), + ) + && let Some(mut brp) = v8_value_create_function( + Some(&"brp".into()), + Some(&mut BrpBuilder::build(frame.clone())), + ) + && let Some(mut emit) = v8_value_create_function( + Some(&"emit".into()), + Some(&mut EmitBuilder::build(frame.clone())), + ) + && let Some(mut listen) = v8_value_create_function( + Some(&"listen".into()), + Some(&mut ListenBuilder::build(frame.clone())), + ) + { + cef.set_value_bykey( + Some(&"brp".into()), + Some(&mut brp), + V8Propertyattribute::default(), + ); + cef.set_value_bykey( + Some(&"emit".into()), + Some(&mut emit), + V8Propertyattribute::default(), + ); + cef.set_value_bykey( + Some(&"listen".into()), + Some(&mut listen), + V8Propertyattribute::default(), + ); + g.set_value_bykey( + Some(&"cef".into()), + Some(&mut cef), + V8Propertyattribute::default(), + ); + }; + } + + fn on_process_message_received( + &self, + _browser: Option<&mut Browser>, + frame: Option<&mut Frame>, + _: ProcessId, + message: Option<&mut ProcessMessage>, + ) -> c_int { + if let Some(message) = message + && let Some(frame) = frame + && let Some(ctx) = frame.v8_context() + { + match message.name().into_string().as_str() { + PROCESS_MESSAGE_BRP => { + handle_brp_message(message, ctx); + } + PROCESS_MESSAGE_HOST_EMIT => { + handle_listen_message(message, ctx); + } + _ => {} + } + }; + 1 + } + + #[inline] + fn get_raw(&self) -> *mut sys::_cef_render_process_handler_t { + self.object.cast() + } +} + +fn handle_brp_message(message: &ProcessMessage, ctx: V8Context) { + let Some(argument_list) = message.argument_list() else { + return; + }; + let id = argument_list.string(0).into_string(); + let payload = argument_list.string(1).into_string(); + let Ok(Some(promise)) = BRP_PROMISES.lock().map(|mut p| p.remove(&id)) else { + return; + }; + + if let Ok(brp_result) = serde_json::from_str::(&payload) { + ctx.enter(); + match brp_result { + Ok(v) => { + promise.resolve_promise(json_to_v8(v).as_mut()); + } + Err(e) => { + promise.reject_promise(Some(&e.message.as_str().into())); + } + } + ctx.exit(); + } +} + +fn handle_listen_message(message: &ProcessMessage, mut ctx: V8Context) { + let Some(argument_list) = message.argument_list() else { + return; + }; + let id = argument_list.string(0).into_string(); + let payload = argument_list.string(1).into_string(); + + ctx.enter(); + if let Ok(value) = serde_json::from_str::(&payload) + && let Ok(events) = LISTEN_EVENTS.lock() + { + let mut obj = v8_value_create_object( + Some(&mut V8DefaultAccessorBuilder::build()), + Some(&mut V8DefaultInterceptorBuilder::build()), + ); + let Some(callback) = events.get(&id) else { + return; + }; + callback.execute_function_with_context( + Some(&mut ctx), + obj.as_mut(), + Some(&[json_to_v8(value)]), + ); + } + ctx.exit(); +} diff --git a/crates/bevy_cef_core/src/util.rs b/crates/bevy_cef_core/src/util.rs new file mode 100644 index 0000000..277262c --- /dev/null +++ b/crates/bevy_cef_core/src/util.rs @@ -0,0 +1,140 @@ +pub mod v8_accessor; +mod v8_handler_wrapper; +pub mod v8_interceptor; + +use crate::util::v8_accessor::V8DefaultAccessorBuilder; +use crate::util::v8_interceptor::V8DefaultInterceptorBuilder; +use cef::rc::ConvertParam; +use cef::{ + CefStringList, CefStringUserfreeUtf16, CefStringUtf16, ImplV8Value, V8Propertyattribute, + v8_value_create_array, v8_value_create_bool, v8_value_create_double, v8_value_create_int, + v8_value_create_null, v8_value_create_object, v8_value_create_string, +}; +use cef_dll_sys::_cef_string_utf16_t; +use cef_dll_sys::cef_scheme_options_t::{ + CEF_SCHEME_OPTION_CORS_ENABLED, CEF_SCHEME_OPTION_LOCAL, CEF_SCHEME_OPTION_SECURE, + CEF_SCHEME_OPTION_STANDARD, +}; +use std::env::home_dir; +use std::path::PathBuf; + +pub const SCHEME_CEF: &str = "cef"; + +pub const HOST_CEF: &str = "localhost"; + +pub fn cef_scheme_flags() -> u32 { + CEF_SCHEME_OPTION_STANDARD as u32 + | CEF_SCHEME_OPTION_SECURE as u32 + | CEF_SCHEME_OPTION_LOCAL as u32 + | CEF_SCHEME_OPTION_CORS_ENABLED as u32 +} + +pub fn debug_chromium_libraries_path() -> PathBuf { + debug_chromium_embedded_framework_dir_path().join("Libraries") +} + +pub fn debug_chromium_embedded_framework_dir_path() -> PathBuf { + debug_cef_path().join("Chromium Embedded Framework.framework") +} + +pub fn debug_cef_path() -> PathBuf { + home_dir().unwrap().join(".local").join("share").join("cef") +} + +pub fn debug_render_process_path() -> PathBuf { + cargo_bin_path().join("bevy_cef_debug_render_process") +} + +pub fn cargo_bin_path() -> PathBuf { + home_dir().unwrap().join(".cargo").join("bin") +} + +pub trait IntoString { + fn into_string(self) -> String; +} + +impl IntoString for CefStringUserfreeUtf16 { + fn into_string(self) -> String { + let ptr: *mut _cef_string_utf16_t = self.into_raw(); + CefStringUtf16::from(ptr).to_string() + } +} + +pub fn v8_value_to_json(v8: &cef::V8Value) -> Option { + if v8.is_bool().is_positive() { + Some(serde_json::Value::Bool(v8.bool_value().is_positive())) + } else if v8.is_int().is_positive() { + Some(serde_json::Value::Number(serde_json::Number::from( + v8.int_value(), + ))) + } else if v8.is_double().is_positive() { + Some(serde_json::Value::Number( + serde_json::Number::from_f64(v8.double_value()).unwrap(), + )) + } else if v8.is_string().is_positive() { + Some(serde_json::Value::String(v8.string_value().into_string())) + } else if v8.is_null().is_positive() || v8.is_undefined().is_positive() { + Some(serde_json::Value::Null) + } else if v8.is_array().is_positive() { + let mut array = Vec::new(); + let mut keys = CefStringList::new(); + v8.keys(Some(&mut keys)); + for key in keys.into_iter() { + if let Some(v) = v8.value_bykey(Some(&key.as_str().into())) + && let Some(serialized) = v8_value_to_json(&v) + { + { + array.push(serialized); + } + } + } + Some(serde_json::Value::Array(array)) + } else if v8.is_object().is_positive() { + let mut object = serde_json::Map::new(); + let mut keys = CefStringList::new(); + v8.keys(Some(&mut keys)); + for key in keys.into_iter() { + if let Some(v) = v8.value_bykey(Some(&key.as_str().into())) + && let Some(serialized) = v8_value_to_json(&v) + { + { + object.insert(key, serialized); + } + } + } + Some(serde_json::Value::Object(object)) + } else { + None + } +} + +pub fn json_to_v8(v: serde_json::Value) -> Option { + match v { + serde_json::Value::Null => v8_value_create_null(), + serde_json::Value::Bool(b) => v8_value_create_bool(b as _), + serde_json::Value::Number(n) if n.is_i64() => v8_value_create_int(n.as_i64()? as i32), + serde_json::Value::Number(n) => v8_value_create_double(n.as_f64()?), + serde_json::Value::String(s) => v8_value_create_string(Some(&s.as_str().into())), + serde_json::Value::Array(arr) => { + let v8_array = v8_value_create_array(arr.len() as _)?; + for (i, item) in arr.into_iter().enumerate() { + v8_array.set_value_byindex(i as _, json_to_v8(item).as_mut()); + } + Some(v8_array) + } + serde_json::Value::Object(obj) => { + let v8_object = v8_value_create_object( + Some(&mut V8DefaultAccessorBuilder::build()), + Some(&mut V8DefaultInterceptorBuilder::build()), + )?; + for (key, value) in obj { + v8_object.set_value_bykey( + Some(&key.as_str().into()), + json_to_v8(value).as_mut(), + V8Propertyattribute::default(), + ); + } + Some(v8_object) + } + } +} diff --git a/crates/bevy_cef_core/src/util/v8_accessor.rs b/crates/bevy_cef_core/src/util/v8_accessor.rs new file mode 100644 index 0000000..258b764 --- /dev/null +++ b/crates/bevy_cef_core/src/util/v8_accessor.rs @@ -0,0 +1,48 @@ +use cef::rc::{Rc, RcImpl}; +use cef::{ImplV8Accessor, V8Accessor, WrapV8Accessor, sys}; +use cef_dll_sys::_cef_v8_accessor_t; + +pub struct V8DefaultAccessorBuilder { + object: *mut RcImpl<_cef_v8_accessor_t, Self>, +} + +impl V8DefaultAccessorBuilder { + pub fn build() -> V8Accessor { + V8Accessor::new(Self { + object: core::ptr::null_mut(), + }) + } +} + +impl Rc for V8DefaultAccessorBuilder { + fn as_base(&self) -> &sys::cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl Clone for V8DefaultAccessorBuilder { + fn clone(&self) -> Self { + let object = unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + rc_impl + }; + Self { object } + } +} + +impl WrapV8Accessor for V8DefaultAccessorBuilder { + fn wrap_rc(&mut self, object: *mut RcImpl<_cef_v8_accessor_t, Self>) { + self.object = object; + } +} + +impl ImplV8Accessor for V8DefaultAccessorBuilder { + #[inline] + fn get_raw(&self) -> *mut _cef_v8_accessor_t { + self.object.cast() + } +} diff --git a/crates/bevy_cef_core/src/util/v8_handler_wrapper.rs b/crates/bevy_cef_core/src/util/v8_handler_wrapper.rs new file mode 100644 index 0000000..2f59244 --- /dev/null +++ b/crates/bevy_cef_core/src/util/v8_handler_wrapper.rs @@ -0,0 +1,70 @@ +// use std::os::raw::c_int; +// use bevy::ecs::reflect::ReflectCommandExt; +// use cef::rc::{Rc, RcImpl}; +// use cef::{sys, CefString, ImplV8Handler, V8Handler, V8Value, WrapV8Handler}; +// use cef_dll_sys::{_cef_v8_handler_t, _cef_v8_value_t, cef_string_t}; +// +// pub struct V8HandlerWrapBuilder { +// base: T, +// } +// +// impl V8HandlerWrapBuilder { +// pub fn build(base: T) -> V8Handler { +// V8Handler::new(Self { +// base, +// }) +// } +// } +// +// impl Rc for V8HandlerWrapBuilder { +// fn as_base(&self) -> &sys::cef_base_ref_counted_t { +// self.base.as_base() +// } +// } +// +// impl ImplV8Handler for V8HandlerWrapBuilder { +// fn execute(&self, name: Option<&CefString>, object: Option<&mut V8Value>, arguments: Option<&[Option]>, retval: Option<&mut Option>, exception: Option<&mut CefString>) -> c_int { +// self.base.execute( +// name, +// object, +// arguments, +// retval, +// exception, +// ) +// } +// +// fn get_raw(&self) -> *mut _cef_v8_handler_t { +// self.base.get_raw() +// } +// +// fn init_methods(object: &mut _cef_v8_handler_t) { +// T::init_methods(object); +// +// } +// } +// +// impl Clone for V8HandlerWrapBuilder { +// fn clone(&self) -> Self { +// Self{ +// base: self.base.clone(), +// } +// } +// } +// +// impl cef::WrapV8Handler for V8HandlerWrapBuilder { +// fn wrap_rc(&mut self, object: *mut RcImpl) { +// self.base.wrap_rc(object); +// } +// } +// +// // extern "C" fn execute( +// // self_: *mut _cef_v8_handler_t, +// // name: *const cef_string_t, +// // object: *mut _cef_v8_value_t, +// // arguments_count: usize, +// // arguments: *const *mut _cef_v8_value_t, +// // retval: *mut *mut _cef_v8_value_t, +// // exception: *mut cef_string_t, +// // ) -> ::std::os::raw::c_int { +// // +// // } diff --git a/crates/bevy_cef_core/src/util/v8_interceptor.rs b/crates/bevy_cef_core/src/util/v8_interceptor.rs new file mode 100644 index 0000000..e3ffbb5 --- /dev/null +++ b/crates/bevy_cef_core/src/util/v8_interceptor.rs @@ -0,0 +1,48 @@ +use cef::rc::{Rc, RcImpl}; +use cef::{ImplV8Interceptor, V8Interceptor, WrapV8Interceptor, sys}; +use cef_dll_sys::_cef_v8_interceptor_t; + +pub struct V8DefaultInterceptorBuilder { + object: *mut RcImpl<_cef_v8_interceptor_t, Self>, +} + +impl V8DefaultInterceptorBuilder { + pub fn build() -> V8Interceptor { + V8Interceptor::new(Self { + object: core::ptr::null_mut(), + }) + } +} + +impl WrapV8Interceptor for V8DefaultInterceptorBuilder { + fn wrap_rc(&mut self, object: *mut RcImpl<_cef_v8_interceptor_t, Self>) { + self.object = object; + } +} + +impl Rc for V8DefaultInterceptorBuilder { + fn as_base(&self) -> &sys::cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} + +impl Clone for V8DefaultInterceptorBuilder { + fn clone(&self) -> Self { + let object = unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + rc_impl + }; + Self { object } + } +} + +impl ImplV8Interceptor for V8DefaultInterceptorBuilder { + #[inline] + fn get_raw(&self) -> *mut _cef_v8_interceptor_t { + self.object.cast() + } +} diff --git a/crates/bevy_cef_debug_render_process/Cargo.toml b/crates/bevy_cef_debug_render_process/Cargo.toml new file mode 100644 index 0000000..ba64cd7 --- /dev/null +++ b/crates/bevy_cef_debug_render_process/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "bevy_cef_debug_render_process" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true + +[dependencies] +cef = { workspace = true } +bevy_cef_core = { workspace = true } +libloading = { version = "0.8"} \ No newline at end of file diff --git a/crates/bevy_cef_debug_render_process/README.md b/crates/bevy_cef_debug_render_process/README.md new file mode 100644 index 0000000..44aa934 --- /dev/null +++ b/crates/bevy_cef_debug_render_process/README.md @@ -0,0 +1,12 @@ +# bevy_cef_debug_render_process + +A debug render process for [bevy_cef](ttps://github.com/not-elm/bevy_cef) + +This application is a development tool to build CEF on macOS without requiring an app bundle, +and if you create a release bundle, should create a separate render process. + +## Install + +```shell +> cargo install bevy_cef_debug_render_process +``` \ No newline at end of file diff --git a/crates/bevy_cef_debug_render_process/src/main.rs b/crates/bevy_cef_debug_render_process/src/main.rs new file mode 100644 index 0000000..b5374f7 --- /dev/null +++ b/crates/bevy_cef_debug_render_process/src/main.rs @@ -0,0 +1,19 @@ +use bevy_cef_core::prelude::{DebugLibraryLoader, RenderProcessAppBuilder}; +use cef::{args::Args, *}; + +fn main() { + let args = Args::new(); + #[cfg(target_os = "macos")] + let _loader = { + let loader = DebugLibraryLoader::new(); + assert!(loader.load()); + loader + }; + let _ = api_hash(sys::CEF_API_VERSION_LAST, 0); + let mut app = RenderProcessAppBuilder::build(); + execute_process( + Some(args.as_main_args()), + Some(&mut app), + std::ptr::null_mut(), + ); +} diff --git a/docs/book.toml b/docs/book.toml new file mode 100644 index 0000000..c45336d --- /dev/null +++ b/docs/book.toml @@ -0,0 +1,5 @@ +[book] +authors = ["not-elm"] +language = "en" +src = "src" +title = "bevy_cef" diff --git a/docs/src/README.md b/docs/src/README.md new file mode 100644 index 0000000..8ce7f5e --- /dev/null +++ b/docs/src/README.md @@ -0,0 +1,91 @@ +# Introduction to bevy_cef + +**bevy_cef** is a powerful Bevy plugin that integrates the Chromium Embedded Framework (CEF) into Bevy applications, enabling you to render web content as 3D objects in your game world or as UI overlays. + +## What is bevy_cef? + +bevy_cef bridges the gap between modern web technologies and Bevy's 3D engine by: + +- **Embedding webviews** as textures on 3D meshes or 2D sprites +- **Supporting bidirectional communication** between JavaScript and Bevy systems +- **Providing a multi-process architecture** for stability and performance +- **Offering local asset serving** through a custom URL scheme +- **Enabling developer tools integration** for debugging web content + +## Key Features + +### 🌐 Web Content Rendering +- Render any web page as a texture on 3D objects +- Support for HTML5, CSS3, and modern JavaScript +- Local file serving via the `cef://localhost/` scheme +- Remote web page loading with full browser compatibility + +### 🔄 Inter-Process Communication (IPC) +- **JS Emit**: Send events from JavaScript to Bevy systems +- **Host Emit**: Send events from Bevy to JavaScript +- **Bevy Remote Protocol (BRP)**: Bidirectional RPC communication + +### 🎮 Interactive Controls +- Keyboard input forwarding to webviews +- Mouse interaction support +- Navigation controls (back, forward, refresh) +- Zoom level management +- Audio muting capabilities + +### 🔧 Developer Experience +- Chrome DevTools integration for debugging +- Hot-reload support for local assets +- Comprehensive error handling and logging +- Extensive customization options + +## Architecture Overview + +bevy_cef uses a multi-process architecture similar to modern web browsers: + +- **Browser Process**: The main Bevy application process +- **Render Process**: Separate CEF process for web content rendering +- **IPC Communication**: Secure inter-process communication channels + +This design ensures stability - if a web page crashes, it won't bring down your entire application. + +## Use Cases + +### Game UI +Create rich, responsive game interfaces using familiar web technologies: +```rust +commands.spawn(( + CefWebviewUri::local("ui/main-menu.html"), + // Render as 2D sprite overlay +)); +``` + +### In-World Displays +Embed interactive web content directly in your 3D world: +```rust +commands.spawn(( + CefWebviewUri::new("https://example.com"), + Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))), + MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())), +)); +``` + +### Data Visualization +Display real-time data using web-based charting libraries: +```rust +// Load a local HTML file with Chart.js or D3.js +commands.spawn(( + CefWebviewUri::local("charts/dashboard.html"), + WebviewSize(Vec2::new(1920.0, 1080.0)), +)); +``` + +### Development Tools +Integrate web-based development and debugging interfaces directly into your game editor or development build. + +## Getting Started + +Ready to integrate web content into your Bevy application? Check out the [Quick Start](quick-start.md) guide to get up and running in minutes, or dive into [Basic Concepts](basic-concepts.md) to understand the fundamental components and systems. + +## Platform Support + +Currently, bevy_cef focuses on macOS development with plans for expanded platform support. The plugin automatically handles CEF framework installation and configuration on supported platforms. \ No newline at end of file diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md new file mode 100644 index 0000000..07f48ef --- /dev/null +++ b/docs/src/SUMMARY.md @@ -0,0 +1,12 @@ +# Summary + +[Introduction](README.md) + +# Getting Started +- [Installation](installation.md) +- [Quick Start](quick-start.md) +- [Basic Concepts](basic-concepts.md) + +# User Guide +- [Core Components](core-components.md) + \ No newline at end of file diff --git a/docs/src/basic-concepts.md b/docs/src/basic-concepts.md new file mode 100644 index 0000000..0f0e194 --- /dev/null +++ b/docs/src/basic-concepts.md @@ -0,0 +1,274 @@ +# Basic Concepts + +Understanding these fundamental concepts will help you make the most of bevy_cef in your projects. + +## Multi-Process Architecture + +bevy_cef follows a multi-process architecture similar to modern web browsers: + +### Browser Process +- **Main Application**: Your Bevy game/app runs here +- **CEF Management**: Handles browser creation and management +- **IPC Coordination**: Manages communication with render processes + +### Render Process +- **Web Content**: Each webview runs in a separate process +- **Isolation**: Crashes in web content don't affect your main application +- **Sandboxing**: Enhanced security through process separation + +### Benefits +- **Stability**: Web content crashes won't crash your game +- **Performance**: CPU-intensive web content won't block your game loop +- **Security**: Sandboxed execution of untrusted web content + +## Core Components + +bevy_cef provides several key components that work together: + +### CefWebviewUri +The primary component that defines what web content to display: + +```rust +// Remote web page +CefWebviewUri::new("https://example.com") + +// Local HTML file from assets/ +CefWebviewUri::local("ui/menu.html") + +// Equivalent to above +CefWebviewUri::new("cef://localhost/ui/menu.html") +``` + +### WebviewSize +Controls the rendering resolution of the webview (not the 3D object size): + +```rust +WebviewSize(Vec2::new(1920.0, 1080.0)) // High resolution +WebviewSize(Vec2::new(800.0, 600.0)) // Standard resolution +WebviewSize::default() // 800x800 pixels +``` + +### Material Integration +Webviews integrate with Bevy's material system: + +```rust +// Standard material with webview texture +WebviewExtendStandardMaterial::default() + +// Custom material properties +WebviewExtendStandardMaterial { + base: StandardMaterial { + unlit: true, + emissive: Color::WHITE.into(), + ..default() + }, + ..default() +} +``` + +## Component Requirements + +When you add a `CefWebviewUri` component, bevy_cef automatically requires several other components: + +```rust +#[require(WebviewSize, ZoomLevel, AudioMuted)] +pub struct CefWebviewUri(pub String); +``` + +This means every webview entity automatically gets: +- **WebviewSize**: Default 800x800 resolution +- **ZoomLevel**: Default zoom (0.0 = browser default) +- **AudioMuted**: Default unmuted (false) + +## Rendering Modes + +bevy_cef supports different rendering approaches: + +### 3D Mesh Rendering +Render web content on 3D objects in your world: + +```rust +commands.spawn(( + CefWebviewUri::local("interface.html"), + Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))), + MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())), +)); +``` + +### 2D Sprite Rendering +Render web content as UI overlays: + +```rust +commands.spawn(( + CefWebviewUri::local("hud.html"), + Sprite::default(), + WebviewSpriteMaterial::default(), +)); +``` + +## Local Asset Serving + +bevy_cef includes a built-in web server that serves files from your `assets/` directory: + +### Custom Scheme +- **Scheme**: `cef://localhost/` +- **Path Mapping**: `assets/` directory maps to the root +- **Example**: `assets/ui/menu.html` → `cef://localhost/ui/menu.html` + +### Supported File Types +- HTML, CSS, JavaScript +- Images (PNG, JPG, SVG, etc.) +- Fonts (WOFF, TTF, etc.) +- JSON, XML, and other data files + +### Asset Organization +``` +assets/ +├── ui/ +│ ├── menu.html +│ ├── styles.css +│ └── script.js +├── images/ +│ └── logo.png +└── data/ + └── config.json +``` + +## Inter-Process Communication (IPC) + +bevy_cef provides three communication patterns: + +### 1. JavaScript to Bevy (JS Emit) +Send events from web content to Bevy systems: + +```javascript +// In your HTML/JavaScript +window.cef.emit('player_action', { action: 'jump', power: 10 }); +``` + +```rust +fn main(){ + App::new() + .add_plugins(JsEmitEventPlugin::::default()) + // ... +} + +// In your Bevy system +#[derive(Event, Deserialize)] +struct PlayerAction { + action: String, + power: i32, +} + +fn handle_player_action(trigger: Trigger) { + let action = trigger.event(); + info!("Player action: {} with power {}", action.action, action.power); +} +``` + +### 2. Bevy to JavaScript (Host Emit) +Send events from Bevy to web content: + +```rust +// In your Bevy system +commands.entity(webview_entity).trigger(HostEmitEvent { + event_name: "score_update".to_string(), + data: json!({ "score": 1000, "level": 3 }), +}); +``` + +```javascript +// In your HTML/JavaScript +window.cef.listen('score_update', (data) => { + document.getElementById('score').textContent = data.score; + document.getElementById('level').textContent = data.level; +}); +``` + +### 3. Bevy Remote Protocol (BRP) + +Please see [here](https://gist.github.com/coreh/1baf6f255d7e86e4be29874d00137d1d) for about the Bevy Remote Protocol (BRP). + +Bidirectional RPC calls for complex interactions: + +```rust +// Register BRP method in Bevy +app.add_plugins(RemotePlugin::default().with_method("get_player_stats", get_stats)); +``` + +```javascript +// Call from JavaScript +const stats = await window.cef.brp({ + method: 'get_player_stats', + params: { playerId: 42 } +}); +``` + +## User Interaction + +### Input Handling +Webviews automatically receive keyboard and mouse input when focused: +- **Keyboard**: All keyboard events are forwarded +- **Mouse**: Click, scroll, and hover events work naturally +- **Focus**: Multiple webviews can coexist; input goes to the focused one + +### Navigation +Control web navigation programmatically: + +```rust +commands.entity(webview).trigger(RequestGoBack); +commands.entity(webview).trigger(ReqeustGoForward); +``` + +### Zoom Control +Manage zoom levels per webview: + +```rust +// Set zoom level (0.0 = default, positive = zoom in, negative = zoom out) +commands.entity(webview).insert(ZoomLevel(1.2)); + +// Reset to default zoom +commands.entity(webview).insert(ZoomLevel(0.0)); +``` + +## Developer Experience + +### Developer Tools +Access Chrome DevTools for debugging: + +```rust +// Show developer tools for a webview +commands.entity(webview).trigger(RequestShowDevTool); + +// Close developer tools +commands.entity(webview).trigger(RequestCloseDevtool); +``` + +### Hot Reload +Local assets automatically reload when changed during development. + +## Best Practices + +### Component Organization +```rust +// Good: Group related components +commands.spawn(( + // The uri convert to `cef://localhost/index.html` + CefWebviewUri::local("index.html"), + WebviewSize(Vec2::new(1920.0, 200.0)), + ZoomLevel(0.8), + AudioMuted(true), + Transform::from_translation(Vec3::new(0.0, 5.0, 0.0)), + Mesh3d(meshes.add(Quad::new(Vec2::new(4.0, 1.0)))), + MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())), +)); +``` + +## Next Steps + +Now that you understand the basic concepts: + +- Explore [Core Components](core-components.md) in detail +- Learn about [Webview Rendering](webview-rendering.md) techniques +- Dive into [Inter-Process Communication](ipc.md) patterns +- Check out practical [Examples](examples/simple.md) \ No newline at end of file diff --git a/docs/src/core-components.md b/docs/src/core-components.md new file mode 100644 index 0000000..d3649d4 --- /dev/null +++ b/docs/src/core-components.md @@ -0,0 +1,407 @@ +# Core Components + +bevy_cef provides several essential components that control webview behavior. Understanding these components is crucial +for effective use of the library. + +## Component Overview + +| Component | Purpose | Default Value | Required | +|-----------------|-------------------------------|-----------------|--------------| +| `CefWebviewUri` | Specifies web content URL | None | ✅ Primary | +| `WebviewSize` | Controls rendering resolution | 800×800 | ✅ Auto-added | +| `ZoomLevel` | Controls webview zoom | 0.0 (default) | ✅ Auto-added | +| `AudioMuted` | Controls audio output | false (unmuted) | ✅ Auto-added | +| `HostWindow` | Parent window specification | Primary window | ❌ Optional | + +## CefWebviewUri + +The primary component that defines what web content to display. + +### Usage + +```rust +use bevy_cef::prelude::*; + +// Remote web page +let webview = CefWebviewUri::new("https://example.com"); + +// Local HTML file from assets/ directory +let webview = CefWebviewUri::local("ui/menu.html"); + +// Equivalent to local() method +let webview = CefWebviewUri::new("cef://localhost/ui/menu.html"); +``` + +### Implementation Details + +```rust +#[derive(Component, Debug, Clone, PartialEq, Eq, Hash, Reflect)] +#[require(WebviewSize, ZoomLevel, AudioMuted)] +pub struct CefWebviewUri(pub String); +``` + +The `#[require(...)]` attribute ensures that every webview automatically gets the essential supporting components. + +### Methods + +- **`new(uri)`**: Create with any valid URL (http, https, cef://localhost/) +- **`local(path)`**: Create with local file path, automatically prefixed with `cef://localhost/` + +### Local File Serving + +When using local files, bevy_cef serves them through a custom scheme: + +- **Scheme**: `cef://localhost/` +- **Root Directory**: Your project's `assets/` folder +- **Path Resolution**: Relative paths from assets/ root + +**Example File Structure:** + +``` +assets/ +├── index.html → cef://localhost/index.html +├── ui/ +│ ├── menu.html → cef://localhost/ui/menu.html +│ └── styles.css → cef://localhost/ui/styles.css +└── js/ + └── app.js → cef://localhost/js/app.js +``` + +## WebviewSize + +Controls the internal rendering resolution of the webview, independent of the 3D object size. + +### Usage + +```rust +use bevy::math::Vec2; + +// High resolution webview +WebviewSize(Vec2::new(1920.0, 1080.0)) + +// Standard resolution +WebviewSize(Vec2::new(800.0, 600.0)) + +// Square webview +WebviewSize(Vec2::splat(512.0)) + +// Default size +WebviewSize::default () // 800×800 +``` + +### Performance Considerations + +- **Higher Resolution**: Better quality, more memory usage +- **Lower Resolution**: Better performance, potential pixelation +- **Aspect Ratio**: Match your 3D mesh proportions for best results + +```rust +// Example: Widescreen webview for cinematic content +commands.spawn(( +CefWebviewUri::local("video-player.html"), +WebviewSize(Vec2::new(1920.0, 800.0)), // 21:9 aspect ratio +Mesh3d(meshes.add(Quad::new(Vec2::new(4.8, 2.0)))), // Match aspect in 3D +MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default ())), +)); +``` + +### Dynamic Resizing + +You can change webview size at runtime: + +```rust +fn resize_webview( + mut webviews: Query<&mut WebviewSize>, + keyboard: Res>, +) { + if keyboard.just_pressed(KeyCode::KeyR) { + for mut size in webviews.iter_mut() { + size.0 *= 1.5; // Increase resolution by 50% + } + } +} +``` + +## ZoomLevel + +Controls the zoom level of web content within the webview. + +### Usage + +```rust +// Default zoom (browser default) +ZoomLevel(0.0) + +// Zoom in 20% +ZoomLevel(1.2) + +// Zoom out 20% +ZoomLevel(0.8) + +// Maximum zoom in +ZoomLevel(3.0) + +// Maximum zoom out +ZoomLevel(0.25) +``` + +### Zoom Behavior + +- **0.0**: Browser default zoom level +- **Positive values**: Zoom in (1.2 = 120% of default) +- **Negative values**: Zoom out (0.8 = 80% of default) +- **Range**: Typically 0.25 to 3.0 (25% to 300%) + +### Dynamic Zoom Control + +```rust +fn zoom_control( + mut webviews: Query<&mut ZoomLevel>, + keyboard: Res>, +) { + for mut zoom in webviews.iter_mut() { + if keyboard.just_pressed(KeyCode::Equal) { + zoom.0 = (zoom.0 + 0.1).min(3.0); // Zoom in + } + if keyboard.just_pressed(KeyCode::Minus) { + zoom.0 = (zoom.0 - 0.1).max(0.25); // Zoom out + } + if keyboard.just_pressed(KeyCode::Digit0) { + zoom.0 = 0.0; // Reset zoom + } + } +} +``` + +### Use Cases + +- **Accessibility**: Larger text for readability +- **Dense Content**: Fit more information in limited space +- **Responsive Design**: Adapt to different screen sizes +- **User Preference**: Allow users to adjust comfortable viewing size + +## AudioMuted + +Controls whether audio from the webview is muted. + +### Usage + +```rust +// Audio enabled (default) +AudioMuted(false) + +// Audio muted +AudioMuted(true) +``` + +### Dynamic Audio Control + +```rust +fn toggle_audio( + mut webviews: Query<&mut AudioMuted>, + keyboard: Res>, +) { + if keyboard.just_pressed(KeyCode::KeyM) { + for mut muted in webviews.iter_mut() { + muted.0 = !muted.0; // Toggle mute state + } + } +} +``` + +### Use Cases + +- **Background Content**: Mute decorative webviews +- **Multiple Webviews**: Prevent audio conflicts +- **User Control**: Provide mute/unmute functionality +- **Game State**: Mute during pause or cutscenes + +## HostWindow (Optional) + +Specifies which Bevy window should be the parent of the webview. If not provided, the primary window is used. + +### Usage + +```rust +// Use primary window (default behavior) +commands.spawn(( +CefWebviewUri::local("ui.html"), +// No HostWindow component needed +)); + +// Specify a particular window +commands.spawn(( +CefWebviewUri::local("ui.html"), +HostWindow(secondary_window_entity), +)); +``` + +### Multi-Window Applications + +```rust +fn setup_multi_window( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // Create secondary window + let secondary_window = commands.spawn(Window { + title: "Secondary Display".to_string(), + resolution: (800.0, 600.0).into(), + ..default() + }).id(); + + // Main webview in primary window + commands.spawn(( + CefWebviewUri::local("main-ui.html"), + Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))), + MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())), + )); + + // Secondary webview in secondary window + commands.spawn(( + CefWebviewUri::local("secondary-ui.html"), + HostWindow(secondary_window), + Mesh3d(meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE))), + MeshMaterial3d(materials.add(WebviewExtendStandardMaterial::default())), + )); +} +``` + +## Component Combinations + +### Common Patterns + +**High-Resolution Interactive Display:** + +```rust +commands.spawn(( +CefWebviewUri::local("dashboard.html"), +WebviewSize(Vec2::new(2560.0, 1440.0)), +ZoomLevel(0.0), +AudioMuted(false), +)); +``` + +**Compact Information Panel:** + +```rust +commands.spawn(( +CefWebviewUri::local("info-panel.html"), +WebviewSize(Vec2::new(400.0, 300.0)), +ZoomLevel(0.8), +AudioMuted(true), +)); +``` + +**Video Player:** + +```rust +commands.spawn(( +CefWebviewUri::new("https://player.example.com"), +WebviewSize(Vec2::new(1920.0, 1080.0)), +ZoomLevel(0.0), +AudioMuted(false), // Keep audio for video +)); +``` + +**Background Decoration:** + +```rust +commands.spawn(( +CefWebviewUri::local("animated-bg.html"), +WebviewSize(Vec2::new(1024.0, 1024.0)), +ZoomLevel(0.0), +AudioMuted(true), // No audio for decoration +)); +``` + +## Component Lifecycle + +### Automatic Requirements + +When you add `CefWebviewUri`, the required components are automatically added with default values: + +```rust +// You only need to specify this: +commands.spawn(CefWebviewUri::local("page.html")); + +// But the entity automatically gets: +// - WebviewSize(Vec2::splat(800.0)) +// - ZoomLevel(0.0) +// - AudioMuted(false) +``` + +### Manual Override + +You can override the defaults by adding components explicitly: + +```rust +commands.spawn(( +CefWebviewUri::local("page.html"), +WebviewSize(Vec2::new(1024.0, 768.0)), // Override default +ZoomLevel(1.2), // Override default +AudioMuted(true), // Override default +)); +``` + +### Runtime Modification + +All components can be modified at runtime through standard Bevy systems: + +```rust +fn modify_webview_properties( + mut query: Query<(&mut WebviewSize, &mut ZoomLevel, &mut AudioMuted)>, + time: Res