Merge remote-tracking branch 'origin/master'

This commit is contained in:
Lucas Nogueira
2020-06-11 10:28:20 -03:00
43 changed files with 9599 additions and 1550 deletions

113
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,113 @@
on:
push:
branches:
- master
pull_request:
jobs:
rustfmt:
runs-on: ubuntu-latest
name: rustfmt
steps:
- uses: actions/checkout@v1
- name: install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
components: rustfmt
override: true
- name: install rustfmt
run: rustup component add rustfmt
- name: cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
test-stable:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macOS-latest, windows-2019, ubuntu-latest]
name: cargo test stable
steps:
- uses: actions/checkout@v1
- name: install libgtk-dev libwebkit2gtk-4.0
run: |
sudo apt update
sudo apt install libwebkit2gtk-4.0-dev
if: contains(matrix.os, 'ubuntu')
- name: install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: cargo test --all
uses: actions-rs/cargo@v1
with:
command: test
args: --all
test-nightly:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macOS-latest, windows-2019, ubuntu-latest]
name: cargo test nightly
steps:
- uses: actions/checkout@v1
- name: install libgtk-dev libwebkit2gtk-4.0
run: |
sudo apt update
sudo apt install libwebkit2gtk-4.0-dev
if: contains(matrix.os, 'ubuntu')
- name: install nightly toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
override: true
- name: cargo test --all
uses: actions-rs/cargo@v1
with:
command: test
args: --all
check-docs:
name: Docs
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macOS-latest, windows-2019, ubuntu-latest]
steps:
- uses: actions/checkout@v1
- name: install libgtk-dev libwebkit2gtk-4.0
run: |
sudo apt update
sudo apt install libwebkit2gtk-4.0-dev
if: contains(matrix.os, 'ubuntu')
- name: install nightly toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
profile: minimal
override: true
- name: check docs
uses: actions-rs/cargo@v1
with:
command: doc
args: --document-private-items

View File

@@ -1,22 +0,0 @@
sudo: required
language: rust
cache: cargo
os:
- linux
- osx
rust:
- stable
- nightly
matrix:
allow_failures:
- rust: nightly
addons:
apt:
packages:
- libwebkit2gtk-4.0-dev
sources:
- sourceline: 'ppa:webkit-team/ppa'

View File

@@ -1,6 +1,6 @@
[package]
name = "web-view"
version = "0.5.4"
version = "0.6.2"
authors = ["Boscop", "zxey <r.hozak@seznam.cz>", "Sam Green <sam.green81@gmail.com>"]
readme = "README.md"
license = "MIT"
@@ -12,8 +12,9 @@ exclude = ["examples/todo-ps/dist/**/*", "examples/elm-counter/index.html"]
[dependencies]
urlencoding = "1.0"
webview-sys = { path = "webview-sys", version = "0.3.1" }
webview-sys = { path = "webview-sys", version = "0.5" }
boxfnonce = "0.1"
tinyfiledialogs = "3.3.9"
[features]
edge = ["webview-sys/edge"]
@@ -27,3 +28,5 @@ actix-rt = "0.2"
rust-embed = "5.1.0"
mime_guess = "2.0.1"
futures = "0.1"
grep = "0.2.4"
walkdir = "2.3.1"

View File

