mirror of
https://github.com/Drop-OSS/wry-cef.git
synced 2026-01-30 20:55:24 +01:00
Refactor new method entry point to rwh (#1041)
* Refactor new method entry point to rwh * Update doc and add change file * Fix mac and ios compile error * Add new_as_child * Add rhw_04, 05 06 flags * Update android port * Remove winit CI * Fix android bindings * windows implementation * Update Cargo.toml * Fix macOS and iOS * Fix Android * fix is_child on WkWebView * x11 impl * expose `new_gtk` * fix winit-gtk version * remove undecorated resizing handling * implement set_position and set_size for child gtk in x11 * fix macos * more fixes * one more time * unreachable * update actions * fix windows * some clippy * Add documentation and fix clippy * Fix windows clippy error * Fix android biuld * Fix documentation test * Fix android again * Reduce clippy noise * use rc? * refine some documentation, add set_visible * fix set_visible * impl set_visible on Windows * fix doctests * more set_visible fixes * fix windows * unsafe * fix set size for child webviews * fix initial visibility with new_gtk * refine examples * fix wpgu example * fix examples on windows and macos * use a better workaround * make set_size work on x11 * Fix size in multiwebview example * Add visible method on macOS and iOS * remvoe `tao` from android backend and update documentation * fix winit example * Add new_as_content_view on macOS * allow using raw-window-handle v0.5 [skip ci] * change trait name [skip ci] * fix linux impl [skip ci] * fix android build [skip ci] * fix windows build * fix(macos): do not autoresize on child webview [skip ci] * fix macos coordinates [skip ci] * fix example [skip ci] * fixed child on macos [skip ci] * fix docs typos [skip ci] * fix webview position when it's not a child [skip ci] * replace new_as_content_view with new_as_subview * with_as_subview instead of constructor [skip ci] * fix position/size when as_subview is true * lint & fmt * Fix ios build * Fix cargo test * Fix mac build * Update macOS contrusctors * cargo fmt * impl drop on Windows * fix child transparency on Windows (still needs PRs in tao and winit) * fix winit usage in the examples, use rwh_05 only for now * fix tests * add webview.focus * fix dropping on Linux * chore clean examples * implement focus on Linux * macos focus --------- Co-authored-by: amrbashir <amr.bashir2015@gmail.com> Co-authored-by: Lucas Nogueira <lucas@tauri.app> Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
committed by
GitHub
parent
81cfa37e32
commit
783b14239d
17
.changes/rwh.md
Normal file
17
.changes/rwh.md
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
"wry": minor
|
||||
---
|
||||
|
||||
Refactor new method to take raw window handle instead. Following are APIs got affected:
|
||||
- `application` module is removed, and `webivew` module is moved to root module.
|
||||
- `WebviewBuilder::new`, `Webview::new` now take `RawWindowHandle` instead.
|
||||
- Attributes `ipc_handler`, `file_drop_handler`, `document_change_handler` don't have window parameter anymore.
|
||||
Users should use closure to capture the types they want to use.
|
||||
- Position field in `FileDrop` event is now `Position` instead of `PhysicalPosition`. Users need to handle scale factor
|
||||
depend on the situation they have.
|
||||
- `Webview::inner_size` is removed.
|
||||
|
||||
This also means that we removed `tao` as a dependency completely which required some changes to the Android backend:
|
||||
- We exposed the `android_setup` function that needs to be called once to setup necessary logic.
|
||||
- Previously the `android_binding!` had internal call to `tao::android_binding` but now that `tao` has been removed,sa
|
||||
the macro signature has changed and you now need to call `tao::android_binding` yourself, checkout the crate documentation for more information.
|
||||
29
.github/workflows/audit.yml
vendored
29
.github/workflows/audit.yml
vendored
@@ -1,20 +1,33 @@
|
||||
name: Audit
|
||||
# Copyright 2022-2022 Tauri Programme within The Commons Conservancy
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: audit
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- "Cargo.lock"
|
||||
- "Cargo.toml"
|
||||
- 'Cargo.lock'
|
||||
- 'Cargo.toml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'Cargo.lock'
|
||||
- 'Cargo.toml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
audit-rust:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: rust audit
|
||||
uses: actions-rs/audit-check@v1
|
||||
- uses: actions/checkout@v4
|
||||
- uses: rustsec/audit-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
17
.github/workflows/build.yml
vendored
17
.github/workflows/build.yml
vendored
@@ -7,7 +7,6 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
rust_version: [stable]
|
||||
platform:
|
||||
- { target: x86_64-pc-windows-msvc, os: windows-latest }
|
||||
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest }
|
||||
@@ -28,7 +27,7 @@ jobs:
|
||||
if: contains(matrix.platform.target, 'gnu')
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y webkit2gtk-4.1 libgtksourceview-3.0-dev libayatana-appindicator3-dev
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev
|
||||
|
||||
- name: install webview2 (windows only)
|
||||
if: contains(matrix.platform.target, 'windows')
|
||||
@@ -44,20 +43,10 @@ jobs:
|
||||
|
||||
- name: build tests and examples
|
||||
shell: bash
|
||||
if: (
|
||||
!contains(matrix.platform.target, 'android') &&
|
||||
!contains(matrix.platform.target, 'ios'))
|
||||
if: (!contains(matrix.platform.target, 'android') && !contains(matrix.platform.target, 'ios'))
|
||||
run: cargo test --no-run --verbose --target ${{ matrix.platform.target }}
|
||||
|
||||
- name: run tests
|
||||
if: (
|
||||
!contains(matrix.platform.target, 'android') &&
|
||||
!contains(matrix.platform.target, 'ios'))
|
||||
if: (!contains(matrix.platform.target, 'android') && !contains(matrix.platform.target, 'ios'))
|
||||
run: cargo test --verbose --target ${{ matrix.platform.target }} --features linux-body
|
||||
|
||||
- name: build wry with winit
|
||||
if: (
|
||||
contains(matrix.platform.target, 'gnu') ||
|
||||
contains(matrix.platform.target, 'windows') ||
|
||||
contains(matrix.platform.target, 'apple'))
|
||||
run: cargo build --no-default-features --features winit --target ${{ matrix.platform.target }}
|
||||
|
||||
48
.github/workflows/clippy-fmt.yml
vendored
Normal file
48
.github/workflows/clippy-fmt.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
# Copyright 2022-2022 Tauri Programme within The Commons Conservancy
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: clippy & fmt
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
clippy:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: install system deps
|
||||
if: matrix.platform == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
|
||||
- run: cargo clippy --all-targets
|
||||
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
|
||||
- run: cargo fmt --all -- --check
|
||||
4
.github/workflows/covector-status.yml
vendored
4
.github/workflows/covector-status.yml
vendored
@@ -6,9 +6,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/checkout@v4
|
||||
- name: covector status
|
||||
uses: jbolda/covector/packages/action@covector-v0
|
||||
id: covector
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: version or publish
|
||||
name: covector version or publish
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -15,15 +15,16 @@ jobs:
|
||||
successfulPublish: ${{ steps.covector.outputs.successfulPublish }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: cargo login
|
||||
run: cargo login ${{ secrets.ORG_CRATES_IO_TOKEN }}
|
||||
|
||||
- name: git config
|
||||
run: |
|
||||
git config --global user.name "${{ github.event.pusher.name }}"
|
||||
git config --global user.email "${{ github.event.pusher.email }}"
|
||||
|
||||
- name: covector version or publish (publish when no change files present)
|
||||
uses: jbolda/covector/packages/action@covector-v0
|
||||
id: covector
|
||||
@@ -34,14 +35,15 @@ jobs:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
command: 'version-or-publish'
|
||||
createRelease: true
|
||||
|
||||
- name: Create Pull Request With Versions Bumped
|
||||
id: cpr
|
||||
uses: tauri-apps/create-pull-request@v3
|
||||
if: steps.covector.outputs.commandRan == 'version'
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
title: "Publish New Versions"
|
||||
commit-message: "publish new versions"
|
||||
labels: "version updates"
|
||||
branch: "release"
|
||||
title: Apply Version Updates From Current Changes
|
||||
commit-message: 'apply version updates'
|
||||
labels: 'version updates'
|
||||
branch: 'release'
|
||||
body: ${{ steps.covector.outputs.change }}
|
||||
|
||||
31
.github/workflows/fmt.yml
vendored
31
.github/workflows/fmt.yml
vendored
@@ -1,31 +0,0 @@
|
||||
name: fmt check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'Cargo.toml'
|
||||
|
||||
jobs:
|
||||
clippy_fmt_check:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt,clippy
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: fmt
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
- name: clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --all --examples -- -D warnings
|
||||
71
Cargo.toml
71
Cargo.toml
@@ -1,71 +1,67 @@
|
||||
workspace = { }
|
||||
workspace = {}
|
||||
|
||||
[package]
|
||||
name = "wry"
|
||||
version = "0.34.2"
|
||||
authors = [ "Tauri Programme within The Commons Conservancy" ]
|
||||
authors = ["Tauri Programme within The Commons Conservancy"]
|
||||
edition = "2021"
|
||||
license = "Apache-2.0 OR MIT"
|
||||
description = "Cross-platform WebView rendering library"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/tauri-apps/wry"
|
||||
documentation = "https://docs.rs/wry"
|
||||
categories = [ "gui" ]
|
||||
categories = ["gui"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
no-default-features = true
|
||||
features = [ "tao", "file-drop", "protocol" ]
|
||||
features = ["file-drop", "protocol"]
|
||||
targets = [
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"x86_64-pc-windows-msvc",
|
||||
"x86_64-apple-darwin"
|
||||
"x86_64-apple-darwin",
|
||||
]
|
||||
rustc-args = [ "--cfg", "docsrs" ]
|
||||
rustdoc-args = [ "--cfg", "docsrs" ]
|
||||
rustc-args = ["--cfg", "docsrs"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
default = [ "file-drop", "objc-exception", "protocol", "tao" ]
|
||||
objc-exception = [ "objc/exception" ]
|
||||
file-drop = [ ]
|
||||
protocol = [ ]
|
||||
devtools = [ ]
|
||||
transparent = [ ]
|
||||
fullscreen = [ ]
|
||||
linux-body = [ "webkit2gtk/v2_40" ]
|
||||
mac-proxy = [ ]
|
||||
default = ["file-drop", "objc-exception", "protocol"]
|
||||
objc-exception = ["objc/exception"]
|
||||
file-drop = []
|
||||
protocol = []
|
||||
devtools = []
|
||||
transparent = []
|
||||
fullscreen = []
|
||||
linux-body = ["webkit2gtk/v2_40"]
|
||||
mac-proxy = []
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
once_cell = "1"
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
thiserror = "1.0"
|
||||
url = "2.4"
|
||||
tao = { version = "0.23", default-features = false, features = [ "serde" ], optional = true }
|
||||
http = "0.2.9"
|
||||
|
||||
[dev-dependencies]
|
||||
http-range = "0.1.5"
|
||||
base64 = "0.21"
|
||||
http = "0.2"
|
||||
raw-window-handle = { version = "0.5", features = ["std"] }
|
||||
|
||||
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
|
||||
javascriptcore-rs = { version = "=1.1", features = [ "v2_28" ] }
|
||||
webkit2gtk = { version = "=2.0", features = [ "v2_38" ] }
|
||||
javascriptcore-rs = { version = "=1.1", features = ["v2_28"] }
|
||||
webkit2gtk = { version = "=2.0", features = ["v2_38"] }
|
||||
webkit2gtk-sys = "=2.0"
|
||||
gtk = "0.18"
|
||||
soup3 = "0.5"
|
||||
winit = { version = "0.29", package = "winit-gtk", features = [ "serde" ], optional = true }
|
||||
x11-dl = "2.9"
|
||||
gdkx11 = "0.18"
|
||||
|
||||
[target."cfg(target_os = \"windows\")".dependencies]
|
||||
webview2-com = "0.27"
|
||||
windows-implement = "0.51"
|
||||
dunce = "1"
|
||||
winit = { version = "0.28", features = [ "serde" ], optional = true }
|
||||
|
||||
[target."cfg(target_os = \"windows\")".dependencies.windows]
|
||||
version = "0.51"
|
||||
features = [
|
||||
[target."cfg(target_os = \"windows\")".dependencies.windows]
|
||||
version = "0.51"
|
||||
features = [
|
||||
"implement",
|
||||
"Win32_Foundation",
|
||||
"Win32_Graphics_Gdi",
|
||||
@@ -80,7 +76,7 @@ winit = { version = "0.28", features = [ "serde" ], optional = true }
|
||||
"Win32_Globalization",
|
||||
"Win32_UI_HiDpi",
|
||||
"Win32_UI_Input",
|
||||
"Win32_UI_Input_KeyboardAndMouse"
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
]
|
||||
|
||||
[target."cfg(any(target_os = \"ios\", target_os = \"macos\"))".dependencies]
|
||||
@@ -89,7 +85,6 @@ cocoa = "0.25"
|
||||
core-graphics = "0.23"
|
||||
objc = "0.2"
|
||||
objc_id = "0.1"
|
||||
winit = { version = "0.28", features = [ "serde" ], optional = true }
|
||||
|
||||
[target."cfg(target_os = \"android\")".dependencies]
|
||||
crossbeam-channel = "0.5"
|
||||
@@ -97,3 +92,15 @@ html5ever = "0.26"
|
||||
kuchiki = { package = "kuchikiki", version = "0.8" }
|
||||
sha2 = "0.10"
|
||||
base64 = "0.21"
|
||||
jni = "0.21"
|
||||
ndk = "0.7"
|
||||
ndk-sys = "0.4"
|
||||
ndk-context = "0.1"
|
||||
tao-macros = { version = "0.1.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
http-range = "0.1.5"
|
||||
pollster = "0.3.0"
|
||||
tao = { git = "https://github.com/tauri-apps/tao", default-features = false, features = ["rwh_05"] }
|
||||
wgpu = "0.18.0"
|
||||
winit = { version = "0.29", features = ["rwh_05"] }
|
||||
|
||||
29
README.md
29
README.md
@@ -117,7 +117,34 @@ WebView2 provided by Microsoft Edge Chromium is used. So wry supports Windows 7,
|
||||
|
||||
Wry supports mobile with the help of [`cargo-mobile2`](https://github.com/tauri-apps/cargo-mobile2) CLI to create template project. If you are interested in playing or hacking it, please follow [MOBILE.md](MOBILE.md).
|
||||
|
||||
If you wish to create Android project yourself, there are a few kotlin files that are needed to run wry on Android and you have to set the following environment variables:
|
||||
If you wish to create Android project yourself, there is a few requirements that your application needs to uphold:
|
||||
|
||||
1. You need to set a few environment variables that will be used to generate the necessary kotlin
|
||||
files that you need to include in your Android application for wry to function properly:
|
||||
|
||||
- `WRY_ANDROID_PACKAGE`: which is the reversed domain name of your android project and the app name in snake_case, for example, `com.wry.example.wry_app`
|
||||
- `WRY_ANDROID_LIBRARY`: for example, if your cargo project has a lib name `wry_app`, it will generate `libwry_app.so` so you se this env var to `wry_app`
|
||||
- `WRY_ANDROID_KOTLIN_FILES_OUT_DIR`: for example, `path/to/app/src/main/kotlin/com/wry/example`
|
||||
|
||||
2. Your main Android Activity needs to inherit `AppCompatActivity`, preferably it should use the generated `WryActivity` or inherit it.
|
||||
3. Your Rust app needs to call `wry::android_setup` function to setup the necessary logic to be able to create webviews later on.
|
||||
4. Your Rust app needs to call `wry::android_binding!` macro to setup the JNI functions that will be called by `WryActivity` and various other places.
|
||||
|
||||
It is recommended to use [`tao`](https://docs.rs/tao/latest/tao/) crate as it provides maximum compatibility with `wry`
|
||||
|
||||
```rs
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
tao::android_binding!(
|
||||
com_example,
|
||||
wry_app,
|
||||
WryActivity,
|
||||
wry::android_setup, // pass the wry::android_setup function to tao which will invoke when the event loop is created
|
||||
_start_app
|
||||
);
|
||||
wry::android_binding!(com_example, ttt);
|
||||
}
|
||||
```
|
||||
|
||||
- `WRY_ANDROID_PACKAGE` which is the reversed domain name of your android project and the app name in snake_case for example: `com.wry.example.wry_app`
|
||||
- `WRY_ANDROID_LIBRARY` for example: if your cargo project has a lib name `wry_app`, it will generate `libwry_app.so` so you se this env var to `wry_app`
|
||||
|
||||
2
build.rs
2
build.rs
@@ -38,7 +38,7 @@ fn main() {
|
||||
});
|
||||
|
||||
let kotlin_files_path =
|
||||
PathBuf::from(env_var("CARGO_MANIFEST_DIR")).join("src/webview/android/kotlin");
|
||||
PathBuf::from(env_var("CARGO_MANIFEST_DIR")).join("src/android/kotlin");
|
||||
println!("cargo:rerun-if-changed={}", kotlin_files_path.display());
|
||||
let kotlin_files = fs::read_dir(kotlin_files_path).expect("failed to read kotlin directory");
|
||||
|
||||
|
||||
104
examples/async_custom_protocol.rs
Normal file
104
examples/async_custom_protocol.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use http::Request;
|
||||
use tao::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
use wry::{
|
||||
http::{header::CONTENT_TYPE, Response},
|
||||
WebViewBuilder,
|
||||
};
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "android"
|
||||
))]
|
||||
let builder = WebViewBuilder::new(&window);
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "android"
|
||||
)))]
|
||||
let builder = {
|
||||
use tao::platform::unix::WindowExtUnix;
|
||||
let vbox = window.default_vbox().unwrap();
|
||||
WebViewBuilder::new_gtk(vbox)
|
||||
};
|
||||
let _webview = builder
|
||||
.with_asynchronous_custom_protocol("wry".into(), move |request, responder| {
|
||||
match get_wry_response(request) {
|
||||
Ok(http_response) => responder.respond(http_response),
|
||||
Err(e) => responder.respond(
|
||||
http::Response::builder()
|
||||
.header(CONTENT_TYPE, "text/plain")
|
||||
.status(500)
|
||||
.body(e.to_string().as_bytes().to_vec())
|
||||
.unwrap(),
|
||||
),
|
||||
}
|
||||
})
|
||||
// tell the webview to load the custom protocol
|
||||
.with_url("wry://localhost")?
|
||||
.build()?;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
if let Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
*control_flow = ControlFlow::Exit
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn get_wry_response(
|
||||
request: Request<Vec<u8>>,
|
||||
) -> Result<http::Response<Vec<u8>>, Box<dyn std::error::Error>> {
|
||||
let path = request.uri().path();
|
||||
// Read the file content from file path
|
||||
let root = PathBuf::from("examples/custom_protocol");
|
||||
let path = if path == "/" {
|
||||
"index.html"
|
||||
} else {
|
||||
// removing leading slash
|
||||
&path[1..]
|
||||
};
|
||||
let content = std::fs::read(std::fs::canonicalize(root.join(path))?)?;
|
||||
|
||||
// Return asset contents and mime types based on file extentions
|
||||
// If you don't want to do this manually, there are some crates for you.
|
||||
// Such as `infer` and `mime_guess`.
|
||||
let mimetype = if path.ends_with(".html") || path == "/" {
|
||||
"text/html"
|
||||
} else if path.ends_with(".js") {
|
||||
"text/javascript"
|
||||
} else if path.ends_with(".png") {
|
||||
"image/png"
|
||||
} else if path.ends_with(".wasm") {
|
||||
"application/wasm"
|
||||
} else {
|
||||
unimplemented!();
|
||||
};
|
||||
|
||||
Response::builder()
|
||||
.header(CONTENT_TYPE, mimetype)
|
||||
.body(content)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
@@ -2,43 +2,53 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::{
|
||||
fs::{canonicalize, read},
|
||||
path::PathBuf,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use http::Request;
|
||||
use wry::{
|
||||
application::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
},
|
||||
http::{header::CONTENT_TYPE, Response},
|
||||
webview::WebViewBuilder,
|
||||
use tao::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
use wry::{
|
||||
http::{header::CONTENT_TYPE, Response},
|
||||
WebViewBuilder,
|
||||
};
|
||||
|
||||
const PAGE1_HTML: &[u8] = include_bytes!("custom_protocol_page1.html");
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Custom Protocol")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
let _webview = WebViewBuilder::new(window)
|
||||
.unwrap()
|
||||
.with_asynchronous_custom_protocol("wry".into(), move |request, responder| {
|
||||
#[cfg(any(
|
||||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "android"
|
||||
))]
|
||||
let builder = WebViewBuilder::new(&window);
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "android"
|
||||
)))]
|
||||
let builder = {
|
||||
use tao::platform::unix::WindowExtUnix;
|
||||
let vbox = window.default_vbox().unwrap();
|
||||
WebViewBuilder::new_gtk(vbox)
|
||||
};
|
||||
|
||||
let _webview = builder
|
||||
.with_custom_protocol("wry".into(), move |request| {
|
||||
match get_wry_response(request) {
|
||||
Ok(http_response) => responder.respond(http_response),
|
||||
Err(e) => responder.respond(
|
||||
http::Response::builder()
|
||||
.header(CONTENT_TYPE, "text/plain")
|
||||
.status(500)
|
||||
.body(e.to_string().as_bytes().to_vec())
|
||||
.unwrap(),
|
||||
),
|
||||
Ok(r) => r.map(Into::into),
|
||||
Err(e) => http::Response::builder()
|
||||
.header(CONTENT_TYPE, "text/plain")
|
||||
.status(500)
|
||||
.body(e.to_string().as_bytes().to_vec())
|
||||
.unwrap()
|
||||
.map(Into::into),
|
||||
}
|
||||
})
|
||||
// tell the webview to load the custom protocol
|
||||
@@ -48,13 +58,12 @@ fn main() -> wry::Result<()> {
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => println!("Wry application started!"),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
if let Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
*control_flow = ControlFlow::Exit
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -64,12 +73,14 @@ fn get_wry_response(
|
||||
) -> Result<http::Response<Vec<u8>>, Box<dyn std::error::Error>> {
|
||||
let path = request.uri().path();
|
||||
// Read the file content from file path
|
||||
let content = if path == "/" {
|
||||
PAGE1_HTML.into()
|
||||
let root = PathBuf::from("examples/custom_protocol");
|
||||
let path = if path == "/" {
|
||||
"index.html"
|
||||
} else {
|
||||
// `1..` for removing leading slash
|
||||
read(canonicalize(PathBuf::from("examples").join(&path[1..]))?)?
|
||||
// removing leading slash
|
||||
&path[1..]
|
||||
};
|
||||
let content = std::fs::read(std::fs::canonicalize(root.join(path))?)?;
|
||||
|
||||
// Return asset contents and mime types based on file extentions
|
||||
// If you don't want to do this manually, there are some crates for you.
|
||||
|
||||
26
examples/custom_protocol/index.html
Normal file
26
examples/custom_protocol/index.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<!-- Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
SPDX-License-Identifier: MIT -->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Welcome to WRY!</h1>
|
||||
<p>Page 1</p>
|
||||
<textarea></textarea>
|
||||
<p id="keypresses"></p>
|
||||
<a href="/page2.html">Link</a>
|
||||
<script type="text/javascript" src="/script.js"></script>
|
||||
<script type="text/javascript">
|
||||
document.body.addEventListener("keydown", (event) => {
|
||||
document.querySelector("#keypresses").innerHTML += " " + event.key;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
22
examples/custom_protocol/script.js
Normal file
22
examples/custom_protocol/script.js
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
if (window.location.pathname.startsWith("/page2")) {
|
||||
console.log("hello from javascript in page2");
|
||||
} else {
|
||||
console.log("hello from javascript in page1");
|
||||
|
||||
if (typeof WebAssembly.instantiateStreaming !== "undefined") {
|
||||
WebAssembly.instantiateStreaming(fetch("/wasm.wasm")).then((wasm) => {
|
||||
console.log(wasm.instance.exports.main()); // should log 42
|
||||
});
|
||||
} else {
|
||||
// Older WKWebView may not support `WebAssembly.instantiateStreaming` yet.
|
||||
fetch("/wasm.wasm")
|
||||
.then((response) => response.arrayBuffer())
|
||||
.then((bytes) => WebAssembly.instantiate(bytes))
|
||||
.then((wasm) => {
|
||||
console.log(wasm.instance.exports.main()); // should log 42
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<!-- Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
SPDX-License-Identifier: MIT -->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Welcome to WRY!</h1>
|
||||
<p>Page 1</p>
|
||||
<textarea></textarea>
|
||||
<p id="keypresses"></p>
|
||||
<a href="/custom_protocol_page2.html">Link</a>
|
||||
<script type="text/javascript" src="/custom_protocol_script.js"></script>
|
||||
<script type="text/javascript">
|
||||
document.body.addEventListener('keydown', (event) => {
|
||||
document.querySelector('#keypresses').innerHTML += ' ' + event.key
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
if (window.location.pathname.startsWith('/custom_protocol_page2')) {
|
||||
console.log("hello from javascript in page2");
|
||||
} else {
|
||||
console.log("hello from javascript in page1");
|
||||
|
||||
if (typeof WebAssembly.instantiateStreaming !== 'undefined') {
|
||||
WebAssembly.instantiateStreaming(fetch("/custom_protocol_wasm.wasm"))
|
||||
.then(wasm => {
|
||||
console.log(wasm.instance.exports.main()); // should log 42
|
||||
});
|
||||
} else {
|
||||
// Older WKWebView may not support `WebAssembly.instantiateStreaming` yet.
|
||||
fetch("/custom_protocol_wasm.wasm")
|
||||
.then(response => response.arrayBuffer())
|
||||
.then(bytes => WebAssembly.instantiate(bytes))
|
||||
.then(wasm => {
|
||||
console.log(wasm.instance.exports.main()); // should log 42
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,21 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use tao::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoopBuilder},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
use wry::WebViewBuilder;
|
||||
|
||||
enum UserEvent {
|
||||
Minimize,
|
||||
Maximize,
|
||||
DragWindow,
|
||||
CloseWindow,
|
||||
}
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
use wry::{
|
||||
application::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoopBuilder},
|
||||
window::{Window, WindowBuilder},
|
||||
},
|
||||
webview::WebViewBuilder,
|
||||
};
|
||||
|
||||
enum UserEvent {
|
||||
CloseWindow,
|
||||
}
|
||||
|
||||
let event_loop = EventLoopBuilder::<UserEvent>::with_user_event().build();
|
||||
let window = WindowBuilder::new()
|
||||
.with_decorations(false)
|
||||
@@ -101,7 +102,6 @@ fn main() -> wry::Result<()> {
|
||||
e.detail === 2
|
||||
? window.ipc.postMessage('maximize')
|
||||
: window.ipc.postMessage('drag_window');
|
||||
}
|
||||
})
|
||||
document.addEventListener('touchstart', (e) => {
|
||||
if (e.target.classList.contains('drag-region')) {
|
||||
@@ -115,25 +115,44 @@ fn main() -> wry::Result<()> {
|
||||
"#;
|
||||
|
||||
let proxy = event_loop.create_proxy();
|
||||
|
||||
let handler = move |window: &Window, req: String| {
|
||||
if req == "minimize" {
|
||||
window.set_minimized(true);
|
||||
let handler = move |req: String| match req.as_str() {
|
||||
"minimize" => {
|
||||
let _ = proxy.send_event(UserEvent::Minimize);
|
||||
}
|
||||
if req == "maximize" {
|
||||
window.set_maximized(!window.is_maximized());
|
||||
"maximize" => {
|
||||
let _ = proxy.send_event(UserEvent::Maximize);
|
||||
}
|
||||
if req == "close" {
|
||||
"drag_window" => {
|
||||
let _ = proxy.send_event(UserEvent::DragWindow);
|
||||
}
|
||||
"close" => {
|
||||
let _ = proxy.send_event(UserEvent::CloseWindow);
|
||||
}
|
||||
if req == "drag_window" {
|
||||
let _ = window.drag_window();
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "android"
|
||||
))]
|
||||
let builder = WebViewBuilder::new(&window);
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "android"
|
||||
)))]
|
||||
let builder = {
|
||||
use tao::platform::unix::WindowExtUnix;
|
||||
let vbox = window.default_vbox().unwrap();
|
||||
WebViewBuilder::new_gtk(vbox)
|
||||
};
|
||||
|
||||
let mut webview = Some(
|
||||
WebViewBuilder::new(window)
|
||||
.unwrap()
|
||||
builder
|
||||
.with_html(HTML)?
|
||||
.with_ipc_handler(handler)
|
||||
.with_accept_first_mouse(true)
|
||||
@@ -153,6 +172,13 @@ fn main() -> wry::Result<()> {
|
||||
let _ = webview.take();
|
||||
*control_flow = ControlFlow::Exit
|
||||
}
|
||||
|
||||
Event::UserEvent(e) => match e {
|
||||
UserEvent::Minimize => window.set_minimized(true),
|
||||
UserEvent::Maximize => window.set_maximized(!window.is_maximized()),
|
||||
UserEvent::DragWindow => window.drag_window().unwrap(),
|
||||
UserEvent::CloseWindow => { /* handled above */ }
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
use wry::{
|
||||
application::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
},
|
||||
webview::WebViewBuilder,
|
||||
};
|
||||
|
||||
const HTML: &str = r#"
|
||||
</html>
|
||||
|
||||
<body>
|
||||
<h2>ECMAScript support list:</h2>
|
||||
<ul id="summary"></ul>
|
||||
<table>
|
||||
<thead>
|
||||
<h3>Details:<h3>
|
||||
</thead>
|
||||
<tbody id="table"></tbody>
|
||||
</table>
|
||||
</body>
|
||||
<script>
|
||||
var featureSupport = [
|
||||
{
|
||||
version: "ECMAScript 5 (2009)",
|
||||
features: [
|
||||
{ name: "String.trim", supported: String.prototype.trim },
|
||||
{ name: "Array.isArray", supported: Array.isArray },
|
||||
{ name: "Array.forEach", supported: Array.prototype.forEach },
|
||||
{ name: "Array.map", supported: Array.prototype.map },
|
||||
{ name: "Array.filter", supported: Array.prototype.filter },
|
||||
{ name: "Array.reduce", supported: Array.prototype.reduce },
|
||||
{ name: "JSON.parse", supported: JSON.parse },
|
||||
{ name: "Date.now", supported: Date.now }
|
||||
]
|
||||
},
|
||||
{
|
||||
version: "ECMAScript 6 (2015)",
|
||||
features: [
|
||||
{ name: "Array.find", supported: Array.prototype.find },
|
||||
{ name: "Math.trunc", supported: Math.trunc },
|
||||
{ name: "Number.isInteger", supported: Number.isInteger }
|
||||
]
|
||||
},
|
||||
{
|
||||
version: "ECMAScript 2016",
|
||||
features: [
|
||||
{ name: "Array.includes", supported: Array.prototype.includes }
|
||||
]
|
||||
},
|
||||
{
|
||||
version: "ECMAScript 2017",
|
||||
features: [
|
||||
{ name: "String.padStart", supported: String.prototype.padStart },
|
||||
{ name: "String.padEnd", supported: String.prototype.padEnd },
|
||||
{ name: "Object.entries", supported: Object.entries },
|
||||
{ name: "Object.values", supported: Object.values },
|
||||
]
|
||||
},
|
||||
{
|
||||
version: "ECMAScript 2018",
|
||||
features: [
|
||||
{ name: "Promise.finally", supported: Promise.prototype.finally }
|
||||
]
|
||||
},
|
||||
{
|
||||
version: "ECMAScript 2019",
|
||||
features: [
|
||||
{ name: "Array.flat", supported: Array.prototype.flat },
|
||||
{ name: "Array.flatMap", supported: Array.prototype.flatMap },
|
||||
{ name: "Object.fromEntries", supported: Object.fromEntries },
|
||||
{ name: "String.trimStart", supported: String.prototype.trimStart },
|
||||
{ name: "String.trimEnd", supported: String.prototype.trimEnd },
|
||||
{ name: "Function.toString", supported: Function.prototype.toString }
|
||||
]
|
||||
},
|
||||
{
|
||||
version: "ECMAScript 2020",
|
||||
features: [
|
||||
{ name: "Promise.allSettled", supported: Promise.allSettled },
|
||||
{ name: "String.matchAll", supported: String.prototype.matchAll }
|
||||
]
|
||||
},
|
||||
{
|
||||
version: "ECMAScript 2021",
|
||||
features: [
|
||||
{ name: "String.replaceAll", supported: String.prototype.replaceAll },
|
||||
{ name: "Promise.any", supported: Promise.any }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
var tableElement = document.getElementById('table');
|
||||
var summaryListElement = document.getElementById('summary');
|
||||
|
||||
for (var i = 0; i < featureSupport.length; i++) {
|
||||
var versionDetails = featureSupport[i];
|
||||
var versionSupported = true;
|
||||
tableElement.innerHTML += `<tr> <td style="width: 200px; font-weight: bold">${versionDetails.version}</td> </tr>`
|
||||
for (var j = 0; j < versionDetails.features.length; j++) {
|
||||
var feature = versionDetails.features[j];
|
||||
tableElement.innerHTML += `<tr> <td style="width: 200px">${feature.name}</td> <td>${feature.supported ? '✔' : '❌'} </td> </tr>`
|
||||
if (!feature.supported) versionSupported = false;
|
||||
}
|
||||
summaryListElement.innerHTML += `<li> ${versionDetails.version}: ${versionSupported ? '✔' : '❌'}`
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
"#;
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Detect ECMAScript")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
let _webview = WebViewBuilder::new(window)
|
||||
.unwrap()
|
||||
.with_html(HTML)?
|
||||
.build()?;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => println!("Wry application started!"),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
use std::{env::temp_dir, path::PathBuf};
|
||||
use wry::{
|
||||
application::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoopBuilder},
|
||||
window::WindowBuilder,
|
||||
},
|
||||
webview::WebViewBuilder,
|
||||
};
|
||||
|
||||
const HTML: &str = r#"
|
||||
<body>
|
||||
<div>
|
||||
<p> WRYYYYYYYYYYYYYYYYYYYYYY! </p>
|
||||
<a download="allow.zip" href='https://github.com/tauri-apps/wry/archive/refs/tags/wry-v0.13.3.zip' id="link">Allowed Download</a>
|
||||
<a download="deny.zip" href='https://github.com/tauri-apps/wry/archive/refs/tags/wry-v0.13.2.zip' id="link">Denied Download</a>
|
||||
</div>
|
||||
</body>
|
||||
"#;
|
||||
|
||||
enum UserEvent {
|
||||
DownloadStarted(String, String),
|
||||
DownloadComplete(Option<PathBuf>, bool),
|
||||
Rejected(String),
|
||||
}
|
||||
|
||||
let event_loop = EventLoopBuilder::<UserEvent>::with_user_event().build();
|
||||
let proxy = event_loop.create_proxy();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Hello World")
|
||||
.build(&event_loop)?;
|
||||
let _webview = WebViewBuilder::new(window)?
|
||||
.with_html(HTML)?
|
||||
.with_download_started_handler({
|
||||
let proxy = proxy.clone();
|
||||
move |uri: String, default_path: &mut PathBuf| {
|
||||
if uri.contains("wry-v0.13.3") {
|
||||
let path = temp_dir().join("example.zip").as_path().to_path_buf();
|
||||
|
||||
*default_path = path.clone();
|
||||
|
||||
let submitted = proxy
|
||||
.send_event(UserEvent::DownloadStarted(uri, path.display().to_string()))
|
||||
.is_ok();
|
||||
|
||||
return submitted;
|
||||
}
|
||||
|
||||
let _ = proxy.send_event(UserEvent::Rejected(uri));
|
||||
|
||||
false
|
||||
}
|
||||
})
|
||||
.with_download_completed_handler({
|
||||
let proxy = proxy;
|
||||
move |_uri, path, success| {
|
||||
let _ = proxy.send_event(UserEvent::DownloadComplete(path, success));
|
||||
}
|
||||
})
|
||||
.build()?;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => println!("Wry has started!"),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
Event::UserEvent(UserEvent::DownloadStarted(uri, temp_dir)) => {
|
||||
println!("Download: {}", uri);
|
||||
println!("Will write to: {:?}", temp_dir);
|
||||
}
|
||||
Event::UserEvent(UserEvent::DownloadComplete(path, success)) => {
|
||||
let path = path.map(|_| temp_dir().join("example.zip"));
|
||||
println!("Succeeded: {}", success);
|
||||
if let Some(path) = path {
|
||||
println!("Path: {}", path.to_string_lossy());
|
||||
let metadata = path.metadata().unwrap();
|
||||
println!("Size of {}Mb", (metadata.len() / 1024) / 1024)
|
||||
} else {
|
||||
println!("No output path")
|
||||
}
|
||||
}
|
||||
Event::UserEvent(UserEvent::Rejected(uri)) => {
|
||||
println!("Rejected download from: {}", uri)
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
use wry::{
|
||||
application::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
},
|
||||
webview::WebViewBuilder,
|
||||
};
|
||||
|
||||
const HTML: &str = r#"data:text/html,
|
||||
Drop files onto the window and read the console!<br>
|
||||
Dropping files onto the following form is also possible:<br><br>
|
||||
<input type="file"/>
|
||||
"#;
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
let _webview = WebViewBuilder::new(window)
|
||||
.unwrap()
|
||||
.with_url(HTML)?
|
||||
.with_file_drop_handler(|_, data| {
|
||||
println!("Window 1: {:?}", data);
|
||||
false // Returning true will block the OS default behaviour.
|
||||
})
|
||||
.build()?;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => println!("Wry application started!"),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
// Copyright 2020-2022 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
use wry::{
|
||||
application::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoopBuilder},
|
||||
window::{Window, WindowBuilder},
|
||||
},
|
||||
webview::WebViewBuilder,
|
||||
};
|
||||
|
||||
enum UserEvent {
|
||||
ExecEval,
|
||||
}
|
||||
|
||||
let event_loop = EventLoopBuilder::<UserEvent>::with_user_event().build();
|
||||
let proxy = event_loop.create_proxy();
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Hello World")
|
||||
.build(&event_loop)?;
|
||||
|
||||
let ipc_handler = move |_: &Window, req: String| {
|
||||
if req == "exec-eval" {
|
||||
let _ = proxy.send_event(UserEvent::ExecEval);
|
||||
}
|
||||
};
|
||||
|
||||
let _webview = WebViewBuilder::new(window)?
|
||||
.with_html(
|
||||
r#"
|
||||
<button onclick="window.ipc.postMessage('exec-eval')">Exec eval</button>
|
||||
"#,
|
||||
)?
|
||||
.with_ipc_handler(ipc_handler)
|
||||
.build()?;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::UserEvent(UserEvent::ExecEval) => {
|
||||
// String
|
||||
_webview
|
||||
.evaluate_script_with_callback(
|
||||
"if (!foo) { var foo = 'morbin'; } `${foo} time`",
|
||||
|result| println!("String: {:?}", result),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Number
|
||||
_webview
|
||||
.evaluate_script_with_callback("var num = 9527; num", |result| {
|
||||
println!("Number: {:?}", result)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Object
|
||||
_webview
|
||||
.evaluate_script_with_callback("var obj = { thank: 'you', '95': 27 }; obj", |result| {
|
||||
println!("Object: {:?}", result)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Array
|
||||
_webview
|
||||
.evaluate_script_with_callback("var ary = [1,2,3,4,'5']; ary", |result| {
|
||||
println!("Array: {:?}", result)
|
||||
})
|
||||
.unwrap();
|
||||
// Exception thrown
|
||||
_webview
|
||||
.evaluate_script_with_callback("throw new Error()", |result| {
|
||||
println!("Exception Occured: {:?}", result)
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
Event::NewEvents(StartCause::Init) => println!("Wry has started!"),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<!-- Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
SPDX-License-Identifier: MIT -->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Welcome to WRY!</h1>
|
||||
<form action="#" method="POST">
|
||||
<label for="fname">First name:</label><br />
|
||||
<input type="text" id="fname" name="fname" value="John" /><br />
|
||||
<label for="lname">Last name:</label><br />
|
||||
<input type="text" id="lname" name="lname" value="Doe" /><br />
|
||||
<label for="thumbnail">Thumbnail:</label><br />
|
||||
<input type="file" id="thumbnail" name="thumbnail" value="Select your thumbnail image" /><br /><br />
|
||||
<input type="submit" value="Submit" />
|
||||
</form>
|
||||
<p>
|
||||
If you click the "Submit" button, the form-data will be sent to the custom
|
||||
protocol.
|
||||
</p>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,71 +0,0 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fs::{canonicalize, read},
|
||||
};
|
||||
|
||||
use wry::{
|
||||
application::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
},
|
||||
http::{header::CONTENT_TYPE, method::Method, Response},
|
||||
webview::WebViewBuilder,
|
||||
};
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Hello World")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let _webview = WebViewBuilder::new(window)
|
||||
.unwrap()
|
||||
.with_custom_protocol("wry".into(), move |request| {
|
||||
if request.method() == Method::POST {
|
||||
let body_string = String::from_utf8_lossy(request.body());
|
||||
for body in body_string.split('&') {
|
||||
println!("Value sent; {:?}", body);
|
||||
}
|
||||
}
|
||||
|
||||
// remove leading slash
|
||||
let path = &request.uri().path()[1..];
|
||||
|
||||
get_response(path).unwrap_or_else(|error| {
|
||||
http::Response::builder()
|
||||
.status(http::StatusCode::BAD_REQUEST)
|
||||
.header(CONTENT_TYPE, "text/plain")
|
||||
.body(error.to_string().as_bytes().to_vec().into())
|
||||
.unwrap()
|
||||
})
|
||||
})
|
||||
// tell the webview to load the custom protocol
|
||||
.with_url("wry://localhost/examples/form.html")?
|
||||
.build()?;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => println!("Wry application started!"),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn get_response(path: &str) -> Result<Response<Cow<'static, [u8]>>, Box<dyn std::error::Error>> {
|
||||
Response::builder()
|
||||
.header(CONTENT_TYPE, "text/html")
|
||||
.body(read(canonicalize(path)?)?.into())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
use wry::{
|
||||
application::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::{Fullscreen, WindowBuilder},
|
||||
},
|
||||
webview::WebViewBuilder,
|
||||
};
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("3D Render Test")
|
||||
.with_fullscreen(Some(Fullscreen::Borderless(None)))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
let _webview = WebViewBuilder::new(window)
|
||||
.unwrap()
|
||||
.with_url("https://browserbench.org/MotionMark1.2/")?
|
||||
.build()?;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => println!("Wry has started!"),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Test Result:
|
||||
// CPU: i7 9750H || GPU: Intel(R) UHD Graphics 630
|
||||
// Linux kernel 5.8.18-18-ibryza-standard-xin
|
||||
// Mesa Mesa 20.2.6
|
||||
// ================================================
|
||||
// Canvas score - Test 1: 542 - Test 2: 368
|
||||
// WebGL score - Test 1: 1390 - Test 2: 1342
|
||||
// Total score: 3642
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
use wry::{
|
||||
application::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
},
|
||||
webview::WebViewBuilder,
|
||||
};
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Hello World")
|
||||
.build(&event_loop)?;
|
||||
let _webview = WebViewBuilder::new(window)?
|
||||
.with_url("https://www.netflix.com/browse")?
|
||||
// .with_incognito(true)
|
||||
.build()?;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => println!("Wry has started!"),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
use std::collections::HashMap;
|
||||
use wry::{
|
||||
application::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget},
|
||||
window::{Window, WindowBuilder, WindowId},
|
||||
},
|
||||
webview::{WebView, WebViewBuilder},
|
||||
};
|
||||
|
||||
enum UserEvent {
|
||||
CloseWindow(WindowId),
|
||||
NewWindow,
|
||||
}
|
||||
|
||||
fn create_new_window(
|
||||
title: String,
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
proxy: EventLoopProxy<UserEvent>,
|
||||
) -> (WindowId, WebView) {
|
||||
let window = WindowBuilder::new()
|
||||
.with_title(title)
|
||||
.build(event_loop)
|
||||
.unwrap();
|
||||
let window_id = window.id();
|
||||
let handler = move |window: &Window, req: String| match req.as_str() {
|
||||
"new-window" => {
|
||||
let _ = proxy.send_event(UserEvent::NewWindow);
|
||||
}
|
||||
"close" => {
|
||||
let _ = proxy.send_event(UserEvent::CloseWindow(window.id()));
|
||||
}
|
||||
_ if req.starts_with("change-title") => {
|
||||
let title = req.replace("change-title:", "");
|
||||
window.set_title(title.as_str());
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
let webview = WebViewBuilder::new(window)
|
||||
.unwrap()
|
||||
.with_html(
|
||||
r#"
|
||||
<button onclick="window.ipc.postMessage('new-window')">Open a new window</button>
|
||||
<button onclick="window.ipc.postMessage('close')">Close current window</button>
|
||||
<input oninput="window.ipc.postMessage(`change-title:${this.value}`)" />
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
.with_ipc_handler(handler)
|
||||
.build()
|
||||
.unwrap();
|
||||
(window_id, webview)
|
||||
}
|
||||
|
||||
let event_loop = EventLoopBuilder::<UserEvent>::with_user_event().build();
|
||||
let mut webviews = HashMap::new();
|
||||
let proxy = event_loop.create_proxy();
|
||||
|
||||
let new_window = create_new_window(
|
||||
format!("Window {}", webviews.len() + 1),
|
||||
&event_loop,
|
||||
proxy.clone(),
|
||||
);
|
||||
webviews.insert(new_window.0, new_window.1);
|
||||
|
||||
event_loop.run(move |event, event_loop, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => println!("Wry application started!"),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
..
|
||||
} => {
|
||||
webviews.remove(&window_id);
|
||||
if webviews.is_empty() {
|
||||
*control_flow = ControlFlow::Exit
|
||||
}
|
||||
}
|
||||
Event::UserEvent(UserEvent::NewWindow) => {
|
||||
let new_window = create_new_window(
|
||||
format!("Window {}", webviews.len() + 1),
|
||||
event_loop,
|
||||
proxy.clone(),
|
||||
);
|
||||
webviews.insert(new_window.0, new_window.1);
|
||||
}
|
||||
Event::UserEvent(UserEvent::CloseWindow(id)) => {
|
||||
webviews.remove(&id);
|
||||
if webviews.is_empty() {
|
||||
*control_flow = ControlFlow::Exit
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
106
examples/multiwebview.rs
Normal file
106
examples/multiwebview.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
use wry::WebViewBuilder;
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
))]
|
||||
{
|
||||
use gtk::prelude::DisplayExtManual;
|
||||
|
||||
gtk::init()?;
|
||||
if gtk::gdk::Display::default().unwrap().backend().is_wayland() {
|
||||
panic!("This example doesn't support wayland!");
|
||||
}
|
||||
|
||||
// we need to ignore this error here otherwise it will be catched by winit and will be
|
||||
// make the example crash
|
||||
winit::platform::x11::register_xlib_error_hook(Box::new(|_display, error| {
|
||||
let error = error as *mut x11_dl::xlib::XErrorEvent;
|
||||
(unsafe { (*error).error_code }) == 170
|
||||
}));
|
||||
}
|
||||
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let window = WindowBuilder::new()
|
||||
.with_inner_size(LogicalSize::new(800, 800))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let size = window.inner_size().to_logical::<u32>(window.scale_factor());
|
||||
|
||||
let webview = WebViewBuilder::new_as_child(&window)
|
||||
.with_position((0, 0))
|
||||
.with_size((size.width / 2, size.height / 2))
|
||||
.with_url("https://tauri.app")?
|
||||
.build()?;
|
||||
let webview2 = WebViewBuilder::new_as_child(&window)
|
||||
.with_position(((size.width / 2) as i32, 0))
|
||||
.with_size((size.width / 2, size.height / 2))
|
||||
.with_url("https://github.com/tauri-apps/wry")?
|
||||
.build()?;
|
||||
let webview3 = WebViewBuilder::new_as_child(&window)
|
||||
.with_position((0, (size.height / 2) as i32))
|
||||
.with_size((size.width / 2, size.height / 2))
|
||||
.with_url("https://twitter.com/TauriApps")?
|
||||
.build()?;
|
||||
let webview4 = WebViewBuilder::new_as_child(&window)
|
||||
.with_position(((size.width / 2) as i32, (size.height / 2) as i32))
|
||||
.with_size((size.width / 2, size.height / 2))
|
||||
.with_url("https://google.com")?
|
||||
.build()?;
|
||||
|
||||
event_loop
|
||||
.run(move |event, evl| {
|
||||
evl.set_control_flow(ControlFlow::Poll);
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
))]
|
||||
while gtk::events_pending() {
|
||||
gtk::main_iteration_do(false);
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::Resized(size),
|
||||
..
|
||||
} => {
|
||||
let size = size.to_logical::<u32>(window.scale_factor());
|
||||
webview.set_size((size.width / 2, size.height / 2));
|
||||
webview.set_position((0, 0));
|
||||
webview2.set_position(((size.width / 2) as i32, 0));
|
||||
webview2.set_size((size.width / 2, size.height / 2));
|
||||
webview3.set_position((0, (size.height / 2) as i32));
|
||||
webview3.set_size((size.width / 2, size.height / 2));
|
||||
webview4.set_position(((size.width / 2) as i32, (size.height / 2) as i32));
|
||||
webview4.set_size((size.width / 2, size.height / 2));
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => evl.exit(),
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
125
examples/multiwindow.rs
Normal file
125
examples/multiwindow.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::collections::HashMap;
|
||||
use tao::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget},
|
||||
window::{Window, WindowBuilder, WindowId},
|
||||
};
|
||||
use wry::{WebView, WebViewBuilder};
|
||||
|
||||
enum UserEvent {
|
||||
CloseWindow(WindowId),
|
||||
NewTitle(WindowId, String),
|
||||
NewWindow,
|
||||
}
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
let event_loop = EventLoopBuilder::<UserEvent>::with_user_event().build();
|
||||
let mut webviews = HashMap::new();
|
||||
let proxy = event_loop.create_proxy();
|
||||
|
||||
let new_window = create_new_window(
|
||||
format!("Window {}", webviews.len() + 1),
|
||||
&event_loop,
|
||||
proxy.clone(),
|
||||
);
|
||||
webviews.insert(new_window.0.id(), (new_window.0, new_window.1));
|
||||
|
||||
event_loop.run(move |event, event_loop, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
window_id,
|
||||
..
|
||||
} => {
|
||||
webviews.remove(&window_id);
|
||||
if webviews.is_empty() {
|
||||
*control_flow = ControlFlow::Exit
|
||||
}
|
||||
}
|
||||
Event::UserEvent(UserEvent::NewWindow) => {
|
||||
let new_window = create_new_window(
|
||||
format!("Window {}", webviews.len() + 1),
|
||||
event_loop,
|
||||
proxy.clone(),
|
||||
);
|
||||
webviews.insert(new_window.0.id(), (new_window.0, new_window.1));
|
||||
}
|
||||
Event::UserEvent(UserEvent::CloseWindow(id)) => {
|
||||
webviews.remove(&id);
|
||||
if webviews.is_empty() {
|
||||
*control_flow = ControlFlow::Exit
|
||||
}
|
||||
}
|
||||
|
||||
Event::UserEvent(UserEvent::NewTitle(id, title)) => {
|
||||
webviews.get(&id).unwrap().0.set_title(&title);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn create_new_window(
|
||||
title: String,
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
proxy: EventLoopProxy<UserEvent>,
|
||||
) -> (Window, WebView) {
|
||||
let window = WindowBuilder::new()
|
||||
.with_title(title)
|
||||
.build(event_loop)
|
||||
.unwrap();
|
||||
let window_id = window.id();
|
||||
let handler = move |req: String| match req.as_str() {
|
||||
"new-window" => {
|
||||
let _ = proxy.send_event(UserEvent::NewWindow);
|
||||
}
|
||||
"close" => {
|
||||
let _ = proxy.send_event(UserEvent::CloseWindow(window_id));
|
||||
}
|
||||
_ if req.starts_with("change-title") => {
|
||||
let title = req.replace("change-title:", "");
|
||||
let _ = proxy.send_event(UserEvent::NewTitle(window_id, title));
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "android"
|
||||
))]
|
||||
let builder = WebViewBuilder::new(&window);
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "android"
|
||||
)))]
|
||||
let builder = {
|
||||
use tao::platform::unix::WindowExtUnix;
|
||||
let vbox = window.default_vbox().unwrap();
|
||||
WebViewBuilder::new_gtk(vbox)
|
||||
};
|
||||
|
||||
let webview = builder
|
||||
.with_html(
|
||||
r#"
|
||||
<button onclick="window.ipc.postMessage('new-window')">Open a new window</button>
|
||||
<button onclick="window.ipc.postMessage('close')">Close current window</button>
|
||||
<input oninput="window.ipc.postMessage(`change-title:${this.value}`)" />
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
.with_ipc_handler(handler)
|
||||
.build()
|
||||
.unwrap();
|
||||
(window, webview)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
use wry::{
|
||||
application::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoopBuilder},
|
||||
window::WindowBuilder,
|
||||
},
|
||||
webview::WebViewBuilder,
|
||||
};
|
||||
|
||||
enum UserEvent {
|
||||
Navigation(String),
|
||||
}
|
||||
|
||||
let event_loop = EventLoopBuilder::<UserEvent>::with_user_event().build();
|
||||
let proxy = event_loop.create_proxy();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Hello World")
|
||||
.build(&event_loop)?;
|
||||
let _webview = WebViewBuilder::new(window)?
|
||||
.with_url("http://neverssl.com")?
|
||||
.with_navigation_handler(move |uri: String| {
|
||||
let submitted = proxy.send_event(UserEvent::Navigation(uri.clone())).is_ok();
|
||||
|
||||
submitted && uri.contains("neverssl")
|
||||
})
|
||||
.build()?;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => println!("Wry has started!"),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
Event::UserEvent(UserEvent::Navigation(uri)) => {
|
||||
println!("{}", uri);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
use wry::{
|
||||
application::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoopBuilder},
|
||||
window::WindowBuilder,
|
||||
},
|
||||
webview::WebViewBuilder,
|
||||
};
|
||||
|
||||
enum UserEvent {
|
||||
NewWindow(String),
|
||||
}
|
||||
|
||||
let html = r#"
|
||||
<body>
|
||||
<div>
|
||||
<p> WRYYYYYYYYYYYYYYYYYYYYYY! </p>
|
||||
<a href="https://www.wikipedia.org" target="_blank">Visit Wikipedia</a>
|
||||
<a href="https://www.github.com" target="_blank">(Try to) visit GitHub</a>
|
||||
</div>
|
||||
</body>
|
||||
"#;
|
||||
|
||||
let event_loop = EventLoopBuilder::<UserEvent>::with_user_event().build();
|
||||
let proxy = event_loop.create_proxy();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Hello World")
|
||||
.build(&event_loop)?;
|
||||
let _webview = WebViewBuilder::new(window)?
|
||||
.with_html(html)?
|
||||
.with_new_window_req_handler(move |uri: String| {
|
||||
let submitted = proxy.send_event(UserEvent::NewWindow(uri.clone())).is_ok();
|
||||
|
||||
submitted && uri.contains("wikipedia")
|
||||
})
|
||||
.build()?;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => println!("Wry has started!"),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
Event::UserEvent(UserEvent::NewWindow(uri)) => {
|
||||
println!("New Window: {}", uri);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use wry::webview::{ProxyConfig, ProxyEndpoint};
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
use wry::{
|
||||
application::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
},
|
||||
webview::WebViewBuilder,
|
||||
};
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Proxy Test")
|
||||
.build(&event_loop)?;
|
||||
|
||||
let http_proxy = ProxyConfig::Http(ProxyEndpoint {
|
||||
host: "localhost".to_string(),
|
||||
port: "3128".to_string(),
|
||||
});
|
||||
|
||||
let _webview = WebViewBuilder::new(window)?
|
||||
.with_proxy_config(http_proxy)
|
||||
.with_url("https://www.myip.com/")?
|
||||
.build()?;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => println!("Wry has started!"),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
49
examples/simple.rs
Normal file
49
examples/simple.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use tao::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
use wry::WebViewBuilder;
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "android"
|
||||
))]
|
||||
let builder = WebViewBuilder::new(&window);
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "android"
|
||||
)))]
|
||||
let builder = {
|
||||
use tao::platform::unix::WindowExtUnix;
|
||||
let vbox = window.default_vbox().unwrap();
|
||||
WebViewBuilder::new_gtk(vbox)
|
||||
};
|
||||
|
||||
let _webview = builder.with_url("https://tauri.app")?.build()?;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
if let Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
*control_flow = ControlFlow::Exit
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<!-- Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
SPDX-License-Identifier: MIT -->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<video id="video_source" style="width: 90vw; height: 90vh" controls="" autoplay="" name="media">
|
||||
<source src="wry://localhost/examples/test_video.mp4" type="video/mp4" />
|
||||
</video>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
if (navigator.userAgent.includes("Windows")) {
|
||||
const video = document.getElementById("video_source");
|
||||
const sources = video.getElementsByTagName("source");
|
||||
sources[0].src = "http://wry.localhost/examples/test_video.mp4";
|
||||
video.load();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,158 +0,0 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use http_range::HttpRange;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fs::{canonicalize, File},
|
||||
io::{Read, Seek, SeekFrom},
|
||||
path::PathBuf,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
use wry::{
|
||||
application::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
},
|
||||
http::{header::CONTENT_TYPE, status::StatusCode, Response},
|
||||
webview::WebViewBuilder,
|
||||
};
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
let video_file = PathBuf::from("examples/test_video.mp4");
|
||||
let video_url =
|
||||
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
|
||||
|
||||
if !video_file.exists() {
|
||||
// Downloading with curl this saves us from adding
|
||||
// a Rust HTTP client dependency.
|
||||
println!("Downloading {}", video_url);
|
||||
let status = Command::new("curl")
|
||||
.arg("-L")
|
||||
.arg("-o")
|
||||
.arg(&video_file)
|
||||
.arg(video_url)
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit())
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
assert!(status.status.success());
|
||||
assert!(video_file.exists());
|
||||
}
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Hello World")
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
let _webview = WebViewBuilder::new(window)
|
||||
.unwrap()
|
||||
.with_custom_protocol("wry".into(), move |request| {
|
||||
get_stream_response(request).unwrap_or_else(|error| {
|
||||
http::Response::builder()
|
||||
.status(http::StatusCode::BAD_REQUEST)
|
||||
.header(CONTENT_TYPE, "text/plain")
|
||||
.body(error.to_string().as_bytes().to_vec().into())
|
||||
.unwrap()
|
||||
})
|
||||
})
|
||||
// tell the webview to load the custom protocol
|
||||
.with_url("wry://localhost/examples/stream.html")?
|
||||
.build()?;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => println!("Wry application started!"),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn get_stream_response(
|
||||
request: http::Request<Vec<u8>>,
|
||||
) -> Result<http::Response<Cow<'static, [u8]>>, Box<dyn std::error::Error>> {
|
||||
// remove leading slash
|
||||
let path = &request.uri().path()[1..];
|
||||
|
||||
// Read the file content from file path
|
||||
let mut content = File::open(canonicalize(path)?)?;
|
||||
|
||||
// Return asset contents and mime types based on file extentions
|
||||
// If you don't want to do this manually, there are some crates for you.
|
||||
// Such as `infer` and `mime_guess`.
|
||||
let mut status_code = StatusCode::OK;
|
||||
let mut buf = Vec::new();
|
||||
|
||||
// guess our mimetype from the path
|
||||
let mimetype = if path.ends_with(".html") {
|
||||
"text/html"
|
||||
} else if path.ends_with(".mp4") {
|
||||
"video/mp4"
|
||||
} else {
|
||||
unimplemented!();
|
||||
};
|
||||
|
||||
// prepare our http response
|
||||
let mut response = Response::builder();
|
||||
|
||||
// read our range header if it exist, so we can return partial content
|
||||
if let Some(range) = request.headers().get("range") {
|
||||
// Get the file size
|
||||
let file_size = content.metadata().unwrap().len();
|
||||
|
||||
// we parse the range header
|
||||
let range = HttpRange::parse(range.to_str().unwrap(), file_size).unwrap();
|
||||
|
||||
// let support only 1 range for now
|
||||
let first_range = range.first();
|
||||
if let Some(range) = first_range {
|
||||
let mut real_length = range.length;
|
||||
|
||||
// prevent max_length;
|
||||
// specially on webview2
|
||||
if range.length > file_size / 3 {
|
||||
// max size sent (400ko / request)
|
||||
// as it's local file system we can afford to read more often
|
||||
real_length = 1024 * 400;
|
||||
}
|
||||
|
||||
// last byte we are reading, the length of the range include the last byte
|
||||
// who should be skipped on the header
|
||||
let last_byte = range.start + real_length - 1;
|
||||
status_code = StatusCode::PARTIAL_CONTENT;
|
||||
|
||||
response = response.header("Connection", "Keep-Alive");
|
||||
response = response.header("Accept-Ranges", "bytes");
|
||||
// we need to overwrite our content length
|
||||
response = response.header("Content-Length", real_length);
|
||||
response = response.header(
|
||||
"Content-Range",
|
||||
format!("bytes {}-{}/{}", range.start, last_byte, file_size),
|
||||
);
|
||||
|
||||
// seek our file bytes
|
||||
content.seek(SeekFrom::Start(range.start))?;
|
||||
content.take(real_length).read_to_end(&mut buf)?;
|
||||
} else {
|
||||
content.read_to_end(&mut buf)?;
|
||||
}
|
||||
} else {
|
||||
content.read_to_end(&mut buf)?;
|
||||
}
|
||||
|
||||
response
|
||||
.header(CONTENT_TYPE, mimetype)
|
||||
.status(status_code)
|
||||
.body(buf.into())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
@@ -2,53 +2,79 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
use wry::{
|
||||
application::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
},
|
||||
webview::WebViewBuilder,
|
||||
};
|
||||
use tao::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
use wry::WebViewBuilder;
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
#[allow(unused_mut)]
|
||||
let mut builder = WindowBuilder::new()
|
||||
.with_decorations(false)
|
||||
// There are actually three layer of background color when creating webview window.
|
||||
// The first is window background...
|
||||
.with_transparent(true)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
.with_transparent(true);
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use tao::platform::windows::WindowBuilderExtWindows;
|
||||
builder = builder.with_undecorated_shadow(false);
|
||||
}
|
||||
let window = builder.build(&event_loop).unwrap();
|
||||
|
||||
let _webview = WebViewBuilder::new(window)?
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use tao::platform::windows::WindowExtWindows;
|
||||
window.set_undecorated_shadow(true);
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "android"
|
||||
))]
|
||||
let builder = WebViewBuilder::new(&window);
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "windows",
|
||||
target_os = "macos",
|
||||
target_os = "ios",
|
||||
target_os = "android"
|
||||
)))]
|
||||
let builder = {
|
||||
use tao::platform::unix::WindowExtUnix;
|
||||
let vbox = window.default_vbox().unwrap();
|
||||
WebViewBuilder::new_gtk(vbox)
|
||||
};
|
||||
|
||||
let _webview = builder
|
||||
// The second is on webview...
|
||||
.with_transparent(true)
|
||||
// And the last is in html.
|
||||
.with_html(
|
||||
r#"
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body style="background-color:rgba(87,87,87,0.5);">hello</body>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
document.body.innerText = `hello, ${navigator.userAgent}`;
|
||||
};
|
||||
</script>
|
||||
</html>"#,
|
||||
r#"<html>
|
||||
<body style="background-color:rgba(87,87,87,0.5);"></body>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
document.body.innerText = `hello, ${navigator.userAgent}`;
|
||||
};
|
||||
</script>
|
||||
</html>"#,
|
||||
)?
|
||||
.build()?;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => println!("Wry has started!"),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => {}
|
||||
if let Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
*control_flow = ControlFlow::Exit
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
use wry::{
|
||||
application::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
},
|
||||
webview::{webview_version, WebViewBuilder},
|
||||
};
|
||||
|
||||
let current_version = env!("CARGO_PKG_VERSION");
|
||||
let current_webview_version = webview_version().unwrap();
|
||||
let user_agent_string = format!(
|
||||
"wry/{} ({}; {})",
|
||||
current_version,
|
||||
std::env::consts::OS,
|
||||
current_webview_version
|
||||
);
|
||||
|
||||
let event_loop = EventLoop::new();
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Hello World")
|
||||
.build(&event_loop)?;
|
||||
let _webview = WebViewBuilder::new(window)?
|
||||
.with_user_agent(&user_agent_string)
|
||||
.with_url("https://www.whatismybrowser.com/detect/what-is-my-user-agent")?
|
||||
.build()?;
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match event {
|
||||
Event::NewEvents(StartCause::Init) => println!("Wry has started!"),
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => *control_flow = ControlFlow::Exit,
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
218
examples/wgpu.rs
Normal file
218
examples/wgpu.rs
Normal file
@@ -0,0 +1,218 @@
|
||||
use std::borrow::Cow;
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::Window,
|
||||
};
|
||||
use wry::WebViewBuilder;
|
||||
|
||||
async fn run(event_loop: EventLoop<()>, window: Window) {
|
||||
let size = window.inner_size();
|
||||
|
||||
let instance = wgpu::Instance::default();
|
||||
|
||||
let surface = unsafe { instance.create_surface(&window) }.unwrap();
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::default(),
|
||||
force_fallback_adapter: false,
|
||||
// Request an adapter which can render to our surface
|
||||
compatible_surface: Some(&surface),
|
||||
})
|
||||
.await
|
||||
.expect("Failed to find an appropriate adapter");
|
||||
|
||||
// Create the logical device and command queue
|
||||
let (device, queue) = adapter
|
||||
.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
label: None,
|
||||
features: wgpu::Features::empty(),
|
||||
// Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain.
|
||||
limits: wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter.limits()),
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to create device");
|
||||
|
||||
// Load the shaders from disk
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: None,
|
||||
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(
|
||||
r#"
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {
|
||||
let x = f32(i32(in_vertex_index) - 1);
|
||||
let y = f32(i32(in_vertex_index & 1u) * 2 - 1);
|
||||
return vec4<f32>(x, y, 0.0, 1.0);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main() -> @location(0) vec4<f32> {
|
||||
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
"#,
|
||||
)),
|
||||
});
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: None,
|
||||
bind_group_layouts: &[],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let swapchain_capabilities = surface.get_capabilities(&adapter);
|
||||
let swapchain_format = swapchain_capabilities.formats[0];
|
||||
|
||||
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: None,
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(swapchain_format.into())],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
let mut config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: swapchain_format,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
present_mode: wgpu::PresentMode::Fifo,
|
||||
alpha_mode: swapchain_capabilities.alpha_modes[0],
|
||||
view_formats: vec![],
|
||||
};
|
||||
|
||||
surface.configure(&device, &config);
|
||||
|
||||
let _webview = WebViewBuilder::new_as_child(&window)
|
||||
.with_position((100, 100))
|
||||
.with_size((400, 400))
|
||||
.with_transparent(true)
|
||||
.with_html(
|
||||
r#"<html>
|
||||
<body style="background-color:rgba(87,87,87,0.5);"></body>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
document.body.innerText = `hello, ${navigator.userAgent}`;
|
||||
};
|
||||
</script>
|
||||
</html>"#,
|
||||
)
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
event_loop
|
||||
.run(move |event, evl| {
|
||||
evl.set_control_flow(ControlFlow::Poll);
|
||||
|
||||
match event {
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::Resized(size),
|
||||
..
|
||||
} => {
|
||||
// Reconfigure the surface with the new size
|
||||
config.width = size.width;
|
||||
config.height = size.height;
|
||||
surface.configure(&device, &config);
|
||||
// On macos the window needs to be redrawn manually after resizing
|
||||
window.request_redraw();
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::RedrawRequested,
|
||||
..
|
||||
} => {
|
||||
let frame = surface
|
||||
.get_current_texture()
|
||||
.expect("Failed to acquire next swap chain texture");
|
||||
let view = frame
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let mut encoder =
|
||||
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
|
||||
{
|
||||
let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: None,
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
rpass.set_pipeline(&render_pipeline);
|
||||
rpass.draw(0..3, 0..1);
|
||||
}
|
||||
|
||||
queue.submit(Some(encoder.finish()));
|
||||
frame.present();
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => evl.exit(),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
))]
|
||||
while gtk::events_pending() {
|
||||
gtk::main_iteration_do(false);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
))]
|
||||
{
|
||||
use gtk::prelude::DisplayExtManual;
|
||||
|
||||
gtk::init().unwrap();
|
||||
if gtk::gdk::Display::default().unwrap().backend().is_wayland() {
|
||||
panic!("This example doesn't support wayland!");
|
||||
}
|
||||
|
||||
// we need to ignore this error here otherwise it will be catched by winit and will be
|
||||
// make the example crash
|
||||
winit::platform::x11::register_xlib_error_hook(Box::new(|_display, error| {
|
||||
let error = error as *mut x11_dl::xlib::XErrorEvent;
|
||||
(unsafe { (*error).error_code }) == 170
|
||||
}));
|
||||
}
|
||||
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
.with_transparent(true)
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
pollster::block_on(run(event_loop, window));
|
||||
}
|
||||
86
examples/winit.rs
Normal file
86
examples/winit.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use winit::{
|
||||
dpi::LogicalSize,
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoop},
|
||||
window::WindowBuilder,
|
||||
};
|
||||
use wry::WebViewBuilder;
|
||||
|
||||
fn main() -> wry::Result<()> {
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
))]
|
||||
{
|
||||
use gtk::prelude::DisplayExtManual;
|
||||
|
||||
gtk::init().unwrap();
|
||||
if gtk::gdk::Display::default().unwrap().backend().is_wayland() {
|
||||
panic!("This example doesn't support wayland!");
|
||||
}
|
||||
|
||||
// we need to ignore this error here otherwise it will be catched by winit and will be
|
||||
// make the example crash
|
||||
winit::platform::x11::register_xlib_error_hook(Box::new(|_display, error| {
|
||||
let error = error as *mut x11_dl::xlib::XErrorEvent;
|
||||
(unsafe { (*error).error_code }) == 170
|
||||
}));
|
||||
}
|
||||
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let window = WindowBuilder::new()
|
||||
.with_inner_size(LogicalSize::new(800, 800))
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut builder = WebViewBuilder::new(&window);
|
||||
let _webview = builder.with_url("https://tauri.app")?.build()?;
|
||||
|
||||
event_loop
|
||||
.run(move |event, evl| {
|
||||
evl.set_control_flow(ControlFlow::Poll);
|
||||
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
))]
|
||||
while gtk::events_pending() {
|
||||
gtk::main_iteration_do(false);
|
||||
}
|
||||
|
||||
match event {
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
))]
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::Resized(size),
|
||||
..
|
||||
} => {
|
||||
_webview.set_size(size.into());
|
||||
}
|
||||
Event::WindowEvent {
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} => evl.exit(),
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -6,35 +6,30 @@ use http::{
|
||||
header::{HeaderName, HeaderValue, CONTENT_TYPE},
|
||||
Request,
|
||||
};
|
||||
pub use tao::platform::android::ndk_glue::jni::sys::{jboolean, jstring};
|
||||
use tao::platform::android::ndk_glue::jni::{
|
||||
errors::Error as JniError,
|
||||
objects::{JClass, JMap, JObject, JString},
|
||||
sys::jobject,
|
||||
use jni::errors::Result as JniResult;
|
||||
pub use jni::{
|
||||
self,
|
||||
objects::{GlobalRef, JClass, JMap, JObject, JString},
|
||||
sys::{jboolean, jobject, jstring},
|
||||
JNIEnv,
|
||||
};
|
||||
pub use ndk;
|
||||
|
||||
use super::{
|
||||
ASSET_LOADER_DOMAIN, IPC, ON_LOAD_HANDLER, REQUEST_HANDLER, TITLE_CHANGE_HANDLER,
|
||||
URL_LOADING_OVERRIDE, WITH_ASSET_LOADER,
|
||||
};
|
||||
|
||||
use crate::webview::PageLoadEvent;
|
||||
use crate::PageLoadEvent;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! android_binding {
|
||||
($domain:ident, $package:ident, $main:ident) => {
|
||||
android_binding!($domain, $package, $main, ::wry)
|
||||
($domain:ident, $package:ident) => {
|
||||
::wry::android_binding!($domain, $package, ::wry)
|
||||
};
|
||||
($domain:ident, $package:ident, $main:ident, $wry:path) => {
|
||||
use $wry::{
|
||||
application::{
|
||||
android_binding as tao_android_binding, android_fn, generate_package_name,
|
||||
platform::android::ndk_glue::*,
|
||||
},
|
||||
webview::prelude::*,
|
||||
};
|
||||
tao_android_binding!($domain, $package, WryActivity, setup, $main);
|
||||
($domain:ident, $package:ident, $wry:path) => {{
|
||||
use $wry::prelude::*;
|
||||
|
||||
android_fn!(
|
||||
$domain,
|
||||
$package,
|
||||
@@ -97,10 +92,10 @@ macro_rules! android_binding {
|
||||
handleReceivedTitle,
|
||||
[JObject, JString],
|
||||
);
|
||||
};
|
||||
}};
|
||||
}
|
||||
|
||||
fn handle_request(env: &mut JNIEnv, request: JObject) -> Result<jobject, JniError> {
|
||||
fn handle_request(env: &mut JNIEnv, request: JObject) -> JniResult<jobject> {
|
||||
if let Some(handler) = REQUEST_HANDLER.get() {
|
||||
let mut request_builder = Request::builder();
|
||||
|
||||
@@ -113,10 +108,10 @@ fn handle_request(env: &mut JNIEnv, request: JObject) -> Result<jobject, JniErro
|
||||
.into();
|
||||
request_builder = request_builder.uri(&env.get_string(&url)?.to_string_lossy().to_string());
|
||||
|
||||
let method: JString = env
|
||||
let method = env
|
||||
.call_method(&request, "getMethod", "()Ljava/lang/String;", &[])?
|
||||
.l()?
|
||||
.into();
|
||||
.l()
|
||||
.map(JString::from)?;
|
||||
request_builder = request_builder.method(
|
||||
env
|
||||
.get_string(&method)?
|
||||
@@ -266,7 +261,7 @@ pub unsafe fn ipc(mut env: JNIEnv, _: JClass, arg: JString) {
|
||||
Ok(arg) => {
|
||||
let arg = arg.to_string_lossy().to_string();
|
||||
if let Some(ipc) = IPC.get() {
|
||||
(ipc.handler)(&ipc.window, arg)
|
||||
(ipc.handler)(arg)
|
||||
}
|
||||
}
|
||||
Err(e) => log::warn!("Failed to parse JString: {}", e),
|
||||
@@ -279,7 +274,7 @@ pub unsafe fn handleReceivedTitle(mut env: JNIEnv, _: JClass, _webview: JObject,
|
||||
Ok(title) => {
|
||||
let title = title.to_string_lossy().to_string();
|
||||
if let Some(title_handler) = TITLE_CHANGE_HANDLER.get() {
|
||||
(title_handler.handler)(&title_handler.window, title)
|
||||
(title_handler.handler)(title)
|
||||
}
|
||||
}
|
||||
Err(e) => log::warn!("Failed to parse JString: {}", e),
|
||||
@@ -2,20 +2,17 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::{webview::RGBA, Error};
|
||||
use crate::{Error, RGBA};
|
||||
use crossbeam_channel::*;
|
||||
use jni::{
|
||||
errors::Result as JniResult,
|
||||
objects::{GlobalRef, JMap, JObject, JString},
|
||||
JNIEnv,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::os::unix::prelude::*;
|
||||
use tao::platform::android::ndk_glue::{
|
||||
jni::{
|
||||
errors::Error as JniError,
|
||||
objects::{GlobalRef, JObject, JString},
|
||||
JNIEnv,
|
||||
},
|
||||
JMap, PACKAGE,
|
||||
};
|
||||
|
||||
use super::find_class;
|
||||
use super::{find_class, PACKAGE};
|
||||
|
||||
static CHANNEL: Lazy<(Sender<WebViewMessage>, Receiver<WebViewMessage>)> = Lazy::new(|| bounded(8));
|
||||
pub static MAIN_PIPE: Lazy<[RawFd; 2]> = Lazy::new(|| {
|
||||
@@ -39,7 +36,7 @@ impl<'a> MainPipe<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recv(&mut self) -> Result<(), JniError> {
|
||||
pub fn recv(&mut self) -> JniResult<()> {
|
||||
let activity = self.activity.as_obj();
|
||||
if let Ok(message) = CHANNEL.1.recv() {
|
||||
match message {
|
||||
@@ -265,7 +262,7 @@ fn load_url<'a>(
|
||||
url: &JString<'a>,
|
||||
headers: Option<http::HeaderMap>,
|
||||
main_thread: bool,
|
||||
) -> Result<(), JniError> {
|
||||
) -> JniResult<()> {
|
||||
let function = if main_thread {
|
||||
"loadUrlMainThread"
|
||||
} else {
|
||||
@@ -294,11 +291,7 @@ fn load_url<'a>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_html<'a>(
|
||||
env: &mut JNIEnv<'a>,
|
||||
webview: &JObject<'a>,
|
||||
html: &JString<'a>,
|
||||
) -> Result<(), JniError> {
|
||||
fn load_html<'a>(env: &mut JNIEnv<'a>, webview: &JObject<'a>, html: &JString<'a>) -> JniResult<()> {
|
||||
env.call_method(
|
||||
webview,
|
||||
"loadHTMLMainThread",
|
||||
@@ -312,7 +305,7 @@ fn set_background_color<'a>(
|
||||
env: &mut JNIEnv<'a>,
|
||||
webview: &JObject<'a>,
|
||||
background_color: RGBA,
|
||||
) -> Result<(), JniError> {
|
||||
) -> JniResult<()> {
|
||||
let color_class = env.find_class("android/graphics/Color")?;
|
||||
let color = env.call_static_method(
|
||||
color_class,
|
||||
@@ -349,6 +342,6 @@ pub(crate) struct CreateWebViewAttributes {
|
||||
pub background_color: Option<RGBA>,
|
||||
pub headers: Option<http::HeaderMap>,
|
||||
pub autoplay: bool,
|
||||
pub on_webview_created: Option<Box<dyn Fn(super::Context) -> Result<(), JniError> + Send>>,
|
||||
pub on_webview_created: Option<Box<dyn Fn(super::Context) -> JniResult<()> + Send>>,
|
||||
pub user_agent: Option<String>,
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use super::{PageLoadEvent, WebContext, WebViewAttributes, RGBA};
|
||||
use crate::{application::window::Window, webview::RequestAsyncResponder, Result};
|
||||
use crate::{RequestAsyncResponder, Result};
|
||||
use base64::{engine::general_purpose, Engine};
|
||||
use crossbeam_channel::*;
|
||||
use html5ever::{interface::QualName, namespace_url, ns, tendril::TendrilSink, LocalName};
|
||||
@@ -11,19 +11,16 @@ use http::{
|
||||
header::{HeaderValue, CONTENT_SECURITY_POLICY, CONTENT_TYPE},
|
||||
Request, Response as HttpResponse,
|
||||
};
|
||||
use jni::{
|
||||
errors::Result as JniResult,
|
||||
objects::{GlobalRef, JClass, JObject},
|
||||
JNIEnv,
|
||||
};
|
||||
use kuchiki::NodeRef;
|
||||
use ndk::looper::{FdEvent, ForeignLooper};
|
||||
use once_cell::sync::OnceCell;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{borrow::Cow, rc::Rc, sync::mpsc::channel};
|
||||
use tao::platform::android::ndk_glue::{
|
||||
jni::{
|
||||
errors::Error as JniError,
|
||||
objects::{GlobalRef, JClass, JObject},
|
||||
JNIEnv,
|
||||
},
|
||||
ndk::looper::{FdEvent, ForeignLooper},
|
||||
PACKAGE,
|
||||
};
|
||||
use std::{borrow::Cow, sync::mpsc::channel};
|
||||
use url::Url;
|
||||
|
||||
pub(crate) mod binding;
|
||||
@@ -55,9 +52,9 @@ macro_rules! define_static_handlers {
|
||||
}
|
||||
|
||||
define_static_handlers! {
|
||||
IPC = UnsafeIpc { handler: Box<dyn Fn(&Window, String)>, window: Rc<Window> };
|
||||
IPC = UnsafeIpc { handler: Box<dyn Fn(String)> };
|
||||
REQUEST_HANDLER = UnsafeRequestHandler { handler: Box<dyn Fn(Request<Vec<u8>>) -> Option<HttpResponse<Cow<'static, [u8]>>>> };
|
||||
TITLE_CHANGE_HANDLER = UnsafeTitleHandler { handler: Box<dyn Fn(&Window, String)>, window: Rc<Window> };
|
||||
TITLE_CHANGE_HANDLER = UnsafeTitleHandler { handler: Box<dyn Fn(String)> };
|
||||
URL_LOADING_OVERRIDE = UnsafeUrlLoadingOverride { handler: Box<dyn Fn(String) -> bool> };
|
||||
ON_LOAD_HANDLER = UnsafeOnPageLoadHandler { handler: Box<dyn Fn(PageLoadEvent, String)> };
|
||||
}
|
||||
@@ -65,7 +62,17 @@ define_static_handlers! {
|
||||
pub static WITH_ASSET_LOADER: OnceCell<bool> = OnceCell::new();
|
||||
pub static ASSET_LOADER_DOMAIN: OnceCell<String> = OnceCell::new();
|
||||
|
||||
pub unsafe fn setup(mut env: JNIEnv, looper: &ForeignLooper, activity: GlobalRef) {
|
||||
pub(crate) static PACKAGE: OnceCell<String> = OnceCell::new();
|
||||
|
||||
/// Sets up the necessary logic for wry to be able to create the webviews later.
|
||||
pub unsafe fn android_setup(
|
||||
package: &str,
|
||||
mut env: JNIEnv,
|
||||
looper: &ForeignLooper,
|
||||
activity: GlobalRef,
|
||||
) {
|
||||
PACKAGE.get_or_init(move || package.to_string());
|
||||
|
||||
// we must create the WebChromeClient here because it calls `registerForActivityResult`,
|
||||
// which gives an `LifecycleOwners must call register before they are STARTED.` error when called outside the onCreate hook
|
||||
let rust_webchrome_client_class = find_class(
|
||||
@@ -103,14 +110,20 @@ pub unsafe fn setup(mut env: JNIEnv, looper: &ForeignLooper, activity: GlobalRef
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub(crate) struct InnerWebView {
|
||||
#[allow(unused)]
|
||||
pub window: Rc<Window>,
|
||||
}
|
||||
pub(crate) struct InnerWebView;
|
||||
|
||||
impl InnerWebView {
|
||||
pub fn new_as_child(
|
||||
_window: &impl raw_window_handle::HasRawWindowHandle,
|
||||
attributes: WebViewAttributes,
|
||||
pl_attrs: super::PlatformSpecificWebViewAttributes,
|
||||
_web_context: Option<&mut WebContext>,
|
||||
) -> Result<Self> {
|
||||
Self::new(_window, attributes, pl_attrs, _web_context)
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
window: Rc<Window>,
|
||||
_window: &impl raw_window_handle::HasRawWindowHandle,
|
||||
attributes: WebViewAttributes,
|
||||
pl_attrs: super::PlatformSpecificWebViewAttributes,
|
||||
_web_context: Option<&mut WebContext>,
|
||||
@@ -247,14 +260,12 @@ impl InnerWebView {
|
||||
}))
|
||||
});
|
||||
|
||||
let w = window.clone();
|
||||
if let Some(i) = ipc_handler {
|
||||
IPC.get_or_init(move || UnsafeIpc::new(Box::new(i), w));
|
||||
IPC.get_or_init(move || UnsafeIpc::new(Box::new(i)));
|
||||
}
|
||||
|
||||
let w = window.clone();
|
||||
if let Some(i) = attributes.document_title_changed_handler {
|
||||
TITLE_CHANGE_HANDLER.get_or_init(move || UnsafeTitleHandler::new(i, w));
|
||||
TITLE_CHANGE_HANDLER.get_or_init(move || UnsafeTitleHandler::new(i));
|
||||
}
|
||||
|
||||
if let Some(i) = attributes.navigation_handler {
|
||||
@@ -265,7 +276,7 @@ impl InnerWebView {
|
||||
ON_LOAD_HANDLER.get_or_init(move || UnsafeOnPageLoadHandler::new(h));
|
||||
}
|
||||
|
||||
Ok(Self { window })
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
pub fn print(&self) {}
|
||||
@@ -312,6 +323,22 @@ impl InnerWebView {
|
||||
MainPipe::send(WebViewMessage::ClearAllBrowsingData);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_position(&self, _position: (i32, i32)) {
|
||||
// Unsupported.
|
||||
}
|
||||
|
||||
pub fn set_size(&self, _size: (u32, u32)) {
|
||||
// Unsupported.
|
||||
}
|
||||
|
||||
pub fn set_visible(&self, _visible: bool) {
|
||||
// Unsupported
|
||||
}
|
||||
|
||||
pub fn focus(&self) {
|
||||
// Unsupported
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -359,7 +386,7 @@ pub fn find_class<'a>(
|
||||
env: &mut JNIEnv<'a>,
|
||||
activity: &JObject<'_>,
|
||||
name: String,
|
||||
) -> std::result::Result<JClass<'a>, JniError> {
|
||||
) -> JniResult<JClass<'a>> {
|
||||
let class_name = env.new_string(name.replace('/', "."))?;
|
||||
let my_class = env
|
||||
.call_method(
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//! Re-exported Tao APIs
|
||||
//!
|
||||
//! This module re-export [tao] APIs for user to create application windows. To learn more about
|
||||
//! how to use tao, please see [its documentation](https://crates.io/crates/tao).
|
||||
//!
|
||||
//! [tao]: https://crates.io/crates/tao
|
||||
|
||||
#[cfg(feature = "tao")]
|
||||
pub use tao::*;
|
||||
|
||||
#[cfg(feature = "winit")]
|
||||
pub use winit::*;
|
||||
91
src/error.rs
Normal file
91
src/error.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
/// Convenient type alias of Result type for wry.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Errors returned by wry.
|
||||
#[non_exhaustive]
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
#[error(transparent)]
|
||||
GlibError(#[from] gtk::glib::Error),
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
#[error(transparent)]
|
||||
GlibBoolError(#[from] gtk::glib::BoolError),
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
#[error("Fail to fetch security manager")]
|
||||
MissingManager,
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
#[error("Couldn't find X11 Display")]
|
||||
X11DisplayNotFound,
|
||||
#[cfg(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
#[error(transparent)]
|
||||
XlibError(#[from] x11_dl::error::OpenError),
|
||||
#[error("Failed to initialize the script")]
|
||||
InitScriptError,
|
||||
#[error("Bad RPC request: {0} ((1))")]
|
||||
RpcScriptError(String, String),
|
||||
#[error(transparent)]
|
||||
NulError(#[from] std::ffi::NulError),
|
||||
#[error(transparent)]
|
||||
ReceiverError(#[from] std::sync::mpsc::RecvError),
|
||||
#[error(transparent)]
|
||||
SenderError(#[from] std::sync::mpsc::SendError<String>),
|
||||
#[error("Failed to send the message")]
|
||||
MessageSender,
|
||||
#[error(transparent)]
|
||||
Json(#[from] serde_json::Error),
|
||||
#[error(transparent)]
|
||||
UrlError(#[from] url::ParseError),
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[cfg(target_os = "windows")]
|
||||
#[error("WebView2 error: {0}")]
|
||||
WebView2Error(webview2_com::Error),
|
||||
#[error("Duplicate custom protocol registered: {0}")]
|
||||
DuplicateCustomProtocol(String),
|
||||
#[error(transparent)]
|
||||
HttpError(#[from] http::Error),
|
||||
#[error("Infallible error, something went really wrong: {0}")]
|
||||
Infallible(#[from] std::convert::Infallible),
|
||||
#[cfg(target_os = "android")]
|
||||
#[error(transparent)]
|
||||
JniError(#[from] jni::errors::Error),
|
||||
#[error("Failed to create proxy endpoint")]
|
||||
ProxyEndpointCreationFailed,
|
||||
#[error(transparent)]
|
||||
WindowHandleError(#[from] raw_window_handle::HandleError),
|
||||
#[error("the window handle kind is not supported")]
|
||||
UnsupportedWindowHandle,
|
||||
#[error(transparent)]
|
||||
Utf8Error(#[from] std::str::Utf8Error),
|
||||
}
|
||||
1672
src/lib.rs
1672
src/lib.rs
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,7 @@
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
use crate::webview::webkitgtk::WebContextImpl;
|
||||
use crate::webkitgtk::WebContextImpl;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@@ -23,7 +23,7 @@ use std::path::{Path, PathBuf};
|
||||
/// some actions like custom protocol on Mac. Please keep both instances when you still wish to
|
||||
/// interact with them.
|
||||
///
|
||||
/// [`WebView`]: crate::webview::WebView
|
||||
/// [`WebView`]: crate::WebView
|
||||
#[derive(Debug)]
|
||||
pub struct WebContext {
|
||||
data: WebContextData,
|
||||
@@ -4,23 +4,15 @@
|
||||
|
||||
use std::{cell::Cell, path::PathBuf, rc::Rc};
|
||||
|
||||
use gtk::prelude::*;
|
||||
use gtk::{glib, prelude::*};
|
||||
use webkit2gtk::WebView;
|
||||
|
||||
use crate::{
|
||||
application::{dpi::LogicalPosition, window::Window},
|
||||
webview::FileDropEvent,
|
||||
};
|
||||
use crate::FileDropEvent;
|
||||
|
||||
pub(crate) fn connect_drag_event(
|
||||
webview: Rc<WebView>,
|
||||
window: Rc<Window>,
|
||||
handler: Box<dyn Fn(&Window, FileDropEvent) -> bool>,
|
||||
) {
|
||||
pub(crate) fn connect_drag_event(webview: WebView, handler: Box<dyn Fn(FileDropEvent) -> bool>) {
|
||||
let listener = Rc::new((handler, Cell::new(None)));
|
||||
|
||||
let listener_ref = listener.clone();
|
||||
let w = window.clone();
|
||||
webview.connect_drag_data_received(move |_, _, x, y, data, info, _| {
|
||||
if info == 2 {
|
||||
let uris = data
|
||||
@@ -33,47 +25,33 @@ pub(crate) fn connect_drag_event(
|
||||
.collect::<Vec<PathBuf>>();
|
||||
listener_ref.1.set(Some(uris.clone()));
|
||||
|
||||
let scale_factor = w.scale_factor();
|
||||
let position = LogicalPosition::new(x, y).to_physical(scale_factor);
|
||||
|
||||
listener_ref.0(
|
||||
&w,
|
||||
FileDropEvent::Hovered {
|
||||
paths: uris,
|
||||
position,
|
||||
},
|
||||
);
|
||||
listener_ref.0(FileDropEvent::Hovered {
|
||||
paths: uris,
|
||||
position: (x, y),
|
||||
});
|
||||
} else {
|
||||
// drag_data_received is called twice, so we can ignore this signal
|
||||
}
|
||||
});
|
||||
|
||||
let listener_ref = listener.clone();
|
||||
let w = window.clone();
|
||||
webview.connect_drag_drop(move |_, _, x, y, _| {
|
||||
let uris = listener_ref.1.take();
|
||||
if let Some(uris) = uris {
|
||||
let scale_factor = w.scale_factor();
|
||||
let position = LogicalPosition::new(x, y).to_physical(scale_factor);
|
||||
|
||||
listener_ref.0(
|
||||
&w,
|
||||
FileDropEvent::Dropped {
|
||||
paths: uris,
|
||||
position,
|
||||
},
|
||||
)
|
||||
listener_ref.0(FileDropEvent::Dropped {
|
||||
paths: uris,
|
||||
position: (x, y),
|
||||
})
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
let listener_ref = listener.clone();
|
||||
let w = window.clone();
|
||||
webview.connect_drag_leave(move |_, _, time| {
|
||||
if time == 0 {
|
||||
// The user cancelled the drag n drop
|
||||
listener_ref.0(&w, FileDropEvent::Cancelled);
|
||||
listener_ref.0(FileDropEvent::Cancelled);
|
||||
} else {
|
||||
// The user dropped the file on the window, but this will be handled in connect_drag_drop instead
|
||||
}
|
||||
@@ -81,10 +59,9 @@ pub(crate) fn connect_drag_event(
|
||||
|
||||
// Called when a drag "fails" - we'll just emit a Cancelled event.
|
||||
let listener_ref = listener.clone();
|
||||
let w = window;
|
||||
webview.connect_drag_failed(move |_, _, _| {
|
||||
if listener_ref.0(&w, FileDropEvent::Cancelled) {
|
||||
gtk::glib::Propagation::Stop
|
||||
if listener_ref.0(FileDropEvent::Cancelled) {
|
||||
glib::Propagation::Stop
|
||||
} else {
|
||||
gtk::glib::Propagation::Proceed
|
||||
}
|
||||
@@ -2,15 +2,20 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use gtk::{gdk::EventMask, gio::Cancellable, prelude::*};
|
||||
use gdkx11::{
|
||||
glib::translate::{FromGlibPtrFull, ToGlibPtr},
|
||||
X11Display,
|
||||
};
|
||||
use gtk::{
|
||||
gdk::{self, EventMask},
|
||||
gio::Cancellable,
|
||||
prelude::*,
|
||||
};
|
||||
use javascriptcore::ValueExt;
|
||||
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::{
|
||||
collections::hash_map::DefaultHasher,
|
||||
hash::{Hash, Hasher},
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use url::Url;
|
||||
use webkit2gtk::{
|
||||
AutoplayPolicy, InputMethodContextExt, LoadEvent, NavigationPolicyDecision,
|
||||
@@ -23,39 +28,156 @@ use webkit2gtk_sys::{
|
||||
webkit_get_major_version, webkit_get_micro_version, webkit_get_minor_version,
|
||||
webkit_policy_decision_ignore, webkit_policy_decision_use,
|
||||
};
|
||||
use x11_dl::xlib::*;
|
||||
|
||||
use web_context::WebContextExt;
|
||||
pub use web_context::WebContextImpl;
|
||||
|
||||
use crate::{
|
||||
application::{platform::unix::*, window::Window},
|
||||
webview::{proxy::ProxyConfig, web_context::WebContext, PageLoadEvent, WebViewAttributes, RGBA},
|
||||
Error, Result,
|
||||
proxy::ProxyConfig, web_context::WebContext, Error, PageLoadEvent, Result, WebViewAttributes,
|
||||
RGBA,
|
||||
};
|
||||
|
||||
mod file_drop;
|
||||
mod synthetic_mouse_events;
|
||||
mod undecorated_resizing;
|
||||
mod web_context;
|
||||
|
||||
use javascriptcore::ValueExt;
|
||||
|
||||
pub(crate) struct InnerWebView {
|
||||
pub webview: Rc<WebView>,
|
||||
pub webview: WebView,
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
is_inspector_open: Arc<AtomicBool>,
|
||||
pending_scripts: Arc<Mutex<Option<Vec<String>>>>,
|
||||
|
||||
is_child: bool,
|
||||
xlib: Option<Xlib>,
|
||||
x11_display: Option<*mut std::ffi::c_void>,
|
||||
x11_window: Option<u64>,
|
||||
display: Option<gdk::Display>,
|
||||
gtk_window: Option<gtk::Window>,
|
||||
}
|
||||
|
||||
impl Drop for InnerWebView {
|
||||
fn drop(&mut self) {
|
||||
unsafe { self.webview.destroy() }
|
||||
|
||||
if let Some(xlib) = &self.xlib {
|
||||
if self.is_child {
|
||||
unsafe { (xlib.XDestroyWindow)(self.x11_display.unwrap() as _, self.x11_window.unwrap()) };
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(window) = &self.gtk_window {
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InnerWebView {
|
||||
pub fn new(
|
||||
window: Rc<Window>,
|
||||
pub fn new<W: HasRawWindowHandle>(
|
||||
window: &W,
|
||||
attributes: WebViewAttributes,
|
||||
pl_attrs: super::PlatformSpecificWebViewAttributes,
|
||||
web_context: Option<&mut WebContext>,
|
||||
) -> Result<Self> {
|
||||
Self::new_x11(window, attributes, pl_attrs, web_context, false)
|
||||
}
|
||||
|
||||
pub fn new_as_child<W: HasRawWindowHandle>(
|
||||
parent: &W,
|
||||
attributes: WebViewAttributes,
|
||||
pl_attrs: super::PlatformSpecificWebViewAttributes,
|
||||
web_context: Option<&mut WebContext>,
|
||||
) -> Result<Self> {
|
||||
Self::new_x11(parent, attributes, pl_attrs, web_context, true)
|
||||
}
|
||||
|
||||
fn new_x11<W: HasRawWindowHandle>(
|
||||
window: &W,
|
||||
attributes: WebViewAttributes,
|
||||
pl_attrs: super::PlatformSpecificWebViewAttributes,
|
||||
web_context: Option<&mut WebContext>,
|
||||
is_child: bool,
|
||||
) -> Result<Self> {
|
||||
let xlib = Xlib::open()?;
|
||||
|
||||
let window_handle = match window.raw_window_handle() {
|
||||
RawWindowHandle::Xlib(w) => w.window,
|
||||
_ => return Err(Error::UnsupportedWindowHandle),
|
||||
};
|
||||
|
||||
let gdk_display = gdk::Display::default().ok_or(Error::X11DisplayNotFound)?;
|
||||
let gx11_display: &X11Display = gdk_display.downcast_ref().unwrap();
|
||||
let raw = gx11_display.to_glib_none().0;
|
||||
let display = unsafe { gdkx11::ffi::gdk_x11_display_get_xdisplay(raw) };
|
||||
|
||||
let window = if is_child {
|
||||
let child = unsafe {
|
||||
(xlib.XCreateSimpleWindow)(
|
||||
display as _,
|
||||
window_handle,
|
||||
attributes.position.map(|p| p.0).unwrap_or(0),
|
||||
attributes.position.map(|p| p.1).unwrap_or(0),
|
||||
attributes.size.map(|s| s.0).unwrap_or(0),
|
||||
attributes.size.map(|s| s.1).unwrap_or(0),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
};
|
||||
if attributes.visible {
|
||||
unsafe { (xlib.XMapWindow)(display as _, child) };
|
||||
}
|
||||
child
|
||||
} else {
|
||||
window_handle
|
||||
};
|
||||
|
||||
let gdk_window = unsafe {
|
||||
let raw = gdkx11::ffi::gdk_x11_window_foreign_new_for_display(raw, window);
|
||||
gdk::Window::from_glib_full(raw)
|
||||
};
|
||||
let gtk_window = gtk::Window::new(gtk::WindowType::Toplevel);
|
||||
gtk_window.connect_realize(move |widget| widget.set_window(gdk_window.clone()));
|
||||
gtk_window.set_has_window(true);
|
||||
gtk_window.realize();
|
||||
|
||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
||||
gtk_window.add(&vbox);
|
||||
|
||||
let hidden = !attributes.visible;
|
||||
|
||||
Self::new_gtk(&vbox, attributes, pl_attrs, web_context).map(|mut w| {
|
||||
// for some reason, if the webview starts as hidden,
|
||||
// we will need about 3 calls to `webview.set_visible`
|
||||
// with alternating value.
|
||||
// calling gtk_window.show_all() then hiding it again
|
||||
// seems to fix the issue.
|
||||
gtk_window.show_all();
|
||||
if hidden {
|
||||
gtk_window.hide();
|
||||
}
|
||||
|
||||
w.is_child = is_child;
|
||||
w.xlib = Some(xlib);
|
||||
w.display = Some(gdk_display);
|
||||
w.x11_display = Some(display as _);
|
||||
w.x11_window = Some(window);
|
||||
w.gtk_window = Some(gtk_window);
|
||||
|
||||
w
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_gtk<W>(
|
||||
container: &W,
|
||||
mut attributes: WebViewAttributes,
|
||||
_pl_attrs: super::PlatformSpecificWebViewAttributes,
|
||||
web_context: Option<&mut WebContext>,
|
||||
) -> Result<Self> {
|
||||
let window_rc = Rc::clone(&window);
|
||||
let window = &window.gtk_window();
|
||||
) -> Result<Self>
|
||||
where
|
||||
W: IsA<gtk::Container>,
|
||||
{
|
||||
let window_id = container.as_ptr() as isize;
|
||||
|
||||
// default_context allows us to create a scoped context on-demand
|
||||
let mut default_context;
|
||||
@@ -108,44 +230,25 @@ impl InnerWebView {
|
||||
web_context.register_automation(webview.clone());
|
||||
|
||||
// Message handler
|
||||
let webview = Rc::new(webview);
|
||||
let w = window_rc.clone();
|
||||
let ipc_handler = attributes.ipc_handler.take();
|
||||
let manager = web_context.manager();
|
||||
// Use the window hash as the script handler name to prevent from conflict when sharing same
|
||||
// web context.
|
||||
let window_hash = {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
w.id().hash(&mut hasher);
|
||||
hasher.finish().to_string()
|
||||
};
|
||||
|
||||
// Connect before registering as recommended by the docs
|
||||
manager.connect_script_message_received(None, move |_m, msg| {
|
||||
if let Some(js) = msg.js_value() {
|
||||
if let Some(ipc_handler) = &ipc_handler {
|
||||
ipc_handler(&w, js.to_string());
|
||||
ipc_handler(js.to_string());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Register the handler we just connected
|
||||
manager.register_script_message_handler(&window_hash);
|
||||
|
||||
// Allow the webview to close it's own window
|
||||
let close_window = window_rc.clone();
|
||||
webview.connect_close(move |_| {
|
||||
close_window.gtk_window().close();
|
||||
});
|
||||
manager.register_script_message_handler(&window_id.to_string());
|
||||
|
||||
// document title changed handler
|
||||
if let Some(document_title_changed_handler) = attributes.document_title_changed_handler {
|
||||
let w = window_rc.clone();
|
||||
webview.connect_title_notify(move |webview| {
|
||||
document_title_changed_handler(
|
||||
&w,
|
||||
webview.title().map(|t| t.to_string()).unwrap_or_default(),
|
||||
)
|
||||
document_title_changed_handler(webview.title().map(|t| t.to_string()).unwrap_or_default())
|
||||
});
|
||||
}
|
||||
|
||||
@@ -174,7 +277,6 @@ impl InnerWebView {
|
||||
);
|
||||
|
||||
synthetic_mouse_events::setup(&webview);
|
||||
undecorated_resizing::setup(&webview);
|
||||
|
||||
if attributes.navigation_handler.is_some() || attributes.new_window_req_handler.is_some() {
|
||||
webview.connect_decide_policy(move |_webview, policy_decision, policy_type| {
|
||||
@@ -216,24 +318,30 @@ impl InnerWebView {
|
||||
)
|
||||
}
|
||||
|
||||
// tao adds a default vertical box so we check for that first
|
||||
if let Some(vbox) = window_rc.default_vbox() {
|
||||
vbox.pack_start(&*webview, true, true, 0);
|
||||
if container.type_().name() == "GtkBox" {
|
||||
container
|
||||
.dynamic_cast_ref::<gtk::Box>()
|
||||
.unwrap()
|
||||
.pack_start(&webview, true, true, 0);
|
||||
} else {
|
||||
window.add(&*webview);
|
||||
container.add(&webview);
|
||||
}
|
||||
|
||||
if attributes.visible {
|
||||
webview.show_all();
|
||||
}
|
||||
|
||||
if attributes.focused {
|
||||
webview.grab_focus();
|
||||
}
|
||||
|
||||
if let Some(context) = WebViewExt::context(&*webview) {
|
||||
if let Some(context) = WebViewExt::context(&webview) {
|
||||
use webkit2gtk::WebContextExt;
|
||||
context.set_use_system_appearance_for_scrollbars(false);
|
||||
}
|
||||
|
||||
// Enable webgl, webaudio, canvas features as default.
|
||||
if let Some(settings) = WebViewExt::settings(&*webview) {
|
||||
if let Some(settings) = WebViewExt::settings(&webview) {
|
||||
settings.set_enable_webgl(true);
|
||||
settings.set_enable_webaudio(true);
|
||||
settings
|
||||
@@ -273,17 +381,13 @@ impl InnerWebView {
|
||||
|
||||
// File drop handling
|
||||
if let Some(file_drop_handler) = attributes.file_drop_handler {
|
||||
file_drop::connect_drag_event(webview.clone(), window_rc, file_drop_handler);
|
||||
}
|
||||
|
||||
if window.get_visible() {
|
||||
window.show_all();
|
||||
file_drop::connect_drag_event(webview.clone(), file_drop_handler);
|
||||
}
|
||||
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
let is_inspector_open = {
|
||||
let is_inspector_open = Arc::new(AtomicBool::default());
|
||||
if let Some(inspector) = WebViewExt::inspector(&*webview) {
|
||||
if let Some(inspector) = WebViewExt::inspector(&webview) {
|
||||
let is_inspector_open_ = is_inspector_open.clone();
|
||||
inspector.connect_bring_to_front(move |_| {
|
||||
is_inspector_open_.store(true, Ordering::Relaxed);
|
||||
@@ -302,12 +406,18 @@ impl InnerWebView {
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
is_inspector_open,
|
||||
pending_scripts: Arc::new(Mutex::new(Some(Vec::new()))),
|
||||
is_child: false,
|
||||
xlib: None,
|
||||
display: None,
|
||||
x11_display: None,
|
||||
x11_window: None,
|
||||
gtk_window: None,
|
||||
};
|
||||
|
||||
// Initialize message handler
|
||||
let mut init = String::with_capacity(115 + 20 + 22);
|
||||
init.push_str("Object.defineProperty(window, 'ipc', {value: Object.freeze({postMessage:function(x){window.webkit.messageHandlers[\"");
|
||||
init.push_str(&window_hash);
|
||||
init.push_str(&window_id.to_string());
|
||||
init.push_str("\"].postMessage(x)}})})");
|
||||
w.init(&init)?;
|
||||
|
||||
@@ -328,7 +438,7 @@ impl InnerWebView {
|
||||
|
||||
// Navigation
|
||||
if let Some(url) = attributes.url {
|
||||
web_context.queue_load_uri(Rc::clone(&w.webview), url, attributes.headers);
|
||||
web_context.queue_load_uri(w.webview.clone(), url, attributes.headers);
|
||||
web_context.flush_queue_loader();
|
||||
} else if let Some(html) = attributes.html {
|
||||
w.webview.load_html(&html, None);
|
||||
@@ -418,7 +528,7 @@ impl InnerWebView {
|
||||
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
pub fn open_devtools(&self) {
|
||||
if let Some(inspector) = WebViewExt::inspector(&*self.webview) {
|
||||
if let Some(inspector) = WebViewExt::inspector(&self.webview) {
|
||||
inspector.show();
|
||||
// `bring-to-front` is not received in this case
|
||||
self.is_inspector_open.store(true, Ordering::Relaxed);
|
||||
@@ -427,7 +537,7 @@ impl InnerWebView {
|
||||
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
pub fn close_devtools(&self) {
|
||||
if let Some(inspector) = WebViewExt::inspector(&*self.webview) {
|
||||
if let Some(inspector) = WebViewExt::inspector(&self.webview) {
|
||||
inspector.close();
|
||||
}
|
||||
}
|
||||
@@ -438,7 +548,7 @@ impl InnerWebView {
|
||||
}
|
||||
|
||||
pub fn zoom(&self, scale_factor: f64) {
|
||||
WebViewExt::set_zoom_level(&*self.webview, scale_factor);
|
||||
WebViewExt::set_zoom_level(&self.webview, scale_factor);
|
||||
}
|
||||
|
||||
pub fn set_background_color(&self, background_color: RGBA) -> Result<()> {
|
||||
@@ -471,8 +581,8 @@ impl InnerWebView {
|
||||
}
|
||||
|
||||
pub fn clear_all_browsing_data(&self) -> Result<()> {
|
||||
if let Some(context) = WebViewExt::context(&*self.webview) {
|
||||
use webkit2gtk::WebContextExt;
|
||||
use webkit2gtk::WebContextExt;
|
||||
if let Some(context) = WebViewExt::context(&self.webview) {
|
||||
if let Some(data_manger) = context.website_data_manager() {
|
||||
webkit2gtk::WebsiteDataManagerExtManual::clear(
|
||||
&data_manger,
|
||||
@@ -486,6 +596,52 @@ impl InnerWebView {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_position(&self, position: (i32, i32)) {
|
||||
if self.is_child {
|
||||
if let Some(window) = &self.gtk_window {
|
||||
window.move_(position.0, position.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_size(&self, size: (u32, u32)) {
|
||||
if let Some(window) = &self.gtk_window {
|
||||
if self.is_child {
|
||||
window.window().unwrap().resize(size.0 as _, size.1 as _);
|
||||
}
|
||||
window.size_allocate(>k::Allocation::new(200, 200, size.0 as _, size.1 as _));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
if self.is_child {
|
||||
let xlib = self.xlib.as_ref().unwrap();
|
||||
if visible {
|
||||
unsafe { (xlib.XMapWindow)(self.x11_display.unwrap() as _, self.x11_window.unwrap()) };
|
||||
} else {
|
||||
unsafe { (xlib.XUnmapWindow)(self.x11_display.unwrap() as _, self.x11_window.unwrap()) };
|
||||
}
|
||||
}
|
||||
|
||||
if visible {
|
||||
self.webview.show_all();
|
||||
} else {
|
||||
self.webview.hide();
|
||||
}
|
||||
|
||||
if let Some(window) = &self.gtk_window {
|
||||
if visible {
|
||||
window.show_all();
|
||||
} else {
|
||||
window.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focus(&self) {
|
||||
self.webview.grab_focus();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn platform_webview_version() -> Result<String> {
|
||||
@@ -4,11 +4,8 @@
|
||||
|
||||
//! Unix platform extensions for [`WebContext`](super::WebContext).
|
||||
|
||||
use crate::{
|
||||
webview::{web_context::WebContextData, RequestAsyncResponder},
|
||||
Error,
|
||||
};
|
||||
use gtk::glib::FileError;
|
||||
use crate::{web_context::WebContextData, Error, RequestAsyncResponder};
|
||||
use gtk::glib;
|
||||
use http::{header::CONTENT_TYPE, Request, Response as HttpResponse};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
@@ -136,7 +133,7 @@ pub trait WebContextExt {
|
||||
/// Add a [`WebView`] to the queue waiting to be opened.
|
||||
///
|
||||
/// See the `WebviewUriLoader` for more information.
|
||||
fn queue_load_uri(&self, webview: Rc<WebView>, url: Url, headers: Option<http::HeaderMap>);
|
||||
fn queue_load_uri(&self, webview: WebView, url: Url, headers: Option<http::HeaderMap>);
|
||||
|
||||
/// Flush all queued [`WebView`]s waiting to load a uri.
|
||||
///
|
||||
@@ -189,7 +186,7 @@ impl WebContextExt for super::WebContext {
|
||||
}
|
||||
}
|
||||
|
||||
fn queue_load_uri(&self, webview: Rc<WebView>, url: Url, headers: Option<http::HeaderMap>) {
|
||||
fn queue_load_uri(&self, webview: WebView, url: Url, headers: Option<http::HeaderMap>) {
|
||||
self.os.webview_uri_loader.push(webview, url, headers)
|
||||
}
|
||||
|
||||
@@ -328,11 +325,7 @@ where
|
||||
|
||||
#[cfg(feature = "linux-body")]
|
||||
{
|
||||
use gtk::{
|
||||
gdk::prelude::{InputStreamExt, InputStreamExtManual},
|
||||
gio::Cancellable,
|
||||
glib::Bytes,
|
||||
};
|
||||
use gtk::{gdk::prelude::InputStreamExtManual, gio::Cancellable};
|
||||
|
||||
// Set request http body
|
||||
let cancellable: Option<&Cancellable> = None;
|
||||
@@ -354,7 +347,7 @@ where
|
||||
}
|
||||
result
|
||||
})
|
||||
.unwrap_or(Vec::new());
|
||||
.unwrap_or_default();
|
||||
}
|
||||
#[cfg(not(feature = "linux-body"))]
|
||||
{
|
||||
@@ -366,7 +359,7 @@ where
|
||||
Err(_) => {
|
||||
request.finish_error(&mut gtk::glib::Error::new(
|
||||
// TODO: use UriError when we can use 2_66 webkit2gtk feature flag
|
||||
FileError::Exist,
|
||||
glib::FileError::Exist,
|
||||
"Could not get uri.",
|
||||
));
|
||||
return;
|
||||
@@ -402,8 +395,8 @@ where
|
||||
|
||||
handler(http_request, RequestAsyncResponder { responder });
|
||||
} else {
|
||||
request.finish_error(&mut gtk::glib::Error::new(
|
||||
FileError::Exist,
|
||||
request.finish_error(&mut glib::Error::new(
|
||||
glib::FileError::Exist,
|
||||
"Could not get uri.",
|
||||
));
|
||||
}
|
||||
@@ -443,7 +436,7 @@ where
|
||||
#[derive(Debug, Default)]
|
||||
struct WebviewUriLoader {
|
||||
lock: AtomicBool,
|
||||
queue: Mutex<VecDeque<(Rc<WebView>, Url, Option<http::HeaderMap>)>>,
|
||||
queue: Mutex<VecDeque<(WebView, Url, Option<http::HeaderMap>)>>,
|
||||
}
|
||||
|
||||
impl WebviewUriLoader {
|
||||
@@ -458,13 +451,13 @@ impl WebviewUriLoader {
|
||||
}
|
||||
|
||||
/// Add a [`WebView`] to the queue.
|
||||
fn push(&self, webview: Rc<WebView>, url: Url, headers: Option<http::HeaderMap>) {
|
||||
fn push(&self, webview: WebView, url: Url, headers: Option<http::HeaderMap>) {
|
||||
let mut queue = self.queue.lock().expect("poisoned load queue");
|
||||
queue.push_back((webview, url, headers))
|
||||
}
|
||||
|
||||
/// Remove a [`WebView`] from the queue and return it.
|
||||
fn pop(&self) -> Option<(Rc<WebView>, Url, Option<http::HeaderMap>)> {
|
||||
fn pop(&self) -> Option<(WebView, Url, Option<http::HeaderMap>)> {
|
||||
let mut queue = self.queue.lock().expect("poisoned load queue");
|
||||
queue.pop_front()
|
||||
}
|
||||
1283
src/webview/mod.rs
1283
src/webview/mod.rs
File diff suppressed because it is too large
Load Diff
@@ -1,103 +0,0 @@
|
||||
use crate::application::platform::unix::*;
|
||||
use gtk::{
|
||||
gdk::{Cursor, WindowEdge},
|
||||
prelude::*,
|
||||
};
|
||||
use webkit2gtk::WebView;
|
||||
|
||||
pub fn setup(webview: &WebView) {
|
||||
webview.connect_motion_notify_event(|webview, event| {
|
||||
// This one should be GtkWindow
|
||||
if let Some(widget) = webview.parent() {
|
||||
// This one should be GtkWindow
|
||||
if let Some(window) = widget.parent() {
|
||||
// Safe to unwrap unless this is not from tao
|
||||
let window: gtk::Window = window.downcast().unwrap();
|
||||
if !window.is_decorated() && window.is_resizable() {
|
||||
if let Some(window) = window.window() {
|
||||
let (cx, cy) = event.root();
|
||||
let edge = hit_test(&window, cx, cy);
|
||||
// FIXME: calling `window.begin_resize_drag` seems to revert the cursor back to normal style
|
||||
window.set_cursor(
|
||||
Cursor::from_name(
|
||||
&window.display(),
|
||||
match edge {
|
||||
WindowEdge::North => "n-resize",
|
||||
WindowEdge::South => "s-resize",
|
||||
WindowEdge::East => "e-resize",
|
||||
WindowEdge::West => "w-resize",
|
||||
WindowEdge::NorthWest => "nw-resize",
|
||||
WindowEdge::NorthEast => "ne-resize",
|
||||
WindowEdge::SouthEast => "se-resize",
|
||||
WindowEdge::SouthWest => "sw-resize",
|
||||
_ => "default",
|
||||
},
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
gtk::glib::Propagation::Proceed
|
||||
});
|
||||
webview.connect_button_press_event(move |webview, event| {
|
||||
if event.button() == 1 {
|
||||
let (cx, cy) = event.root();
|
||||
// This one should be GtkBox
|
||||
if let Some(widget) = webview.parent() {
|
||||
// This one should be GtkWindow
|
||||
if let Some(window) = widget.parent() {
|
||||
// Safe to unwrap unless this is not from tao
|
||||
let window: gtk::Window = window.downcast().unwrap();
|
||||
if !window.is_decorated() && window.is_resizable() {
|
||||
if let Some(window) = window.window() {
|
||||
// Safe to unwrap since it's a valid GtkWindow
|
||||
let result = hit_test(&window, cx, cy);
|
||||
|
||||
// we ignore the `__Unknown` variant so the webview receives the click correctly if it is not on the edges.
|
||||
match result {
|
||||
WindowEdge::__Unknown(_) => (),
|
||||
_ => window.begin_resize_drag(result, 1, cx as i32, cy as i32, event.time()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
gtk::glib::Propagation::Proceed
|
||||
});
|
||||
webview.connect_touch_event(|webview, event| {
|
||||
// This one should be GtkBox
|
||||
if let Some(widget) = webview.parent() {
|
||||
// This one should be GtkWindow
|
||||
if let Some(window) = widget.parent() {
|
||||
// Safe to unwrap unless this is not from tao
|
||||
let window: gtk::Window = window.downcast().unwrap();
|
||||
if !window.is_decorated() && window.is_resizable() && !window.is_maximized() {
|
||||
if let Some(window) = window.window() {
|
||||
if let Some((cx, cy)) = event.root_coords() {
|
||||
if let Some(device) = event.device() {
|
||||
let result = hit_test(&window, cx, cy);
|
||||
|
||||
// we ignore the `__Unknown` variant so the window receives the click correctly if it is not on the edges.
|
||||
match result {
|
||||
WindowEdge::__Unknown(_) => (),
|
||||
_ => window.begin_resize_drag_for_device(
|
||||
result,
|
||||
&device,
|
||||
0,
|
||||
cx as i32,
|
||||
cy as i32,
|
||||
event.time(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
gtk::glib::Propagation::Proceed
|
||||
});
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use windows::{
|
||||
core::HRESULT,
|
||||
Win32::{
|
||||
Foundation::{HWND, LPARAM, LRESULT, RECT, WPARAM},
|
||||
Graphics::Gdi::{
|
||||
GetDC, GetDeviceCaps, MonitorFromWindow, HMONITOR, LOGPIXELSX, MONITOR_DEFAULTTONEAREST,
|
||||
},
|
||||
UI::{
|
||||
HiDpi::{MDT_EFFECTIVE_DPI, MONITOR_DPI_TYPE},
|
||||
Input::KeyboardAndMouse::ReleaseCapture,
|
||||
WindowsAndMessaging::{
|
||||
GetWindowRect, IsProcessDPIAware, PostMessageW, HTBOTTOM, HTBOTTOMLEFT, HTBOTTOMRIGHT,
|
||||
HTCLIENT, HTLEFT, HTNOWHERE, HTRIGHT, HTTOP, HTTOPLEFT, HTTOPRIGHT,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use super::get_function;
|
||||
|
||||
#[inline]
|
||||
pub fn MAKELPARAM(x: i16, y: i16) -> LPARAM {
|
||||
LPARAM(((x as u16 as u32) | ((y as u16 as u32) << 16)) as usize as _)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn begin_resize_drag(
|
||||
hwnd: isize,
|
||||
edge: isize,
|
||||
button: u32,
|
||||
x: i32,
|
||||
y: i32,
|
||||
) -> windows::core::Result<()> {
|
||||
unsafe {
|
||||
let w_param = WPARAM(edge as _);
|
||||
let l_param = MAKELPARAM(x as i16, y as i16);
|
||||
|
||||
ReleaseCapture()?;
|
||||
PostMessageW(HWND(hwnd), button, w_param, l_param)
|
||||
}
|
||||
}
|
||||
|
||||
type GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> u32;
|
||||
type GetDpiForMonitor = unsafe extern "system" fn(
|
||||
hmonitor: HMONITOR,
|
||||
dpi_type: MONITOR_DPI_TYPE,
|
||||
dpi_x: *mut u32,
|
||||
dpi_y: *mut u32,
|
||||
) -> HRESULT;
|
||||
|
||||
static GET_DPI_FOR_WINDOW: Lazy<Option<GetDpiForWindow>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetDpiForWindow));
|
||||
static GET_DPI_FOR_MONITOR: Lazy<Option<GetDpiForMonitor>> =
|
||||
Lazy::new(|| get_function!("shcore.dll", GetDpiForMonitor));
|
||||
|
||||
const BASE_DPI: u32 = 96;
|
||||
fn dpi_to_scale_factor(dpi: u32) -> f64 {
|
||||
dpi as f64 / BASE_DPI as f64
|
||||
}
|
||||
|
||||
unsafe fn hwnd_dpi(hwnd: HWND) -> u32 {
|
||||
let hdc = GetDC(hwnd);
|
||||
if hdc.is_invalid() {
|
||||
panic!("[tao] `GetDC` returned null!");
|
||||
}
|
||||
if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW {
|
||||
// We are on Windows 10 Anniversary Update (1607) or later.
|
||||
match GetDpiForWindow(hwnd) {
|
||||
0 => BASE_DPI, // 0 is returned if hwnd is invalid
|
||||
dpi => dpi as u32,
|
||||
}
|
||||
} else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
|
||||
// We are on Windows 8.1 or later.
|
||||
let monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
||||
if monitor.is_invalid() {
|
||||
return BASE_DPI;
|
||||
}
|
||||
|
||||
let mut dpi_x = 0;
|
||||
let mut dpi_y = 0;
|
||||
if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y).is_ok() {
|
||||
dpi_x as u32
|
||||
} else {
|
||||
BASE_DPI
|
||||
}
|
||||
} else {
|
||||
// We are on Vista or later.
|
||||
if IsProcessDPIAware().as_bool() {
|
||||
// If the process is DPI aware, then scaling must be handled by the application using
|
||||
// this DPI value.
|
||||
GetDeviceCaps(hdc, LOGPIXELSX) as u32
|
||||
} else {
|
||||
// If the process is DPI unaware, then scaling is performed by the OS; we thus return
|
||||
// 96 (scale factor 1.0) to prevent the window from being re-scaled by both the
|
||||
// application and the WM.
|
||||
BASE_DPI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const BORDERLESS_RESIZE_INSET: i32 = 5;
|
||||
|
||||
pub fn hit_test(hwnd: isize, cx: i32, cy: i32) -> LRESULT {
|
||||
let hwnd = HWND(hwnd);
|
||||
let mut window_rect = RECT::default();
|
||||
unsafe {
|
||||
if GetWindowRect(hwnd, &mut window_rect).is_ok() {
|
||||
const CLIENT: isize = 0b0000;
|
||||
const LEFT: isize = 0b0001;
|
||||
const RIGHT: isize = 0b0010;
|
||||
const TOP: isize = 0b0100;
|
||||
const BOTTOM: isize = 0b1000;
|
||||
const TOPLEFT: isize = TOP | LEFT;
|
||||
const TOPRIGHT: isize = TOP | RIGHT;
|
||||
const BOTTOMLEFT: isize = BOTTOM | LEFT;
|
||||
const BOTTOMRIGHT: isize = BOTTOM | RIGHT;
|
||||
|
||||
let RECT {
|
||||
left,
|
||||
right,
|
||||
bottom,
|
||||
top,
|
||||
} = window_rect;
|
||||
|
||||
let dpi = hwnd_dpi(hwnd);
|
||||
let scale_factor = dpi_to_scale_factor(dpi);
|
||||
let inset = (BORDERLESS_RESIZE_INSET as f64 * scale_factor) as i32;
|
||||
|
||||
#[rustfmt::skip]
|
||||
let result =
|
||||
(LEFT * (if cx < (left + inset) { 1 } else { 0 }))
|
||||
| (RIGHT * (if cx >= (right - inset) { 1 } else { 0 }))
|
||||
| (TOP * (if cy < (top + inset) { 1 } else { 0 }))
|
||||
| (BOTTOM * (if cy >= (bottom - inset) { 1 } else { 0 }));
|
||||
|
||||
LRESULT(match result {
|
||||
CLIENT => HTCLIENT,
|
||||
LEFT => HTLEFT,
|
||||
RIGHT => HTRIGHT,
|
||||
TOP => HTTOP,
|
||||
BOTTOM => HTBOTTOM,
|
||||
TOPLEFT => HTTOPLEFT,
|
||||
TOPRIGHT => HTTOPRIGHT,
|
||||
BOTTOMLEFT => HTBOTTOMLEFT,
|
||||
BOTTOMRIGHT => HTBOTTOMRIGHT,
|
||||
_ => HTNOWHERE,
|
||||
} as _)
|
||||
} else {
|
||||
LRESULT(HTNOWHERE as _)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use crate::webview::FileDropEvent;
|
||||
|
||||
// A silly implementation of file drop handling for Windows!
|
||||
// This can be pretty much entirely replaced when WebView2 SDK 1.0.721-prerelease becomes stable.
|
||||
// https://docs.microsoft.com/en-us/microsoft-edge/webview2/releasenotes#10721-prerelease
|
||||
// https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/icorewebview2experimentalcompositioncontroller3?view=webview2-1.0.721-prerelease&preserve-view=true
|
||||
|
||||
use crate::FileDropEvent;
|
||||
|
||||
use std::{
|
||||
cell::UnsafeCell,
|
||||
@@ -37,90 +34,57 @@ use windows::Win32::{
|
||||
|
||||
use windows_implement::implement;
|
||||
|
||||
use crate::application::{
|
||||
dpi::PhysicalPosition, platform::windows::WindowExtWindows, window::Window,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct FileDropController {
|
||||
drop_targets: Vec<IDropTarget>,
|
||||
}
|
||||
|
||||
impl FileDropController {
|
||||
pub(crate) fn new() -> Self {
|
||||
FileDropController {
|
||||
drop_targets: Vec::new(),
|
||||
}
|
||||
}
|
||||
pub(crate) fn new(hwnd: HWND, handler: Box<dyn Fn(FileDropEvent) -> bool>) -> Self {
|
||||
let mut controller = FileDropController::default();
|
||||
|
||||
pub(crate) fn listen(
|
||||
&mut self,
|
||||
hwnd: HWND,
|
||||
window: Rc<Window>,
|
||||
handler: Box<dyn Fn(&Window, FileDropEvent) -> bool>,
|
||||
) {
|
||||
let listener = Rc::new(handler);
|
||||
let handler = Rc::new(handler);
|
||||
let mut callback = |hwnd| controller.inject_in_hwnd(hwnd, handler.clone());
|
||||
|
||||
// Enumerate child windows to find the WebView2 "window" and override!
|
||||
enumerate_child_windows(hwnd, |hwnd| {
|
||||
self.inject(hwnd, window.clone(), listener.clone())
|
||||
});
|
||||
{
|
||||
let mut trait_obj: &mut dyn FnMut(HWND) -> bool = &mut callback;
|
||||
let closure_pointer_pointer: *mut c_void = unsafe { std::mem::transmute(&mut trait_obj) };
|
||||
let lparam = LPARAM(closure_pointer_pointer as _);
|
||||
unsafe extern "system" fn enumerate_callback(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
||||
let closure = &mut *(lparam.0 as *mut c_void as *mut &mut dyn FnMut(HWND) -> bool);
|
||||
closure(hwnd).into()
|
||||
}
|
||||
unsafe { EnumChildWindows(hwnd, Some(enumerate_callback), lparam) };
|
||||
}
|
||||
|
||||
controller
|
||||
}
|
||||
|
||||
fn inject(
|
||||
&mut self,
|
||||
hwnd: HWND,
|
||||
window: Rc<Window>,
|
||||
listener: Rc<dyn Fn(&Window, FileDropEvent) -> bool>,
|
||||
) -> bool {
|
||||
// Safety: WinAPI calls are unsafe
|
||||
unsafe {
|
||||
let file_drop_handler: IDropTarget = FileDropHandler::new(window, listener).into();
|
||||
|
||||
if RevokeDragDrop(hwnd) != Err(DRAGDROP_E_INVALIDHWND.into())
|
||||
&& RegisterDragDrop(hwnd, &file_drop_handler).is_ok()
|
||||
{
|
||||
// Not a great solution. But there is no reliable way to get the window handle of the webview, for whatever reason...
|
||||
self.drop_targets.push(file_drop_handler);
|
||||
}
|
||||
fn inject_in_hwnd(&mut self, hwnd: HWND, handler: Rc<dyn Fn(FileDropEvent) -> bool>) -> bool {
|
||||
let file_drop_handler: IDropTarget = FileDropHandler::new(hwnd, handler).into();
|
||||
if unsafe { RevokeDragDrop(hwnd) } != Err(DRAGDROP_E_INVALIDHWND.into())
|
||||
&& unsafe { RegisterDragDrop(hwnd, &file_drop_handler) }.is_ok()
|
||||
{
|
||||
self.drop_targets.push(file_drop_handler);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// https://gist.github.com/application-developer-DA/5a460d9ca02948f1d2bfa53100c941da
|
||||
// Safety: WinAPI calls are unsafe
|
||||
|
||||
fn enumerate_child_windows<F>(hwnd: HWND, mut callback: F)
|
||||
where
|
||||
F: FnMut(HWND) -> bool,
|
||||
{
|
||||
let mut trait_obj: &mut dyn FnMut(HWND) -> bool = &mut callback;
|
||||
let closure_pointer_pointer: *mut c_void = unsafe { std::mem::transmute(&mut trait_obj) };
|
||||
let lparam = LPARAM(closure_pointer_pointer as _);
|
||||
unsafe { EnumChildWindows(hwnd, Some(enumerate_callback), lparam) };
|
||||
}
|
||||
|
||||
unsafe extern "system" fn enumerate_callback(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
||||
let closure = &mut *(lparam.0 as *mut c_void as *mut &mut dyn FnMut(HWND) -> bool);
|
||||
closure(hwnd).into()
|
||||
}
|
||||
|
||||
#[implement(IDropTarget)]
|
||||
pub struct FileDropHandler {
|
||||
window: Rc<Window>,
|
||||
listener: Rc<dyn Fn(&Window, FileDropEvent) -> bool>,
|
||||
hwnd: HWND,
|
||||
listener: Rc<dyn Fn(FileDropEvent) -> bool>,
|
||||
cursor_effect: UnsafeCell<DROPEFFECT>,
|
||||
hovered_is_valid: UnsafeCell<bool>, /* If the currently hovered item is not valid there must not be any `HoveredFileCancelled` emitted */
|
||||
}
|
||||
|
||||
impl FileDropHandler {
|
||||
pub fn new(
|
||||
window: Rc<Window>,
|
||||
listener: Rc<dyn Fn(&Window, FileDropEvent) -> bool>,
|
||||
) -> FileDropHandler {
|
||||
pub fn new(hwnd: HWND, listener: Rc<dyn Fn(FileDropEvent) -> bool>) -> FileDropHandler {
|
||||
Self {
|
||||
window,
|
||||
hwnd,
|
||||
listener,
|
||||
cursor_effect: DROPEFFECT_NONE.into(),
|
||||
hovered_is_valid: false.into(),
|
||||
@@ -208,16 +172,13 @@ impl IDropTarget_Impl for FileDropHandler {
|
||||
*self.cursor_effect.get() = cursor_effect;
|
||||
|
||||
let mut pt = POINT { x: pt.x, y: pt.y };
|
||||
ScreenToClient(HWND(self.window.hwnd() as _), &mut pt);
|
||||
ScreenToClient(self.hwnd, &mut pt);
|
||||
}
|
||||
|
||||
(self.listener)(
|
||||
&self.window,
|
||||
FileDropEvent::Hovered {
|
||||
paths,
|
||||
position: PhysicalPosition::new(pt.x as _, pt.y as _),
|
||||
},
|
||||
);
|
||||
(self.listener)(FileDropEvent::Hovered {
|
||||
paths,
|
||||
position: (pt.x as _, pt.y as _),
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -234,7 +195,7 @@ impl IDropTarget_Impl for FileDropHandler {
|
||||
|
||||
fn DragLeave(&self) -> windows::core::Result<()> {
|
||||
if unsafe { *self.hovered_is_valid.get() } {
|
||||
(self.listener)(&self.window, FileDropEvent::Cancelled);
|
||||
(self.listener)(FileDropEvent::Cancelled);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -254,16 +215,13 @@ impl IDropTarget_Impl for FileDropHandler {
|
||||
}
|
||||
|
||||
let mut pt = POINT { x: pt.x, y: pt.y };
|
||||
ScreenToClient(HWND(self.window.hwnd() as _), &mut pt);
|
||||
ScreenToClient(self.hwnd, &mut pt);
|
||||
}
|
||||
|
||||
(self.listener)(
|
||||
&self.window,
|
||||
FileDropEvent::Dropped {
|
||||
paths,
|
||||
position: PhysicalPosition::new(pt.x as _, pt.y as _),
|
||||
},
|
||||
);
|
||||
(self.listener)(FileDropEvent::Dropped {
|
||||
paths,
|
||||
position: (pt.x as _, pt.y as _),
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -3,57 +3,48 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
mod file_drop;
|
||||
mod resize;
|
||||
|
||||
use crate::{
|
||||
webview::{
|
||||
proxy::ProxyConfig, MemoryUsageLevel, PageLoadEvent, RequestAsyncResponder, WebContext,
|
||||
WebViewAttributes, RGBA,
|
||||
},
|
||||
Error, Result,
|
||||
};
|
||||
|
||||
use file_drop::FileDropController;
|
||||
use url::Url;
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::HashSet,
|
||||
fmt::Write,
|
||||
iter::once,
|
||||
os::windows::prelude::OsStrExt,
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
sync::{mpsc, Arc},
|
||||
borrow::Cow, collections::HashSet, fmt::Write, iter::once, os::windows::prelude::OsStrExt,
|
||||
path::PathBuf, rc::Rc, sync::mpsc,
|
||||
};
|
||||
|
||||
use once_cell::{sync::Lazy, unsync::OnceCell};
|
||||
|
||||
use http::{Request, Response as HttpResponse, StatusCode};
|
||||
use once_cell::sync::Lazy;
|
||||
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
|
||||
use url::Url;
|
||||
use webview2_com::{Microsoft::Web::WebView2::Win32::*, *};
|
||||
use windows::{
|
||||
core::{s, ComInterface, PCSTR, PCWSTR, PWSTR},
|
||||
Win32::{
|
||||
Foundation::*,
|
||||
Globalization::{self, MAX_LOCALE_NAME},
|
||||
Graphics::Gdi::{RedrawWindow, HRGN, RDW_INTERNALPAINT},
|
||||
Graphics::Gdi::{RedrawWindow, HBRUSH, HRGN, RDW_INTERNALPAINT},
|
||||
System::{
|
||||
Com::IStream,
|
||||
LibraryLoader::{GetProcAddress, LoadLibraryW},
|
||||
Com::{CoInitializeEx, IStream, COINIT_APARTMENTTHREADED},
|
||||
LibraryLoader::{GetModuleHandleW, GetProcAddress, LoadLibraryW},
|
||||
SystemInformation::OSVERSIONINFOW,
|
||||
WinRT::EventRegistrationToken,
|
||||
},
|
||||
UI::{
|
||||
Shell::{DefSubclassProc, SHCreateMemStream, SetWindowSubclass},
|
||||
WindowsAndMessaging::{self as win32wm, PostMessageW, RegisterWindowMessageA},
|
||||
WindowsAndMessaging::{
|
||||
self as win32wm, CreateWindowExW, DefWindowProcW, DestroyWindow, PostMessageW,
|
||||
RegisterClassExW, RegisterWindowMessageA, SetWindowPos, ShowWindow, CS_HREDRAW, CS_VREDRAW,
|
||||
CW_USEDEFAULT, HCURSOR, HICON, HMENU, SWP_ASYNCWINDOWPOS, SWP_NOACTIVATE, SWP_NOMOVE,
|
||||
SWP_NOSIZE, SWP_NOZORDER, SW_HIDE, SW_SHOW, WINDOW_EX_STYLE, WNDCLASSEXW, WS_CHILD,
|
||||
WS_CLIPCHILDREN, WS_VISIBLE,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use webview2_com::{Microsoft::Web::WebView2::Win32::*, *};
|
||||
|
||||
use crate::application::{platform::windows::WindowExtWindows, window::Window};
|
||||
use http::{Request, Response as HttpResponse, StatusCode};
|
||||
|
||||
use self::file_drop::FileDropController;
|
||||
use super::Theme;
|
||||
use crate::{
|
||||
proxy::ProxyConfig, Error, MemoryUsageLevel, PageLoadEvent, RequestAsyncResponder, Result,
|
||||
WebContext, WebViewAttributes, RGBA,
|
||||
};
|
||||
|
||||
impl From<webview2_com::Error> for Error {
|
||||
fn from(err: webview2_com::Error) -> Self {
|
||||
@@ -62,39 +53,137 @@ impl From<webview2_com::Error> for Error {
|
||||
}
|
||||
|
||||
pub(crate) struct InnerWebView {
|
||||
hwnd: HWND,
|
||||
is_child: bool,
|
||||
pub controller: ICoreWebView2Controller,
|
||||
webview: ICoreWebView2,
|
||||
env: ICoreWebView2Environment,
|
||||
// Store FileDropController in here to make sure it gets dropped when
|
||||
// the webview gets dropped, otherwise we'll have a memory leak
|
||||
#[allow(dead_code)]
|
||||
file_drop_controller: Rc<OnceCell<FileDropController>>,
|
||||
file_drop_controller: Option<FileDropController>,
|
||||
}
|
||||
|
||||
impl Drop for InnerWebView {
|
||||
fn drop(&mut self) {
|
||||
let _ = unsafe { self.controller.Close() };
|
||||
if self.is_child {
|
||||
let _ = unsafe { DestroyWindow(self.hwnd) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InnerWebView {
|
||||
pub fn new(
|
||||
window: Rc<Window>,
|
||||
window: &impl HasRawWindowHandle,
|
||||
attributes: WebViewAttributes,
|
||||
pl_attrs: super::PlatformSpecificWebViewAttributes,
|
||||
web_context: Option<&mut WebContext>,
|
||||
) -> Result<Self> {
|
||||
let window = match window.raw_window_handle() {
|
||||
RawWindowHandle::Win32(window) => window.hwnd as _,
|
||||
_ => return Err(Error::UnsupportedWindowHandle),
|
||||
};
|
||||
Self::new_hwnd(HWND(window), attributes, pl_attrs, web_context)
|
||||
}
|
||||
|
||||
pub fn new_as_child(
|
||||
parent: &impl HasRawWindowHandle,
|
||||
attributes: WebViewAttributes,
|
||||
pl_attrs: super::PlatformSpecificWebViewAttributes,
|
||||
web_context: Option<&mut WebContext>,
|
||||
) -> Result<Self> {
|
||||
let parent = match parent.raw_window_handle() {
|
||||
RawWindowHandle::Win32(parent) => parent.hwnd as _,
|
||||
_ => return Err(Error::UnsupportedWindowHandle),
|
||||
};
|
||||
|
||||
let class_name = encode_wide("WRY_WEBVIEW");
|
||||
|
||||
unsafe extern "system" fn default_window_proc(
|
||||
hwnd: HWND,
|
||||
msg: u32,
|
||||
wparam: WPARAM,
|
||||
lparam: LPARAM,
|
||||
) -> LRESULT {
|
||||
DefWindowProcW(hwnd, msg, wparam, lparam)
|
||||
}
|
||||
|
||||
let class = WNDCLASSEXW {
|
||||
cbSize: std::mem::size_of::<WNDCLASSEXW>() as u32,
|
||||
style: CS_HREDRAW | CS_VREDRAW,
|
||||
lpfnWndProc: Some(default_window_proc),
|
||||
cbClsExtra: 0,
|
||||
cbWndExtra: 0,
|
||||
hInstance: unsafe { HINSTANCE(GetModuleHandleW(PCWSTR::null()).unwrap_or_default().0) },
|
||||
hIcon: HICON::default(),
|
||||
hCursor: HCURSOR::default(), // must be null in order for cursor state to work properly
|
||||
hbrBackground: HBRUSH::default(),
|
||||
lpszMenuName: PCWSTR::null(),
|
||||
lpszClassName: PCWSTR::from_raw(class_name.as_ptr()),
|
||||
hIconSm: HICON::default(),
|
||||
};
|
||||
|
||||
unsafe { RegisterClassExW(&class) };
|
||||
|
||||
let mut flags = WS_CHILD | WS_CLIPCHILDREN;
|
||||
if attributes.visible {
|
||||
flags |= WS_VISIBLE;
|
||||
}
|
||||
|
||||
let child = unsafe {
|
||||
CreateWindowExW(
|
||||
WINDOW_EX_STYLE::default(),
|
||||
PCWSTR::from_raw(class_name.as_ptr()),
|
||||
PCWSTR::null(),
|
||||
flags,
|
||||
attributes.position.map(|a| a.0).unwrap_or(CW_USEDEFAULT),
|
||||
attributes.position.map(|a| a.1).unwrap_or(CW_USEDEFAULT),
|
||||
attributes.size.map(|a| a.0 as i32).unwrap_or(CW_USEDEFAULT),
|
||||
attributes.size.map(|a| a.1 as i32).unwrap_or(CW_USEDEFAULT),
|
||||
HWND(parent),
|
||||
HMENU::default(),
|
||||
GetModuleHandleW(PCWSTR::null()).unwrap_or_default(),
|
||||
None,
|
||||
)
|
||||
};
|
||||
|
||||
Self::new_as_child_hwnd(child, attributes, pl_attrs, web_context)
|
||||
}
|
||||
|
||||
fn new_as_child_hwnd(
|
||||
hwnd: HWND,
|
||||
attributes: WebViewAttributes,
|
||||
pl_attrs: super::PlatformSpecificWebViewAttributes,
|
||||
web_context: Option<&mut WebContext>,
|
||||
) -> Result<Self> {
|
||||
Self::new_hwnd(hwnd, attributes, pl_attrs, web_context).map(|mut w| {
|
||||
w.is_child = true;
|
||||
w
|
||||
})
|
||||
}
|
||||
|
||||
fn new_hwnd(
|
||||
hwnd: HWND,
|
||||
mut attributes: WebViewAttributes,
|
||||
pl_attrs: super::PlatformSpecificWebViewAttributes,
|
||||
web_context: Option<&mut WebContext>,
|
||||
) -> Result<Self> {
|
||||
let hwnd = HWND(window.hwnd() as _);
|
||||
let file_drop_controller: Rc<OnceCell<FileDropController>> = Rc::new(OnceCell::new());
|
||||
let file_drop_handler = attributes.file_drop_handler.take();
|
||||
let file_drop_window = window.clone();
|
||||
let _ = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) };
|
||||
|
||||
let file_drop_controller = attributes
|
||||
.file_drop_handler
|
||||
.take()
|
||||
.map(|handler| FileDropController::new(hwnd, handler));
|
||||
|
||||
let env = Self::create_environment(&web_context, pl_attrs.clone(), &attributes)?;
|
||||
let controller = Self::create_controller(hwnd, &env, attributes.incognito)?;
|
||||
let webview = Self::init_webview(window, hwnd, attributes, &env, &controller, pl_attrs)?;
|
||||
|
||||
if let Some(file_drop_handler) = file_drop_handler {
|
||||
let mut controller = FileDropController::new();
|
||||
controller.listen(hwnd, file_drop_window, file_drop_handler);
|
||||
let _ = file_drop_controller.set(controller);
|
||||
}
|
||||
let webview = Self::init_webview(hwnd, attributes, &env, &controller, pl_attrs)?;
|
||||
|
||||
Ok(Self {
|
||||
hwnd,
|
||||
controller,
|
||||
is_child: false,
|
||||
webview,
|
||||
env,
|
||||
file_drop_controller,
|
||||
@@ -227,7 +316,6 @@ impl InnerWebView {
|
||||
}
|
||||
|
||||
fn init_webview(
|
||||
window: Rc<Window>,
|
||||
hwnd: HWND,
|
||||
mut attributes: WebViewAttributes,
|
||||
env: &ICoreWebView2Environment,
|
||||
@@ -312,7 +400,6 @@ impl InnerWebView {
|
||||
|
||||
// document title changed handler
|
||||
if let Some(document_title_changed_handler) = attributes.document_title_changed_handler {
|
||||
let window_ = window.clone();
|
||||
unsafe {
|
||||
webview
|
||||
.add_DocumentTitleChanged(
|
||||
@@ -321,7 +408,7 @@ impl InnerWebView {
|
||||
if let Some(webview) = webview {
|
||||
webview.DocumentTitle(&mut title)?;
|
||||
let title = take_pwstr(title);
|
||||
document_title_changed_handler(&window_, title);
|
||||
document_title_changed_handler(title);
|
||||
}
|
||||
Ok(())
|
||||
})),
|
||||
@@ -332,7 +419,7 @@ impl InnerWebView {
|
||||
}
|
||||
|
||||
if let Some(on_page_load_handler) = attributes.on_page_load_handler {
|
||||
let on_page_load_handler = Arc::new(on_page_load_handler);
|
||||
let on_page_load_handler = Rc::new(on_page_load_handler);
|
||||
let on_page_load_handler_ = on_page_load_handler.clone();
|
||||
|
||||
unsafe {
|
||||
@@ -368,22 +455,13 @@ impl InnerWebView {
|
||||
Self::add_script_to_execute_on_document_created(
|
||||
&webview,
|
||||
String::from(
|
||||
r#"Object.defineProperty(window, 'ipc', {
|
||||
value: Object.freeze({postMessage:s=>window.chrome.webview.postMessage(s)})
|
||||
});
|
||||
|
||||
window.addEventListener('mousedown', (e) => {
|
||||
if (e.buttons === 1) window.chrome.webview.postMessage('__WEBVIEW_LEFT_MOUSE_DOWN__')
|
||||
});
|
||||
window.addEventListener('mousemove', (e) => window.chrome.webview.postMessage('__WEBVIEW_MOUSE_MOVE__'));"#,
|
||||
r#"Object.defineProperty(window, 'ipc', { value: Object.freeze({ postMessage: s=> window.chrome.webview.postMessage(s) }) });"#,
|
||||
),
|
||||
)?;
|
||||
for js in attributes.initialization_scripts {
|
||||
Self::add_script_to_execute_on_document_created(&webview, js)?;
|
||||
}
|
||||
|
||||
let window_ = window.clone();
|
||||
|
||||
// Message handler
|
||||
let ipc_handler = attributes.ipc_handler.take();
|
||||
unsafe {
|
||||
@@ -393,49 +471,8 @@ window.addEventListener('mousemove', (e) => window.chrome.webview.postMessage('_
|
||||
let mut js = PWSTR::null();
|
||||
args.TryGetWebMessageAsString(&mut js)?;
|
||||
let js = take_pwstr(js);
|
||||
if js == "__WEBVIEW_LEFT_MOUSE_DOWN__" || js == "__WEBVIEW_MOUSE_MOVE__" {
|
||||
if !window_.is_decorated() && window_.is_resizable() && !window_.is_maximized() {
|
||||
use crate::application::window::CursorIcon;
|
||||
|
||||
let mut point = POINT::default();
|
||||
win32wm::GetCursorPos(&mut point)?;
|
||||
let result = resize::hit_test(window_.hwnd(), point.x, point.y);
|
||||
let cursor = match result.0 as u32 {
|
||||
win32wm::HTLEFT => CursorIcon::WResize,
|
||||
win32wm::HTTOP => CursorIcon::NResize,
|
||||
win32wm::HTRIGHT => CursorIcon::EResize,
|
||||
win32wm::HTBOTTOM => CursorIcon::SResize,
|
||||
win32wm::HTTOPLEFT => CursorIcon::NwResize,
|
||||
win32wm::HTTOPRIGHT => CursorIcon::NeResize,
|
||||
win32wm::HTBOTTOMLEFT => CursorIcon::SwResize,
|
||||
win32wm::HTBOTTOMRIGHT => CursorIcon::SeResize,
|
||||
_ => CursorIcon::Arrow,
|
||||
};
|
||||
// don't use `CursorIcon::Arrow` variant or cursor manipulation using css will cause cursor flickering
|
||||
if cursor != CursorIcon::Arrow {
|
||||
window_.set_cursor_icon(cursor);
|
||||
}
|
||||
|
||||
if js == "__WEBVIEW_LEFT_MOUSE_DOWN__" {
|
||||
// we ignore `HTCLIENT` variant so the webview receives the click correctly if it is not on the edges
|
||||
// and prevent conflict with `tao::window::drag_window`.
|
||||
if result.0 as u32 != win32wm::HTCLIENT {
|
||||
resize::begin_resize_drag(
|
||||
window_.hwnd(),
|
||||
result.0,
|
||||
win32wm::WM_NCLBUTTONDOWN,
|
||||
point.x,
|
||||
point.y,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
// these are internal messages, ipc_handlers don't need it so exit early
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(ipc_handler) = &ipc_handler {
|
||||
ipc_handler(&window_, js);
|
||||
ipc_handler(js);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -589,7 +626,6 @@ window.addEventListener('mousemove', (e) => window.chrome.webview.postMessage('_
|
||||
let env = env.clone();
|
||||
let main_thread_id = std::thread::current().id();
|
||||
|
||||
let hwnd = window.hwnd();
|
||||
unsafe {
|
||||
webview
|
||||
.add_WebResourceRequested(
|
||||
@@ -852,7 +888,7 @@ window.addEventListener('mousemove', (e) => window.chrome.webview.postMessage('_
|
||||
|
||||
unsafe {
|
||||
controller
|
||||
.SetIsVisible(true)
|
||||
.SetIsVisible(attributes.visible)
|
||||
.map_err(webview2_com::Error::WindowsError)?;
|
||||
if attributes.focused {
|
||||
controller
|
||||
@@ -969,6 +1005,62 @@ window.addEventListener('mousemove', (e) => window.chrome.webview.postMessage('_
|
||||
set_theme(&self.webview, theme);
|
||||
}
|
||||
|
||||
pub fn set_position(&self, position: (i32, i32)) {
|
||||
if self.is_child {
|
||||
unsafe {
|
||||
let _ = SetWindowPos(
|
||||
self.hwnd,
|
||||
HWND::default(),
|
||||
position.0,
|
||||
position.1,
|
||||
0,
|
||||
0,
|
||||
SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_size(&self, size: (u32, u32)) {
|
||||
if self.is_child {
|
||||
unsafe {
|
||||
let _ = SetWindowPos(
|
||||
self.hwnd,
|
||||
HWND::default(),
|
||||
0,
|
||||
0,
|
||||
size.0 as _,
|
||||
size.1 as _,
|
||||
SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
unsafe {
|
||||
if self.is_child {
|
||||
ShowWindow(
|
||||
self.hwnd,
|
||||
match visible {
|
||||
true => SW_SHOW,
|
||||
false => SW_HIDE,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let _ = self.controller.SetIsVisible(visible);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focus(&self) {
|
||||
unsafe {
|
||||
let _ = self
|
||||
.controller
|
||||
.MoveFocus(COREWEBVIEW2_MOVE_FOCUS_REASON_PROGRAMMATIC);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_memory_usage_level(&self, level: MemoryUsageLevel) {
|
||||
let Ok(webview) = self.webview.cast::<ICoreWebView2_19>() else {
|
||||
return;
|
||||
@@ -1125,11 +1217,8 @@ fn get_function_impl(library: &str, function: &str) -> Option<FARPROC> {
|
||||
|
||||
macro_rules! get_function {
|
||||
($lib:expr, $func:ident) => {
|
||||
crate::webview::webview2::get_function_impl(
|
||||
concat!($lib, '\0'),
|
||||
concat!(stringify!($func), '\0'),
|
||||
)
|
||||
.map(|f| unsafe { std::mem::transmute::<windows::Win32::Foundation::FARPROC, $func>(f) })
|
||||
crate::webview2::get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0'))
|
||||
.map(|f| unsafe { std::mem::transmute::<windows::Win32::Foundation::FARPROC, $func>(f) })
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1171,7 +1260,7 @@ fn url_from_webview(webview: &ICoreWebView2) -> String {
|
||||
|
||||
static EXEC_MSG_ID: Lazy<u32> = Lazy::new(|| unsafe { RegisterWindowMessageA(s!("Wry::ExecMsg")) });
|
||||
|
||||
unsafe fn dispatch_handler<F>(hwnd: isize, function: F)
|
||||
unsafe fn dispatch_handler<F>(hwnd: HWND, function: F)
|
||||
where
|
||||
F: FnMut() + 'static,
|
||||
{
|
||||
@@ -1181,7 +1270,7 @@ where
|
||||
|
||||
let raw = Box::into_raw(boxed2);
|
||||
|
||||
let res = PostMessageW(HWND(hwnd), *EXEC_MSG_ID, WPARAM(raw as _), LPARAM(0));
|
||||
let res = PostMessageW(hwnd, *EXEC_MSG_ID, WPARAM(raw as _), LPARAM(0));
|
||||
assert!(
|
||||
res.is_ok(),
|
||||
"PostMessage failed ; is the messages queue full?"
|
||||
@@ -5,7 +5,6 @@
|
||||
use std::{
|
||||
ffi::{c_void, CStr},
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use cocoa::{
|
||||
@@ -18,10 +17,7 @@ use objc::{
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::{
|
||||
application::{dpi::LogicalPosition, platform::macos::WindowExtMacOS, window::Window},
|
||||
webview::FileDropEvent,
|
||||
};
|
||||
use crate::FileDropEvent;
|
||||
|
||||
pub(crate) type NSDragOperation = cocoa::foundation::NSUInteger;
|
||||
#[allow(non_upper_case_globals)]
|
||||
@@ -61,20 +57,17 @@ static OBJC_DRAGGING_UPDATED: Lazy<extern "C" fn(*const Object, Sel, id) -> NSDr
|
||||
// Safety: objc runtime calls are unsafe
|
||||
pub(crate) unsafe fn set_file_drop_handler(
|
||||
webview: *mut Object,
|
||||
window: Rc<Window>,
|
||||
handler: Box<dyn Fn(&Window, FileDropEvent) -> bool>,
|
||||
) -> *mut (Box<dyn Fn(&Window, FileDropEvent) -> bool>, Rc<Window>) {
|
||||
let listener = Box::into_raw(Box::new((handler, window)));
|
||||
handler: Box<dyn Fn(FileDropEvent) -> bool>,
|
||||
) -> *mut Box<dyn Fn(FileDropEvent) -> bool> {
|
||||
let listener = Box::into_raw(Box::new(handler));
|
||||
(*webview).set_ivar("FileDropHandler", listener as *mut _ as *mut c_void);
|
||||
listener
|
||||
}
|
||||
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
unsafe fn get_handler(
|
||||
this: &Object,
|
||||
) -> &mut (Box<dyn Fn(&Window, FileDropEvent) -> bool>, Rc<Window>) {
|
||||
unsafe fn get_handler(this: &Object) -> &mut Box<dyn Fn(FileDropEvent) -> bool> {
|
||||
let delegate: *mut c_void = *this.get_ivar("FileDropHandler");
|
||||
&mut *(delegate as *mut (Box<dyn Fn(&Window, FileDropEvent) -> bool>, Rc<Window>))
|
||||
&mut *(delegate as *mut Box<dyn Fn(FileDropEvent) -> bool>)
|
||||
}
|
||||
|
||||
unsafe fn collect_paths(drag_info: id) -> Vec<PathBuf> {
|
||||
@@ -116,13 +109,10 @@ extern "C" fn dragging_entered(this: &mut Object, sel: Sel, drag_info: id) -> NS
|
||||
let paths = unsafe { collect_paths(drag_info) };
|
||||
|
||||
let dl: NSPoint = unsafe { msg_send![drag_info, draggingLocation] };
|
||||
let scale_factor = listener.1.scale_factor();
|
||||
let ns_window = listener.1.ns_window() as id;
|
||||
let frame: NSRect = unsafe { msg_send![ns_window, frame] };
|
||||
let position =
|
||||
LogicalPosition::<f64>::from((dl.x, frame.size.height - dl.y)).to_physical(scale_factor);
|
||||
let frame: NSRect = unsafe { msg_send![this, frame] };
|
||||
let position = (dl.x as i32, (frame.size.height - dl.y) as i32);
|
||||
|
||||
if !listener.0(&listener.1, FileDropEvent::Hovered { paths, position }) {
|
||||
if !listener(FileDropEvent::Hovered { paths, position }) {
|
||||
// Reject the Wry file drop (invoke the OS default behaviour)
|
||||
OBJC_DRAGGING_ENTERED(this, sel, drag_info)
|
||||
} else {
|
||||
@@ -135,13 +125,10 @@ extern "C" fn perform_drag_operation(this: &mut Object, sel: Sel, drag_info: id)
|
||||
let paths = unsafe { collect_paths(drag_info) };
|
||||
|
||||
let dl: NSPoint = unsafe { msg_send![drag_info, draggingLocation] };
|
||||
let scale_factor = listener.1.scale_factor();
|
||||
let ns_window = listener.1.ns_window() as id;
|
||||
let frame: NSRect = unsafe { msg_send![ns_window, frame] };
|
||||
let position =
|
||||
LogicalPosition::<f64>::from((dl.x, frame.size.height - dl.y)).to_physical(scale_factor);
|
||||
let frame: NSRect = unsafe { msg_send![this, frame] };
|
||||
let position = (dl.x as i32, (frame.size.height - dl.y) as i32);
|
||||
|
||||
if !listener.0(&listener.1, FileDropEvent::Dropped { paths, position }) {
|
||||
if !listener(FileDropEvent::Dropped { paths, position }) {
|
||||
// Reject the Wry file drop (invoke the OS default behaviour)
|
||||
OBJC_PERFORM_DRAG_OPERATION(this, sel, drag_info)
|
||||
} else {
|
||||
@@ -151,7 +138,7 @@ extern "C" fn perform_drag_operation(this: &mut Object, sel: Sel, drag_info: id)
|
||||
|
||||
extern "C" fn dragging_exited(this: &mut Object, sel: Sel, drag_info: id) {
|
||||
let listener = unsafe { get_handler(this) };
|
||||
if !listener.0(&listener.1, FileDropEvent::Cancelled) {
|
||||
if !listener(FileDropEvent::Cancelled) {
|
||||
// Reject the Wry file drop (invoke the OS default behaviour)
|
||||
OBJC_DRAGGING_EXITED(this, sel, drag_info);
|
||||
}
|
||||
@@ -11,42 +11,36 @@ mod proxy;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod synthetic_mouse_events;
|
||||
|
||||
use url::Url;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use cocoa::appkit::{NSView, NSViewHeightSizable, NSViewWidthSizable};
|
||||
use cocoa::appkit::{NSView, NSViewHeightSizable, NSViewMinYMargin, NSViewWidthSizable};
|
||||
use cocoa::{
|
||||
base::{id, nil, NO, YES},
|
||||
foundation::{NSDictionary, NSFastEnumeration, NSInteger},
|
||||
};
|
||||
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};
|
||||
use url::Url;
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
ffi::{c_void, CStr},
|
||||
os::raw::c_char,
|
||||
ptr::{null, null_mut},
|
||||
rc::Rc,
|
||||
slice, str,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use core_graphics::geometry::CGRect;
|
||||
use core_graphics::geometry::{CGPoint, CGRect, CGSize};
|
||||
use objc::{
|
||||
declare::ClassDecl,
|
||||
runtime::{Class, Object, Sel, BOOL},
|
||||
};
|
||||
use objc_id::Id;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::application::platform::macos::WindowExtMacOS;
|
||||
#[cfg(target_os = "macos")]
|
||||
use file_drop::{add_file_drop_methods, set_file_drop_handler};
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
use crate::application::platform::ios::WindowExtIOS;
|
||||
|
||||
#[cfg(feature = "mac-proxy")]
|
||||
use crate::webview::{
|
||||
use crate::{
|
||||
proxy::ProxyConfig,
|
||||
wkwebview::proxy::{
|
||||
nw_endpoint_t, nw_proxy_config_create_http_connect, nw_proxy_config_create_socksv5,
|
||||
@@ -54,21 +48,14 @@ use crate::webview::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
application::{
|
||||
dpi::{LogicalSize, PhysicalSize},
|
||||
window::Window,
|
||||
},
|
||||
webview::{
|
||||
wkwebview::{
|
||||
download::{
|
||||
add_download_methods, download_did_fail, download_did_finish, download_policy,
|
||||
set_download_delegate,
|
||||
},
|
||||
navigation::{add_navigation_mathods, drop_navigation_methods, set_navigation_methods},
|
||||
wkwebview::{
|
||||
download::{
|
||||
add_download_methods, download_did_fail, download_did_finish, download_policy,
|
||||
set_download_delegate,
|
||||
},
|
||||
FileDropEvent, PageLoadEvent, RequestAsyncResponder, WebContext, WebViewAttributes, RGBA,
|
||||
navigation::{add_navigation_mathods, drop_navigation_methods, set_navigation_methods},
|
||||
},
|
||||
Result,
|
||||
Error, PageLoadEvent, RequestAsyncResponder, Result, WebContext, WebViewAttributes, RGBA,
|
||||
};
|
||||
|
||||
use http::{
|
||||
@@ -79,6 +66,7 @@ use http::{
|
||||
};
|
||||
|
||||
const IPC_MESSAGE_HANDLER_NAME: &str = "ipc";
|
||||
#[cfg(target_os = "macos")]
|
||||
const ACCEPT_FIRST_MOUSE: &str = "accept_first_mouse";
|
||||
|
||||
const NS_JSON_WRITING_FRAGMENTS_ALLOWED: u64 = 4;
|
||||
@@ -88,25 +76,61 @@ pub(crate) struct InnerWebView {
|
||||
#[cfg(target_os = "macos")]
|
||||
pub ns_window: id,
|
||||
pub manager: id,
|
||||
is_child: bool,
|
||||
pending_scripts: Arc<Mutex<Option<Vec<String>>>>,
|
||||
// Note that if following functions signatures are changed in the future,
|
||||
// all functions pointer declarations in objc callbacks below all need to get updated.
|
||||
ipc_handler_ptr: *mut (Box<dyn Fn(&Window, String)>, Rc<Window>),
|
||||
document_title_changed_handler: *mut (Box<dyn Fn(&Window, String)>, Rc<Window>),
|
||||
ipc_handler_ptr: *mut Box<dyn Fn(String)>,
|
||||
document_title_changed_handler: *mut Box<dyn Fn(String)>,
|
||||
navigation_decide_policy_ptr: *mut Box<dyn Fn(String, bool) -> bool>,
|
||||
page_load_handler: *mut Box<dyn Fn(PageLoadEvent)>,
|
||||
#[cfg(target_os = "macos")]
|
||||
file_drop_ptr: *mut (Box<dyn Fn(&Window, FileDropEvent) -> bool>, Rc<Window>),
|
||||
file_drop_ptr: *mut Box<dyn Fn(crate::FileDropEvent) -> bool>,
|
||||
download_delegate: id,
|
||||
protocol_ptrs: Vec<*mut Box<dyn Fn(Request<Vec<u8>>, RequestAsyncResponder)>>,
|
||||
}
|
||||
|
||||
impl InnerWebView {
|
||||
pub fn new(
|
||||
window: Rc<Window>,
|
||||
window: &impl HasRawWindowHandle,
|
||||
attributes: WebViewAttributes,
|
||||
pl_attrs: super::PlatformSpecificWebViewAttributes,
|
||||
_web_context: Option<&mut WebContext>,
|
||||
) -> Result<Self> {
|
||||
let ns_view = match window.raw_window_handle() {
|
||||
#[cfg(target_os = "macos")]
|
||||
RawWindowHandle::AppKit(w) => w.ns_view,
|
||||
#[cfg(target_os = "ios")]
|
||||
RawWindowHandle::UiKit(w) => w.ui_view,
|
||||
_ => return Err(Error::UnsupportedWindowHandle),
|
||||
};
|
||||
|
||||
Self::new_ns_view(ns_view as _, attributes, pl_attrs, _web_context, false)
|
||||
}
|
||||
|
||||
pub fn new_as_child(
|
||||
window: &impl HasRawWindowHandle,
|
||||
attributes: WebViewAttributes,
|
||||
pl_attrs: super::PlatformSpecificWebViewAttributes,
|
||||
_web_context: Option<&mut WebContext>,
|
||||
) -> Result<Self> {
|
||||
let ns_view = match window.raw_window_handle() {
|
||||
#[cfg(target_os = "macos")]
|
||||
RawWindowHandle::AppKit(w) => w.ns_view,
|
||||
#[cfg(target_os = "ios")]
|
||||
RawWindowHandle::UiKit(w) => w.ui_view,
|
||||
_ => return Err(Error::UnsupportedWindowHandle),
|
||||
};
|
||||
|
||||
Self::new_ns_view(ns_view as _, attributes, pl_attrs, _web_context, true)
|
||||
}
|
||||
|
||||
fn new_ns_view(
|
||||
ns_view: id,
|
||||
attributes: WebViewAttributes,
|
||||
_pl_attrs: super::PlatformSpecificWebViewAttributes,
|
||||
_web_context: Option<&mut WebContext>,
|
||||
is_child: bool,
|
||||
) -> Result<Self> {
|
||||
// Function for ipc handler
|
||||
extern "C" fn did_receive(this: &Object, _: Sel, _: id, msg: id) {
|
||||
@@ -114,13 +138,12 @@ impl InnerWebView {
|
||||
unsafe {
|
||||
let function = this.get_ivar::<*mut c_void>("function");
|
||||
if !function.is_null() {
|
||||
let function =
|
||||
&mut *(*function as *mut (Box<dyn for<'r> Fn(&'r Window, String)>, Rc<Window>));
|
||||
let function = &mut *(*function as *mut Box<dyn Fn(String)>);
|
||||
let body: id = msg_send![msg, body];
|
||||
let utf8: *const c_char = msg_send![body, UTF8String];
|
||||
let js = CStr::from_ptr(utf8).to_str().expect("Invalid UTF8 string");
|
||||
|
||||
(function.0)(&function.1, js.to_string());
|
||||
(function)(js.to_string());
|
||||
} else {
|
||||
log::warn!("WebView instance is dropped! This handler shouldn't be called.");
|
||||
}
|
||||
@@ -371,17 +394,37 @@ impl InnerWebView {
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use core_graphics::geometry::{CGPoint, CGSize};
|
||||
let frame: CGRect = CGRect::new(&CGPoint::new(0., 0.), &CGSize::new(0., 0.));
|
||||
let (x, y) = attributes.position.unwrap_or((0, 0));
|
||||
|
||||
let (w, h) = attributes.size.unwrap_or_else(|| {
|
||||
if is_child {
|
||||
let frame = NSView::frame(ns_view);
|
||||
(frame.size.width as u32, frame.size.height as u32)
|
||||
} else {
|
||||
(0, 0)
|
||||
}
|
||||
});
|
||||
let frame: CGRect = CGRect::new(
|
||||
&window_position(
|
||||
if is_child { ns_view } else { webview },
|
||||
(x, y),
|
||||
(w as f64, h as f64),
|
||||
),
|
||||
&CGSize::new(w as f64, h as f64),
|
||||
);
|
||||
let _: () = msg_send![webview, initWithFrame:frame configuration:config];
|
||||
// Auto-resize on macOS
|
||||
webview.setAutoresizingMask_(NSViewHeightSizable | NSViewWidthSizable);
|
||||
if is_child {
|
||||
// fixed element
|
||||
webview.setAutoresizingMask_(NSViewMinYMargin);
|
||||
} else {
|
||||
// Auto-resize
|
||||
webview.setAutoresizingMask_(NSViewHeightSizable | NSViewWidthSizable);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
{
|
||||
let ui_view = window.ui_view() as id;
|
||||
let frame: CGRect = msg_send![ui_view, frame];
|
||||
let frame: CGRect = msg_send![ns_view, frame];
|
||||
// set all autoresizingmasks
|
||||
let () = msg_send![webview, setAutoresizingMask: 31];
|
||||
let _: () = msg_send![webview, initWithFrame:frame configuration:config];
|
||||
@@ -391,6 +434,10 @@ impl InnerWebView {
|
||||
let _: () = msg_send![scroll, setBounces: NO];
|
||||
}
|
||||
|
||||
if !attributes.visible {
|
||||
let () = msg_send![webview, setHidden: YES];
|
||||
}
|
||||
|
||||
#[cfg(any(debug_assertions, feature = "devtools"))]
|
||||
if attributes.devtools {
|
||||
let has_inspectable_property: BOOL =
|
||||
@@ -425,7 +472,7 @@ impl InnerWebView {
|
||||
None => class!(WebViewDelegate),
|
||||
};
|
||||
let handler: id = msg_send![cls, new];
|
||||
let ipc_handler_ptr = Box::into_raw(Box::new((ipc_handler, window.clone())));
|
||||
let ipc_handler_ptr = Box::into_raw(Box::new(ipc_handler));
|
||||
|
||||
(*handler).set_ivar("function", ipc_handler_ptr as *mut _ as *mut c_void);
|
||||
let ipc = NSString::new(IPC_MESSAGE_HANDLER_NAME);
|
||||
@@ -460,10 +507,9 @@ impl InnerWebView {
|
||||
unsafe {
|
||||
let function = this.get_ivar::<*mut c_void>("function");
|
||||
if !function.is_null() {
|
||||
let function = &mut *(*function
|
||||
as *mut (Box<dyn for<'r> Fn(&'r Window, String)>, Rc<Window>));
|
||||
let function = &mut *(*function as *mut Box<dyn Fn(String)>);
|
||||
let title: id = msg_send![of_object, title];
|
||||
(function.0)(&function.1, NSString(title).to_str().to_string());
|
||||
(function)(NSString(title).to_str().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -475,7 +521,7 @@ impl InnerWebView {
|
||||
|
||||
let handler: id = msg_send![cls, new];
|
||||
let document_title_changed_handler =
|
||||
Box::into_raw(Box::new((document_title_changed_handler, window.clone())));
|
||||
Box::into_raw(Box::new(document_title_changed_handler));
|
||||
|
||||
(*handler).set_ivar(
|
||||
"function",
|
||||
@@ -752,17 +798,15 @@ impl InnerWebView {
|
||||
#[cfg(target_os = "macos")]
|
||||
let file_drop_ptr = match attributes.file_drop_handler {
|
||||
// if we have a file_drop_handler defined, use the defined handler
|
||||
Some(file_drop_handler) => {
|
||||
set_file_drop_handler(webview, window.clone(), file_drop_handler)
|
||||
}
|
||||
Some(file_drop_handler) => set_file_drop_handler(webview, file_drop_handler),
|
||||
// prevent panic by using a blank handler
|
||||
None => set_file_drop_handler(webview, window.clone(), Box::new(|_, _| false)),
|
||||
None => set_file_drop_handler(webview, Box::new(|_| false)),
|
||||
};
|
||||
|
||||
// ns window is required for the print operation
|
||||
#[cfg(target_os = "macos")]
|
||||
let ns_window = {
|
||||
let ns_window = window.ns_window() as id;
|
||||
let ns_window: id = msg_send![ns_view, window];
|
||||
|
||||
let can_set_titlebar_style: BOOL = msg_send![
|
||||
ns_window,
|
||||
@@ -790,6 +834,7 @@ impl InnerWebView {
|
||||
page_load_handler,
|
||||
download_delegate,
|
||||
protocol_ptrs,
|
||||
is_child,
|
||||
};
|
||||
|
||||
// Initialize scripts
|
||||
@@ -825,37 +870,41 @@ r#"Object.defineProperty(window, 'ipc', {
|
||||
// Inject the web view into the window as main content
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let parent_view_cls = match ClassDecl::new("WryWebViewParent", class!(NSView)) {
|
||||
Some(mut decl) => {
|
||||
decl.add_method(
|
||||
sel!(keyDown:),
|
||||
key_down as extern "C" fn(&mut Object, Sel, id),
|
||||
);
|
||||
if is_child {
|
||||
let _: () = msg_send![ns_view, addSubview: webview];
|
||||
} else {
|
||||
let parent_view_cls = match ClassDecl::new("WryWebViewParent", class!(NSView)) {
|
||||
Some(mut decl) => {
|
||||
decl.add_method(
|
||||
sel!(keyDown:),
|
||||
key_down as extern "C" fn(&mut Object, Sel, id),
|
||||
);
|
||||
|
||||
extern "C" fn key_down(_this: &mut Object, _sel: Sel, event: id) {
|
||||
unsafe {
|
||||
let app = cocoa::appkit::NSApp();
|
||||
let menu: id = msg_send![app, mainMenu];
|
||||
let () = msg_send![menu, performKeyEquivalent: event];
|
||||
extern "C" fn key_down(_this: &mut Object, _sel: Sel, event: id) {
|
||||
unsafe {
|
||||
let app = cocoa::appkit::NSApp();
|
||||
let menu: id = msg_send![app, mainMenu];
|
||||
let () = msg_send![menu, performKeyEquivalent: event];
|
||||
}
|
||||
}
|
||||
|
||||
decl.register()
|
||||
}
|
||||
None => class!(NSView),
|
||||
};
|
||||
|
||||
decl.register()
|
||||
}
|
||||
None => class!(NSView),
|
||||
};
|
||||
let parent_view: id = msg_send![parent_view_cls, alloc];
|
||||
let _: () = msg_send![parent_view, init];
|
||||
parent_view.setAutoresizingMask_(NSViewHeightSizable | NSViewWidthSizable);
|
||||
let _: () = msg_send![parent_view, addSubview: webview];
|
||||
|
||||
let parent_view: id = msg_send![parent_view_cls, alloc];
|
||||
let _: () = msg_send![parent_view, init];
|
||||
parent_view.setAutoresizingMask_(NSViewHeightSizable | NSViewWidthSizable);
|
||||
let _: () = msg_send![parent_view, addSubview: webview];
|
||||
|
||||
// inject the webview into the window
|
||||
let ns_window = window.ns_window() as id;
|
||||
// Tell the webview receive keyboard events in the window.
|
||||
// See https://github.com/tauri-apps/wry/issues/739
|
||||
let _: () = msg_send![ns_window, setContentView: parent_view];
|
||||
let _: () = msg_send![ns_window, makeFirstResponder: webview];
|
||||
// inject the webview into the window
|
||||
let ns_window: id = msg_send![ns_view, window];
|
||||
// Tell the webview receive keyboard events in the window.
|
||||
// See https://github.com/tauri-apps/wry/issues/739
|
||||
let _: () = msg_send![ns_window, setContentView: parent_view];
|
||||
let _: () = msg_send![ns_window, makeFirstResponder: webview];
|
||||
}
|
||||
|
||||
// make sure the window is always on top when we create a new webview
|
||||
let app_class = class!(NSApplication);
|
||||
@@ -865,8 +914,7 @@ r#"Object.defineProperty(window, 'ipc', {
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
{
|
||||
let ui_view = window.ui_view() as id;
|
||||
let _: () = msg_send![ui_view, addSubview: webview];
|
||||
let _: () = msg_send![ns_view, addSubview: webview];
|
||||
}
|
||||
|
||||
Ok(w)
|
||||
@@ -1030,13 +1078,6 @@ r#"Object.defineProperty(window, 'ipc', {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn inner_size(&self, scale_factor: f64) -> PhysicalSize<u32> {
|
||||
let view_frame = unsafe { NSView::frame(self.webview) };
|
||||
let logical: LogicalSize<f64> = (view_frame.size.width, view_frame.size.height).into();
|
||||
logical.to_physical(scale_factor)
|
||||
}
|
||||
|
||||
pub fn zoom(&self, scale_factor: f64) {
|
||||
unsafe {
|
||||
let _: () = msg_send![self.webview, setPageZoom: scale_factor];
|
||||
@@ -1046,6 +1087,43 @@ r#"Object.defineProperty(window, 'ipc', {
|
||||
pub fn set_background_color(&self, _background_color: RGBA) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_position(&self, position: (i32, i32)) {
|
||||
if self.is_child {
|
||||
unsafe {
|
||||
let mut frame: CGRect = msg_send![self.webview, frame];
|
||||
frame.origin = window_position(
|
||||
msg_send![self.webview, superview],
|
||||
(position.0, position.1),
|
||||
(frame.size.width, frame.size.height),
|
||||
);
|
||||
let () = msg_send![self.webview, setFrame: frame];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_size(&self, size: (u32, u32)) {
|
||||
if self.is_child {
|
||||
unsafe {
|
||||
let mut frame: CGRect = msg_send![self.webview, frame];
|
||||
frame.size = CGSize::new(size.0 as f64, size.1 as f64);
|
||||
let () = msg_send![self.webview, setFrame: frame];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_visible(&self, visible: bool) {
|
||||
unsafe {
|
||||
let () = msg_send![self.webview, setHidden: !visible];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focus(&self) {
|
||||
unsafe {
|
||||
let window: id = msg_send![self.webview, window];
|
||||
let _: () = msg_send![window, makeFirstResponder: self.webview];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn url_from_webview(webview: id) -> String {
|
||||
@@ -1179,3 +1257,14 @@ impl From<NSData> for NSString {
|
||||
}
|
||||
|
||||
struct NSData(id);
|
||||
|
||||
/// Converts from wry screen-coordinates to macOS screen-coordinates.
|
||||
/// wry: top-left is (0, 0) and y increasing downwards
|
||||
/// macOS: bottom-left is (0, 0) and y increasing upwards
|
||||
unsafe fn window_position(view: id, position: (i32, i32), size: (f64, f64)) -> CGPoint {
|
||||
let frame: CGRect = msg_send![view, frame];
|
||||
CGPoint::new(
|
||||
position.0 as f64,
|
||||
frame.size.height - position.1 as f64 - size.1,
|
||||
)
|
||||
}
|
||||
@@ -11,7 +11,7 @@ use objc::{
|
||||
};
|
||||
|
||||
use super::{url_from_webview, InnerWebView, NSString};
|
||||
use crate::webview::PageLoadEvent;
|
||||
use crate::PageLoadEvent;
|
||||
|
||||
extern "C" fn did_commit_navigation(this: &Object, _: Sel, webview: id, _navigation: id) {
|
||||
unsafe {
|
||||
@@ -5,7 +5,7 @@
|
||||
use cocoa::base::nil;
|
||||
use libc::c_char;
|
||||
|
||||
use crate::{webview::proxy::ProxyEndpoint, Error};
|
||||
use crate::{proxy::ProxyEndpoint, Error};
|
||||
|
||||
use super::NSString;
|
||||
|
||||
Reference in New Issue
Block a user