webview-ffi 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +18 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +10 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/ext/webview/extconf.rb +8 -0
- data/ext/webview/webview.cc +3 -0
- data/ext/webview/webview.h +1348 -0
- data/lib/webview.rb +3 -0
- data/lib/webview/version.rb +5 -0
- data/lib/webview/webview.rb +64 -0
- data/webview.gemspec +40 -0
- metadata +92 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d0f63ce057c152790a6e2b7161e0ea166922ef706a8f1bbdd7c37f82b61325b4
|
4
|
+
data.tar.gz: 5467ae74c00b5a0eb5bc6258198419e418b518d9dd90f040bc06cecdde7ddbd7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 17d7d4246d29c5787cef4ef098b65e9e8fa010f85965fef16be876d3a2d2924b7f92f4544d797d0e51b7aaa9cb1bcc4250bfca5dc7e77e1f4d2d653d4fa686b3
|
7
|
+
data.tar.gz: a7b11530e06109046a21ee776f5fddd0ed58755188d31f63d1623d7cca98fda7a4642ceec990135418f7400537e5d52ac443882c101b007bb8ed610f0fc571f1
|
@@ -0,0 +1,18 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on: [push,pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
steps:
|
9
|
+
- uses: actions/checkout@v2
|
10
|
+
- name: Set up Ruby
|
11
|
+
uses: ruby/setup-ruby@v1
|
12
|
+
with:
|
13
|
+
ruby-version: 3.0.0
|
14
|
+
- name: Run the default task
|
15
|
+
run: |
|
16
|
+
gem install bundler -v 2.2.3
|
17
|
+
bundle install
|
18
|
+
bundle exec rake
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
6
|
+
|
7
|
+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
8
|
+
|
9
|
+
## Our Standards
|
10
|
+
|
11
|
+
Examples of behavior that contributes to a positive environment for our community include:
|
12
|
+
|
13
|
+
* Demonstrating empathy and kindness toward other people
|
14
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
15
|
+
* Giving and gracefully accepting constructive feedback
|
16
|
+
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
17
|
+
* Focusing on what is best not just for us as individuals, but for the overall community
|
18
|
+
|
19
|
+
Examples of unacceptable behavior include:
|
20
|
+
|
21
|
+
* The use of sexualized language or imagery, and sexual attention or
|
22
|
+
advances of any kind
|
23
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
24
|
+
* Public or private harassment
|
25
|
+
* Publishing others' private information, such as a physical or email
|
26
|
+
address, without their explicit permission
|
27
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
28
|
+
professional setting
|
29
|
+
|
30
|
+
## Enforcement Responsibilities
|
31
|
+
|
32
|
+
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
33
|
+
|
34
|
+
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
35
|
+
|
36
|
+
## Scope
|
37
|
+
|
38
|
+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
39
|
+
|
40
|
+
## Enforcement
|
41
|
+
|
42
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at TODO: Write your email address. All complaints will be reviewed and investigated promptly and fairly.
|
43
|
+
|
44
|
+
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
45
|
+
|
46
|
+
## Enforcement Guidelines
|
47
|
+
|
48
|
+
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
49
|
+
|
50
|
+
### 1. Correction
|
51
|
+
|
52
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
53
|
+
|
54
|
+
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
55
|
+
|
56
|
+
### 2. Warning
|
57
|
+
|
58
|
+
**Community Impact**: A violation through a single incident or series of actions.
|
59
|
+
|
60
|
+
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
61
|
+
|
62
|
+
### 3. Temporary Ban
|
63
|
+
|
64
|
+
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
65
|
+
|
66
|
+
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
67
|
+
|
68
|
+
### 4. Permanent Ban
|
69
|
+
|
70
|
+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
71
|
+
|
72
|
+
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
73
|
+
|
74
|
+
## Attribution
|
75
|
+
|
76
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
|
77
|
+
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
78
|
+
|
79
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
80
|
+
|
81
|
+
[homepage]: https://www.contributor-covenant.org
|
82
|
+
|
83
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
84
|
+
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 TODO: Write your name
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# Webview
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/webview`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'webview'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle install
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install webview
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/webview. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/webview/blob/master/CODE_OF_CONDUCT.md).
|
36
|
+
|
37
|
+
## License
|
38
|
+
|
39
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
40
|
+
|
41
|
+
## Code of Conduct
|
42
|
+
|
43
|
+
Everyone interacting in the Webview project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/webview/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rubocop/rake_task"
|
5
|
+
require "rake/extensiontask"
|
6
|
+
|
7
|
+
RuboCop::RakeTask.new
|
8
|
+
task default: :rubocop
|
9
|
+
|
10
|
+
Rake::ExtensionTask.new "webview" do |ext|
|
11
|
+
ext.lib_dir = "lib/webview"
|
12
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "webview"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
require "mkmf"
|
2
|
+
require "rbconfig"
|
3
|
+
abort("ERROR: OS not supported yet!") unless RbConfig::CONFIG["host_os"] =~ /linux|cygwin/
|
4
|
+
abort("ERROR: gtk+-3.0 is missing.") unless have_library("gtk-3")
|
5
|
+
abort("ERORR: webkit2gtk-4.0 is missing.") unless have_library("webkit2gtk-4.0")
|
6
|
+
pkg_config("--cflags --libs gtk+-3.0 webkit2gtk-4.0")
|
7
|
+
create_header
|
8
|
+
create_makefile 'webview/webview'
|
@@ -0,0 +1,1348 @@
|
|
1
|
+
/*
|
2
|
+
* MIT License
|
3
|
+
*
|
4
|
+
* Copyright (c) 2017 Serge Zaitsev
|
5
|
+
*
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
* of this software and associated documentation files (the "Software"), to deal
|
8
|
+
* in the Software without restriction, including without limitation the rights
|
9
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
* copies of the Software, and to permit persons to whom the Software is
|
11
|
+
* furnished to do so, subject to the following conditions:
|
12
|
+
*
|
13
|
+
* The above copyright notice and this permission notice shall be included in
|
14
|
+
* all copies or substantial portions of the Software.
|
15
|
+
*
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
22
|
+
* SOFTWARE.
|
23
|
+
*/
|
24
|
+
#ifndef WEBVIEW_H
|
25
|
+
#define WEBVIEW_H
|
26
|
+
|
27
|
+
#ifndef WEBVIEW_API
|
28
|
+
#define WEBVIEW_API extern
|
29
|
+
#endif
|
30
|
+
|
31
|
+
#ifdef __cplusplus
|
32
|
+
extern "C" {
|
33
|
+
#endif
|
34
|
+
|
35
|
+
typedef void *webview_t;
|
36
|
+
|
37
|
+
// Creates a new webview instance. If debug is non-zero - developer tools will
|
38
|
+
// be enabled (if the platform supports them). Window parameter can be a
|
39
|
+
// pointer to the native window handle. If it's non-null - then child WebView
|
40
|
+
// is embedded into the given parent window. Otherwise a new window is created.
|
41
|
+
// Depending on the platform, a GtkWindow, NSWindow or HWND pointer can be
|
42
|
+
// passed here.
|
43
|
+
WEBVIEW_API webview_t webview_create(int debug, void *window);
|
44
|
+
|
45
|
+
// Destroys a webview and closes the native window.
|
46
|
+
WEBVIEW_API void webview_destroy(webview_t w);
|
47
|
+
|
48
|
+
// Runs the main loop until it's terminated. After this function exits - you
|
49
|
+
// must destroy the webview.
|
50
|
+
WEBVIEW_API void webview_run(webview_t w);
|
51
|
+
|
52
|
+
// Stops the main loop. It is safe to call this function from another other
|
53
|
+
// background thread.
|
54
|
+
WEBVIEW_API void webview_terminate(webview_t w);
|
55
|
+
|
56
|
+
// Posts a function to be executed on the main thread. You normally do not need
|
57
|
+
// to call this function, unless you want to tweak the native window.
|
58
|
+
WEBVIEW_API void
|
59
|
+
webview_dispatch(webview_t w, void (*fn)(webview_t w, void *arg), void *arg);
|
60
|
+
|
61
|
+
// Returns a native window handle pointer. When using GTK backend the pointer
|
62
|
+
// is GtkWindow pointer, when using Cocoa backend the pointer is NSWindow
|
63
|
+
// pointer, when using Win32 backend the pointer is HWND pointer.
|
64
|
+
WEBVIEW_API void *webview_get_window(webview_t w);
|
65
|
+
|
66
|
+
// Updates the title of the native window. Must be called from the UI thread.
|
67
|
+
WEBVIEW_API void webview_set_title(webview_t w, const char *title);
|
68
|
+
|
69
|
+
// Window size hints
|
70
|
+
#define WEBVIEW_HINT_NONE 0 // Width and height are default size
|
71
|
+
#define WEBVIEW_HINT_MIN 1 // Width and height are minimum bounds
|
72
|
+
#define WEBVIEW_HINT_MAX 2 // Width and height are maximum bounds
|
73
|
+
#define WEBVIEW_HINT_FIXED 3 // Window size can not be changed by a user
|
74
|
+
// Updates native window size. See WEBVIEW_HINT constants.
|
75
|
+
WEBVIEW_API void webview_set_size(webview_t w, int width, int height,
|
76
|
+
int hints);
|
77
|
+
|
78
|
+
// Navigates webview to the given URL. URL may be a data URI, i.e.
|
79
|
+
// "data:text/text,<html>...</html>". It is often ok not to url-encode it
|
80
|
+
// properly, webview will re-encode it for you.
|
81
|
+
WEBVIEW_API void webview_navigate(webview_t w, const char *url);
|
82
|
+
|
83
|
+
// Injects JavaScript code at the initialization of the new page. Every time
|
84
|
+
// the webview will open a the new page - this initialization code will be
|
85
|
+
// executed. It is guaranteed that code is executed before window.onload.
|
86
|
+
WEBVIEW_API void webview_init(webview_t w, const char *js);
|
87
|
+
|
88
|
+
// Evaluates arbitrary JavaScript code. Evaluation happens asynchronously, also
|
89
|
+
// the result of the expression is ignored. Use RPC bindings if you want to
|
90
|
+
// receive notifications about the results of the evaluation.
|
91
|
+
WEBVIEW_API void webview_eval(webview_t w, const char *js);
|
92
|
+
|
93
|
+
// Binds a native C callback so that it will appear under the given name as a
|
94
|
+
// global JavaScript function. Internally it uses webview_init(). Callback
|
95
|
+
// receives a request string and a user-provided argument pointer. Request
|
96
|
+
// string is a JSON array of all the arguments passed to the JavaScript
|
97
|
+
// function.
|
98
|
+
WEBVIEW_API void webview_bind(webview_t w, const char *name,
|
99
|
+
void (*fn)(const char *seq, const char *req,
|
100
|
+
void *arg),
|
101
|
+
void *arg);
|
102
|
+
|
103
|
+
// Allows to return a value from the native binding. Original request pointer
|
104
|
+
// must be provided to help internal RPC engine match requests with responses.
|
105
|
+
// If status is zero - result is expected to be a valid JSON result value.
|
106
|
+
// If status is not zero - result is an error JSON object.
|
107
|
+
WEBVIEW_API void webview_return(webview_t w, const char *seq, int status,
|
108
|
+
const char *result);
|
109
|
+
|
110
|
+
#ifdef __cplusplus
|
111
|
+
}
|
112
|
+
#endif
|
113
|
+
|
114
|
+
#ifndef WEBVIEW_HEADER
|
115
|
+
|
116
|
+
#if !defined(WEBVIEW_GTK) && !defined(WEBVIEW_COCOA) && !defined(WEBVIEW_EDGE)
|
117
|
+
#if defined(__linux__)
|
118
|
+
#define WEBVIEW_GTK
|
119
|
+
#elif defined(__APPLE__)
|
120
|
+
#define WEBVIEW_COCOA
|
121
|
+
#elif defined(_WIN32)
|
122
|
+
#define WEBVIEW_EDGE
|
123
|
+
#else
|
124
|
+
#error "please, specify webview backend"
|
125
|
+
#endif
|
126
|
+
#endif
|
127
|
+
|
128
|
+
#include <atomic>
|
129
|
+
#include <functional>
|
130
|
+
#include <future>
|
131
|
+
#include <map>
|
132
|
+
#include <string>
|
133
|
+
#include <utility>
|
134
|
+
#include <vector>
|
135
|
+
|
136
|
+
#include <cstring>
|
137
|
+
|
138
|
+
namespace webview {
|
139
|
+
using dispatch_fn_t = std::function<void()>;
|
140
|
+
|
141
|
+
inline std::string url_encode(const std::string s) {
|
142
|
+
std::string encoded;
|
143
|
+
for (unsigned int i = 0; i < s.length(); i++) {
|
144
|
+
auto c = s[i];
|
145
|
+
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
|
146
|
+
encoded = encoded + c;
|
147
|
+
} else {
|
148
|
+
char hex[4];
|
149
|
+
snprintf(hex, sizeof(hex), "%%%02x", c);
|
150
|
+
encoded = encoded + hex;
|
151
|
+
}
|
152
|
+
}
|
153
|
+
return encoded;
|
154
|
+
}
|
155
|
+
|
156
|
+
inline std::string url_decode(const std::string s) {
|
157
|
+
std::string decoded;
|
158
|
+
for (unsigned int i = 0; i < s.length(); i++) {
|
159
|
+
if (s[i] == '%') {
|
160
|
+
int n;
|
161
|
+
n = std::stoul(s.substr(i + 1, 2), nullptr, 16);
|
162
|
+
decoded = decoded + static_cast<char>(n);
|
163
|
+
i = i + 2;
|
164
|
+
} else if (s[i] == '+') {
|
165
|
+
decoded = decoded + ' ';
|
166
|
+
} else {
|
167
|
+
decoded = decoded + s[i];
|
168
|
+
}
|
169
|
+
}
|
170
|
+
return decoded;
|
171
|
+
}
|
172
|
+
|
173
|
+
inline std::string html_from_uri(const std::string s) {
|
174
|
+
if (s.substr(0, 15) == "data:text/html,") {
|
175
|
+
return url_decode(s.substr(15));
|
176
|
+
}
|
177
|
+
return "";
|
178
|
+
}
|
179
|
+
|
180
|
+
inline int json_parse_c(const char *s, size_t sz, const char *key, size_t keysz,
|
181
|
+
const char **value, size_t *valuesz) {
|
182
|
+
enum {
|
183
|
+
JSON_STATE_VALUE,
|
184
|
+
JSON_STATE_LITERAL,
|
185
|
+
JSON_STATE_STRING,
|
186
|
+
JSON_STATE_ESCAPE,
|
187
|
+
JSON_STATE_UTF8
|
188
|
+
} state = JSON_STATE_VALUE;
|
189
|
+
const char *k = NULL;
|
190
|
+
int index = 1;
|
191
|
+
int depth = 0;
|
192
|
+
int utf8_bytes = 0;
|
193
|
+
|
194
|
+
if (key == NULL) {
|
195
|
+
index = keysz;
|
196
|
+
keysz = 0;
|
197
|
+
}
|
198
|
+
|
199
|
+
*value = NULL;
|
200
|
+
*valuesz = 0;
|
201
|
+
|
202
|
+
for (; sz > 0; s++, sz--) {
|
203
|
+
enum {
|
204
|
+
JSON_ACTION_NONE,
|
205
|
+
JSON_ACTION_START,
|
206
|
+
JSON_ACTION_END,
|
207
|
+
JSON_ACTION_START_STRUCT,
|
208
|
+
JSON_ACTION_END_STRUCT
|
209
|
+
} action = JSON_ACTION_NONE;
|
210
|
+
unsigned char c = *s;
|
211
|
+
switch (state) {
|
212
|
+
case JSON_STATE_VALUE:
|
213
|
+
if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' ||
|
214
|
+
c == ':') {
|
215
|
+
continue;
|
216
|
+
} else if (c == '"') {
|
217
|
+
action = JSON_ACTION_START;
|
218
|
+
state = JSON_STATE_STRING;
|
219
|
+
} else if (c == '{' || c == '[') {
|
220
|
+
action = JSON_ACTION_START_STRUCT;
|
221
|
+
} else if (c == '}' || c == ']') {
|
222
|
+
action = JSON_ACTION_END_STRUCT;
|
223
|
+
} else if (c == 't' || c == 'f' || c == 'n' || c == '-' ||
|
224
|
+
(c >= '0' && c <= '9')) {
|
225
|
+
action = JSON_ACTION_START;
|
226
|
+
state = JSON_STATE_LITERAL;
|
227
|
+
} else {
|
228
|
+
return -1;
|
229
|
+
}
|
230
|
+
break;
|
231
|
+
case JSON_STATE_LITERAL:
|
232
|
+
if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' ||
|
233
|
+
c == ']' || c == '}' || c == ':') {
|
234
|
+
state = JSON_STATE_VALUE;
|
235
|
+
s--;
|
236
|
+
sz++;
|
237
|
+
action = JSON_ACTION_END;
|
238
|
+
} else if (c < 32 || c > 126) {
|
239
|
+
return -1;
|
240
|
+
} // fallthrough
|
241
|
+
case JSON_STATE_STRING:
|
242
|
+
if (c < 32 || (c > 126 && c < 192)) {
|
243
|
+
return -1;
|
244
|
+
} else if (c == '"') {
|
245
|
+
action = JSON_ACTION_END;
|
246
|
+
state = JSON_STATE_VALUE;
|
247
|
+
} else if (c == '\\') {
|
248
|
+
state = JSON_STATE_ESCAPE;
|
249
|
+
} else if (c >= 192 && c < 224) {
|
250
|
+
utf8_bytes = 1;
|
251
|
+
state = JSON_STATE_UTF8;
|
252
|
+
} else if (c >= 224 && c < 240) {
|
253
|
+
utf8_bytes = 2;
|
254
|
+
state = JSON_STATE_UTF8;
|
255
|
+
} else if (c >= 240 && c < 247) {
|
256
|
+
utf8_bytes = 3;
|
257
|
+
state = JSON_STATE_UTF8;
|
258
|
+
} else if (c >= 128 && c < 192) {
|
259
|
+
return -1;
|
260
|
+
}
|
261
|
+
break;
|
262
|
+
case JSON_STATE_ESCAPE:
|
263
|
+
if (c == '"' || c == '\\' || c == '/' || c == 'b' || c == 'f' ||
|
264
|
+
c == 'n' || c == 'r' || c == 't' || c == 'u') {
|
265
|
+
state = JSON_STATE_STRING;
|
266
|
+
} else {
|
267
|
+
return -1;
|
268
|
+
}
|
269
|
+
break;
|
270
|
+
case JSON_STATE_UTF8:
|
271
|
+
if (c < 128 || c > 191) {
|
272
|
+
return -1;
|
273
|
+
}
|
274
|
+
utf8_bytes--;
|
275
|
+
if (utf8_bytes == 0) {
|
276
|
+
state = JSON_STATE_STRING;
|
277
|
+
}
|
278
|
+
break;
|
279
|
+
default:
|
280
|
+
return -1;
|
281
|
+
}
|
282
|
+
|
283
|
+
if (action == JSON_ACTION_END_STRUCT) {
|
284
|
+
depth--;
|
285
|
+
}
|
286
|
+
|
287
|
+
if (depth == 1) {
|
288
|
+
if (action == JSON_ACTION_START || action == JSON_ACTION_START_STRUCT) {
|
289
|
+
if (index == 0) {
|
290
|
+
*value = s;
|
291
|
+
} else if (keysz > 0 && index == 1) {
|
292
|
+
k = s;
|
293
|
+
} else {
|
294
|
+
index--;
|
295
|
+
}
|
296
|
+
} else if (action == JSON_ACTION_END ||
|
297
|
+
action == JSON_ACTION_END_STRUCT) {
|
298
|
+
if (*value != NULL && index == 0) {
|
299
|
+
*valuesz = (size_t)(s + 1 - *value);
|
300
|
+
return 0;
|
301
|
+
} else if (keysz > 0 && k != NULL) {
|
302
|
+
if (keysz == (size_t)(s - k - 1) && memcmp(key, k + 1, keysz) == 0) {
|
303
|
+
index = 0;
|
304
|
+
} else {
|
305
|
+
index = 2;
|
306
|
+
}
|
307
|
+
k = NULL;
|
308
|
+
}
|
309
|
+
}
|
310
|
+
}
|
311
|
+
|
312
|
+
if (action == JSON_ACTION_START_STRUCT) {
|
313
|
+
depth++;
|
314
|
+
}
|
315
|
+
}
|
316
|
+
return -1;
|
317
|
+
}
|
318
|
+
|
319
|
+
inline std::string json_escape(std::string s) {
|
320
|
+
// TODO: implement
|
321
|
+
return '"' + s + '"';
|
322
|
+
}
|
323
|
+
|
324
|
+
inline int json_unescape(const char *s, size_t n, char *out) {
|
325
|
+
int r = 0;
|
326
|
+
if (*s++ != '"') {
|
327
|
+
return -1;
|
328
|
+
}
|
329
|
+
while (n > 2) {
|
330
|
+
char c = *s;
|
331
|
+
if (c == '\\') {
|
332
|
+
s++;
|
333
|
+
n--;
|
334
|
+
switch (*s) {
|
335
|
+
case 'b':
|
336
|
+
c = '\b';
|
337
|
+
break;
|
338
|
+
case 'f':
|
339
|
+
c = '\f';
|
340
|
+
break;
|
341
|
+
case 'n':
|
342
|
+
c = '\n';
|
343
|
+
break;
|
344
|
+
case 'r':
|
345
|
+
c = '\r';
|
346
|
+
break;
|
347
|
+
case 't':
|
348
|
+
c = '\t';
|
349
|
+
break;
|
350
|
+
case '\\':
|
351
|
+
c = '\\';
|
352
|
+
break;
|
353
|
+
case '/':
|
354
|
+
c = '/';
|
355
|
+
break;
|
356
|
+
case '\"':
|
357
|
+
c = '\"';
|
358
|
+
break;
|
359
|
+
default: // TODO: support unicode decoding
|
360
|
+
return -1;
|
361
|
+
}
|
362
|
+
}
|
363
|
+
if (out != NULL) {
|
364
|
+
*out++ = c;
|
365
|
+
}
|
366
|
+
s++;
|
367
|
+
n--;
|
368
|
+
r++;
|
369
|
+
}
|
370
|
+
if (*s != '"') {
|
371
|
+
return -1;
|
372
|
+
}
|
373
|
+
if (out != NULL) {
|
374
|
+
*out = '\0';
|
375
|
+
}
|
376
|
+
return r;
|
377
|
+
}
|
378
|
+
|
379
|
+
inline std::string json_parse(const std::string s, const std::string key,
|
380
|
+
const int index) {
|
381
|
+
const char *value;
|
382
|
+
size_t value_sz;
|
383
|
+
if (key == "") {
|
384
|
+
json_parse_c(s.c_str(), s.length(), nullptr, index, &value, &value_sz);
|
385
|
+
} else {
|
386
|
+
json_parse_c(s.c_str(), s.length(), key.c_str(), key.length(), &value,
|
387
|
+
&value_sz);
|
388
|
+
}
|
389
|
+
if (value != nullptr) {
|
390
|
+
if (value[0] != '"') {
|
391
|
+
return std::string(value, value_sz);
|
392
|
+
}
|
393
|
+
int n = json_unescape(value, value_sz, nullptr);
|
394
|
+
if (n > 0) {
|
395
|
+
char *decoded = new char[n + 1];
|
396
|
+
json_unescape(value, value_sz, decoded);
|
397
|
+
std::string result(decoded, n);
|
398
|
+
delete[] decoded;
|
399
|
+
return result;
|
400
|
+
}
|
401
|
+
}
|
402
|
+
return "";
|
403
|
+
}
|
404
|
+
|
405
|
+
} // namespace webview
|
406
|
+
|
407
|
+
#if defined(WEBVIEW_GTK)
|
408
|
+
//
|
409
|
+
// ====================================================================
|
410
|
+
//
|
411
|
+
// This implementation uses webkit2gtk backend. It requires gtk+3.0 and
|
412
|
+
// webkit2gtk-4.0 libraries. Proper compiler flags can be retrieved via:
|
413
|
+
//
|
414
|
+
// pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0
|
415
|
+
//
|
416
|
+
// ====================================================================
|
417
|
+
//
|
418
|
+
#include <JavaScriptCore/JavaScript.h>
|
419
|
+
#include <gtk/gtk.h>
|
420
|
+
#include <webkit2/webkit2.h>
|
421
|
+
|
422
|
+
namespace webview {
|
423
|
+
|
424
|
+
class gtk_webkit_engine {
|
425
|
+
public:
|
426
|
+
gtk_webkit_engine(bool debug, void *window)
|
427
|
+
: m_window(static_cast<GtkWidget *>(window)) {
|
428
|
+
gtk_init_check(0, NULL);
|
429
|
+
m_window = static_cast<GtkWidget *>(window);
|
430
|
+
if (m_window == nullptr) {
|
431
|
+
m_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
432
|
+
}
|
433
|
+
g_signal_connect(G_OBJECT(m_window), "destroy",
|
434
|
+
G_CALLBACK(+[](GtkWidget *, gpointer arg) {
|
435
|
+
static_cast<gtk_webkit_engine *>(arg)->terminate();
|
436
|
+
}),
|
437
|
+
this);
|
438
|
+
// Initialize webview widget
|
439
|
+
m_webview = webkit_web_view_new();
|
440
|
+
WebKitUserContentManager *manager =
|
441
|
+
webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview));
|
442
|
+
g_signal_connect(manager, "script-message-received::external",
|
443
|
+
G_CALLBACK(+[](WebKitUserContentManager *,
|
444
|
+
WebKitJavascriptResult *r, gpointer arg) {
|
445
|
+
auto *w = static_cast<gtk_webkit_engine *>(arg);
|
446
|
+
#if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22
|
447
|
+
JSCValue *value =
|
448
|
+
webkit_javascript_result_get_js_value(r);
|
449
|
+
char *s = jsc_value_to_string(value);
|
450
|
+
#else
|
451
|
+
JSGlobalContextRef ctx =
|
452
|
+
webkit_javascript_result_get_global_context(r);
|
453
|
+
JSValueRef value = webkit_javascript_result_get_value(r);
|
454
|
+
JSStringRef js = JSValueToStringCopy(ctx, value, NULL);
|
455
|
+
size_t n = JSStringGetMaximumUTF8CStringSize(js);
|
456
|
+
char *s = g_new(char, n);
|
457
|
+
JSStringGetUTF8CString(js, s, n);
|
458
|
+
JSStringRelease(js);
|
459
|
+
#endif
|
460
|
+
w->on_message(s);
|
461
|
+
g_free(s);
|
462
|
+
}),
|
463
|
+
this);
|
464
|
+
webkit_user_content_manager_register_script_message_handler(manager,
|
465
|
+
"external");
|
466
|
+
init("window.external={invoke:function(s){window.webkit.messageHandlers."
|
467
|
+
"external.postMessage(s);}}");
|
468
|
+
|
469
|
+
gtk_container_add(GTK_CONTAINER(m_window), GTK_WIDGET(m_webview));
|
470
|
+
gtk_widget_grab_focus(GTK_WIDGET(m_webview));
|
471
|
+
|
472
|
+
WebKitSettings *settings =
|
473
|
+
webkit_web_view_get_settings(WEBKIT_WEB_VIEW(m_webview));
|
474
|
+
webkit_settings_set_javascript_can_access_clipboard(settings, true);
|
475
|
+
if (debug) {
|
476
|
+
webkit_settings_set_enable_write_console_messages_to_stdout(settings,
|
477
|
+
true);
|
478
|
+
webkit_settings_set_enable_developer_extras(settings, true);
|
479
|
+
}
|
480
|
+
|
481
|
+
gtk_widget_show_all(m_window);
|
482
|
+
}
|
483
|
+
void *window() { return (void *)m_window; }
|
484
|
+
void run() { gtk_main(); }
|
485
|
+
void terminate() { gtk_main_quit(); }
|
486
|
+
void dispatch(std::function<void()> f) {
|
487
|
+
g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)([](void *f) -> int {
|
488
|
+
(*static_cast<dispatch_fn_t *>(f))();
|
489
|
+
return G_SOURCE_REMOVE;
|
490
|
+
}),
|
491
|
+
new std::function<void()>(f),
|
492
|
+
[](void *f) { delete static_cast<dispatch_fn_t *>(f); });
|
493
|
+
}
|
494
|
+
|
495
|
+
void set_title(const std::string title) {
|
496
|
+
gtk_window_set_title(GTK_WINDOW(m_window), title.c_str());
|
497
|
+
}
|
498
|
+
|
499
|
+
void set_size(int width, int height, int hints) {
|
500
|
+
gtk_window_set_resizable(GTK_WINDOW(m_window), hints != WEBVIEW_HINT_FIXED);
|
501
|
+
if (hints == WEBVIEW_HINT_NONE) {
|
502
|
+
gtk_window_resize(GTK_WINDOW(m_window), width, height);
|
503
|
+
} else if (hints == WEBVIEW_HINT_FIXED) {
|
504
|
+
gtk_widget_set_size_request(m_window, width, height);
|
505
|
+
} else {
|
506
|
+
GdkGeometry g;
|
507
|
+
g.min_width = g.max_width = width;
|
508
|
+
g.min_height = g.max_height = height;
|
509
|
+
GdkWindowHints h =
|
510
|
+
(hints == WEBVIEW_HINT_MIN ? GDK_HINT_MIN_SIZE : GDK_HINT_MAX_SIZE);
|
511
|
+
// This defines either MIN_SIZE, or MAX_SIZE, but not both:
|
512
|
+
gtk_window_set_geometry_hints(GTK_WINDOW(m_window), nullptr, &g, h);
|
513
|
+
}
|
514
|
+
}
|
515
|
+
|
516
|
+
void navigate(const std::string url) {
|
517
|
+
webkit_web_view_load_uri(WEBKIT_WEB_VIEW(m_webview), url.c_str());
|
518
|
+
}
|
519
|
+
|
520
|
+
void init(const std::string js) {
|
521
|
+
WebKitUserContentManager *manager =
|
522
|
+
webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview));
|
523
|
+
webkit_user_content_manager_add_script(
|
524
|
+
manager, webkit_user_script_new(
|
525
|
+
js.c_str(), WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
|
526
|
+
WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, NULL, NULL));
|
527
|
+
}
|
528
|
+
|
529
|
+
void eval(const std::string js) {
|
530
|
+
webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(m_webview), js.c_str(), NULL,
|
531
|
+
NULL, NULL);
|
532
|
+
}
|
533
|
+
|
534
|
+
private:
|
535
|
+
virtual void on_message(const std::string msg) = 0;
|
536
|
+
GtkWidget *m_window;
|
537
|
+
GtkWidget *m_webview;
|
538
|
+
};
|
539
|
+
|
540
|
+
using browser_engine = gtk_webkit_engine;
|
541
|
+
|
542
|
+
} // namespace webview
|
543
|
+
|
544
|
+
#elif defined(WEBVIEW_COCOA)
|
545
|
+
|
546
|
+
//
|
547
|
+
// ====================================================================
|
548
|
+
//
|
549
|
+
// This implementation uses Cocoa WKWebView backend on macOS. It is
|
550
|
+
// written using ObjC runtime and uses WKWebView class as a browser runtime.
|
551
|
+
// You should pass "-framework Webkit" flag to the compiler.
|
552
|
+
//
|
553
|
+
// ====================================================================
|
554
|
+
//
|
555
|
+
|
556
|
+
#include <CoreGraphics/CoreGraphics.h>
|
557
|
+
#include <objc/objc-runtime.h>
|
558
|
+
|
559
|
+
#define NSBackingStoreBuffered 2
|
560
|
+
|
561
|
+
#define NSWindowStyleMaskResizable 8
|
562
|
+
#define NSWindowStyleMaskMiniaturizable 4
|
563
|
+
#define NSWindowStyleMaskTitled 1
|
564
|
+
#define NSWindowStyleMaskClosable 2
|
565
|
+
|
566
|
+
#define NSApplicationActivationPolicyRegular 0
|
567
|
+
|
568
|
+
#define WKUserScriptInjectionTimeAtDocumentStart 0
|
569
|
+
|
570
|
+
namespace webview {
|
571
|
+
|
572
|
+
// Helpers to avoid too much typing
|
573
|
+
id operator"" _cls(const char *s, std::size_t) { return (id)objc_getClass(s); }
|
574
|
+
SEL operator"" _sel(const char *s, std::size_t) { return sel_registerName(s); }
|
575
|
+
id operator"" _str(const char *s, std::size_t) {
|
576
|
+
return ((id(*)(id, SEL, const char *))objc_msgSend)(
|
577
|
+
"NSString"_cls, "stringWithUTF8String:"_sel, s);
|
578
|
+
}
|
579
|
+
|
580
|
+
class cocoa_wkwebview_engine {
|
581
|
+
public:
|
582
|
+
cocoa_wkwebview_engine(bool debug, void *window) {
|
583
|
+
// Application
|
584
|
+
id app = ((id(*)(id, SEL))objc_msgSend)("NSApplication"_cls,
|
585
|
+
"sharedApplication"_sel);
|
586
|
+
((void (*)(id, SEL, long))objc_msgSend)(
|
587
|
+
app, "setActivationPolicy:"_sel, NSApplicationActivationPolicyRegular);
|
588
|
+
|
589
|
+
// Delegate
|
590
|
+
auto cls =
|
591
|
+
objc_allocateClassPair((Class) "NSResponder"_cls, "AppDelegate", 0);
|
592
|
+
class_addProtocol(cls, objc_getProtocol("NSTouchBarProvider"));
|
593
|
+
class_addMethod(cls, "applicationShouldTerminateAfterLastWindowClosed:"_sel,
|
594
|
+
(IMP)(+[](id, SEL, id) -> BOOL { return 1; }), "c@:@");
|
595
|
+
class_addMethod(cls, "userContentController:didReceiveScriptMessage:"_sel,
|
596
|
+
(IMP)(+[](id self, SEL, id, id msg) {
|
597
|
+
auto w =
|
598
|
+
(cocoa_wkwebview_engine *)objc_getAssociatedObject(
|
599
|
+
self, "webview");
|
600
|
+
assert(w);
|
601
|
+
w->on_message(((const char *(*)(id, SEL))objc_msgSend)(
|
602
|
+
((id(*)(id, SEL))objc_msgSend)(msg, "body"_sel),
|
603
|
+
"UTF8String"_sel));
|
604
|
+
}),
|
605
|
+
"v@:@@");
|
606
|
+
objc_registerClassPair(cls);
|
607
|
+
|
608
|
+
auto delegate = ((id(*)(id, SEL))objc_msgSend)((id)cls, "new"_sel);
|
609
|
+
objc_setAssociatedObject(delegate, "webview", (id)this,
|
610
|
+
OBJC_ASSOCIATION_ASSIGN);
|
611
|
+
((void (*)(id, SEL, id))objc_msgSend)(app, sel_registerName("setDelegate:"),
|
612
|
+
delegate);
|
613
|
+
|
614
|
+
// Main window
|
615
|
+
if (window == nullptr) {
|
616
|
+
m_window = ((id(*)(id, SEL))objc_msgSend)("NSWindow"_cls, "alloc"_sel);
|
617
|
+
m_window =
|
618
|
+
((id(*)(id, SEL, CGRect, int, unsigned long, int))objc_msgSend)(
|
619
|
+
m_window, "initWithContentRect:styleMask:backing:defer:"_sel,
|
620
|
+
CGRectMake(0, 0, 0, 0), 0, NSBackingStoreBuffered, 0);
|
621
|
+
} else {
|
622
|
+
m_window = (id)window;
|
623
|
+
}
|
624
|
+
|
625
|
+
// Webview
|
626
|
+
auto config =
|
627
|
+
((id(*)(id, SEL))objc_msgSend)("WKWebViewConfiguration"_cls, "new"_sel);
|
628
|
+
m_manager =
|
629
|
+
((id(*)(id, SEL))objc_msgSend)(config, "userContentController"_sel);
|
630
|
+
m_webview = ((id(*)(id, SEL))objc_msgSend)("WKWebView"_cls, "alloc"_sel);
|
631
|
+
|
632
|
+
if (debug) {
|
633
|
+
// Equivalent Obj-C:
|
634
|
+
// [[config preferences] setValue:@YES forKey:@"developerExtrasEnabled"];
|
635
|
+
((id(*)(id, SEL, id, id))objc_msgSend)(
|
636
|
+
((id(*)(id, SEL))objc_msgSend)(config, "preferences"_sel),
|
637
|
+
"setValue:forKey:"_sel,
|
638
|
+
((id(*)(id, SEL, BOOL))objc_msgSend)("NSNumber"_cls,
|
639
|
+
"numberWithBool:"_sel, 1),
|
640
|
+
"developerExtrasEnabled"_str);
|
641
|
+
}
|
642
|
+
|
643
|
+
// Equivalent Obj-C:
|
644
|
+
// [[config preferences] setValue:@YES forKey:@"fullScreenEnabled"];
|
645
|
+
((id(*)(id, SEL, id, id))objc_msgSend)(
|
646
|
+
((id(*)(id, SEL))objc_msgSend)(config, "preferences"_sel),
|
647
|
+
"setValue:forKey:"_sel,
|
648
|
+
((id(*)(id, SEL, BOOL))objc_msgSend)("NSNumber"_cls,
|
649
|
+
"numberWithBool:"_sel, 1),
|
650
|
+
"fullScreenEnabled"_str);
|
651
|
+
|
652
|
+
// Equivalent Obj-C:
|
653
|
+
// [[config preferences] setValue:@YES forKey:@"javaScriptCanAccessClipboard"];
|
654
|
+
((id(*)(id, SEL, id, id))objc_msgSend)(
|
655
|
+
((id(*)(id, SEL))objc_msgSend)(config, "preferences"_sel),
|
656
|
+
"setValue:forKey:"_sel,
|
657
|
+
((id(*)(id, SEL, BOOL))objc_msgSend)("NSNumber"_cls,
|
658
|
+
"numberWithBool:"_sel, 1),
|
659
|
+
"javaScriptCanAccessClipboard"_str);
|
660
|
+
|
661
|
+
// Equivalent Obj-C:
|
662
|
+
// [[config preferences] setValue:@YES forKey:@"DOMPasteAllowed"];
|
663
|
+
((id(*)(id, SEL, id, id))objc_msgSend)(
|
664
|
+
((id(*)(id, SEL))objc_msgSend)(config, "preferences"_sel),
|
665
|
+
"setValue:forKey:"_sel,
|
666
|
+
((id(*)(id, SEL, BOOL))objc_msgSend)("NSNumber"_cls,
|
667
|
+
"numberWithBool:"_sel, 1),
|
668
|
+
"DOMPasteAllowed"_str);
|
669
|
+
|
670
|
+
((void (*)(id, SEL, CGRect, id))objc_msgSend)(
|
671
|
+
m_webview, "initWithFrame:configuration:"_sel, CGRectMake(0, 0, 0, 0),
|
672
|
+
config);
|
673
|
+
((void (*)(id, SEL, id, id))objc_msgSend)(
|
674
|
+
m_manager, "addScriptMessageHandler:name:"_sel, delegate,
|
675
|
+
"external"_str);
|
676
|
+
|
677
|
+
init(R"script(
|
678
|
+
window.external = {
|
679
|
+
invoke: function(s) {
|
680
|
+
window.webkit.messageHandlers.external.postMessage(s);
|
681
|
+
},
|
682
|
+
};
|
683
|
+
)script");
|
684
|
+
((void (*)(id, SEL, id))objc_msgSend)(m_window, "setContentView:"_sel,
|
685
|
+
m_webview);
|
686
|
+
((void (*)(id, SEL, id))objc_msgSend)(m_window, "makeKeyAndOrderFront:"_sel,
|
687
|
+
nullptr);
|
688
|
+
}
|
689
|
+
~cocoa_wkwebview_engine() { close(); }
|
690
|
+
void *window() { return (void *)m_window; }
|
691
|
+
void terminate() {
|
692
|
+
close();
|
693
|
+
((void (*)(id, SEL, id))objc_msgSend)("NSApp"_cls, "terminate:"_sel,
|
694
|
+
nullptr);
|
695
|
+
}
|
696
|
+
void run() {
|
697
|
+
id app = ((id(*)(id, SEL))objc_msgSend)("NSApplication"_cls,
|
698
|
+
"sharedApplication"_sel);
|
699
|
+
dispatch([&]() {
|
700
|
+
((void (*)(id, SEL, BOOL))objc_msgSend)(
|
701
|
+
app, "activateIgnoringOtherApps:"_sel, 1);
|
702
|
+
});
|
703
|
+
((void (*)(id, SEL))objc_msgSend)(app, "run"_sel);
|
704
|
+
}
|
705
|
+
void dispatch(std::function<void()> f) {
|
706
|
+
dispatch_async_f(dispatch_get_main_queue(), new dispatch_fn_t(f),
|
707
|
+
(dispatch_function_t)([](void *arg) {
|
708
|
+
auto f = static_cast<dispatch_fn_t *>(arg);
|
709
|
+
(*f)();
|
710
|
+
delete f;
|
711
|
+
}));
|
712
|
+
}
|
713
|
+
void set_title(const std::string title) {
|
714
|
+
((void (*)(id, SEL, id))objc_msgSend)(
|
715
|
+
m_window, "setTitle:"_sel,
|
716
|
+
((id(*)(id, SEL, const char *))objc_msgSend)(
|
717
|
+
"NSString"_cls, "stringWithUTF8String:"_sel, title.c_str()));
|
718
|
+
}
|
719
|
+
void set_size(int width, int height, int hints) {
|
720
|
+
auto style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
|
721
|
+
NSWindowStyleMaskMiniaturizable;
|
722
|
+
if (hints != WEBVIEW_HINT_FIXED) {
|
723
|
+
style = style | NSWindowStyleMaskResizable;
|
724
|
+
}
|
725
|
+
((void (*)(id, SEL, unsigned long))objc_msgSend)(
|
726
|
+
m_window, "setStyleMask:"_sel, style);
|
727
|
+
|
728
|
+
if (hints == WEBVIEW_HINT_MIN) {
|
729
|
+
((void (*)(id, SEL, CGSize))objc_msgSend)(
|
730
|
+
m_window, "setContentMinSize:"_sel, CGSizeMake(width, height));
|
731
|
+
} else if (hints == WEBVIEW_HINT_MAX) {
|
732
|
+
((void (*)(id, SEL, CGSize))objc_msgSend)(
|
733
|
+
m_window, "setContentMaxSize:"_sel, CGSizeMake(width, height));
|
734
|
+
} else {
|
735
|
+
((void (*)(id, SEL, CGRect, BOOL, BOOL))objc_msgSend)(
|
736
|
+
m_window, "setFrame:display:animate:"_sel,
|
737
|
+
CGRectMake(0, 0, width, height), 1, 0);
|
738
|
+
}
|
739
|
+
((void (*)(id, SEL))objc_msgSend)(m_window, "center"_sel);
|
740
|
+
}
|
741
|
+
void navigate(const std::string url) {
|
742
|
+
auto nsurl = ((id(*)(id, SEL, id))objc_msgSend)(
|
743
|
+
"NSURL"_cls, "URLWithString:"_sel,
|
744
|
+
((id(*)(id, SEL, const char *))objc_msgSend)(
|
745
|
+
"NSString"_cls, "stringWithUTF8String:"_sel, url.c_str()));
|
746
|
+
|
747
|
+
((void (*)(id, SEL, id))objc_msgSend)(
|
748
|
+
m_webview, "loadRequest:"_sel,
|
749
|
+
((id(*)(id, SEL, id))objc_msgSend)("NSURLRequest"_cls,
|
750
|
+
"requestWithURL:"_sel, nsurl));
|
751
|
+
}
|
752
|
+
void init(const std::string js) {
|
753
|
+
// Equivalent Obj-C:
|
754
|
+
// [m_manager addUserScript:[[WKUserScript alloc] initWithSource:[NSString stringWithUTF8String:js.c_str()] injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]]
|
755
|
+
((void (*)(id, SEL, id))objc_msgSend)(
|
756
|
+
m_manager, "addUserScript:"_sel,
|
757
|
+
((id(*)(id, SEL, id, long, BOOL))objc_msgSend)(
|
758
|
+
((id(*)(id, SEL))objc_msgSend)("WKUserScript"_cls, "alloc"_sel),
|
759
|
+
"initWithSource:injectionTime:forMainFrameOnly:"_sel,
|
760
|
+
((id(*)(id, SEL, const char *))objc_msgSend)(
|
761
|
+
"NSString"_cls, "stringWithUTF8String:"_sel, js.c_str()),
|
762
|
+
WKUserScriptInjectionTimeAtDocumentStart, 1));
|
763
|
+
}
|
764
|
+
void eval(const std::string js) {
|
765
|
+
((void (*)(id, SEL, id, id))objc_msgSend)(
|
766
|
+
m_webview, "evaluateJavaScript:completionHandler:"_sel,
|
767
|
+
((id(*)(id, SEL, const char *))objc_msgSend)(
|
768
|
+
"NSString"_cls, "stringWithUTF8String:"_sel, js.c_str()),
|
769
|
+
nullptr);
|
770
|
+
}
|
771
|
+
|
772
|
+
private:
|
773
|
+
virtual void on_message(const std::string msg) = 0;
|
774
|
+
void close() { ((void (*)(id, SEL))objc_msgSend)(m_window, "close"_sel); }
|
775
|
+
id m_window;
|
776
|
+
id m_webview;
|
777
|
+
id m_manager;
|
778
|
+
};
|
779
|
+
|
780
|
+
using browser_engine = cocoa_wkwebview_engine;
|
781
|
+
|
782
|
+
} // namespace webview
|
783
|
+
|
784
|
+
#elif defined(WEBVIEW_EDGE)
|
785
|
+
|
786
|
+
//
|
787
|
+
// ====================================================================
|
788
|
+
//
|
789
|
+
// This implementation uses Win32 API to create a native window. It can
|
790
|
+
// use either EdgeHTML or Edge/Chromium backend as a browser engine.
|
791
|
+
//
|
792
|
+
// ====================================================================
|
793
|
+
//
|
794
|
+
|
795
|
+
#define WIN32_LEAN_AND_MEAN
|
796
|
+
#include <Shlwapi.h>
|
797
|
+
#include <codecvt>
|
798
|
+
#include <stdlib.h>
|
799
|
+
#include <windows.h>
|
800
|
+
|
801
|
+
#pragma comment(lib, "user32.lib")
|
802
|
+
#pragma comment(lib, "Shlwapi.lib")
|
803
|
+
|
804
|
+
// EdgeHTML headers and libs
|
805
|
+
#include <objbase.h>
|
806
|
+
#include <winrt/Windows.Foundation.Collections.h>
|
807
|
+
#include <winrt/Windows.Foundation.h>
|
808
|
+
#include <winrt/Windows.Web.UI.Interop.h>
|
809
|
+
#pragma comment(lib, "windowsapp")
|
810
|
+
|
811
|
+
// Edge/Chromium headers and libs
|
812
|
+
#include "webview2.h"
|
813
|
+
#pragma comment(lib, "ole32.lib")
|
814
|
+
#pragma comment(lib, "oleaut32.lib")
|
815
|
+
|
816
|
+
namespace webview {
|
817
|
+
|
818
|
+
using msg_cb_t = std::function<void(const std::string)>;
|
819
|
+
|
820
|
+
// Common interface for EdgeHTML and Edge/Chromium
|
821
|
+
class browser {
|
822
|
+
public:
|
823
|
+
virtual ~browser() = default;
|
824
|
+
virtual bool embed(HWND, bool, msg_cb_t) = 0;
|
825
|
+
virtual void navigate(const std::string url) = 0;
|
826
|
+
virtual void eval(const std::string js) = 0;
|
827
|
+
virtual void init(const std::string js) = 0;
|
828
|
+
virtual void resize(HWND) = 0;
|
829
|
+
};
|
830
|
+
|
831
|
+
//
|
832
|
+
// EdgeHTML browser engine
|
833
|
+
//
|
834
|
+
using namespace winrt;
|
835
|
+
using namespace Windows::Foundation;
|
836
|
+
using namespace Windows::Web::UI;
|
837
|
+
using namespace Windows::Web::UI::Interop;
|
838
|
+
|
839
|
+
class edge_html : public browser {
|
840
|
+
public:
|
841
|
+
bool embed(HWND wnd, bool debug, msg_cb_t cb) override {
|
842
|
+
init_apartment(winrt::apartment_type::single_threaded);
|
843
|
+
auto process = WebViewControlProcess();
|
844
|
+
auto op = process.CreateWebViewControlAsync(reinterpret_cast<int64_t>(wnd),
|
845
|
+
Rect());
|
846
|
+
if (op.Status() != AsyncStatus::Completed) {
|
847
|
+
handle h(CreateEvent(nullptr, false, false, nullptr));
|
848
|
+
op.Completed([h = h.get()](auto, auto) { SetEvent(h); });
|
849
|
+
HANDLE hs[] = {h.get()};
|
850
|
+
DWORD i;
|
851
|
+
CoWaitForMultipleHandles(COWAIT_DISPATCH_WINDOW_MESSAGES |
|
852
|
+
COWAIT_DISPATCH_CALLS |
|
853
|
+
COWAIT_INPUTAVAILABLE,
|
854
|
+
INFINITE, 1, hs, &i);
|
855
|
+
}
|
856
|
+
m_webview = op.GetResults();
|
857
|
+
m_webview.Settings().IsScriptNotifyAllowed(true);
|
858
|
+
m_webview.IsVisible(true);
|
859
|
+
m_webview.ScriptNotify([=](auto const &sender, auto const &args) {
|
860
|
+
std::string s = winrt::to_string(args.Value());
|
861
|
+
cb(s.c_str());
|
862
|
+
});
|
863
|
+
m_webview.NavigationStarting([=](auto const &sender, auto const &args) {
|
864
|
+
m_webview.AddInitializeScript(winrt::to_hstring(init_js));
|
865
|
+
});
|
866
|
+
init("window.external.invoke = s => window.external.notify(s)");
|
867
|
+
return true;
|
868
|
+
}
|
869
|
+
|
870
|
+
void navigate(const std::string url) override {
|
871
|
+
std::string html = html_from_uri(url);
|
872
|
+
if (html != "") {
|
873
|
+
m_webview.NavigateToString(winrt::to_hstring(html));
|
874
|
+
} else {
|
875
|
+
Uri uri(winrt::to_hstring(url));
|
876
|
+
m_webview.Navigate(uri);
|
877
|
+
}
|
878
|
+
}
|
879
|
+
|
880
|
+
void init(const std::string js) override {
|
881
|
+
init_js = init_js + "(function(){" + js + "})();";
|
882
|
+
}
|
883
|
+
|
884
|
+
void eval(const std::string js) override {
|
885
|
+
m_webview.InvokeScriptAsync(
|
886
|
+
L"eval", single_threaded_vector<hstring>({winrt::to_hstring(js)}));
|
887
|
+
}
|
888
|
+
|
889
|
+
void resize(HWND wnd) override {
|
890
|
+
if (m_webview == nullptr) {
|
891
|
+
return;
|
892
|
+
}
|
893
|
+
RECT r;
|
894
|
+
GetClientRect(wnd, &r);
|
895
|
+
Rect bounds(r.left, r.top, r.right - r.left, r.bottom - r.top);
|
896
|
+
m_webview.Bounds(bounds);
|
897
|
+
}
|
898
|
+
|
899
|
+
private:
|
900
|
+
WebViewControl m_webview = nullptr;
|
901
|
+
std::string init_js = "";
|
902
|
+
};
|
903
|
+
|
904
|
+
//
|
905
|
+
// Edge/Chromium browser engine
|
906
|
+
//
|
907
|
+
class edge_chromium : public browser {
|
908
|
+
public:
|
909
|
+
bool embed(HWND wnd, bool debug, msg_cb_t cb) override {
|
910
|
+
CoInitializeEx(nullptr, 0);
|
911
|
+
std::atomic_flag flag = ATOMIC_FLAG_INIT;
|
912
|
+
flag.test_and_set();
|
913
|
+
|
914
|
+
char currentExePath[MAX_PATH];
|
915
|
+
GetModuleFileNameA(NULL, currentExePath, MAX_PATH);
|
916
|
+
char *currentExeName = PathFindFileNameA(currentExePath);
|
917
|
+
|
918
|
+
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> wideCharConverter;
|
919
|
+
std::wstring userDataFolder =
|
920
|
+
wideCharConverter.from_bytes(std::getenv("APPDATA"));
|
921
|
+
std::wstring currentExeNameW = wideCharConverter.from_bytes(currentExeName);
|
922
|
+
|
923
|
+
HRESULT res = CreateCoreWebView2EnvironmentWithOptions(
|
924
|
+
nullptr, (userDataFolder + L"/" + currentExeNameW).c_str(), nullptr,
|
925
|
+
new webview2_com_handler(wnd, cb,
|
926
|
+
[&](ICoreWebView2Controller *controller) {
|
927
|
+
m_controller = controller;
|
928
|
+
m_controller->get_CoreWebView2(&m_webview);
|
929
|
+
m_webview->AddRef();
|
930
|
+
flag.clear();
|
931
|
+
}));
|
932
|
+
if (res != S_OK) {
|
933
|
+
CoUninitialize();
|
934
|
+
return false;
|
935
|
+
}
|
936
|
+
MSG msg = {};
|
937
|
+
while (flag.test_and_set() && GetMessage(&msg, NULL, 0, 0)) {
|
938
|
+
TranslateMessage(&msg);
|
939
|
+
DispatchMessage(&msg);
|
940
|
+
}
|
941
|
+
init("window.external={invoke:s=>window.chrome.webview.postMessage(s)}");
|
942
|
+
return true;
|
943
|
+
}
|
944
|
+
|
945
|
+
void resize(HWND wnd) override {
|
946
|
+
if (m_controller == nullptr) {
|
947
|
+
return;
|
948
|
+
}
|
949
|
+
RECT bounds;
|
950
|
+
GetClientRect(wnd, &bounds);
|
951
|
+
m_controller->put_Bounds(bounds);
|
952
|
+
}
|
953
|
+
|
954
|
+
void navigate(const std::string url) override {
|
955
|
+
auto wurl = to_lpwstr(url);
|
956
|
+
m_webview->Navigate(wurl);
|
957
|
+
delete[] wurl;
|
958
|
+
}
|
959
|
+
|
960
|
+
void init(const std::string js) override {
|
961
|
+
LPCWSTR wjs = to_lpwstr(js);
|
962
|
+
m_webview->AddScriptToExecuteOnDocumentCreated(wjs, nullptr);
|
963
|
+
delete[] wjs;
|
964
|
+
}
|
965
|
+
|
966
|
+
void eval(const std::string js) override {
|
967
|
+
LPCWSTR wjs = to_lpwstr(js);
|
968
|
+
m_webview->ExecuteScript(wjs, nullptr);
|
969
|
+
delete[] wjs;
|
970
|
+
}
|
971
|
+
|
972
|
+
private:
|
973
|
+
LPWSTR to_lpwstr(const std::string s) {
|
974
|
+
int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0);
|
975
|
+
wchar_t *ws = new wchar_t[n];
|
976
|
+
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, ws, n);
|
977
|
+
return ws;
|
978
|
+
}
|
979
|
+
|
980
|
+
ICoreWebView2 *m_webview = nullptr;
|
981
|
+
ICoreWebView2Controller *m_controller = nullptr;
|
982
|
+
|
983
|
+
class webview2_com_handler
|
984
|
+
: public ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler,
|
985
|
+
public ICoreWebView2CreateCoreWebView2ControllerCompletedHandler,
|
986
|
+
public ICoreWebView2WebMessageReceivedEventHandler,
|
987
|
+
public ICoreWebView2PermissionRequestedEventHandler {
|
988
|
+
using webview2_com_handler_cb_t =
|
989
|
+
std::function<void(ICoreWebView2Controller *)>;
|
990
|
+
|
991
|
+
public:
|
992
|
+
webview2_com_handler(HWND hwnd, msg_cb_t msgCb,
|
993
|
+
webview2_com_handler_cb_t cb)
|
994
|
+
: m_window(hwnd), m_msgCb(msgCb), m_cb(cb) {}
|
995
|
+
ULONG STDMETHODCALLTYPE AddRef() { return 1; }
|
996
|
+
ULONG STDMETHODCALLTYPE Release() { return 1; }
|
997
|
+
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppv) {
|
998
|
+
return S_OK;
|
999
|
+
}
|
1000
|
+
HRESULT STDMETHODCALLTYPE Invoke(HRESULT res,
|
1001
|
+
ICoreWebView2Environment *env) {
|
1002
|
+
env->CreateCoreWebView2Controller(m_window, this);
|
1003
|
+
return S_OK;
|
1004
|
+
}
|
1005
|
+
HRESULT STDMETHODCALLTYPE Invoke(HRESULT res,
|
1006
|
+
ICoreWebView2Controller *controller) {
|
1007
|
+
controller->AddRef();
|
1008
|
+
|
1009
|
+
ICoreWebView2 *webview;
|
1010
|
+
::EventRegistrationToken token;
|
1011
|
+
controller->get_CoreWebView2(&webview);
|
1012
|
+
webview->add_WebMessageReceived(this, &token);
|
1013
|
+
webview->add_PermissionRequested(this, &token);
|
1014
|
+
|
1015
|
+
m_cb(controller);
|
1016
|
+
return S_OK;
|
1017
|
+
}
|
1018
|
+
HRESULT STDMETHODCALLTYPE Invoke(
|
1019
|
+
ICoreWebView2 *sender, ICoreWebView2WebMessageReceivedEventArgs *args) {
|
1020
|
+
LPWSTR message;
|
1021
|
+
args->TryGetWebMessageAsString(&message);
|
1022
|
+
|
1023
|
+
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> wideCharConverter;
|
1024
|
+
m_msgCb(wideCharConverter.to_bytes(message));
|
1025
|
+
sender->PostWebMessageAsString(message);
|
1026
|
+
|
1027
|
+
CoTaskMemFree(message);
|
1028
|
+
return S_OK;
|
1029
|
+
}
|
1030
|
+
HRESULT STDMETHODCALLTYPE
|
1031
|
+
Invoke(ICoreWebView2 *sender,
|
1032
|
+
ICoreWebView2PermissionRequestedEventArgs *args) {
|
1033
|
+
COREWEBVIEW2_PERMISSION_KIND kind;
|
1034
|
+
args->get_PermissionKind(&kind);
|
1035
|
+
if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ) {
|
1036
|
+
args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW);
|
1037
|
+
}
|
1038
|
+
return S_OK;
|
1039
|
+
}
|
1040
|
+
|
1041
|
+
private:
|
1042
|
+
HWND m_window;
|
1043
|
+
msg_cb_t m_msgCb;
|
1044
|
+
webview2_com_handler_cb_t m_cb;
|
1045
|
+
};
|
1046
|
+
};
|
1047
|
+
|
1048
|
+
class win32_edge_engine {
|
1049
|
+
public:
|
1050
|
+
win32_edge_engine(bool debug, void *window) {
|
1051
|
+
if (window == nullptr) {
|
1052
|
+
HINSTANCE hInstance = GetModuleHandle(nullptr);
|
1053
|
+
HICON icon = (HICON)LoadImage(
|
1054
|
+
hInstance, IDI_APPLICATION, IMAGE_ICON, GetSystemMetrics(SM_CXSMICON),
|
1055
|
+
GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);
|
1056
|
+
|
1057
|
+
WNDCLASSEX wc;
|
1058
|
+
ZeroMemory(&wc, sizeof(WNDCLASSEX));
|
1059
|
+
wc.cbSize = sizeof(WNDCLASSEX);
|
1060
|
+
wc.hInstance = hInstance;
|
1061
|
+
wc.lpszClassName = "webview";
|
1062
|
+
wc.hIcon = icon;
|
1063
|
+
wc.hIconSm = icon;
|
1064
|
+
wc.lpfnWndProc =
|
1065
|
+
(WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> int {
|
1066
|
+
auto w = (win32_edge_engine *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
1067
|
+
switch (msg) {
|
1068
|
+
case WM_SIZE:
|
1069
|
+
w->m_browser->resize(hwnd);
|
1070
|
+
break;
|
1071
|
+
case WM_CLOSE:
|
1072
|
+
DestroyWindow(hwnd);
|
1073
|
+
break;
|
1074
|
+
case WM_DESTROY:
|
1075
|
+
w->terminate();
|
1076
|
+
break;
|
1077
|
+
case WM_GETMINMAXINFO: {
|
1078
|
+
auto lpmmi = (LPMINMAXINFO)lp;
|
1079
|
+
if (w == nullptr) {
|
1080
|
+
return 0;
|
1081
|
+
}
|
1082
|
+
if (w->m_maxsz.x > 0 && w->m_maxsz.y > 0) {
|
1083
|
+
lpmmi->ptMaxSize = w->m_maxsz;
|
1084
|
+
lpmmi->ptMaxTrackSize = w->m_maxsz;
|
1085
|
+
}
|
1086
|
+
if (w->m_minsz.x > 0 && w->m_minsz.y > 0) {
|
1087
|
+
lpmmi->ptMinTrackSize = w->m_minsz;
|
1088
|
+
}
|
1089
|
+
} break;
|
1090
|
+
default:
|
1091
|
+
return DefWindowProc(hwnd, msg, wp, lp);
|
1092
|
+
}
|
1093
|
+
return 0;
|
1094
|
+
});
|
1095
|
+
RegisterClassEx(&wc);
|
1096
|
+
m_window = CreateWindow("webview", "", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
|
1097
|
+
CW_USEDEFAULT, 640, 480, nullptr, nullptr,
|
1098
|
+
GetModuleHandle(nullptr), nullptr);
|
1099
|
+
SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this);
|
1100
|
+
} else {
|
1101
|
+
m_window = *(static_cast<HWND *>(window));
|
1102
|
+
}
|
1103
|
+
|
1104
|
+
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
|
1105
|
+
ShowWindow(m_window, SW_SHOW);
|
1106
|
+
UpdateWindow(m_window);
|
1107
|
+
SetFocus(m_window);
|
1108
|
+
|
1109
|
+
auto cb =
|
1110
|
+
std::bind(&win32_edge_engine::on_message, this, std::placeholders::_1);
|
1111
|
+
|
1112
|
+
if (!m_browser->embed(m_window, debug, cb)) {
|
1113
|
+
m_browser = std::make_unique<webview::edge_html>();
|
1114
|
+
m_browser->embed(m_window, debug, cb);
|
1115
|
+
}
|
1116
|
+
|
1117
|
+
m_browser->resize(m_window);
|
1118
|
+
}
|
1119
|
+
|
1120
|
+
void run() {
|
1121
|
+
MSG msg;
|
1122
|
+
BOOL res;
|
1123
|
+
while ((res = GetMessage(&msg, nullptr, 0, 0)) != -1) {
|
1124
|
+
if (msg.hwnd) {
|
1125
|
+
TranslateMessage(&msg);
|
1126
|
+
DispatchMessage(&msg);
|
1127
|
+
continue;
|
1128
|
+
}
|
1129
|
+
if (msg.message == WM_APP) {
|
1130
|
+
auto f = (dispatch_fn_t *)(msg.lParam);
|
1131
|
+
(*f)();
|
1132
|
+
delete f;
|
1133
|
+
} else if (msg.message == WM_QUIT) {
|
1134
|
+
return;
|
1135
|
+
}
|
1136
|
+
}
|
1137
|
+
}
|
1138
|
+
void *window() { return (void *)m_window; }
|
1139
|
+
void terminate() { PostQuitMessage(0); }
|
1140
|
+
void dispatch(dispatch_fn_t f) {
|
1141
|
+
PostThreadMessage(m_main_thread, WM_APP, 0, (LPARAM) new dispatch_fn_t(f));
|
1142
|
+
}
|
1143
|
+
|
1144
|
+
void set_title(const std::string title) {
|
1145
|
+
SetWindowText(m_window, title.c_str());
|
1146
|
+
}
|
1147
|
+
|
1148
|
+
void set_size(int width, int height, int hints) {
|
1149
|
+
auto style = GetWindowLong(m_window, GWL_STYLE);
|
1150
|
+
if (hints == WEBVIEW_HINT_FIXED) {
|
1151
|
+
style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
|
1152
|
+
} else {
|
1153
|
+
style |= (WS_THICKFRAME | WS_MAXIMIZEBOX);
|
1154
|
+
}
|
1155
|
+
SetWindowLong(m_window, GWL_STYLE, style);
|
1156
|
+
|
1157
|
+
if (hints == WEBVIEW_HINT_MAX) {
|
1158
|
+
m_maxsz.x = width;
|
1159
|
+
m_maxsz.y = height;
|
1160
|
+
} else if (hints == WEBVIEW_HINT_MIN) {
|
1161
|
+
m_minsz.x = width;
|
1162
|
+
m_minsz.y = height;
|
1163
|
+
} else {
|
1164
|
+
RECT r;
|
1165
|
+
r.left = r.top = 0;
|
1166
|
+
r.right = width;
|
1167
|
+
r.bottom = height;
|
1168
|
+
AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, 0);
|
1169
|
+
SetWindowPos(
|
1170
|
+
m_window, NULL, r.left, r.top, r.right - r.left, r.bottom - r.top,
|
1171
|
+
SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE | SWP_FRAMECHANGED);
|
1172
|
+
m_browser->resize(m_window);
|
1173
|
+
}
|
1174
|
+
}
|
1175
|
+
|
1176
|
+
void navigate(const std::string url) { m_browser->navigate(url); }
|
1177
|
+
void eval(const std::string js) { m_browser->eval(js); }
|
1178
|
+
void init(const std::string js) { m_browser->init(js); }
|
1179
|
+
|
1180
|
+
private:
|
1181
|
+
virtual void on_message(const std::string msg) = 0;
|
1182
|
+
|
1183
|
+
HWND m_window;
|
1184
|
+
POINT m_minsz = POINT{0, 0};
|
1185
|
+
POINT m_maxsz = POINT{0, 0};
|
1186
|
+
DWORD m_main_thread = GetCurrentThreadId();
|
1187
|
+
std::unique_ptr<webview::browser> m_browser =
|
1188
|
+
std::make_unique<webview::edge_chromium>();
|
1189
|
+
};
|
1190
|
+
|
1191
|
+
using browser_engine = win32_edge_engine;
|
1192
|
+
} // namespace webview
|
1193
|
+
|
1194
|
+
#endif /* WEBVIEW_GTK, WEBVIEW_COCOA, WEBVIEW_EDGE */
|
1195
|
+
|
1196
|
+
namespace webview {
|
1197
|
+
|
1198
|
+
class webview : public browser_engine {
|
1199
|
+
public:
|
1200
|
+
webview(bool debug = false, void *wnd = nullptr)
|
1201
|
+
: browser_engine(debug, wnd) {}
|
1202
|
+
|
1203
|
+
void navigate(const std::string url) {
|
1204
|
+
if (url == "") {
|
1205
|
+
browser_engine::navigate("data:text/html," +
|
1206
|
+
url_encode("<html><body>Hello</body></html>"));
|
1207
|
+
return;
|
1208
|
+
}
|
1209
|
+
std::string html = html_from_uri(url);
|
1210
|
+
if (html != "") {
|
1211
|
+
browser_engine::navigate("data:text/html," + url_encode(html));
|
1212
|
+
} else {
|
1213
|
+
browser_engine::navigate(url);
|
1214
|
+
}
|
1215
|
+
}
|
1216
|
+
|
1217
|
+
using binding_t = std::function<void(std::string, std::string, void *)>;
|
1218
|
+
using binding_ctx_t = std::pair<binding_t *, void *>;
|
1219
|
+
|
1220
|
+
using sync_binding_t = std::function<std::string(std::string)>;
|
1221
|
+
using sync_binding_ctx_t = std::pair<webview *, sync_binding_t>;
|
1222
|
+
|
1223
|
+
void bind(const std::string name, sync_binding_t fn) {
|
1224
|
+
bind(
|
1225
|
+
name,
|
1226
|
+
[](std::string seq, std::string req, void *arg) {
|
1227
|
+
auto pair = static_cast<sync_binding_ctx_t *>(arg);
|
1228
|
+
pair->first->resolve(seq, 0, pair->second(req));
|
1229
|
+
},
|
1230
|
+
new sync_binding_ctx_t(this, fn));
|
1231
|
+
}
|
1232
|
+
|
1233
|
+
void bind(const std::string name, binding_t f, void *arg) {
|
1234
|
+
auto js = "(function() { var name = '" + name + "';" + R"(
|
1235
|
+
var RPC = window._rpc = (window._rpc || {nextSeq: 1});
|
1236
|
+
window[name] = function() {
|
1237
|
+
var seq = RPC.nextSeq++;
|
1238
|
+
var promise = new Promise(function(resolve, reject) {
|
1239
|
+
RPC[seq] = {
|
1240
|
+
resolve: resolve,
|
1241
|
+
reject: reject,
|
1242
|
+
};
|
1243
|
+
});
|
1244
|
+
window.external.invoke(JSON.stringify({
|
1245
|
+
id: seq,
|
1246
|
+
method: name,
|
1247
|
+
params: Array.prototype.slice.call(arguments),
|
1248
|
+
}));
|
1249
|
+
return promise;
|
1250
|
+
}
|
1251
|
+
})())";
|
1252
|
+
init(js);
|
1253
|
+
bindings[name] = new binding_ctx_t(new binding_t(f), arg);
|
1254
|
+
}
|
1255
|
+
|
1256
|
+
void resolve(const std::string seq, int status, const std::string result) {
|
1257
|
+
dispatch([=]() {
|
1258
|
+
if (status == 0) {
|
1259
|
+
eval("window._rpc[" + seq + "].resolve(" + result + "); window._rpc[" +
|
1260
|
+
seq + "] = undefined");
|
1261
|
+
} else {
|
1262
|
+
eval("window._rpc[" + seq + "].reject(" + result + "); window._rpc[" +
|
1263
|
+
seq + "] = undefined");
|
1264
|
+
}
|
1265
|
+
});
|
1266
|
+
}
|
1267
|
+
|
1268
|
+
private:
|
1269
|
+
void on_message(const std::string msg) {
|
1270
|
+
auto seq = json_parse(msg, "id", 0);
|
1271
|
+
auto name = json_parse(msg, "method", 0);
|
1272
|
+
auto args = json_parse(msg, "params", 0);
|
1273
|
+
if (bindings.find(name) == bindings.end()) {
|
1274
|
+
return;
|
1275
|
+
}
|
1276
|
+
auto fn = bindings[name];
|
1277
|
+
(*fn->first)(seq, args, fn->second);
|
1278
|
+
}
|
1279
|
+
std::map<std::string, binding_ctx_t *> bindings;
|
1280
|
+
};
|
1281
|
+
} // namespace webview
|
1282
|
+
|
1283
|
+
WEBVIEW_API webview_t webview_create(int debug, void *wnd) {
|
1284
|
+
return new webview::webview(debug, wnd);
|
1285
|
+
}
|
1286
|
+
|
1287
|
+
WEBVIEW_API void webview_destroy(webview_t w) {
|
1288
|
+
delete static_cast<webview::webview *>(w);
|
1289
|
+
}
|
1290
|
+
|
1291
|
+
WEBVIEW_API void webview_run(webview_t w) {
|
1292
|
+
static_cast<webview::webview *>(w)->run();
|
1293
|
+
}
|
1294
|
+
|
1295
|
+
WEBVIEW_API void webview_terminate(webview_t w) {
|
1296
|
+
static_cast<webview::webview *>(w)->terminate();
|
1297
|
+
}
|
1298
|
+
|
1299
|
+
WEBVIEW_API void webview_dispatch(webview_t w, void (*fn)(webview_t, void *),
|
1300
|
+
void *arg) {
|
1301
|
+
static_cast<webview::webview *>(w)->dispatch([=]() { fn(w, arg); });
|
1302
|
+
}
|
1303
|
+
|
1304
|
+
WEBVIEW_API void *webview_get_window(webview_t w) {
|
1305
|
+
return static_cast<webview::webview *>(w)->window();
|
1306
|
+
}
|
1307
|
+
|
1308
|
+
WEBVIEW_API void webview_set_title(webview_t w, const char *title) {
|
1309
|
+
static_cast<webview::webview *>(w)->set_title(title);
|
1310
|
+
}
|
1311
|
+
|
1312
|
+
WEBVIEW_API void webview_set_size(webview_t w, int width, int height,
|
1313
|
+
int hints) {
|
1314
|
+
static_cast<webview::webview *>(w)->set_size(width, height, hints);
|
1315
|
+
}
|
1316
|
+
|
1317
|
+
WEBVIEW_API void webview_navigate(webview_t w, const char *url) {
|
1318
|
+
static_cast<webview::webview *>(w)->navigate(url);
|
1319
|
+
}
|
1320
|
+
|
1321
|
+
WEBVIEW_API void webview_init(webview_t w, const char *js) {
|
1322
|
+
static_cast<webview::webview *>(w)->init(js);
|
1323
|
+
}
|
1324
|
+
|
1325
|
+
WEBVIEW_API void webview_eval(webview_t w, const char *js) {
|
1326
|
+
static_cast<webview::webview *>(w)->eval(js);
|
1327
|
+
}
|
1328
|
+
|
1329
|
+
WEBVIEW_API void webview_bind(webview_t w, const char *name,
|
1330
|
+
void (*fn)(const char *seq, const char *req,
|
1331
|
+
void *arg),
|
1332
|
+
void *arg) {
|
1333
|
+
static_cast<webview::webview *>(w)->bind(
|
1334
|
+
name,
|
1335
|
+
[=](std::string seq, std::string req, void *arg) {
|
1336
|
+
fn(seq.c_str(), req.c_str(), arg);
|
1337
|
+
},
|
1338
|
+
arg);
|
1339
|
+
}
|
1340
|
+
|
1341
|
+
WEBVIEW_API void webview_return(webview_t w, const char *seq, int status,
|
1342
|
+
const char *result) {
|
1343
|
+
static_cast<webview::webview *>(w)->resolve(seq, status, result);
|
1344
|
+
}
|
1345
|
+
|
1346
|
+
#endif /* WEBVIEW_HEADER */
|
1347
|
+
|
1348
|
+
#endif /* WEBVIEW_H */
|