@@ -1,4 +1,4 @@
# web-view &emsp; [![Build Status]][travis] [![Latest Version]][crates.io] <!-- omit in toc -->
# web-view &emsp; ![.github/workflows/ci.yml](https://github.com/Boscop/web-view/workflows/.github/workflows/ci.yml/badge.svg) [![Latest Version]][crates.io] <!-- omit in toc -->
[Build Status]: https://api.travis-ci.org/Boscop/web-view.svg?branch=master
[travis]: https://travis-ci.org/Boscop/web-view
@@ -91,7 +91,7 @@ As this library can be found as a crate on the [Rust Community Registry](https:/
web-view = { version = "0.5.4" }
```
If you want to make use of **Edge** on Windows environments then you'll need to use the following syntax instead:
If you want to make use of **Edge** on Windows environments, you will need to have Windows 10 SDK installed through Visual Studio Installer and you'll need to use the following syntax instead:
```toml
[dependencies]
@@ -126,20 +126,19 @@ fn main() {
```
You should now be able to run `cargo build` and see something similar to the output below:
```text
$ cargo build
Updating crates.io index
Compiling pkg-config v0.3.17
Compiling bitflags v1.2.1
Compiling cc v1.0.47
Compiling boxfnonce v0.1.1
Compiling urlencoding v1.0.0
Compiling webview-sys v0.3.3
Compiling web-view v0.5.4
Compiling my-project v0.1.0 (C:\Users\Username\source\rust-projects\my-project)
Finished dev [unoptimized + debuginfo] target(s) in 8.36s
```
```text
$ cargo build
Updating crates.io index
Compiling pkg-config v0.3.17
Compiling cc v1.0.47
Compiling boxfnonce v0.1.1
Compiling urlencoding v1.0.0
Compiling webview-sys v0.3.3
Compiling web-view v0.5.4
Compiling my-project v0.1.0 (C:\Users\Username\source\rust-projects\my-project)
Finished dev [unoptimized + debuginfo] target(s) in 8.36s
```
Assuming you get a successful build all you have to do now is run it with: `cargo run`. Hopefully you'll see the same as below:
@@ -149,7 +148,7 @@ For more usage info please check out the [examples](https://github.com/Boscop/we
## Known Issues and Limitations
* `Edge` feature switch not working on Windows 10. As of version `0.5.4` the Edge feature switch seems to have regressed and will not render content reliably. Trying to run the application with `cargo run` will always display an empty window before faulting, but if you run the binary created during the build process directly it will work as expected. For more information [see issue #96](https://github.com/Boscop/web-view/issues/96).
* `Edge` feature switch not working on Windows 10 if run as `Administrator`. This was the root cause of the issue raised in [#96](https://github.com/Boscop/web-view/issues/96) and is the result of a bug in `Microsoft.Toolkit.Win32` which is [tracked here](https://github.com/windows-toolkit/Microsoft.Toolkit.Win32/issues/50).
* `Edge` sandbox restrictions. If you decide to make use of an embedded Web Server to return your content you will need to run the following command to bypass the restriction that prevents communication with `localhost`:
``` powershell

View File

@@ -1,17 +1,17 @@
#![windows_subsystem = "windows"]
extern crate actix_web;
extern crate actix_rt;
extern crate rust_embed;
extern crate mime_guess;
extern crate web_view;
extern crate actix_web;
extern crate futures;
extern crate mime_guess;
extern crate rust_embed;
extern crate web_view;
use actix_web::{web, App, HttpRequest, HttpServer, HttpResponse, body::Body};
use rust_embed::RustEmbed;
use actix_web::{body::Body, web, App, HttpRequest, HttpResponse, HttpServer};
use futures::future::Future;
use mime_guess::from_path;
use std::{borrow::Cow, thread, sync::mpsc};
use rust_embed::RustEmbed;
use std::{borrow::Cow, sync::mpsc, thread};
use web_view::*;
#[derive(RustEmbed)]
@@ -19,7 +19,7 @@ use web_view::*;
struct Asset;
fn assets(req: HttpRequest) -> HttpResponse {
let path = if req.path() == "/" {
let path = if req.path() == "/" {
// if there is no path, return default file
"index.html"
} else {
@@ -34,7 +34,9 @@ fn assets(req: HttpRequest) -> HttpResponse {
Cow::Borrowed(bytes) => bytes.into(),
Cow::Owned(bytes) => bytes.into(),
};
HttpResponse::Ok().content_type(from_path(path).first_or_octet_stream().as_ref()).body(body)
HttpResponse::Ok()
.content_type(from_path(path).first_or_octet_stream().as_ref())
.body(body)
}
None => HttpResponse::NotFound().body("404 Not Found"),
}
@@ -48,12 +50,10 @@ fn main() {
thread::spawn(move || {
let sys = actix_rt::System::new("actix-example");
let server = HttpServer::new(|| {
App::new().route("*", web::get().to(assets))
})
let server = HttpServer::new(|| App::new().route("*", web::get().to(assets)))
.bind("127.0.0.1:0")
.unwrap();
// we specified the port to be 0,
// meaning the operating system
// will choose some available port
@@ -62,12 +62,12 @@ fn main() {
// so we know where to point webview at
let port = server.addrs().first().unwrap().port();
let server = server.start();
let _ = port_tx.send(port);
let _ = server_tx.send(server);
let _ = sys.run();
});
let port = port_rx.recv().unwrap();
let server = server_rx.recv().unwrap();
@@ -86,7 +86,5 @@ fn main() {
.unwrap();
// gracefully shutdown actix web server
let _ = server
.stop(true)
.wait();
}
let _ = server.stop(true).wait();
}

36
examples/color_change.rs Normal file
View File

@@ -0,0 +1,36 @@
extern crate web_view;
use web_view::*;
fn main() {
web_view::builder()
.title("Change background color")
.content(Content::Html(HTML))
.size(200, 100)
.resizable(true)
.debug(true)
.user_data("")
.invoke_handler(|webview, arg| {
match arg {
"red" => webview.set_color((255, 0, 0)),
"green" => webview.set_color((0, 255, 0)),
"blue" => webview.set_color((0, 0, 255)),
_ => (),
}
Ok(())
})
.run()
.unwrap();
}
const HTML: &str = r#"
<!doctype html>
<html>
<body>
<button onclick="external.invoke('red')">red</button>
<button onclick="external.invoke('green')">green</button>
<button onclick="external.invoke('blue')">blue</button>
</body>
</html>
"#;

View File

@@ -1,7 +1,9 @@
//#![windows_subsystem = "windows"]
extern crate tinyfiledialogs as tfd;
extern crate web_view;
use tfd::MessageBoxIcon;
use web_view::*;
fn main() -> WVResult {
@@ -14,15 +16,34 @@ fn main() -> WVResult {
.user_data(())
.invoke_handler(|webview, arg| {
match arg {
"open" => match webview.dialog().open_file("Please choose a file...", "")? {
Some(path) => webview.dialog().info("File chosen", path.to_string_lossy()),
None => webview
.dialog()
.warning("Warning", "You didn't choose a file."),
}?,
"exit" => {
webview.terminate();
"open" => match tfd::open_file_dialog("Please choose a file...", "", None) {
Some(path) => tfd::message_box_ok("File chosen", &path, MessageBoxIcon::Info),
None => tfd::message_box_ok(
"Warning",
"You didn't choose a file.",
MessageBoxIcon::Warning,
),
},
"save" => match tfd::save_file_dialog("Save file...", "") {
Some(path) => tfd::message_box_ok("File chosen", &path, MessageBoxIcon::Info),
None => tfd::message_box_ok(
"Warning",
"You didn't choose a file.",
MessageBoxIcon::Warning,
),
},
"info" => {
tfd::message_box_ok("Info", "This is a info dialog", MessageBoxIcon::Info)
}
"warning" => tfd::message_box_ok(
"Warning",
"This is a warning dialog",
MessageBoxIcon::Warning,
),
"error" => {
tfd::message_box_ok("Error", "This is a error dialog", MessageBoxIcon::Error)
}
"exit" => webview.exit(),
_ => unimplemented!(),
};
Ok(())
@@ -35,9 +56,13 @@ fn main() -> WVResult {
const HTML: &str = r#"
<!doctype html>
<html>
<body>
<button onclick="external.invoke('open')">Open</button>
<button onclick="external.invoke('exit')">Exit</button>
</body>
<body>
<button onclick="external.invoke('open')">Open</button>
<button onclick="external.invoke('save')">Save</button>
<button onclick="external.invoke('info')">Info</button>
<button onclick="external.invoke('warning')">Warning</button>
<button onclick="external.invoke('error')">Error</button>
<button onclick="external.invoke('exit')">Exit</button>
</body>
</html>
"#;

31
examples/frameless.rs Normal file
View File

@@ -0,0 +1,31 @@
extern crate web_view;
use web_view::*;
fn main() {
web_view::builder()
.title("Frameless example")
.content(Content::Html(HTML))
.size(150, 150)
.frameless(true)
.debug(true)
.user_data("")
.invoke_handler(|webview, arg| {
match arg {
"exit" => webview.exit(),
_ => (),
}
Ok(())
})
.run()
.unwrap();
}
const HTML: &str = r#"
<!doctype html>
<html>
<body>
<button onclick="external.invoke('exit')" style="display:block;width:100px;height:100px;font-size:24pt;margin:25px auto;">exit</button>
</body>
</html>
"#;

33
examples/fullscreen.rs Normal file
View File

@@ -0,0 +1,33 @@
extern crate web_view;
use web_view::*;
fn main() {
web_view::builder()
.title("Fullscreen example")
.content(Content::Html(HTML))
.size(800, 100)
.resizable(true)
.debug(true)
.user_data("")
.invoke_handler(|webview, arg| {
match arg {
"enter" => webview.set_fullscreen(true),
"exit" => webview.set_fullscreen(false),
_ => (),
}
Ok(())
})
.run()
.unwrap();
}
const HTML: &str = r#"
<!doctype html>
<html>
<body>
<button onclick="external.invoke('enter')">enter fullscreen</button>
<button onclick="external.invoke('exit')">exit fullscreen</button>
</body>
</html>
"#;

31
examples/graceful_exit.rs Normal file
View File

@@ -0,0 +1,31 @@
extern crate web_view;
use web_view::*;
fn main() {
let res = web_view::builder()
.title("Graceful Exit Example")
.content(Content::Html(include_str!("graceful_exit/index.html")))
.size(800, 600)
.resizable(true)
.debug(true)
.user_data(0)
.invoke_handler(invoke_handler)
.run()
.unwrap();
println!("res: {:?}", res)
}
fn invoke_handler(wv: &mut WebView<usize>, arg: &str) -> WVResult {
if arg == "init" {
wv.eval("init()")?;
} else if arg == "update" {
*wv.user_data_mut() += 1;
let js = format!("setCurrentCount({})", wv.user_data());
wv.eval(&js)?;
} else if arg == "exit" {
println!("exiting!");
wv.exit();
}
Ok(())
}

View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<style>
#counter-message.preload {
display: none;
}
#counter-message {
display: block;
}
</style>
</head>
<body>
<h1 id="counter-message" class="preload"></h1>
<script>
var t;
function init() {
var h1 = document.getElementById('counter-message');
h1.setAttribute('class', '');
setCurrentCount(0);
t = setTimeout(tick, 500);
}
function tick() {
window.external.invoke('update');
t = setTimeout(tick, 500);
}
function setCurrentCount(count) {
if (count > 10) {
return window.external.invoke('exit');
}
var h1 = document.getElementById('counter-message');
var rem = 11 - count;
var suffix = rem < 2 ? ' second' : ' seconds';
h1.innerText = 'Exiting in ' + rem + suffix;
document.body.appendChild(h1);
}
external.invoke('init');
</script>
</body>
</html>

39
examples/multi_window.rs Normal file
View File

@@ -0,0 +1,39 @@
#![windows_subsystem = "windows"]
extern crate web_view;
use web_view::*;
fn main() {
let mut webview1 = web_view::builder()
.title("Multi window example - Window 1")
.content(Content::Url("https://en.m.wikipedia.org/wiki/Main_Page"))
.size(800, 600)
.resizable(true)
.debug(true)
.user_data(())
.invoke_handler(|_webview, _arg| Ok(()))
.build()
.unwrap();
let mut webview2 = web_view::builder()
.title("Multi window example - Window 2")
.content(Content::Url("https://en.m.wikipedia.org/wiki/Main_Page"))
.size(800, 600)
.resizable(true)
.debug(true)
.user_data(())
.invoke_handler(|_webview, _arg| Ok(()))
.build()
.unwrap();
loop {
if webview1.step().is_none() {
break;
}
if webview2.step().is_none() {
break;
}
}
}

187
examples/simple_grep.rs Normal file
View File

@@ -0,0 +1,187 @@
extern crate grep;
extern crate walkdir;
extern crate web_view;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate tinyfiledialogs as tfd;
use grep::regex::RegexMatcher;
use grep::searcher::sinks::UTF8;
use grep::searcher::{BinaryDetection, SearcherBuilder};
use std::error::Error;
use std::ffi::OsString;
use tfd::MessageBoxIcon;
use walkdir::WalkDir;
use web_view::*;
fn main() {
web_view::builder()
.title("Simple Grep Example")
.content(Content::Html(HTML))
.size(825, 625)
.resizable(true)
.debug(true)
.user_data(())
.invoke_handler(|webview, arg| {
use Cmd::*;
match serde_json::from_str(arg).unwrap() {
Search { pattern, path } => {
let result = match search(&pattern, OsString::from(path)) {
Ok(s) => s,
Err(err) => {
let err_str = format!("{}", err);
tfd::message_box_ok("Error", &err_str, MessageBoxIcon::Error);
OsString::from("")
}
};
if result.is_empty() {
tfd::message_box_ok(
"Information",
"No results were found!",
MessageBoxIcon::Info,
);
} else {
let eval_str = format!("LoadTextArea({:?});", result);
webview.eval(&eval_str)?;
}
}
Browse {} => match tfd::open_file_dialog("Please choose a file...", "", None) {
Some(path_selected) => {
let eval_str = format!("SetPath({:?});", path_selected);
webview.eval(&eval_str)?;
}
None => {
tfd::message_box_ok(
"Warning",
"You didn't choose a file.",
MessageBoxIcon::Warning,
);
}
},
Error { msg } => tfd::message_box_ok("Error", &msg, MessageBoxIcon::Error),
}
Ok(())
})
.run()
.unwrap();
}
#[derive(Deserialize)]
#[serde(tag = "cmd", rename_all = "camelCase")]
pub enum Cmd {
Search { pattern: String, path: String },
Browse {},
Error { msg: String },
}
fn search(pattern: &str, path: OsString) -> Result<OsString, Box<dyn Error>> {
let matcher = RegexMatcher::new_line_matcher(&pattern)?;
let mut matches: OsString = OsString::new();
let mut searcher = SearcherBuilder::new()
.binary_detection(BinaryDetection::quit(b'\x00'))
.line_number(true)
.build();
let mut matched_line = OsString::new();
for result in WalkDir::new(path) {
let entry = match result {
Ok(entry) => entry,
Err(err) => {
let err_str = format!("{}", err);
tfd::message_box_ok("Error", &err_str, MessageBoxIcon::Error);
continue;
}
};
if !entry.file_type().is_file() {
continue;
}
match searcher.search_path(
&matcher,
entry.path(),
UTF8(|lnum, line| {
matched_line = OsString::from(format!(
"{:?}\t {}:\t {}",
entry.path(),
lnum.to_string(),
line.to_string()
));
matches.push(&matched_line);
matches.push("\n");
Ok(true)
}),
) {
Ok(()) => (),
Err(err) => {
let err_str = format!("{}: {:?}", err, entry.path());
tfd::message_box_ok("Error", &err_str, MessageBoxIcon::Error);
continue;
}
}
}
Ok(matches)
}
const HTML: &str = r#"
<!doctype html>
<html>
<head>
<style>
.textarea {
width: 100%;
height: 30em;
font-size: 1em;
}
</style>
<script type="text/javascript">
'use strict';
var rpc = {
invoke : function(arg) { window.external.invoke(JSON.stringify(arg)); },
search : function() {
var pattern = document.getElementById("pattern");
var path = document.getElementById("path");
if (pattern.value.trim().length === 0) {
rpc.error("No pattern entered!");
return;
}
if (path.value.trim().length === 0) {
rpc.error("No path entered!");
return;
}
var textArea = document.getElementById("text_box");
textArea.value = "";
rpc.invoke({cmd : 'search', path : path.value, pattern : pattern.value});
},
browse : function() { rpc.invoke({cmd : 'browse'}); },
error : function(msg) { rpc.invoke({cmd : 'error', msg : msg}); },
};
function LoadTextArea(data) {
var textArea = document.getElementById("text_box");
textArea.value = data;
}
function SetPath(path_selected) {
var path = document.getElementById("path");
path.value = path_selected;
}
</script>
</head>
<body>
<label for="pattern">Pattern to search for:</label>
<input style="font-size:16px" id="pattern" type="text" size="35" />
<label style="font-size:14px"> (prefix text with "(?i)" to ignore case)</label><br><br>
<label for="path">Path (directory or file):</label>
<input id="path" type="text" size="70" />
<button onclick="rpc.browse()">Browse</button>
<button onclick="rpc.search()">Search</button>
<textarea class="textarea" id="text_box"></textarea>
</body>
</html>
"#;

View File

@@ -29,7 +29,7 @@ fn main() {
render(webview, *counter)?;
}
"exit" => {
webview.terminate();
webview.exit();
}
_ => unimplemented!(),
};

32
examples/title_change.rs Normal file
View File

@@ -0,0 +1,32 @@
extern crate web_view;
use web_view::*;
fn main() {
web_view::builder()
.title("Change title with input")
.content(Content::Html(HTML))
.size(800, 100)
.resizable(true)
.debug(true)
.user_data("")
.invoke_handler(|webview, arg| webview.set_title(arg))
.run()
.unwrap();
}
const HTML: &str = r#"
<!doctype html>
<html>
<body>
<input id="title" type="text" placeholder="Title" style="width: 100%; padding: none; margin: none; font-size: large;"/>
<script type="text/javascript">
function updateTitle(e) {
external.invoke(e.target.value);
}
var el = document.getElementById("title");
el.addEventListener("input", updateTitle, false);
</script>
</body>
</html>
"#;

View File

@@ -8,7 +8,8 @@ extern crate web_view;
use web_view::*;
fn main() {
let html = format!(r#"<!doctype html>
let html = format!(
r#"<!doctype html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
@@ -27,9 +28,10 @@ fn main() {
</body>
</html>
"#,
styles = inline_style(include_str!("todo-elm/styles.css")),
scripts = inline_script(include_str!("todo-elm/elm.js")) + &inline_script(include_str!("todo-elm/app.js")),
);
styles = inline_style(include_str!("todo-elm/styles.css")),
scripts = inline_script(include_str!("todo-elm/elm.js"))
+ &inline_script(include_str!("todo-elm/app.js")),
);
let mut webview = web_view::builder()
.title("Rust Todo App")
@@ -46,13 +48,11 @@ fn main() {
match serde_json::from_str(arg).unwrap() {
Init => {
*tasks = vec![
Task {
name: "Create Elm example".to_string(),
done: true,
},
];
},
*tasks = vec![Task {
name: "Create Elm example".to_string(),
done: true,
}];
}
Log { text } => println!("{}", text),
AddTask { name } => tasks.push(Task { name, done: false }),
MarkTask { index, done } => tasks[index].done = done,
@@ -79,7 +79,10 @@ fn render(webview: &mut WebView<Vec<Task>>) -> WVResult {
let render_tasks = {
let tasks = webview.user_data();
println!("{:#?}", tasks);
format!("app.ports.fromRust.send({})", serde_json::to_string(tasks).unwrap())
format!(
"app.ports.fromRust.send({})",
serde_json::to_string(tasks).unwrap()
)
};
webview.eval(&render_tasks)
}

104
examples/todo-vue.rs Normal file
View File

@@ -0,0 +1,104 @@
//#![windows_subsystem = "windows"]
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate web_view;
use web_view::*;
fn main() {
let html = format!(
r#"
<!doctype html>
<html>
<head>
{styles}
</head>
<body>
<!--[if lt IE 9]>
<div class="ie-upgrade-container">
<p class="ie-upgrade-message">Please, upgrade Internet Explorer to continue using this software.</p>
<a class="ie-upgrade-link" target="_blank" href="https://www.microsoft.com/en-us/download/internet-explorer.aspx">Upgrade</a>
</div>
<![endif]-->
<!--[if gte IE 9 | !IE ]> <!-->
<div id="app"></div>
{scripts}
<![endif]-->
</body>
</html>
"#,
styles = inline_style(include_str!("todo-vue/build/app.css")),
scripts = inline_script(include_str!("todo-vue/build/app.js"))
);
let mut webview = web_view::builder()
.title("Rust Todo App")
.content(Content::Html(html))
.size(320, 480)
.resizable(false)
.debug(true)
.user_data(vec![])
.invoke_handler(|webview, arg| {
use Cmd::*;
let tasks_len = {
let tasks = webview.user_data_mut();
match serde_json::from_str(arg).unwrap() {
Init => (),
Log { text } => println!("{}", text),
AddTask { name } => tasks.push(Task { name, done: false }),
MarkTask { index, done } => tasks[index].done = done,
ClearDoneTasks => tasks.retain(|t| !t.done),
}
tasks.len()
};
webview.set_title(&format!("Rust Todo App ({} Tasks)", tasks_len))?;
render(webview)
})
.build()
.unwrap();
webview.set_color((156, 39, 176));
let res = webview.run().unwrap();
println!("final state: {:?}", res);
}
fn render(webview: &mut WebView<Vec<Task>>) -> WVResult {
let render_tasks = {
let tasks = webview.user_data();
println!("{:#?}", tasks);
format!("app.fromRust({})", serde_json::to_string(tasks).unwrap())
};
webview.eval(&render_tasks)
}
#[derive(Debug, Serialize, Deserialize)]
struct Task {
name: String,
done: bool,
}
#[derive(Deserialize)]
#[serde(tag = "cmd", rename_all = "camelCase")]
pub enum Cmd {
Init,
Log { text: String },
AddTask { name: String },
MarkTask { index: usize, done: bool },
ClearDoneTasks,
}
fn inline_style(s: &str) -> String {
format!(r#"<style type="text/css">{}</style>"#, s)
}
fn inline_script(s: &str) -> String {
format!(r#"<script type="text/javascript">{}</script>"#, s)
}

4
examples/todo-vue/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.cache
node_modules
dist
build/*.map

View File

@@ -0,0 +1 @@
.text-input-wrapper[data-v-84f9f2]{padding:.5em;position:fixed;top:0;left:0;right:0}.text-input[data-v-84f9f2]{width:100%;line-height:1.5em;padding:0 .2em;height:1.5em;outline:none;border:none;color:#4a148c;background-color:hsla(0,0%,100%,.87)}.text-input[data-v-84f9f2]:focus{background-color:#fff}.task-list[data-v-bbb001]{overflow-y:auto;position:fixed;top:2.5em;bottom:1.2em;left:0;right:0}.task-item[data-v-bbb001]{height:1.5em;color:hsla(0,0%,100%,.87);padding:.5em;cursor:pointer}.task-item.checked[data-v-bbb001]{text-decoration:line-through;color:hsla(0,0%,100%,.38)}.footer[data-v-b5ba49]{position:fixed;left:0;bottom:0;right:0;background-color:#fff;color:#9c27b0}.btn-clear-tasks[data-v-b5ba49]{width:100%;text-align:center;font-size:18px;height:2.5em;line-height:2.5em;text-transform:uppercase;cursor:pointer}.container[data-v-220073]{width:100%;height:100%;background-color:#9c27b0}*{margin:0;padding:0;box-sizing:border-box;font-size:28px;font-family:sans-serif}body,html{height:100%;overflow:none}.ie-upgrade-container{width:100%;height:100%;font-family:Arial,sans-serif;font-size:32px;color:#fff;background-color:#1ebbee;padding:3em 1em 1em}.ie-upgrade-link{margin:2em 0;padding:0 1em;color:#1ebbee;background-color:#fff;font-weight:700;text-align:center;display:block;width:100%;height:2em;line-height:2em;text-transform:uppercase}

File diff suppressed because one or more lines are too long

7469
examples/todo-vue/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
{
"scripts": {
"start": "parcel src/index.html --global app",
"build": "parcel build src/app.js --out-dir build --global app"
},
"dependencies": {
"vue": "^2.6.11"
},
"devDependencies": {
"@vue/component-compiler-utils": "^3.1.1",
"parcel-bundler": "^1.12.4",
"vue-template-compiler": "^2.6.11",
"vue-hot-reload-api": "^2.3.4"
}
}

View File

@@ -0,0 +1,74 @@
<template>
<div class="container">
<input-form></input-form>
<task-list :tasks="tasks"></task-list>
<app-fotter></app-fotter>
</div>
</template>
<script>
import InputForm from "./components/InputForm.vue";
import TaskList from "./components/TaskList.vue";
import AppFotter from "./components/Footer.vue";
export default {
props: {
tasks: {
type: Array,
required: true
}
},
components: {
InputForm,
TaskList,
AppFotter
}
};
</script>
<style scoped>
.container {
width: 100%;
height: 100%;
background-color: #9c27b0;
}
</style>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-size: 28px;
font-family: sans-serif;
}
html, body {
height: 100%;
overflow: none;
}
.ie-upgrade-container {
width: 100%;
height: 100%;
font-family: Arial, sans-serif;
font-size: 32px;
color: #ffffff;
background-color: #1ebbee;
padding: 3em 1em 1em 1em;
}
.ie-upgrade-link {
margin: 2em 0em;
padding: 0 1em;
color: #1ebbee;
background-color: #ffffff;
font-weight: bold;
text-align: center;
display: block;
width: 100%;
height: 2em;
line-height: 2em;
text-transform: uppercase;
}
</style>

View File

@@ -0,0 +1,23 @@
import Vue from 'vue';
import App from './App.vue';
import { init } from './rpc';
let vm = new Vue({
el: "#app",
data: function () {
return {
tasks: []
}
},
render: function (h) {
return h(App, { attrs: { tasks: this.tasks } })
}
});
window.onload = function () { init(); };
function fromRust(tasks) {
vm.tasks = tasks;
}
export { fromRust };

View File

@@ -0,0 +1,38 @@
<template>
<div class="footer">
<div class="btn-clear-tasks" @click="clearTasks">Delete completed</div>
</div>
</template>
<script>
import { clearDoneTasks } from "../rpc";
export default {
methods: {
clearTasks: function() {
clearDoneTasks();
}
}
};
</script>
<style scoped>
.footer {
position: fixed;
left: 0;
bottom: 0;
right: 0;
background-color: #ffffff;
color: #9c27b0;
}
.btn-clear-tasks {
width: 100%;
text-align: center;
font-size: 18px;
height: 2.5em;
line-height: 2.5em;
text-transform: uppercase;
cursor: pointer;
}
</style>

View File

@@ -0,0 +1,46 @@
<template>
<form class="text-input-wrapper" @submit.prevent="submit">
<input class="text-input" type="text" autofocus="true" v-model="text" />
</form>
</template>
<script>
import { addTask } from "../rpc";
export default {
data: function() {
return {
text: ""
};
},
methods: {
submit: function(e) {
addTask(this.text);
this.text = "";
}
}
};
</script>
<style scoped>
.text-input-wrapper {
padding: 0.5em;
position: fixed;
top: 0;
left: 0;
right: 0;
}
.text-input {
width: 100%;
line-height: 1.5em;
padding: 0 0.2em;
height: 1.5em;
outline: none;
border: none;
color: #4a148c;
background-color: rgba(255, 255, 255, 0.87);
}
.text-input:focus {
background-color: #ffffff;
}
</style>

View File

@@ -0,0 +1,53 @@
<template>
<div class="task-list">
<div
:class="isDone(task)"
v-for="(task, index) in tasks"
:key="`task.name-${index}`"
@click="_markTask(index, task.done)"
>{{task.name}}</div>
</div>
</template>
<script>
import { markTask } from "../rpc";
export default {
props: {
tasks: {
type: Array,
required: true
}
},
methods: {
isDone: function(task) {
let checked = task.done ? "checked" : "unchecked";
return "task-item " + checked;
},
_markTask: function(i, done) {
markTask(i, !done);
}
}
};
</script>
<style scoped>
.task-list {
overflow-y: auto;
position: fixed;
top: 2.5em;
bottom: 1.2em;
left: 0;
right: 0;
}
.task-item {
height: 1.5em;
color: rgba(255, 255, 255, 0.87);
padding: 0.5em;
cursor: pointer;
}
.task-item.checked {
text-decoration: line-through;
color: rgba(255, 255, 255, 0.38);
}
</style>

View File

@@ -0,0 +1,9 @@
<!doctype html>
<html>
<head>
</head>
<body>
<div id="app"></div>
<script src="app.js"></script>
</body>
</html>

View File

@@ -0,0 +1,27 @@
function invoke(arg) {
window.external.invoke(JSON.stringify(arg));
}
function init() {
invoke({ cmd: 'init' });
}
function log() {
var s = '';
for (var i = 0; i < arguments.length; i++) {
if (i != 0) {
s = s + ' ';
}
s = s + JSON.stringify(arguments[i]);
}
invoke({ cmd: 'log', text: s });
}
function addTask(name) {
invoke({ cmd: 'addTask', name: name });
}
function clearDoneTasks() {
invoke({ cmd: 'clearDoneTasks' });
}
function markTask(index, done) {
invoke({ cmd: 'markTask', index: index, done: done });
}
export { init, log, addTask, clearDoneTasks, markTask };

View File

@@ -1,17 +1,17 @@
#![windows_subsystem = "windows"]
extern crate actix_web;
extern crate actix_rt;
extern crate rust_embed;
extern crate mime_guess;
extern crate web_view;
extern crate actix_web;
extern crate futures;
extern crate mime_guess;
extern crate rust_embed;
extern crate web_view;
use actix_web::{web, App, HttpRequest, HttpServer, HttpResponse, body::Body};
use rust_embed::RustEmbed;
use actix_web::{body::Body, web, App, HttpRequest, HttpResponse, HttpServer};
use futures::future::Future;
use mime_guess::from_path;
use std::{borrow::Cow, thread, sync::mpsc};
use rust_embed::RustEmbed;
use std::{borrow::Cow, sync::mpsc, thread};
use web_view::*;
#[derive(RustEmbed)]
@@ -19,7 +19,7 @@ use web_view::*;
struct Asset;
fn assets(req: HttpRequest) -> HttpResponse {
let path = if req.path() == "/" {
let path = if req.path() == "/" {
// if there is no path, return default file
"index.html"
} else {
@@ -34,7 +34,9 @@ fn assets(req: HttpRequest) -> HttpResponse {
Cow::Borrowed(bytes) => bytes.into(),
Cow::Owned(bytes) => bytes.into(),
};
HttpResponse::Ok().content_type(from_path(path).first_or_octet_stream().as_ref()).body(body)
HttpResponse::Ok()
.content_type(from_path(path).first_or_octet_stream().as_ref())
.body(body)
}
None => HttpResponse::NotFound().body("404 Not Found"),
}
@@ -48,12 +50,10 @@ fn main() {
thread::spawn(move || {
let sys = actix_rt::System::new("actix-example");
let server = HttpServer::new(|| {
App::new().route("*", web::get().to(assets))
})
let server = HttpServer::new(|| App::new().route("*", web::get().to(assets)))
.bind("127.0.0.1:0")
.unwrap();
// we specified the port to be 0,
// meaning the operating system
// will choose some available port
@@ -62,12 +62,12 @@ fn main() {
// so we know where to point webview at
let port = server.addrs().first().unwrap().port();
let server = server.start();
let _ = port_tx.send(port);
let _ = server_tx.send(server);
let _ = sys.run();
});
let port = port_rx.recv().unwrap();
let server = server_rx.recv().unwrap();
@@ -86,7 +86,5 @@ fn main() {
.unwrap();
// gracefully shutdown actix web server
let _ = server
.stop(true)
.wait();
}
let _ = server.stop(true).wait();
}

View File

@@ -8,7 +8,8 @@ extern crate web_view;
use web_view::*;
fn main() {
let html = format!(r#"
let html = format!(
r#"
<!doctype html>
<html>
<head>
@@ -27,9 +28,10 @@ fn main() {
</body>
</html>
"#,
styles = inline_style(include_str!("todo/styles.css")),
scripts = inline_script(include_str!("todo/picodom.js")) + &inline_script(include_str!("todo/app.js")),
);
styles = inline_style(include_str!("todo/styles.css")),
scripts = inline_script(include_str!("todo/picodom.js"))
+ &inline_script(include_str!("todo/app.js")),
);
let mut webview = web_view::builder()
.title("Rust Todo App")

View File

@@ -1,62 +0,0 @@
max_width = 100
hard_tabs = false
tab_spaces = 4
newline_style = "Auto"
use_small_heuristics = "Default"
indent_style = "Block"
wrap_comments = false
format_doc_comments = false
comment_width = 80
normalize_comments = false
normalize_doc_attributes = false
format_strings = false
format_macro_matchers = false
format_macro_bodies = true
empty_item_single_line = true
struct_lit_single_line = true
fn_single_line = false
where_single_line = false
imports_indent = "Block"
imports_layout = "Mixed"
merge_imports = false
reorder_imports = true
reorder_modules = true
reorder_impl_items = false
type_punctuation_density = "Wide"
space_before_colon = false
space_after_colon = true
spaces_around_ranges = false
binop_separator = "Front"
remove_nested_parens = true
combine_control_expr = true
struct_field_align_threshold = 0
enum_discrim_align_threshold = 0
match_arm_blocks = true
force_multiline_blocks = false
fn_args_density = "Tall"
brace_style = "SameLineWhere"
control_brace_style = "AlwaysSameLine"
trailing_semicolon = true
trailing_comma = "Vertical"
match_block_trailing_comma = false
blank_lines_upper_bound = 1
blank_lines_lower_bound = 0
edition = "2015"
merge_derives = true
use_try_shorthand = false
use_field_init_shorthand = false
force_explicit_abi = true
condense_wildcard_suffixes = false
color = "Auto"
required_version = "1.0.0"
unstable_features = false
disable_all_formatting = false
skip_children = false
hide_parse_errors = false
error_on_line_overflow = false
error_on_unformatted = false
report_todo = "Never"
report_fixme = "Never"
ignore = []
emit_mode = "Files"
make_backup = false

View File

@@ -1,10 +1,11 @@
use ffi::{self, DialogFlags, DialogType};
use std::{ffi::CString, path::PathBuf};
use {read_str, WVResult, WebView};
const STR_BUF_SIZE: usize = 4096;
use std::path::PathBuf;
use tfd::MessageBoxIcon;
use {WVResult, WebView};
/// A builder for opening a new dialog window.
#[deprecated(
note = "Please use crates like 'tinyfiledialogs' for dialog handling, see example in examples/dialog.rs"
)]
#[derive(Debug)]
pub struct DialogBuilder<'a: 'b, 'b, T: 'a> {
webview: &'b mut WebView<'a, T>,
@@ -16,52 +17,24 @@ impl<'a: 'b, 'b, T: 'a> DialogBuilder<'a, 'b, T> {
DialogBuilder { webview }
}
fn dialog(
&mut self,
title: String,
arg: String,
dialog_type: DialogType,
dialog_flags: DialogFlags,
) -> WVResult<String> {
let mut s = [0u8; STR_BUF_SIZE];
let title_cstr = CString::new(title)?;
let arg_cstr = CString::new(arg)?;
unsafe {
ffi::webview_dialog(
self.webview.inner,
dialog_type,
dialog_flags,
title_cstr.as_ptr(),
arg_cstr.as_ptr(),
s.as_mut_ptr() as _,
s.len(),
);
}
Ok(read_str(&s))
}
/// Opens a new open file dialog and returns the chosen file path.
pub fn open_file<S, P>(&mut self, title: S, default_file: P) -> WVResult<Option<PathBuf>>
where
S: Into<String>,
P: Into<PathBuf>,
{
self.dialog(
title.into(),
default_file.into().to_string_lossy().into_owned(),
DialogType::Open,
DialogFlags::FILE,
)
.map(|path| {
if path.is_empty() {
None
} else {
Some(PathBuf::from(path))
}
})
let default_file = default_file.into().into_os_string();
let default_file = default_file
.to_str()
.expect("default_file is not valid utf-8");
let result = tfd::open_file_dialog(&title.into(), default_file, None).map(|p| p.into());
Ok(result)
}
/// Opens a new save file dialog and returns the chosen file path.
pub fn save_file(&mut self) -> WVResult<Option<PathBuf>> {
Ok(tfd::save_file_dialog("", "").map(|p| p.into()))
}
/// Opens a new choose directory dialog as returns the chosen directory path.
@@ -74,19 +47,13 @@ impl<'a: 'b, 'b, T: 'a> DialogBuilder<'a, 'b, T> {
S: Into<String>,
P: Into<PathBuf>,
{
self.dialog(
title.into(),
default_directory.into().to_string_lossy().into_owned(),
DialogType::Open,
DialogFlags::DIRECTORY,
)
.map(|path| {
if path.is_empty() {
None
} else {
Some(PathBuf::from(path))
}
})
let default_directory = default_directory.into().into_os_string();
let default_directory = default_directory
.to_str()
.expect("default_directory is not valid utf-8");
let result = tfd::select_folder_dialog(&title.into(), default_directory).map(|p| p.into());
Ok(result)
}
/// Opens an info alert dialog.
@@ -95,13 +62,8 @@ impl<'a: 'b, 'b, T: 'a> DialogBuilder<'a, 'b, T> {
TS: Into<String>,
MS: Into<String>,
{
self.dialog(
title.into(),
message.into(),
DialogType::Alert,
DialogFlags::INFO,
)
.map(|_| ())
tfd::message_box_ok(&title.into(), &message.into(), MessageBoxIcon::Info);
Ok(())
}
/// Opens a warning alert dialog.
@@ -110,13 +72,8 @@ impl<'a: 'b, 'b, T: 'a> DialogBuilder<'a, 'b, T> {
TS: Into<String>,
MS: Into<String>,
{
self.dialog(
title.into(),
message.into(),
DialogType::Alert,
DialogFlags::WARNING,
)
.map(|_| ())
tfd::message_box_ok(&title.into(), &message.into(), MessageBoxIcon::Warning);
Ok(())
}
/// Opens an error alert dialog.
@@ -125,12 +82,7 @@ impl<'a: 'b, 'b, T: 'a> DialogBuilder<'a, 'b, T> {
TS: Into<String>,
MS: Into<String>,
{
self.dialog(
title.into(),
message.into(),
DialogType::Alert,
DialogFlags::ERROR,
)
.map(|_| ())
tfd::message_box_ok(&title.into(), &message.into(), MessageBoxIcon::Error);
Ok(())
}
}

View File

@@ -21,7 +21,10 @@
//! [the examples]: https://github.com/Boscop/web-view/tree/master/examples
//! [original readme]: https://github.com/zserge/webview/blob/master/README.md
#![allow(deprecated)] // TODO: remove this when removing dialogs
extern crate boxfnonce;
extern crate tinyfiledialogs as tfd;
extern crate urlencoding;
extern crate webview_sys as ffi;
@@ -29,6 +32,7 @@ mod color;
mod dialog;
mod error;
mod escape;
pub use color::Color;
pub use dialog::DialogBuilder;
pub use error::{CustomError, Error, WVResult};
@@ -45,6 +49,17 @@ use std::{
};
use urlencoding::encode;
/// JavaScript function used to insert new css rules to webview.
/// This function should be called with only one argument
/// and that is the css to insert int webview.
/// With every call of this function new style element
/// will get created with css pasted as its children.
const CSS_INJECT_FUNCTION: &str = "(function(e){var \
t=document.createElement('style'),d=document.head||document.\
getElementsByTagName('head')[0];t.setAttribute('type','text/\
css'),t.styleSheet?t.styleSheet.cssText=e:t.appendChild(document.\
createTextNode(e)),d.appendChild(t)})";
/// Content displayable inside a [`WebView`].
///
/// # Variants
@@ -94,6 +109,7 @@ pub struct WebViewBuilder<'a, T: 'a, I, C> {
pub debug: bool,
pub invoke_handler: Option<I>,
pub user_data: Option<T>,
pub frameless: bool,
}
impl<'a, T: 'a, I, C> Default for WebViewBuilder<'a, T, I, C>
@@ -116,6 +132,7 @@ where
debug,
invoke_handler: None,
user_data: None,
frameless: false,
}
}
}
@@ -170,6 +187,13 @@ where
self.debug = debug;
self
}
/// The window crated will be frameless
///
/// defaults to `false`
pub fn frameless(mut self, frameless: bool) -> Self {
self.frameless = frameless;
self
}
/// Sets the invoke handler callback. This will be called when a message is received from
/// JavaScript.
@@ -218,6 +242,7 @@ where
self.height,
self.resizable,
self.debug,
self.frameless,
user_data,
invoke_handler,
)
@@ -259,7 +284,7 @@ struct UserData<'a, T> {
/// [`WebViewBuilder`]: struct.WebViewBuilder.html
#[derive(Debug)]
pub struct WebView<'a, T: 'a> {
inner: *mut CWebView,
inner: Option<*mut CWebView>,
_phantom: PhantomData<&'a mut T>,
}
@@ -272,6 +297,7 @@ impl<'a, T> WebView<'a, T> {
height: i32,
resizable: bool,
debug: bool,
frameless: bool,
user_data: T,
invoke_handler: I,
) -> WVResult<WebView<'a, T>>
@@ -294,6 +320,7 @@ impl<'a, T> WebView<'a, T> {
height,
resizable as _,
debug as _,
frameless as _,
Some(ffi_invoke_handler::<T>),
user_data_ptr as _,
);
@@ -309,7 +336,7 @@ impl<'a, T> WebView<'a, T> {
unsafe fn from_ptr(inner: *mut CWebView) -> WebView<'a, T> {
WebView {
inner,
inner: Some(inner),
_phantom: PhantomData,
}
}
@@ -319,14 +346,14 @@ impl<'a, T> WebView<'a, T> {
/// [`Handle`]: struct.Handle.html
pub fn handle(&self) -> Handle<T> {
Handle {
inner: self.inner,
inner: self.inner.unwrap(),
live: Arc::downgrade(&self.user_data_wrapper().live),
_phantom: PhantomData,
}
}
fn user_data_wrapper_ptr(&self) -> *mut UserData<'a, T> {
unsafe { webview_get_user_data(self.inner) as _ }
unsafe { webview_get_user_data(self.inner.unwrap()) as _ }
}
fn user_data_wrapper(&self) -> &UserData<'a, T> {
@@ -347,15 +374,20 @@ impl<'a, T> WebView<'a, T> {
&mut self.user_data_wrapper_mut().inner
}
/// Forces the `WebView` instance to end, without dropping.
#[deprecated(note = "Please use exit instead")]
pub fn terminate(&mut self) {
unsafe { webview_terminate(self.inner) }
self.exit();
}
/// Gracefully exits the webview
pub fn exit(&mut self) {
unsafe { webview_exit(self.inner.unwrap()) }
}
/// Executes the provided string as JavaScript code within the `WebView` instance.
pub fn eval(&mut self, js: &str) -> WVResult {
let js = CString::new(js)?;
let ret = unsafe { webview_eval(self.inner, js.as_ptr()) };
let ret = unsafe { webview_eval(self.inner.unwrap(), js.as_ptr()) };
if ret != 0 {
Err(Error::JsEvaluation)
} else {
@@ -365,13 +397,8 @@ impl<'a, T> WebView<'a, T> {
/// Injects the provided string as CSS within the `WebView` instance.
pub fn inject_css(&mut self, css: &str) -> WVResult {
let css = CString::new(css)?;
let ret = unsafe { webview_inject_css(self.inner, css.as_ptr()) };
if ret != 0 {
Err(Error::CssInjection)
} else {
Ok(())
}
let inject_func = format!("{}({})", CSS_INJECT_FUNCTION, escape(css));
self.eval(&inject_func).map_err(|_| Error::CssInjection)
}
/// Sets the color of the title bar.
@@ -389,7 +416,7 @@ impl<'a, T> WebView<'a, T> {
/// ```
pub fn set_color<C: Into<Color>>(&mut self, color: C) {
let color = color.into();
unsafe { webview_set_color(self.inner, color.r, color.g, color.b, color.a) }
unsafe { webview_set_color(self.inner.unwrap(), color.r, color.g, color.b, color.a) }
}
/// Sets the title displayed at the top of the window.
@@ -401,16 +428,19 @@ impl<'a, T> WebView<'a, T> {
/// [`Error::NulByte`]: enum.Error.html#variant.NulByte
pub fn set_title(&mut self, title: &str) -> WVResult {
let title = CString::new(title)?;
unsafe { webview_set_title(self.inner, title.as_ptr()) }
unsafe { webview_set_title(self.inner.unwrap(), title.as_ptr()) }
Ok(())
}
/// Enables or disables fullscreen.
pub fn set_fullscreen(&mut self, fullscreen: bool) {
unsafe { webview_set_fullscreen(self.inner, fullscreen as _) };
unsafe { webview_set_fullscreen(self.inner.unwrap(), fullscreen as _) };
}
/// Returns a builder for opening a new dialog window.
#[deprecated(
note = "Please use crates like 'tinyfiledialogs' for dialog handling, see example in examples/dialog.rs"
)]
pub fn dialog<'b>(&'b mut self) -> DialogBuilder<'a, 'b, T> {
DialogBuilder::new(self)
}
@@ -418,7 +448,7 @@ impl<'a, T> WebView<'a, T> {
/// Iterates the event loop. Returns `None` if the view has been closed or terminated.
pub fn step(&mut self) -> Option<WVResult> {
unsafe {
match webview_loop(self.inner, 1) {
match webview_loop(self.inner.unwrap(), 1) {
0 => {
let closure_result = &mut self.user_data_wrapper_mut().result;
match closure_result {
@@ -452,24 +482,28 @@ impl<'a, T> WebView<'a, T> {
}
unsafe fn _into_inner(&mut self) -> T {
let _lock = self
let lock = self
.user_data_wrapper()
.live
.write()
.expect("A dispatch channel thread panicked while holding mutex to WebView.");
let user_data_ptr = self.user_data_wrapper_ptr();
webview_exit(self.inner);
webview_free(self.inner);
webview_exit(self.inner.unwrap());
webview_free(self.inner.unwrap());
let user_data = *Box::from_raw(user_data_ptr);
std::mem::drop(lock);
user_data.inner
}
}
impl<'a, T> Drop for WebView<'a, T> {
fn drop(&mut self) {
unsafe {
self._into_inner();
if self.inner.is_some() {
unsafe {
self._into_inner();
}
self.inner = None;
}
}
}
@@ -483,6 +517,16 @@ pub struct Handle<T> {
_phantom: PhantomData<T>,
}
impl<T> Clone for Handle<T> {
fn clone(&self) -> Self {
Handle {
inner: self.inner,
live: self.live.clone(),
_phantom: PhantomData,
}
}
}
impl<T> Handle<T> {
/// Schedules a closure to be run on the [`WebView`] thread.
///
@@ -520,31 +564,27 @@ impl<T> Handle<T> {
unsafe impl<T> Send for Handle<T> {}
unsafe impl<T> Sync for Handle<T> {}
fn read_str(s: &[u8]) -> String {
let end = s.iter().position(|&b| b == 0).map_or(0, |i| i + 1);
match CStr::from_bytes_with_nul(&s[..end]) {
Ok(s) => s.to_string_lossy().into_owned(),
Err(_) => "".to_string(),
}
}
extern "C" fn ffi_dispatch_handler<T>(webview: *mut CWebView, arg: *mut c_void) {
unsafe {
let mut handle = mem::ManuallyDrop::new(WebView::<T>::from_ptr(webview));
let mut handle = WebView::<T>::from_ptr(webview);
let result = {
let callback =
Box::<SendBoxFnOnce<'static, (&mut WebView<T>,), WVResult>>::from_raw(arg as _);
callback.call(&mut handle)
};
handle.user_data_wrapper_mut().result = result;
// Do not clean up the webview on drop of the temporary WebView in handle
handle.inner = None;
}
}
extern "C" fn ffi_invoke_handler<T>(webview: *mut CWebView, arg: *const c_char) {
unsafe {
let arg = CStr::from_ptr(arg).to_string_lossy().to_string();
let mut handle = mem::ManuallyDrop::new(WebView::<T>::from_ptr(webview));
let result = ((*handle.user_data_wrapper_ptr()).invoke_handler)(&mut *handle, &arg);
let mut handle = WebView::<T>::from_ptr(webview);
let result = ((*handle.user_data_wrapper_ptr()).invoke_handler)(&mut handle, &arg);
handle.user_data_wrapper_mut().result = result;
// Do not clean up the webview on drop of the temporary WebView in handle
handle.inner = None;
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "webview-sys"
version = "0.3.3"
version = "0.5.0"
authors = ["Boscop", "zxey <r.hozak@seznam.cz>"]
license = "MIT"
repository = "https://github.com/Boscop/web-view"
@@ -9,6 +9,7 @@ keywords = ["web", "gui", "desktop", "electron", "webkit"]
categories = ["gui", "web-programming", "api-bindings", "rendering", "visualization"]
build = "build.rs"
links = "webview"
edition = "2018"
[lib]
name = "webview_sys"
@@ -17,8 +18,15 @@ path = "lib.rs"
[features]
edge = []
[dependencies]
bitflags = "1"
[target.'cfg(all(target_family = "unix", not(target_os = "macos")))'.dependencies]
javascriptcore-rs-sys = "0.2"
gtk-sys = "0.9"
glib-sys = "0.9"
gobject-sys = "0.9"
webkit2gtk-sys = { version = "0.10", features = ["v2_8"] }
gdk-sys = "0.9"
gio-sys = "0.9"
libc = "0.2"
[build-dependencies]
cc = "1"

View File

@@ -38,16 +38,10 @@ fn main() {
}
}
} else if target.contains("linux") || target.contains("bsd") {
let webkit = pkg_config::Config::new()
pkg_config::Config::new()
.atleast_version("2.8")
.probe("webkit2gtk-4.0")
.unwrap();
build.file("webview_gtk.c");
for path in webkit.include_paths {
build.include(path);
}
} else if target.contains("apple") {
build
.file("webview_cocoa.c")

339
webview-sys/gtk.rs Normal file
View File

@@ -0,0 +1,339 @@
#![cfg(all(target_family = "unix", not(target_os = "macos")))]
use gdk_sys::{gdk_threads_add_idle, GdkRGBA};
use gio_sys::GAsyncResult;
use glib_sys::*;
use gobject_sys::{g_signal_connect_data, GObject};
use gtk_sys::*;
use javascriptcore_sys::*;
use libc::{c_char, c_double, c_int, c_void};
use std::ffi::CStr;
use std::mem;
use std::ptr;
use webkit2gtk_sys::*;
type ExternalInvokeCallback = extern "C" fn(webview: *mut WebView, arg: *const c_char);
#[repr(C)]
struct WebView {
url: *const c_char,
title: *const c_char,
width: c_int,
height: c_int,
resizable: c_int,
debug: c_int,
frameless: c_int,
external_invoke_cb: ExternalInvokeCallback,
window: *mut GtkWidget,
scroller: *mut GtkWidget,
webview: *mut GtkWidget,
inspector_window: *mut GtkWidget,
queue: *mut GAsyncQueue,
ready: c_int,
js_busy: c_int,
should_exit: c_int,
userdata: *mut c_void,
}
#[no_mangle]
unsafe extern "C" fn webview_set_title(webview: *mut WebView, title: *const c_char) {
gtk_window_set_title(mem::transmute((*webview).window), title);
}
#[no_mangle]
unsafe extern "C" fn webview_set_fullscreen(webview: *mut WebView, fullscreen: c_int) {
if fullscreen > 0 {
gtk_window_fullscreen(mem::transmute((*webview).window));
} else {
gtk_window_unfullscreen(mem::transmute((*webview).window));
}
}
#[no_mangle]
unsafe extern "C" fn webview_new(
title: *const c_char,
url: *const c_char,
width: c_int,
height: c_int,
resizable: c_int,
debug: c_int,
frameless: c_int,
external_invoke_cb: ExternalInvokeCallback,
userdata: *mut c_void,
) -> *mut WebView {
let w = Box::new(WebView {
url,
title,
width,
height,
resizable,
debug,
frameless,
external_invoke_cb,
window: ptr::null_mut(),
scroller: ptr::null_mut(),
webview: ptr::null_mut(),
inspector_window: ptr::null_mut(),
queue: ptr::null_mut(),
ready: 0,
js_busy: 0,
should_exit: 0,
userdata,
});
let w = Box::into_raw(w);
if gtk_init_check(ptr::null_mut(), ptr::null_mut()) == GFALSE {
return ptr::null_mut();
}
(*w).queue = g_async_queue_new();
let window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(mem::transmute(window), title);
(*w).window = window;
if resizable > 0 {
gtk_window_set_default_size(mem::transmute(window), width, height);
} else {
gtk_widget_set_size_request(mem::transmute(window), width, height);
}
if frameless > 0 {
gtk_window_set_decorated(mem::transmute(window), 0);
}
gtk_window_set_resizable(mem::transmute(window), resizable);
gtk_window_set_position(mem::transmute(window), GTK_WIN_POS_CENTER);
let scroller = gtk_scrolled_window_new(ptr::null_mut(), ptr::null_mut());
gtk_container_add(mem::transmute(window), scroller);
(*w).scroller = scroller;
let m = webkit_user_content_manager_new();
webkit_user_content_manager_register_script_message_handler(
m,
CStr::from_bytes_with_nul_unchecked(b"external\0").as_ptr(),
);
g_signal_connect_data(
mem::transmute(m),
CStr::from_bytes_with_nul_unchecked(b"script-message-received::external\0").as_ptr(),
Some(mem::transmute(external_message_received_cb as *const ())),
mem::transmute(w),
None,
0,
);
let webview = webkit_web_view_new_with_user_content_manager(m);
(*w).webview = webview;
webkit_web_view_load_uri(
mem::transmute(webview),
if url.is_null() {
b"\0".as_ptr() as *const _
} else {
url
},
);
g_signal_connect_data(
mem::transmute(webview),
CStr::from_bytes_with_nul_unchecked(b"load-changed\0").as_ptr(),
Some(mem::transmute(webview_load_changed_cb as *const ())),
mem::transmute(w),
None,
0,
);
gtk_container_add(mem::transmute(scroller), webview);
let settings = webkit_web_view_get_settings(mem::transmute(webview));
// Enable webgl and canvas features.
webkit_settings_set_enable_webgl(settings, 1);
webkit_settings_set_enable_accelerated_2d_canvas(settings, 1);
if debug > 0 {
webkit_settings_set_enable_write_console_messages_to_stdout(settings, 1);
webkit_settings_set_enable_developer_extras(settings, 1);
} else {
g_signal_connect_data(
mem::transmute(webview),
CStr::from_bytes_with_nul_unchecked(b"context-menu\0").as_ptr(),
Some(mem::transmute(webview_context_menu_cb as *const ())),
mem::transmute(w),
None,
0,
);
}
gtk_widget_show_all(window);
webkit_web_view_run_javascript(
mem::transmute(webview),
CStr::from_bytes_with_nul_unchecked(b"window.external={invoke:function(x){window.webkit.messageHandlers.external.postMessage(x);}}\0").as_ptr(),
ptr::null_mut(),
None,
ptr::null_mut(),
);
g_signal_connect_data(
mem::transmute(window),
CStr::from_bytes_with_nul_unchecked(b"destroy\0").as_ptr(),
Some(mem::transmute(webview_destroy_cb as *const ())),
mem::transmute(w),
None,
0,
);
w
}
extern "C" fn webview_context_menu_cb(
_webview: *mut WebKitWebView,
_default_menu: *mut GtkWidget,
_hit_test_result: *mut WebKitHitTestResult,
_triggered_with_keyboard: gboolean,
_userdata: gboolean,
) -> gboolean {
GTRUE
}
unsafe extern "C" fn external_message_received_cb(
_m: *mut WebKitUserContentManager,
r: *mut WebKitJavascriptResult,
arg: gpointer,
) {
let webview: *mut WebView = mem::transmute(arg);
let context = webkit_javascript_result_get_global_context(r);
let value = webkit_javascript_result_get_value(r);
let js = JSValueToStringCopy(context, value, ptr::null_mut());
let n = JSStringGetMaximumUTF8CStringSize(js);
let mut s = Vec::new();
s.reserve(n);
JSStringGetUTF8CString(js, s.as_mut_ptr(), n);
((*webview).external_invoke_cb)(webview, s.as_ptr());
}
#[no_mangle]
unsafe extern "C" fn webview_get_user_data(webview: *mut WebView) -> *mut c_void {
(*webview).userdata
}
#[no_mangle]
unsafe extern "C" fn webview_free(webview: *mut WebView) {
let _ = Box::from_raw(webview);
}
#[no_mangle]
unsafe extern "C" fn webview_loop(webview: *mut WebView, blocking: c_int) -> c_int {
gtk_main_iteration_do(blocking);
(*webview).should_exit
}
#[no_mangle]
unsafe extern "C" fn webview_set_color(webview: *mut WebView, r: u8, g: u8, b: u8, a: u8) {
let color = GdkRGBA {
red: r as c_double / 255.0,
green: g as c_double / 255.0,
blue: b as c_double / 255.0,
alpha: a as c_double / 255.0,
};
webkit_web_view_set_background_color(mem::transmute((*webview).webview), &color);
}
unsafe extern "C" fn webview_load_changed_cb(
_webview: *mut WebKitWebView,
event: WebKitLoadEvent,
arg: gpointer,
) {
let w: *mut WebView = mem::transmute(arg);
if event == WEBKIT_LOAD_FINISHED {
(*w).ready = 1;
}
}
unsafe extern "C" fn webview_eval_finished(
_object: *mut GObject,
_result: *mut GAsyncResult,
userdata: gpointer,
) {
let webview: *mut WebView = mem::transmute(userdata);
(*webview).js_busy = 0;
}
#[no_mangle]
unsafe extern "C" fn webview_eval(webview: *mut WebView, js: *const c_char) -> c_int {
while (*webview).ready == 0 {
g_main_context_iteration(ptr::null_mut(), GTRUE);
}
(*webview).js_busy = 1;
webkit_web_view_run_javascript(
mem::transmute((*webview).webview),
js,
ptr::null_mut(),
Some(webview_eval_finished),
mem::transmute(webview),
);
while (*webview).js_busy == 1 {
g_main_context_iteration(ptr::null_mut(), GTRUE);
}
0
}
type DispatchFn = extern "C" fn(webview: *mut WebView, arg: *mut c_void);
#[repr(C)]
struct DispatchArg {
func: DispatchFn,
webview: *mut WebView,
arg: *mut c_void,
}
unsafe extern "C" fn webview_dispatch_wrapper(userdata: gpointer) -> gboolean {
let webview: *mut WebView = mem::transmute(userdata);
loop {
let arg: *mut DispatchArg = mem::transmute(g_async_queue_try_pop((*webview).queue));
if arg.is_null() {
break;
}
((*arg).func)(webview, (*arg).arg);
let _ = Box::from_raw(arg);
}
GFALSE
}
#[no_mangle]
unsafe extern "C" fn webview_dispatch(webview: *mut WebView, func: DispatchFn, arg: *mut c_void) {
let arg = Box::new(DispatchArg { func, webview, arg });
let queue = (*webview).queue;
g_async_queue_lock(queue);
g_async_queue_push_unlocked(queue, mem::transmute(Box::into_raw(arg)));
if g_async_queue_length_unlocked(queue) == 1 {
gdk_threads_add_idle(Some(webview_dispatch_wrapper), mem::transmute(webview));
}
g_async_queue_unlock(queue);
}
#[no_mangle]
unsafe extern "C" fn webview_destroy_cb(_widget: *mut GtkWidget, arg: gpointer) {
webview_exit(mem::transmute(arg));
}
#[no_mangle]
unsafe extern "C" fn webview_exit(webview: *mut WebView) {
(*webview).should_exit = 1;
}
#[no_mangle]
unsafe extern "C" fn webview_print_log(s: *const c_char) {
let format = std::ffi::CString::new("%s\n").unwrap();
libc::printf(format.as_ptr(), s);
}

View File

@@ -5,8 +5,8 @@
//!
//! [webview]: https://github.com/zserge/webview
#[macro_use]
extern crate bitflags;
#[cfg(all(target_family = "unix", not(target_os = "macos")))]
mod gtk;
use std::os::raw::*;
@@ -15,36 +15,25 @@ pub enum CWebView {} // opaque type, only used in ffi pointers
type ErasedExternalInvokeFn = extern "C" fn(webview: *mut CWebView, arg: *const c_char);
type ErasedDispatchFn = extern "C" fn(webview: *mut CWebView, arg: *mut c_void);
#[repr(C)]
pub enum DialogType {
Open = 0,
Save = 1,
Alert = 2,
}
bitflags! {
#[repr(C)]
pub struct DialogFlags: u32 {
const FILE = 0b0000;
const DIRECTORY = 0b0001;
const INFO = 0b0010;
const WARNING = 0b0100;
const ERROR = 0b0110;
}
}
extern {
pub fn webview_free(this: *mut CWebView);
pub fn webview_new(title: *const c_char, url: *const c_char, width: c_int, height: c_int, resizable: c_int, debug: c_int, external_invoke_cb: Option<ErasedExternalInvokeFn>, userdata: *mut c_void) -> *mut CWebView;
pub fn webview_loop(this: *mut CWebView, blocking: c_int) -> c_int;
pub fn webview_terminate(this: *mut CWebView);
pub fn webview_exit(this: *mut CWebView);
pub fn webview_get_user_data(this: *mut CWebView) -> *mut c_void;
pub fn webview_dispatch(this: *mut CWebView, f: Option<ErasedDispatchFn>, arg: *mut c_void);
pub fn webview_eval(this: *mut CWebView, js: *const c_char) -> c_int;
pub fn webview_inject_css(this: *mut CWebView, css: *const c_char) -> c_int;
pub fn webview_set_title(this: *mut CWebView, title: *const c_char);
pub fn webview_set_fullscreen(this: *mut CWebView, fullscreen: c_int);
pub fn webview_set_color(this: *mut CWebView, red: u8, green: u8, blue: u8, alpha: u8);
pub fn webview_dialog(this: *mut CWebView, dialog_type: DialogType, flags: DialogFlags, title: *const c_char, arg: *const c_char, result: *mut c_char, result_size: usize);
extern "C" {
pub fn webview_free(this: *mut CWebView);
pub fn webview_new(
title: *const c_char,
url: *const c_char,
width: c_int,
height: c_int,
resizable: c_int,
debug: c_int,
frameless: c_int,
external_invoke_cb: Option<ErasedExternalInvokeFn>,
userdata: *mut c_void,
) -> *mut CWebView;
pub fn webview_loop(this: *mut CWebView, blocking: c_int) -> c_int;
pub fn webview_exit(this: *mut CWebView);
pub fn webview_get_user_data(this: *mut CWebView) -> *mut c_void;
pub fn webview_dispatch(this: *mut CWebView, f: Option<ErasedDispatchFn>, arg: *mut c_void);
pub fn webview_eval(this: *mut CWebView, js: *const c_char) -> c_int;
pub fn webview_set_title(this: *mut CWebView, title: *const c_char);
pub fn webview_set_fullscreen(this: *mut CWebView, fullscreen: c_int);
pub fn webview_set_color(this: *mut CWebView, red: u8, green: u8, blue: u8, alpha: u8);
}

View File

@@ -20,110 +20,51 @@ typedef void* webview_t;
typedef void (*webview_external_invoke_cb_t)(webview_t w, const char *arg);
typedef void (*webview_dispatch_fn)(webview_t w, void *arg);
enum webview_dialog_type {
WEBVIEW_DIALOG_TYPE_OPEN = 0,
WEBVIEW_DIALOG_TYPE_SAVE = 1,
WEBVIEW_DIALOG_TYPE_ALERT = 2
};
WEBVIEW_API void webview_run(webview_t w);
WEBVIEW_API int webview_loop(webview_t w, int blocking);
WEBVIEW_API int webview_eval(webview_t w, const char *js);
WEBVIEW_API int webview_inject_css(webview_t w, const char *css);
WEBVIEW_API void webview_set_title(webview_t w, const char *title);
WEBVIEW_API void webview_set_fullscreen(webview_t w, int fullscreen);
WEBVIEW_API void webview_set_color(webview_t w, uint8_t r, uint8_t g,
uint8_t b, uint8_t a);
WEBVIEW_API void webview_dialog(webview_t w,
enum webview_dialog_type dlgtype, int flags,
const char *title, const char *arg,
char *result, size_t resultsz);
WEBVIEW_API void webview_dispatch(webview_t w, webview_dispatch_fn fn,
void *arg);
WEBVIEW_API void webview_terminate(webview_t w);
WEBVIEW_API void webview_exit(webview_t w);
WEBVIEW_API void webview_debug(const char *format, ...);
WEBVIEW_API void webview_print_log(const char *s);
WEBVIEW_API void* webview_get_user_data(webview_t w);
WEBVIEW_API webview_t webview_new(const char* title, const char* url, int width, int height, int resizable, int debug, webview_external_invoke_cb_t external_invoke_cb, void* userdata);
WEBVIEW_API webview_t webview_new(const char* title, const char* url, int width, int height, int resizable, int debug, int frameless, webview_external_invoke_cb_t external_invoke_cb, void* userdata);
WEBVIEW_API void webview_free(webview_t w);
WEBVIEW_API void webview_destroy(webview_t w);
// TODO WEBVIEW_API void webview_navigate(webview_t w, const char* url);
#define WEBVIEW_DIALOG_FLAG_FILE (0 << 0)
#define WEBVIEW_DIALOG_FLAG_DIRECTORY (1 << 0)
#define WEBVIEW_DIALOG_FLAG_INFO (1 << 1)
#define WEBVIEW_DIALOG_FLAG_WARNING (2 << 1)
#define WEBVIEW_DIALOG_FLAG_ERROR (3 << 1)
#define WEBVIEW_DIALOG_FLAG_ALERT_MASK (3 << 1)
struct webview_dispatch_arg {
webview_dispatch_fn fn;
webview_t w;
void *arg;
};
#define DEFAULT_URL \
"data:text/" \
"html,%3C%21DOCTYPE%20html%3E%0A%3Chtml%20lang=%22en%22%3E%0A%3Chead%3E%" \
"3Cmeta%20charset=%22utf-8%22%3E%3Cmeta%20http-equiv=%22X-UA-Compatible%22%" \
"20content=%22IE=edge%22%3E%3C%2Fhead%3E%0A%3Cbody%3E%3Cdiv%20id=%22app%22%" \
"3E%3C%2Fdiv%3E%3Cscript%20type=%22text%2Fjavascript%22%3E%3C%2Fscript%3E%" \
"3C%2Fbody%3E%0A%3C%2Fhtml%3E"
#define CSS_INJECT_FUNCTION \
"(function(e){var " \
"t=document.createElement('style'),d=document.head||document." \
"getElementsByTagName('head')[0];t.setAttribute('type','text/" \
"css'),t.styleSheet?t.styleSheet.cssText=e:t.appendChild(document." \
"createTextNode(e)),d.appendChild(t)})"
static int webview_js_encode(const char *s, char *esc, size_t n) {
int r = 1; /* At least one byte for trailing zero */
for (; *s; s++) {
const unsigned char c = *s;
if (c >= 0x20 && c < 0x80 && strchr("<>\\'\"", c) == NULL) {
if (n > 0) {
*esc++ = c;
n--;
}
r++;
} else {
if (n > 0) {
snprintf(esc, n, "\\x%02x", (int)c);
esc += 4;
n -= 4;
}
r += 4;
}
// Convert ASCII hex digit to a nibble (four bits, 0 - 15).
//
// Use unsigned to avoid signed overflow UB.
static inline unsigned char hex2nibble(unsigned char c) {
if (c >= '0' && c <= '9') {
return c - '0';
} else if (c >= 'a' && c <= 'f') {
return 10 + (c - 'a');
} else if (c >= 'A' && c <= 'F') {
return 10 + (c - 'A');
}
return r;
return 0;
}
WEBVIEW_API int webview_inject_css(webview_t w, const char *css) {
int n = webview_js_encode(css, NULL, 0);
char *esc = (char *)calloc(1, sizeof(CSS_INJECT_FUNCTION) + n + 4);
if (esc == NULL) {
return -1;
}
char *js = (char *)calloc(1, n);
webview_js_encode(css, js, n);
snprintf(esc, sizeof(CSS_INJECT_FUNCTION) + n + 4, "%s(\"%s\")",
CSS_INJECT_FUNCTION, js);
int r = webview_eval(w, esc);
free(js);
free(esc);
return r;
}
static inline const char *webview_check_url(const char *url) {
if (url == NULL || strlen(url) == 0) {
return DEFAULT_URL;
}
return url;
// Convert ASCII hex string (two characters) to byte.
//
// E.g., "0B" => 0x0B, "af" => 0xAF.
static inline char hex2char(const char* p) {
return hex2nibble(p[0]) * 16 + hex2nibble(p[1]);
}
#ifdef __cplusplus

View File

@@ -19,6 +19,7 @@ struct cocoa_webview {
int height;
int resizable;
int debug;
int frameless;
webview_external_invoke_cb_t external_invoke_cb;
struct webview_priv priv;
void *userdata;
@@ -33,7 +34,10 @@ WEBVIEW_API void* webview_get_user_data(webview_t w) {
return wv->userdata;
}
WEBVIEW_API webview_t webview_new(const char* title, const char* url, int width, int height, int resizable, int debug, webview_external_invoke_cb_t external_invoke_cb, void* userdata) {
WEBVIEW_API webview_t webview_new(
const char* title, const char* url,
int width, int height, int resizable, int debug, int frameless,
webview_external_invoke_cb_t external_invoke_cb, void* userdata) {
struct cocoa_webview* wv = (struct cocoa_webview*)calloc(1, sizeof(*wv));
wv->width = width;
wv->height = height;
@@ -41,6 +45,7 @@ WEBVIEW_API webview_t webview_new(const char* title, const char* url, int width,
wv->url = url;
wv->resizable = resizable;
wv->debug = debug;
wv->frameless = frameless;
wv->external_invoke_cb = external_invoke_cb;
wv->userdata = userdata;
if (webview_init(wv) != 0) {
@@ -71,6 +76,8 @@ WEBVIEW_API webview_t webview_new(const char* title, const char* url, int width,
#define WKNavigationResponsePolicyAllow 1
#define WKUserScriptInjectionTimeAtDocumentStart 0
#define NSApplicationActivationPolicyRegular 0
#define NSApplicationDefinedEvent 15
#define NSWindowStyleMaskBorderless 0
static id get_nsstring(const char *c_str) {
return objc_msgSend((id)objc_getClass("NSString"),
@@ -88,9 +95,39 @@ static id create_menu_item(id title, const char *action, const char *key) {
}
static void webview_window_will_close(id self, SEL cmd, id notification) {
struct cocoa_webview *w =
struct cocoa_webview *wv =
(struct cocoa_webview *)objc_getAssociatedObject(self, "webview");
webview_terminate(w);
wv->priv.should_exit = 1;
/***
Since by default for `webview_loop` is set to be blocking
we need to somehow signal the application that our
state has changed. The activity in the `invoke_handler` does
not interact with the `webview_loop` at all. This means that
the `exit` wouldn't be recognized by the application until
another event occurs like mouse movement or a key press.
To enable the invoke_handler to notify the application
correctly we need to send a custom event to the application.
We are going to first create an event with the type
NSApplicationDefined, and zero for all the other properties.
***/
id event = objc_msgSend((id)objc_getClass("NSEvent"),
sel_registerName("otherEventWithType:location:modifierFlags:timestamp:windowNumber:context:subtype:data1:data2:"),
NSApplicationDefinedEvent,
(id)objc_getClass("NSZeroPoint"),
0, 0.0, 0, NULL, 0, 0, 0);
id app = objc_msgSend((id)objc_getClass("NSApplication"),
sel_registerName("sharedApplication"));
/***
With a custom event crated and a pointer to the sharedApplication
we can now send the event. We need to make sure it get's queued as
early as possible, so we will set the argument atStart to
the NSDate distantPast constructor. This will trigger a noop
event on the application allowing the `webview_loop` to continue
its current iteration.
***/
objc_msgSend(app, sel_registerName("postEvent:atStart:"), event,
objc_msgSend((id)objc_getClass("NSDate"),
sel_registerName("distantPast")));
}
static void webview_external_invoke(id self, SEL cmd, id contentController,
@@ -211,13 +248,17 @@ WEBVIEW_API int webview_init(webview_t w) {
objc_msgSend((id)objc_getClass("NSApplication"),
sel_registerName("sharedApplication"));
Class __WKScriptMessageHandler = objc_allocateClassPair(
static Class __WKScriptMessageHandler;
if(__WKScriptMessageHandler == NULL) {
__WKScriptMessageHandler = objc_allocateClassPair(
objc_getClass("NSObject"), "__WKScriptMessageHandler", 0);
class_addMethod(
__WKScriptMessageHandler,
sel_registerName("userContentController:didReceiveScriptMessage:"),
(IMP)webview_external_invoke, "v@:@@");
objc_registerClassPair(__WKScriptMessageHandler);
class_addProtocol(__WKScriptMessageHandler, objc_getProtocol("WKScriptMessageHandler"));
class_addMethod(
__WKScriptMessageHandler,
sel_registerName("userContentController:didReceiveScriptMessage:"),
(IMP)webview_external_invoke, "v@:@@");
objc_registerClassPair(__WKScriptMessageHandler);
}
id scriptMessageHandler =
objc_msgSend((id)__WKScriptMessageHandler, sel_registerName("new"));
@@ -231,27 +272,36 @@ WEBVIEW_API int webview_init(webview_t w) {
https://github.com/WebKit/webkit/blob/master/Tools/TestWebKitAPI/Tests/WebKitCocoa/Download.mm
***/
Class __WKDownloadDelegate = objc_allocateClassPair(
static Class __WKDownloadDelegate;
if(__WKDownloadDelegate == NULL) {
__WKDownloadDelegate = objc_allocateClassPair(
objc_getClass("NSObject"), "__WKDownloadDelegate", 0);
class_addMethod(
__WKDownloadDelegate,
sel_registerName("_download:decideDestinationWithSuggestedFilename:"
"completionHandler:"),
(IMP)run_save_panel, "v@:@@?");
class_addMethod(__WKDownloadDelegate,
sel_registerName("_download:didFailWithError:"),
(IMP)download_failed, "v@:@@");
objc_registerClassPair(__WKDownloadDelegate);
class_addProtocol(__WKDownloadDelegate, objc_getProtocol("WKDownloadDelegate"));
class_addMethod(
__WKDownloadDelegate,
sel_registerName("_download:decideDestinationWithSuggestedFilename:"
"completionHandler:"),
(IMP)run_save_panel, "v@:@@?");
class_addMethod(__WKDownloadDelegate,
sel_registerName("_download:didFailWithError:"),
(IMP)download_failed, "v@:@@");
objc_registerClassPair(__WKDownloadDelegate);
}
id downloadDelegate =
objc_msgSend((id)__WKDownloadDelegate, sel_registerName("new"));
Class __WKPreferences = objc_allocateClassPair(objc_getClass("WKPreferences"),
static Class __WKPreferences;
if(__WKPreferences == NULL) {
__WKPreferences = objc_allocateClassPair(objc_getClass("WKPreferences"),
"__WKPreferences", 0);
objc_property_attribute_t type = {"T", "c"};
objc_property_attribute_t ownership = {"N", ""};
objc_property_attribute_t attrs[] = {type, ownership};
class_replaceProperty(__WKPreferences, "developerExtrasEnabled", attrs, 2);
objc_registerClassPair(__WKPreferences);
objc_property_attribute_t type = {"T", "c"};
objc_property_attribute_t ownership = {"N", ""};
objc_property_attribute_t attrs[] = {type, ownership};
class_replaceProperty(__WKPreferences, "developerExtrasEnabled", attrs, 2);
objc_registerClassPair(__WKPreferences);
}
id wkPref = objc_msgSend((id)__WKPreferences, sel_registerName("new"));
objc_msgSend(wkPref, sel_registerName("setValue:forKey:"),
objc_msgSend((id)objc_getClass("NSNumber"),
@@ -298,12 +348,15 @@ WEBVIEW_API int webview_init(webview_t w) {
userController);
objc_msgSend(config, sel_registerName("setPreferences:"), wkPref);
Class __NSWindowDelegate = objc_allocateClassPair(objc_getClass("NSObject"),
static Class __NSWindowDelegate;
if(__NSWindowDelegate == NULL) {
__NSWindowDelegate = objc_allocateClassPair(objc_getClass("NSObject"),
"__NSWindowDelegate", 0);
class_addProtocol(__NSWindowDelegate, objc_getProtocol("NSWindowDelegate"));
class_replaceMethod(__NSWindowDelegate, sel_registerName("windowWillClose:"),
(IMP)webview_window_will_close, "v@:@");
objc_registerClassPair(__NSWindowDelegate);
class_addProtocol(__NSWindowDelegate, objc_getProtocol("NSWindowDelegate"));
class_replaceMethod(__NSWindowDelegate, sel_registerName("windowWillClose:"),
(IMP)webview_window_will_close, "v@:@");
objc_registerClassPair(__NSWindowDelegate);
}
wv->priv.windowDelegate =
objc_msgSend((id)__NSWindowDelegate, sel_registerName("new"));
@@ -316,9 +369,13 @@ WEBVIEW_API int webview_init(webview_t w) {
sel_registerName("stringWithUTF8String:"), wv->title);
CGRect r = CGRectMake(0, 0, wv->width, wv->height);
unsigned int style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
NSWindowStyleMaskMiniaturizable;
unsigned int style;
if (wv->frameless) {
style = NSWindowStyleMaskBorderless;
} else {
style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
NSWindowStyleMaskMiniaturizable;
}
if (wv->resizable) {
style = style | NSWindowStyleMaskResizable;
}
@@ -335,35 +392,40 @@ WEBVIEW_API int webview_init(webview_t w) {
wv->priv.windowDelegate);
objc_msgSend(wv->priv.window, sel_registerName("center"));
Class __WKUIDelegate =
objc_allocateClassPair(objc_getClass("NSObject"), "__WKUIDelegate", 0);
class_addProtocol(__WKUIDelegate, objc_getProtocol("WKUIDelegate"));
class_addMethod(__WKUIDelegate,
sel_registerName("webView:runOpenPanelWithParameters:"
"initiatedByFrame:completionHandler:"),
(IMP)run_open_panel, "v@:@@@?");
class_addMethod(__WKUIDelegate,
sel_registerName("webView:runJavaScriptAlertPanelWithMessage:"
"initiatedByFrame:completionHandler:"),
(IMP)run_alert_panel, "v@:@@@?");
class_addMethod(
__WKUIDelegate,
sel_registerName("webView:runJavaScriptConfirmPanelWithMessage:"
"initiatedByFrame:completionHandler:"),
(IMP)run_confirmation_panel, "v@:@@@?");
objc_registerClassPair(__WKUIDelegate);
static Class __WKUIDelegate;
if(__WKUIDelegate == NULL) {
__WKUIDelegate = objc_allocateClassPair(objc_getClass("NSObject"), "__WKUIDelegate", 0);
class_addProtocol(__WKUIDelegate, objc_getProtocol("WKUIDelegate"));
class_addMethod(__WKUIDelegate,
sel_registerName("webView:runOpenPanelWithParameters:"
"initiatedByFrame:completionHandler:"),
(IMP)run_open_panel, "v@:@@@?");
class_addMethod(__WKUIDelegate,
sel_registerName("webView:runJavaScriptAlertPanelWithMessage:"
"initiatedByFrame:completionHandler:"),
(IMP)run_alert_panel, "v@:@@@?");
class_addMethod(
__WKUIDelegate,
sel_registerName("webView:runJavaScriptConfirmPanelWithMessage:"
"initiatedByFrame:completionHandler:"),
(IMP)run_confirmation_panel, "v@:@@@?");
objc_registerClassPair(__WKUIDelegate);
}
id uiDel = objc_msgSend((id)__WKUIDelegate, sel_registerName("new"));
Class __WKNavigationDelegate = objc_allocateClassPair(
static Class __WKNavigationDelegate;
if(__WKNavigationDelegate == NULL) {
__WKNavigationDelegate = objc_allocateClassPair(
objc_getClass("NSObject"), "__WKNavigationDelegate", 0);
class_addProtocol(__WKNavigationDelegate,
objc_getProtocol("WKNavigationDelegate"));
class_addMethod(
__WKNavigationDelegate,
sel_registerName(
"webView:decidePolicyForNavigationResponse:decisionHandler:"),
(IMP)make_nav_policy_decision, "v@:@@?");
objc_registerClassPair(__WKNavigationDelegate);
class_addProtocol(__WKNavigationDelegate,
objc_getProtocol("WKNavigationDelegate"));
class_addMethod(
__WKNavigationDelegate,
sel_registerName(
"webView:decidePolicyForNavigationResponse:decisionHandler:"),
(IMP)make_nav_policy_decision, "v@:@@?");
objc_registerClassPair(__WKNavigationDelegate);
}
id navDel = objc_msgSend((id)__WKNavigationDelegate, sel_registerName("new"));
wv->priv.webview =
@@ -376,7 +438,7 @@ WEBVIEW_API int webview_init(webview_t w) {
id nsURL = objc_msgSend((id)objc_getClass("NSURL"),
sel_registerName("URLWithString:"),
get_nsstring(webview_check_url(wv->url)));
get_nsstring(wv->url == NULL ? "" : wv->url));
objc_msgSend(wv->priv.webview, sel_registerName("loadRequest:"),
objc_msgSend((id)objc_getClass("NSURLRequest"),
@@ -463,10 +525,10 @@ WEBVIEW_API int webview_loop(webview_t w, int blocking) {
sel_registerName("distantFuture"))
: objc_msgSend((id)objc_getClass("NSDate"),
sel_registerName("distantPast")));
id app = objc_msgSend((id)objc_getClass("NSApplication"),
sel_registerName("sharedApplication"));
id event = objc_msgSend(
objc_msgSend((id)objc_getClass("NSApplication"),
sel_registerName("sharedApplication")),
app,
sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"),
ULONG_MAX, until,
objc_msgSend((id)objc_getClass("NSString"),
@@ -479,7 +541,6 @@ WEBVIEW_API int webview_loop(webview_t w, int blocking) {
sel_registerName("sharedApplication")),
sel_registerName("sendEvent:"), event);
}
return wv->priv.should_exit;
}
@@ -537,87 +598,6 @@ WEBVIEW_API void webview_set_color(webview_t w, uint8_t r, uint8_t g,
objc_msgSend(wv->priv.window,
sel_registerName("setTitlebarAppearsTransparent:"), 1);
}
WEBVIEW_API void webview_dialog(webview_t w,
enum webview_dialog_type dlgtype, int flags,
const char *title, const char *arg,
char *result, size_t resultsz) {
struct cocoa_webview* wv = (struct cocoa_webview*)w;
if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN ||
dlgtype == WEBVIEW_DIALOG_TYPE_SAVE) {
id panel = (id)objc_getClass("NSSavePanel");
if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN) {
id openPanel = objc_msgSend((id)objc_getClass("NSOpenPanel"),
sel_registerName("openPanel"));
if (flags & WEBVIEW_DIALOG_FLAG_DIRECTORY) {
objc_msgSend(openPanel, sel_registerName("setCanChooseFiles:"), 0);
objc_msgSend(openPanel, sel_registerName("setCanChooseDirectories:"),
1);
} else {
objc_msgSend(openPanel, sel_registerName("setCanChooseFiles:"), 1);
objc_msgSend(openPanel, sel_registerName("setCanChooseDirectories:"),
0);
}
objc_msgSend(openPanel, sel_registerName("setResolvesAliases:"), 0);
objc_msgSend(openPanel, sel_registerName("setAllowsMultipleSelection:"),
0);
panel = openPanel;
} else {
panel = objc_msgSend((id)objc_getClass("NSSavePanel"),
sel_registerName("savePanel"));
}
objc_msgSend(panel, sel_registerName("setCanCreateDirectories:"), 1);
objc_msgSend(panel, sel_registerName("setShowsHiddenFiles:"), 1);
objc_msgSend(panel, sel_registerName("setExtensionHidden:"), 0);
objc_msgSend(panel, sel_registerName("setCanSelectHiddenExtension:"), 0);
objc_msgSend(panel, sel_registerName("setTreatsFilePackagesAsDirectories:"),
1);
objc_msgSend(
panel, sel_registerName("beginSheetModalForWindow:completionHandler:"),
wv->priv.window, ^(id result) {
objc_msgSend(objc_msgSend((id)objc_getClass("NSApplication"),
sel_registerName("sharedApplication")),
sel_registerName("stopModalWithCode:"), result);
});
if (objc_msgSend(objc_msgSend((id)objc_getClass("NSApplication"),
sel_registerName("sharedApplication")),
sel_registerName("runModalForWindow:"),
panel) == (id)NSModalResponseOK) {
id url = objc_msgSend(panel, sel_registerName("URL"));
id path = objc_msgSend(url, sel_registerName("path"));
const char *filename =
(const char *)objc_msgSend(path, sel_registerName("UTF8String"));
strlcpy(result, filename, resultsz);
}
} else if (dlgtype == WEBVIEW_DIALOG_TYPE_ALERT) {
id a = objc_msgSend((id)objc_getClass("NSAlert"), sel_registerName("new"));
switch (flags & WEBVIEW_DIALOG_FLAG_ALERT_MASK) {
case WEBVIEW_DIALOG_FLAG_INFO:
objc_msgSend(a, sel_registerName("setAlertStyle:"),
NSAlertStyleInformational);
break;
case WEBVIEW_DIALOG_FLAG_WARNING:
printf("Warning\n");
objc_msgSend(a, sel_registerName("setAlertStyle:"), NSAlertStyleWarning);
break;
case WEBVIEW_DIALOG_FLAG_ERROR:
printf("Error\n");
objc_msgSend(a, sel_registerName("setAlertStyle:"), NSAlertStyleCritical);
break;
}
objc_msgSend(a, sel_registerName("setShowsHelp:"), 0);
objc_msgSend(a, sel_registerName("setShowsSuppressionButton:"), 0);
objc_msgSend(a, sel_registerName("setMessageText:"), get_nsstring(title));
objc_msgSend(a, sel_registerName("setInformativeText:"), get_nsstring(arg));
objc_msgSend(a, sel_registerName("addButtonWithTitle:"),
get_nsstring("OK"));
objc_msgSend(a, sel_registerName("runModal"));
objc_msgSend(a, sel_registerName("release"));
}
}
static void webview_dispatch_cb(void *arg) {
struct webview_dispatch_arg *context = (struct webview_dispatch_arg *)arg;
(context->fn)(context->w, context->arg);
@@ -635,16 +615,10 @@ WEBVIEW_API void webview_dispatch(webview_t w, webview_dispatch_fn fn,
dispatch_async_f(dispatch_get_main_queue(), context, webview_dispatch_cb);
}
WEBVIEW_API void webview_terminate(webview_t w) {
struct cocoa_webview* wv = (struct cocoa_webview*)w;
wv->priv.should_exit = 1;
}
WEBVIEW_API void webview_exit(webview_t w) {
struct cocoa_webview* wv = (struct cocoa_webview*)w;
id app = objc_msgSend((id)objc_getClass("NSApplication"),
sel_registerName("sharedApplication"));
objc_msgSend(app, sel_registerName("terminate:"), app);
wv->external_invoke_cb = NULL;
objc_msgSend(wv->priv.window, sel_registerName("close"));
}
WEBVIEW_API void webview_print_log(const char *s) { printf("%s\n", s); }
WEBVIEW_API void webview_print_log(const char *s) { printf("%s\n", s); }

View File

@@ -41,9 +41,7 @@ inline std::string url_decode(const char *s)
size_t length = strlen(s);
for (unsigned int i = 0; i < length; i++) {
if (s[i] == '%') {
int n;
sscanf(s + i + 1, "%2x", &n);
decoded.push_back(static_cast<char>(n));
decoded.push_back(hex2char(s + i + 1));
i = i + 2;
} else if (s[i] == '+') {
decoded.push_back(' ');
@@ -66,33 +64,101 @@ inline std::string html_from_uri(const char *s)
LRESULT CALLBACK WebviewWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
class browser_window {
private:
bool EnableDpiAwareness() {
auto lib_user32 = GetModuleHandleW(L"user32.dll");
if(lib_user32) {
auto fn_set_thread_dpi_awareness_context =
reinterpret_cast<decltype(&SetThreadDpiAwarenessContext)>(
GetProcAddress(lib_user32, "SetThreadDpiAwarenessContext")
);
if (
fn_set_thread_dpi_awareness_context
&& fn_set_thread_dpi_awareness_context(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
) {
return true;
}
}
// Don't worry when SetThreadDpiAwarenessContext is not available. If
// it's not available, we are not on a recent windows 10, and we don't
// have edge...
return false;
}
int MyGetDpiForWindow(HWND hWnd) {
auto lib_user32 = GetModuleHandleW(L"user32.dll");
if (lib_user32) {
auto fn_get_dpi_for_window = reinterpret_cast<decltype(&GetDpiForWindow)>(
GetProcAddress(lib_user32, "GetDpiForWindow")
);
if (fn_get_dpi_for_window) {
return fn_get_dpi_for_window(hWnd);
}
}
// Again, don't worry when GetDpiForWindow is not available. If we are
// not on Windows 10, we don't have edge...
return 96;
}
public:
browser_window(msg_cb_t cb, const char* title, int width, int height, bool resizable)
browser_window(msg_cb_t cb, const char* title, int width, int height, bool resizable, bool frameless)
: m_cb(cb)
{
HINSTANCE hInstance = GetModuleHandle(nullptr);
HICON winresIcon = (HICON)LoadImage(
hInstance,
(LPWSTR)(1),
IMAGE_ICON,
0,
0,
LR_DEFAULTSIZE
);
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.hInstance = hInstance;
wc.lpfnWndProc = WebviewWndProc;
wc.lpszClassName = L"webview";
wc.hIcon = winresIcon;
RegisterClassEx(&wc);
EnableDpiAwareness();
DWORD style = WS_OVERLAPPEDWINDOW;
if (!resizable) {
style = WS_OVERLAPPED | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU;
style &= ~(WS_SIZEBOX);
}
if (frameless) {
style &= ~(WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
}
// Create window first, because we need the window to get DPI for the window.
BSTR window_title = webview_to_bstr(title);
m_window = CreateWindowEx(0, L"webview", window_title, style, 0, 0, 0, 0,
HWND_DESKTOP, NULL, hInstance, (void *)this);
SysFreeString(window_title);
// Have to call this before SetWindowPos or it will crash!
SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this);
if (frameless)
{
SetWindowLongPtr(m_window, GWL_STYLE, style);
}
this->saved_style = style;
UINT dpi = MyGetDpiForWindow(m_window);
if (dpi == 0) {
dpi = 96;
}
RECT clientRect;
RECT rect;
rect.left = 0;
rect.top = 0;
rect.right = width;
rect.bottom = height;
rect.right = MulDiv(width, dpi, 96);
rect.bottom = MulDiv(height, dpi, 96);
AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, 0);
GetClientRect(GetDesktopWindow(), &clientRect);
int left = (clientRect.right / 2) - ((rect.right - rect.left) / 2);
int top = (clientRect.bottom / 2) - ((rect.bottom - rect.top) / 2);
@@ -101,15 +167,10 @@ public:
rect.bottom = rect.bottom - rect.top + top;
rect.top = top;
BSTR window_title = webview_to_bstr(title);
m_window = CreateWindowEx(0, L"webview", window_title, style, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
HWND_DESKTOP, NULL, hInstance, (void *)this);
SysFreeString(window_title);
// Set position, size and show window *atomically*.
SetWindowPos(m_window, HWND_TOP, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top, SWP_SHOWWINDOW);
SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this);
ShowWindow(m_window, SW_SHOW);
UpdateWindow(m_window);
SetFocus(m_window);
}
@@ -146,15 +207,16 @@ public:
return 0;
}
void terminate() { PostQuitMessage(0); }
void exit() { PostQuitMessage(0); }
void dispatch(dispatch_fn_t f)
{
PostThreadMessage(m_main_thread, WM_APP, 0, (LPARAM) new dispatch_fn_t(f));
}
void set_title(const char* title)
{
BSTR window_title = webview_to_bstr(title);
SetWindowText(m_window, window_title);
SysFreeString(window_title);
@@ -162,6 +224,7 @@ public:
void set_size(int width, int height)
{
RECT r;
r.left = 50;
r.top = 50;
@@ -174,6 +237,7 @@ public:
void set_fullscreen(bool fullscreen)
{
if (this->is_fullscreen == fullscreen) {
return;
}
@@ -215,6 +279,7 @@ public:
void set_color(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
HBRUSH brush = CreateSolidBrush(RGB(r, g, b));
SetClassLongPtr(this->m_window, GCLP_HBRBACKGROUND, (LONG_PTR)brush);
}
@@ -238,11 +303,20 @@ LRESULT CALLBACK WebviewWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
case WM_SIZE:
w->resize();
break;
case WM_DPICHANGED: {
auto rect = reinterpret_cast<LPRECT>(lp);
auto x = rect->left;
auto y = rect->top;
auto w = rect->right - x;
auto h = rect->bottom - y;
SetWindowPos(hwnd, nullptr, x, y, w, h, SWP_NOZORDER | SWP_NOACTIVATE);
break;
}
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
w->terminate();
w->exit();
break;
default:
return DefWindowProc(hwnd, msg, wp, lp);
@@ -257,8 +331,8 @@ using namespace Windows::Web::UI::Interop;
class webview : public browser_window {
public:
webview(webview_external_invoke_cb_t invoke_cb, const char* title, int width, int height, bool resizable, bool debug)
: browser_window(std::bind(&webview::on_message, this, std::placeholders::_1), title, width, height, resizable)
webview(webview_external_invoke_cb_t invoke_cb, const char* title, int width, int height, bool resizable, bool debug, bool frameless)
: browser_window(std::bind(&webview::on_message, this, std::placeholders::_1), title, width, height, resizable, frameless)
, invoke_cb(invoke_cb)
{
init_apartment(winrt::apartment_type::single_threaded);
@@ -292,6 +366,7 @@ public:
void navigate(const char* url)
{
std::string html = html_from_uri(url);
if (html != "") {
m_webview.NavigateToString(winrt::to_hstring(html.c_str()));
@@ -302,18 +377,19 @@ public:
}
void init(const char* js)
{
init_js.append("(function(){")
.append(js)
.append("})();");
}
void eval(const char* js)
{
m_webview.InvokeScriptAsync(
L"eval", single_threaded_vector<hstring>({ winrt::to_hstring(js) }));
}
void terminate() {
browser_window::terminate();
void exit() {
m_webview.Close();
}
@@ -324,6 +400,7 @@ public:
private:
void on_message(const char* msg)
{
this->invoke_cb(this, msg);
}
@@ -355,103 +432,6 @@ static inline char *webview_from_utf16(WCHAR *ws) {
return s;
}
#include <shobjidl.h>
#ifdef __cplusplus
#define iid_ref(x) &(x)
#define iid_unref(x) *(x)
#else
#define iid_ref(x) (x)
#define iid_unref(x) (x)
#endif
/* These are missing parts from MinGW */
#ifndef __IFileDialog_INTERFACE_DEFINED__
#define __IFileDialog_INTERFACE_DEFINED__
enum _FILEOPENDIALOGOPTIONS {
FOS_OVERWRITEPROMPT = 0x2,
FOS_STRICTFILETYPES = 0x4,
FOS_NOCHANGEDIR = 0x8,
FOS_PICKFOLDERS = 0x20,
FOS_FORCEFILESYSTEM = 0x40,
FOS_ALLNONSTORAGEITEMS = 0x80,
FOS_NOVALIDATE = 0x100,
FOS_ALLOWMULTISELECT = 0x200,
FOS_PATHMUSTEXIST = 0x800,
FOS_FILEMUSTEXIST = 0x1000,
FOS_CREATEPROMPT = 0x2000,
FOS_SHAREAWARE = 0x4000,
FOS_NOREADONLYRETURN = 0x8000,
FOS_NOTESTFILECREATE = 0x10000,
FOS_HIDEMRUPLACES = 0x20000,
FOS_HIDEPINNEDPLACES = 0x40000,
FOS_NODEREFERENCELINKS = 0x100000,
FOS_DONTADDTORECENT = 0x2000000,
FOS_FORCESHOWHIDDEN = 0x10000000,
FOS_DEFAULTNOMINIMODE = 0x20000000,
FOS_FORCEPREVIEWPANEON = 0x40000000
};
typedef DWORD FILEOPENDIALOGOPTIONS;
typedef enum FDAP { FDAP_BOTTOM = 0, FDAP_TOP = 1 } FDAP;
DEFINE_GUID(IID_IFileDialog, 0x42f85136, 0xdb7e, 0x439c, 0x85, 0xf1, 0xe4, 0x07,
0x5d, 0x13, 0x5f, 0xc8);
typedef struct IFileDialogVtbl {
BEGIN_INTERFACE
HRESULT(STDMETHODCALLTYPE *QueryInterface)
(IFileDialog *This, REFIID riid, void **ppvObject);
ULONG(STDMETHODCALLTYPE *AddRef)(IFileDialog *This);
ULONG(STDMETHODCALLTYPE *Release)(IFileDialog *This);
HRESULT(STDMETHODCALLTYPE *Show)(IFileDialog *This, HWND hwndOwner);
HRESULT(STDMETHODCALLTYPE *SetFileTypes)
(IFileDialog *This, UINT cFileTypes, const COMDLG_FILTERSPEC *rgFilterSpec);
HRESULT(STDMETHODCALLTYPE *SetFileTypeIndex)
(IFileDialog *This, UINT iFileType);
HRESULT(STDMETHODCALLTYPE *GetFileTypeIndex)
(IFileDialog *This, UINT *piFileType);
HRESULT(STDMETHODCALLTYPE *Advise)
(IFileDialog *This, IFileDialogEvents *pfde, DWORD *pdwCookie);
HRESULT(STDMETHODCALLTYPE *Unadvise)(IFileDialog *This, DWORD dwCookie);
HRESULT(STDMETHODCALLTYPE *SetOptions)
(IFileDialog *This, FILEOPENDIALOGOPTIONS fos);
HRESULT(STDMETHODCALLTYPE *GetOptions)
(IFileDialog *This, FILEOPENDIALOGOPTIONS *pfos);
HRESULT(STDMETHODCALLTYPE *SetDefaultFolder)
(IFileDialog *This, IShellItem *psi);
HRESULT(STDMETHODCALLTYPE *SetFolder)(IFileDialog *This, IShellItem *psi);
HRESULT(STDMETHODCALLTYPE *GetFolder)(IFileDialog *This, IShellItem **ppsi);
HRESULT(STDMETHODCALLTYPE *GetCurrentSelection)
(IFileDialog *This, IShellItem **ppsi);
HRESULT(STDMETHODCALLTYPE *SetFileName)(IFileDialog *This, LPCWSTR pszName);
HRESULT(STDMETHODCALLTYPE *GetFileName)(IFileDialog *This, LPWSTR *pszName);
HRESULT(STDMETHODCALLTYPE *SetTitle)(IFileDialog *This, LPCWSTR pszTitle);
HRESULT(STDMETHODCALLTYPE *SetOkButtonLabel)
(IFileDialog *This, LPCWSTR pszText);
HRESULT(STDMETHODCALLTYPE *SetFileNameLabel)
(IFileDialog *This, LPCWSTR pszLabel);
HRESULT(STDMETHODCALLTYPE *GetResult)(IFileDialog *This, IShellItem **ppsi);
HRESULT(STDMETHODCALLTYPE *AddPlace)
(IFileDialog *This, IShellItem *psi, FDAP fdap);
HRESULT(STDMETHODCALLTYPE *SetDefaultExtension)
(IFileDialog *This, LPCWSTR pszDefaultExtension);
HRESULT(STDMETHODCALLTYPE *Close)(IFileDialog *This, HRESULT hr);
HRESULT(STDMETHODCALLTYPE *SetClientGuid)(IFileDialog *This, REFGUID guid);
HRESULT(STDMETHODCALLTYPE *ClearClientData)(IFileDialog *This);
HRESULT(STDMETHODCALLTYPE *SetFilter)
(IFileDialog *This, IShellItemFilter *pFilter);
END_INTERFACE
} IFileDialogVtbl;
interface IFileDialog {
CONST_VTBL IFileDialogVtbl *lpVtbl;
};
DEFINE_GUID(IID_IFileOpenDialog, 0xd57c7288, 0xd4ad, 0x4768, 0xbe, 0x02, 0x9d,
0x96, 0x95, 0x32, 0xd9, 0x60);
DEFINE_GUID(IID_IFileSaveDialog, 0x84bccd23, 0x5fde, 0x4cdb, 0xae, 0xa4, 0xaf,
0x64, 0xb8, 0x3d, 0x78, 0xab);
#endif
// HERE
WEBVIEW_API void webview_run(webview_t w)
{
static_cast<webview::webview*>(w)->run();
@@ -484,114 +464,16 @@ WEBVIEW_API void webview_set_color(webview_t w, uint8_t r, uint8_t g,
static_cast<webview::webview*>(w)->set_color(r, g, b, a);
}
WEBVIEW_API void webview_dialog(webview_t w,
enum webview_dialog_type dlgtype, int flags,
const char *title, const char *arg,
char *result, size_t resultsz) {
HWND hwnd = static_cast<webview::webview*>(w)->m_window;
if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN ||
dlgtype == WEBVIEW_DIALOG_TYPE_SAVE) {
IFileDialog *dlg = NULL;
IShellItem *res = NULL;
WCHAR *ws = NULL;
char *s = NULL;
FILEOPENDIALOGOPTIONS opts = 0, add_opts = 0;
if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN) {
if (CoCreateInstance(
iid_unref(&CLSID_FileOpenDialog), NULL, CLSCTX_INPROC_SERVER,
iid_unref(&IID_IFileOpenDialog), (void **)&dlg) != S_OK) {
goto error_dlg;
}
if (flags & WEBVIEW_DIALOG_FLAG_DIRECTORY) {
add_opts |= FOS_PICKFOLDERS;
}
add_opts |= FOS_NOCHANGEDIR | FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE |
FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_SHAREAWARE |
FOS_NOTESTFILECREATE | FOS_NODEREFERENCELINKS |
FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE;
} else {
if (CoCreateInstance(
iid_unref(&CLSID_FileSaveDialog), NULL, CLSCTX_INPROC_SERVER,
iid_unref(&IID_IFileSaveDialog), (void **)&dlg) != S_OK) {
goto error_dlg;
}
add_opts |= FOS_OVERWRITEPROMPT | FOS_NOCHANGEDIR |
FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE | FOS_SHAREAWARE |
FOS_NOTESTFILECREATE | FOS_NODEREFERENCELINKS |
FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE;
}
if (dlg->GetOptions(&opts) != S_OK) {
goto error_dlg;
}
opts &= ~FOS_NOREADONLYRETURN;
opts |= add_opts;
if (dlg->SetOptions(opts) != S_OK) {
goto error_dlg;
}
if (dlg->Show(hwnd) != S_OK) {
goto error_dlg;
}
if (dlg->GetResult(&res) != S_OK) {
goto error_dlg;
}
if (res->GetDisplayName(SIGDN_FILESYSPATH, &ws) != S_OK) {
goto error_result;
}
s = webview_from_utf16(ws);
CoTaskMemFree(ws);
if (!s) goto error_result;
strncpy(result, s, resultsz);
GlobalFree(s);
result[resultsz - 1] = '\0';
error_result:
res->Release();
error_dlg:
dlg->Release();
return;
} else if (dlgtype == WEBVIEW_DIALOG_TYPE_ALERT) {
#if 0
/* MinGW often doesn't contain TaskDialog, we'll use MessageBox for now */
WCHAR *wtitle = webview_to_utf16(title);
WCHAR *warg = webview_to_utf16(arg);
TaskDialog(hwnd, NULL, NULL, wtitle, warg, 0, NULL, NULL);
GlobalFree(warg);
GlobalFree(wtitle);
#else
UINT type = MB_OK;
switch (flags & WEBVIEW_DIALOG_FLAG_ALERT_MASK) {
case WEBVIEW_DIALOG_FLAG_INFO:
type |= MB_ICONINFORMATION;
break;
case WEBVIEW_DIALOG_FLAG_WARNING:
type |= MB_ICONWARNING;
break;
case WEBVIEW_DIALOG_FLAG_ERROR:
type |= MB_ICONERROR;
break;
}
BSTR box_title = webview_to_bstr(title);
BSTR box_text = webview_to_bstr(arg);
MessageBox(hwnd, box_text, box_title, type);
SysFreeString(box_title);
SysFreeString(box_text);
#endif
}
}
WEBVIEW_API void webview_dispatch(webview_t w, webview_dispatch_fn fn,
void *arg)
{
static_cast<webview::webview*>(w)->dispatch([=]() { fn(w, arg); });
}
WEBVIEW_API void webview_terminate(webview_t w)
{
static_cast<webview::webview*>(w)->terminate();
}
WEBVIEW_API void webview_exit(webview_t w)
{
webview_terminate(w);
webview::webview* wv = static_cast<webview::webview*>(w);
DestroyWindow(wv->m_window);
}
WEBVIEW_API void webview_debug(const char *format, ...)
@@ -609,9 +491,11 @@ WEBVIEW_API void* webview_get_user_data(webview_t w)
return static_cast<webview::webview*>(w)->get_user_data();
}
WEBVIEW_API webview_t webview_new(const char* title, const char* url, int width, int height, int resizable, int debug, webview_external_invoke_cb_t external_invoke_cb, void* userdata)
WEBVIEW_API webview_t webview_new(
const char* title, const char* url, int width, int height, int resizable, int debug,
int frameless, webview_external_invoke_cb_t external_invoke_cb, void* userdata)
{
auto w = new webview::webview(external_invoke_cb, title, width, height, resizable, debug);
auto w = new webview::webview(external_invoke_cb, title, width, height, resizable, debug, frameless);
w->set_user_data(userdata);
w->navigate(url);
return w;

View File

@@ -1,309 +0,0 @@
#include "webview.h"
#include <JavaScriptCore/JavaScript.h>
#include <gtk/gtk.h>
#include <webkit2/webkit2.h>
struct webview_priv {
GtkWidget *window;
GtkWidget *scroller;
GtkWidget *webview;
GtkWidget *inspector_window;
GAsyncQueue *queue;
int ready;
int js_busy;
int should_exit;
};
struct gtk_webview {
const char *url;
const char *title;
int width;
int height;
int resizable;
int debug;
webview_external_invoke_cb_t external_invoke_cb;
struct webview_priv priv;
void *userdata;
};
WEBVIEW_API void webview_free(webview_t w) {
free(w);
}
WEBVIEW_API void* webview_get_user_data(webview_t w) {
struct gtk_webview *wv = (struct webview *)w;
return wv->userdata;
}
WEBVIEW_API webview_t webview_new(const char* title, const char* url, int width, int height, int resizable, int debug, webview_external_invoke_cb_t external_invoke_cb, void* userdata) {
struct gtk_webview* w = (struct gtk_webview*)calloc(1, sizeof(*w));
w->width = width;
w->height = height;
w->title = title;
w->url = url;
w->resizable = resizable;
w->debug = debug;
w->external_invoke_cb = external_invoke_cb;
w->userdata = userdata;
if (webview_init(w) != 0) {
webview_free(w);
return NULL;
}
return w;
}
static void external_message_received_cb(WebKitUserContentManager *m,
WebKitJavascriptResult *r,
gpointer arg) {
(void)m;
struct gtk_webview *w = (struct gtk_webview *)arg;
if (w->external_invoke_cb == NULL) {
return;
}
JSGlobalContextRef context = webkit_javascript_result_get_global_context(r);
JSValueRef value = webkit_javascript_result_get_value(r);
JSStringRef js = JSValueToStringCopy(context, value, NULL);
size_t n = JSStringGetMaximumUTF8CStringSize(js);
char *s = g_new(char, n);
JSStringGetUTF8CString(js, s, n);
w->external_invoke_cb(w, s);
JSStringRelease(js);
g_free(s);
}
static void webview_load_changed_cb(WebKitWebView *webview,
WebKitLoadEvent event, gpointer arg) {
(void)webview;
struct gtk_webview *w = (struct gtk_webview *)arg;
if (event == WEBKIT_LOAD_FINISHED) {
w->priv.ready = 1;
}
}
static void webview_destroy_cb(GtkWidget *widget, gpointer arg) {
(void)widget;
webview_terminate((webview_t)arg);
}
static gboolean webview_context_menu_cb(WebKitWebView *webview,
GtkWidget *default_menu,
WebKitHitTestResult *hit_test_result,
gboolean triggered_with_keyboard,
gpointer userdata) {
(void)webview;
(void)default_menu;
(void)hit_test_result;
(void)triggered_with_keyboard;
(void)userdata;
return TRUE;
}
int webview_init(struct gtk_webview *w) {
if (gtk_init_check(0, NULL) == FALSE) {
return -1;
}
w->priv.ready = 0;
w->priv.should_exit = 0;
w->priv.queue = g_async_queue_new();
w->priv.window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(w->priv.window), w->title);
if (w->resizable) {
gtk_window_set_default_size(GTK_WINDOW(w->priv.window), w->width,
w->height);
} else {
gtk_widget_set_size_request(w->priv.window, w->width, w->height);
}
gtk_window_set_resizable(GTK_WINDOW(w->priv.window), !!w->resizable);
gtk_window_set_position(GTK_WINDOW(w->priv.window), GTK_WIN_POS_CENTER);
w->priv.scroller = gtk_scrolled_window_new(NULL, NULL);
gtk_container_add(GTK_CONTAINER(w->priv.window), w->priv.scroller);
WebKitUserContentManager *m = webkit_user_content_manager_new();
webkit_user_content_manager_register_script_message_handler(m, "external");
g_signal_connect(m, "script-message-received::external",
G_CALLBACK(external_message_received_cb), w);
w->priv.webview = webkit_web_view_new_with_user_content_manager(m);
webkit_web_view_load_uri(WEBKIT_WEB_VIEW(w->priv.webview),
webview_check_url(w->url));
g_signal_connect(G_OBJECT(w->priv.webview), "load-changed",
G_CALLBACK(webview_load_changed_cb), w);
gtk_container_add(GTK_CONTAINER(w->priv.scroller), w->priv.webview);
WebKitSettings *settings =
webkit_web_view_get_settings(WEBKIT_WEB_VIEW(w->priv.webview));
// Enable webgl and canvas features.
webkit_settings_set_enable_webgl(settings, true);
webkit_settings_set_enable_accelerated_2d_canvas(settings, true);
if (w->debug) {
WebKitSettings *settings =
webkit_web_view_get_settings(WEBKIT_WEB_VIEW(w->priv.webview));
webkit_settings_set_enable_write_console_messages_to_stdout(settings, true);
webkit_settings_set_enable_developer_extras(settings, true);
} else {
g_signal_connect(G_OBJECT(w->priv.webview), "context-menu",
G_CALLBACK(webview_context_menu_cb), w);
}
gtk_widget_show_all(w->priv.window);
webkit_web_view_run_javascript(
WEBKIT_WEB_VIEW(w->priv.webview),
"window.external={invoke:function(x){"
"window.webkit.messageHandlers.external.postMessage(x);}}",
NULL, NULL, NULL);
g_signal_connect(G_OBJECT(w->priv.window), "destroy",
G_CALLBACK(webview_destroy_cb), w);
return 0;
}
WEBVIEW_API int webview_loop(webview_t w, int blocking) {
gtk_main_iteration_do(blocking);
return ((struct gtk_webview*)w)->priv.should_exit;
}
WEBVIEW_API void webview_set_title(webview_t w, const char *title) {
struct gtk_webview *wv = (struct webview *)w;
gtk_window_set_title(GTK_WINDOW(wv->priv.window), title);
}
WEBVIEW_API void webview_set_fullscreen(webview_t w, int fullscreen) {
struct gtk_webview *wv = (struct webview *)w;
if (fullscreen) {
gtk_window_fullscreen(GTK_WINDOW(wv->priv.window));
} else {
gtk_window_unfullscreen(GTK_WINDOW(wv->priv.window));
}
}
WEBVIEW_API void webview_set_color(webview_t w, uint8_t r, uint8_t g,
uint8_t b, uint8_t a) {
struct gtk_webview *wv = (struct webview *)w;
GdkRGBA color = {r / 255.0, g / 255.0, b / 255.0, a / 255.0};
webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(wv->priv.webview),
&color);
}
WEBVIEW_API void webview_dialog(webview_t w,
enum webview_dialog_type dlgtype, int flags,
const char *title, const char *arg,
char *result, size_t resultsz) {
struct gtk_webview *wv = (struct webview *)w;
GtkWidget *dlg;
if (result != NULL) {
result[0] = '\0';
}
if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN ||
dlgtype == WEBVIEW_DIALOG_TYPE_SAVE) {
dlg = gtk_file_chooser_dialog_new(
title, GTK_WINDOW(wv->priv.window),
(dlgtype == WEBVIEW_DIALOG_TYPE_OPEN
? (flags & WEBVIEW_DIALOG_FLAG_DIRECTORY
? GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
: GTK_FILE_CHOOSER_ACTION_OPEN)
: GTK_FILE_CHOOSER_ACTION_SAVE),
"_Cancel", GTK_RESPONSE_CANCEL,
(dlgtype == WEBVIEW_DIALOG_TYPE_OPEN ? "_Open" : "_Save"),
GTK_RESPONSE_ACCEPT, NULL);
gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dlg), FALSE);
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dlg), FALSE);
gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dlg), TRUE);
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dlg), TRUE);
gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER(dlg), TRUE);
gint response = gtk_dialog_run(GTK_DIALOG(dlg));
if (response == GTK_RESPONSE_ACCEPT) {
gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dlg));
g_strlcpy(result, filename, resultsz);
g_free(filename);
}
gtk_widget_destroy(dlg);
} else if (dlgtype == WEBVIEW_DIALOG_TYPE_ALERT) {
GtkMessageType type = GTK_MESSAGE_OTHER;
switch (flags & WEBVIEW_DIALOG_FLAG_ALERT_MASK) {
case WEBVIEW_DIALOG_FLAG_INFO:
type = GTK_MESSAGE_INFO;
break;
case WEBVIEW_DIALOG_FLAG_WARNING:
type = GTK_MESSAGE_WARNING;
break;
case WEBVIEW_DIALOG_FLAG_ERROR:
type = GTK_MESSAGE_ERROR;
break;
}
dlg = gtk_message_dialog_new(GTK_WINDOW(wv->priv.window), GTK_DIALOG_MODAL,
type, GTK_BUTTONS_OK, "%s", title);
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dlg), "%s",
arg);
gtk_dialog_run(GTK_DIALOG(dlg));
gtk_widget_destroy(dlg);
}
}
static void webview_eval_finished(GObject *object, GAsyncResult *result,
gpointer userdata) {
(void)object;
(void)result;
struct gtk_webview *w = (struct gtk_webview *)userdata;
w->priv.js_busy = 0;
}
WEBVIEW_API int webview_eval(webview_t w, const char *js) {
struct gtk_webview *wv = (struct webview *)w;
while (wv->priv.ready == 0) {
g_main_context_iteration(NULL, TRUE);
}
wv->priv.js_busy = 1;
webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(wv->priv.webview), js, NULL,
webview_eval_finished, w);
while (wv->priv.js_busy) {
g_main_context_iteration(NULL, TRUE);
}
return 0;
}
static gboolean webview_dispatch_wrapper(gpointer userdata) {
struct gtk_webview *w = (struct gtk_webview *)userdata;
for (;;) {
struct webview_dispatch_arg *arg =
(struct webview_dispatch_arg *)g_async_queue_try_pop(w->priv.queue);
if (arg == NULL) {
break;
}
(arg->fn)(w, arg->arg);
g_free(arg);
}
return FALSE;
}
WEBVIEW_API void webview_dispatch(webview_t w, webview_dispatch_fn fn,
void *arg) {
struct gtk_webview *wv = (struct webview *)w;
struct webview_dispatch_arg *context =
(struct webview_dispatch_arg *)g_new(struct webview_dispatch_arg, 1);
context->w = w;
context->arg = arg;
context->fn = fn;
g_async_queue_lock(wv->priv.queue);
g_async_queue_push_unlocked(wv->priv.queue, context);
if (g_async_queue_length_unlocked(wv->priv.queue) == 1) {
gdk_threads_add_idle(webview_dispatch_wrapper, w);
}
g_async_queue_unlock(wv->priv.queue);
}
WEBVIEW_API void webview_terminate(webview_t w) {
struct gtk_webview *wv = (struct webview *)w;
wv->priv.should_exit = 1;
}
WEBVIEW_API void webview_exit(webview_t w) { (void)w; }
WEBVIEW_API void webview_print_log(const char *s) {
fprintf(stderr, "%s\n", s);
}

File diff suppressed because it is too large Load Diff