├── .gitignore ├── LICENSE ├── README.md ├── build_dll.sh ├── go.mod ├── libwebview2 └── build │ └── native │ ├── include │ ├── WebView2.h │ └── WebView2EnvironmentOptions.h │ └── x64 │ └── WebView2Loader.dll ├── main.cc ├── webview.cc ├── webview.go ├── webview.h ├── webview_darwin.h ├── webview_darwin.m ├── webview_linux.h ├── webview_test.cc ├── webview_test.go └── webview_win.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Build atrifacts 2 | /build 3 | webview 4 | webview_test 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 PoleVPN 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webview for golang and c/c++ 2 | 3 | A tiny cross-platform webview library for C/C++/Golang to build modern cross-platform GUIs. 4 | 5 | The goal of the project is to create a common HTML5 UI abstraction layer for the most widely used platforms. 6 | 7 | It supports two-way JavaScript bindings (to call JavaScript from C/C++/Go and to call C/C++/Go from JavaScript). 8 | 9 | It uses Cocoa/WebKit on macOS, gtk-webkit2 on Linux and Edge on Windows 10. 10 | 11 | ## Webview for Go developers 12 | 13 | If you are interested in writing Webview apps in C/C++, [skip to the next section](#webview-for-cc-developers). 14 | 15 | ### Getting started 16 | 17 | Install Webview library with `go get`: 18 | 19 | ``` 20 | $ go get github.com/polevpn/webview 21 | ``` 22 | 23 | Import the package and start using it: 24 | 25 | ```go 26 | package main 27 | 28 | import "github.com/polevpn/webview" 29 | 30 | func main() { 31 | debug := true 32 | w := webview.New(false,true) 33 | defer w.Destroy() 34 | w.SetTitle("Minimal webview example") 35 | w.SetSize(800, 600, webview.HintNone) 36 | w.Navigate("https://en.m.wikipedia.org/wiki/Main_Page") 37 | w.Run() 38 | } 39 | ``` 40 | 41 | To build the app use the following commands: 42 | 43 | ```bash 44 | # Linux 45 | $ go build -o webview-example && ./webview-example 46 | 47 | # MacOS uses app bundles for GUI apps 48 | $ mkdir -p example.app/Contents/MacOS 49 | $ go build -o example.app/Contents/MacOS/example 50 | $ open example.app # Or click on the app in Finder 51 | 52 | # Windows requires special linker flags for GUI apps. 53 | # It's also recommended to use (mingw) compiler for CGo 54 | # https://github.com/niXman/mingw-builds-binaries/releases use win32-seh-msvcrt-rt_v10 version 55 | $ go build -ldflags="-H windowsgui" -o webview-example.exe 56 | ``` 57 | 58 | ### Distributing webview apps 59 | 60 | On Linux you get a standalone executable. It will depend on GTK3 and GtkWebkit2, so if you distribute your app in DEB or RPM format include those dependencies. An application icon can be specified by providing a `.desktop` file. 61 | 62 | On MacOS you are likely to ship an app bundle. Make the following directory structure and just zip it: 63 | 64 | ``` 65 | example.app 66 | └── Contents 67 |    ├── Info.plist 68 |    ├── MacOS 69 |    | └── example 70 |    └── Resources 71 | └── example.icns 72 | ``` 73 | 74 | Here, `Info.plist` is a [property list file](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html) and `*.icns` is a special icon format. You may convert PNG to icns [online](https://iconverticons.com/online/). 75 | 76 | On Windows you probably would like to have a custom icon for your executable. It can be done by providing a resource file, compiling it and linking with it. Typically, `windres` utility is used to compile resources. Also, on Windows, `WebView2Loader.dll` must be placed into the same directory with your app executable. 77 | 78 | ## Webview for C/C++ developers 79 | 80 | ### C++: 81 | ```c 82 | // main.cc 83 | #include "webview.h" 84 | #ifdef WIN32 85 | int WINAPI WinMain(HINSTANCE hInt, HINSTANCE hPrevInst, LPSTR lpCmdLine, 86 | int nCmdShow) { 87 | #else 88 | int main() { 89 | #endif 90 | webview::webview w(false,true, nullptr); 91 | w.set_title("Minimal example"); 92 | w.set_size(480, 320, WEBVIEW_HINT_NONE); 93 | w.navigate("https://en.m.wikipedia.org/wiki/Main_Page"); 94 | w.run(); 95 | return 0; 96 | } 97 | ``` 98 | Build it: 99 | 100 | ```bash 101 | # Linux 102 | $ g++ main.cc `pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0` -o webview-example 103 | # MacOS 104 | $ g++ main.cc -std=c++11 -framework WebKit -o webview-example 105 | # Windows (x64) 106 | $ g++ main.cc -std=c++11 -mwindows -o webview-example.exe -I./libwebview2/build/native/include -Wl,-Bstatic -lstdc++ -lgcc_eh -lpthread -Wl,-Bdynamic -lole32 -lShlwapi -L./libwebview2/build/native/x64 -lWebView2Loader 107 | ``` 108 | 109 | ### C: 110 | ```c 111 | // main .c 112 | #include "webview.h" 113 | #ifdef WIN32 114 | int WINAPI WinMain(HINSTANCE hInt, HINSTANCE hPrevInst, LPSTR lpCmdLine, 115 | int nCmdShow) { 116 | #else 117 | int main() { 118 | #endif 119 | webview_t w = webview_create(0,0, NULL); 120 | webview_set_title(w, "Webview Example"); 121 | webview_set_size(w, 480, 320, WEBVIEW_HINT_NONE); 122 | webview_navigate(w, "https://en.m.wikipedia.org/wiki/Main_Page"); 123 | webview_run(w); 124 | webview_destroy(w); 125 | return 0; 126 | } 127 | ``` 128 | Build it: 129 | 130 | ```bash 131 | # Linux 132 | $ g++ main.c `pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0` -o webview-example 133 | # MacOS 134 | $ g++ main.c -std=c++11 -framework WebKit -o webview-example 135 | # Windows (x64) 136 | $ g++ main.c -mwindows -o webview-example.exe -I./libwebview2/build/native/include -lole32 -lShlwapi -L./libwebview2/build/native/x64 -lWebView2Loader 137 | ``` 138 | 139 | On Windows it is possible to use webview library directly when compiling with cgo, but WebView2Loader.dll is still required. To use MinGW you may dynamically link prebuilt webview.dll (this approach is used in Cgo bindings). 140 | 141 | Full C/C++ API is described at the top of the `webview.h` file. 142 | 143 | ## Notes 144 | 145 | Execution on OpenBSD requires `wxallowed` [mount(8)](https://man.openbsd.org/mount.8) option. 146 | For Ubuntu Users run `sudo apt install webkit2gtk-4.0`(Try with webkit2gtk-4.0-dev if webkit2gtk-4.0 is not found) to install webkit2gtk-4.0 related items. 147 | FreeBSD is also supported, to install webkit2 run `pkg install webkit2-gtk3`. 148 | 149 | ## License 150 | 151 | Code is distributed under MIT license, feel free to use it in your proprietary 152 | projects as well. 153 | 154 | -------------------------------------------------------------------------------- /build_dll.sh: -------------------------------------------------------------------------------- 1 | g++ -o dll/x64/webview.dll -shared -DBUILD_DLL webview.cc -I./libwebview2/build/native/include -lole32 -lShlwapi -L./libwebview2/build/native/x64 -lWebView2Loader 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/polevpn/webview 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /libwebview2/build/native/include/WebView2EnvironmentOptions.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) Microsoft Corporation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | #ifndef __core_webview2_environment_options_h__ 6 | #define __core_webview2_environment_options_h__ 7 | 8 | #include 9 | #include 10 | 11 | #include "WebView2.h" 12 | #define CORE_WEBVIEW_TARGET_PRODUCT_VERSION L"131.0.2903.40" 13 | 14 | #define COREWEBVIEW2ENVIRONMENTOPTIONS_STRING_PROPERTY(p) \ 15 | public: \ 16 | HRESULT STDMETHODCALLTYPE get_##p(LPWSTR* value) override { \ 17 | if (!value) \ 18 | return E_POINTER; \ 19 | *value = m_##p.Copy(); \ 20 | if ((*value == nullptr) && (m_##p.Get() != nullptr)) \ 21 | return HRESULT_FROM_WIN32(GetLastError()); \ 22 | return S_OK; \ 23 | } \ 24 | HRESULT STDMETHODCALLTYPE put_##p(LPCWSTR value) override { \ 25 | LPCWSTR result = m_##p.Set(value); \ 26 | if ((result == nullptr) && (value != nullptr)) \ 27 | return HRESULT_FROM_WIN32(GetLastError()); \ 28 | return S_OK; \ 29 | } \ 30 | \ 31 | protected: \ 32 | AutoCoMemString m_##p; 33 | 34 | #define COREWEBVIEW2ENVIRONMENTOPTIONS_BOOL_PROPERTY(p, defPVal) \ 35 | public: \ 36 | HRESULT STDMETHODCALLTYPE get_##p(BOOL* value) override { \ 37 | if (!value) \ 38 | return E_POINTER; \ 39 | *value = m_##p; \ 40 | return S_OK; \ 41 | } \ 42 | HRESULT STDMETHODCALLTYPE put_##p(BOOL value) override { \ 43 | m_##p = value; \ 44 | return S_OK; \ 45 | } \ 46 | \ 47 | protected: \ 48 | BOOL m_##p = defPVal ? TRUE : FALSE; 49 | 50 | #define DEFINE_AUTO_COMEM_STRING() \ 51 | protected: \ 52 | class AutoCoMemString { \ 53 | public: \ 54 | AutoCoMemString() {} \ 55 | ~AutoCoMemString() { Release(); } \ 56 | void Release() { \ 57 | if (m_string) { \ 58 | deallocate_fn(m_string); \ 59 | m_string = nullptr; \ 60 | } \ 61 | } \ 62 | \ 63 | LPCWSTR Set(LPCWSTR str) { \ 64 | Release(); \ 65 | if (str) { \ 66 | m_string = MakeCoMemString(str); \ 67 | } \ 68 | return m_string; \ 69 | } \ 70 | LPCWSTR Get() { return m_string; } \ 71 | LPWSTR Copy() { \ 72 | if (m_string) \ 73 | return MakeCoMemString(m_string); \ 74 | return nullptr; \ 75 | } \ 76 | \ 77 | protected: \ 78 | LPWSTR MakeCoMemString(LPCWSTR source) { \ 79 | const size_t length = wcslen(source); \ 80 | const size_t bytes = (length + 1) * sizeof(*source); \ 81 | \ 82 | if (bytes <= length) { \ 83 | return nullptr; \ 84 | } \ 85 | \ 86 | wchar_t* result = reinterpret_cast(allocate_fn(bytes)); \ 87 | \ 88 | if (result) \ 89 | memcpy(result, source, bytes); \ 90 | return result; \ 91 | } \ 92 | LPWSTR m_string = nullptr; \ 93 | }; 94 | 95 | template 99 | class CoreWebView2CustomSchemeRegistrationBase 100 | : public Microsoft::WRL::RuntimeClass< 101 | Microsoft::WRL::RuntimeClassFlags, 102 | ICoreWebView2CustomSchemeRegistration> { 103 | public: 104 | CoreWebView2CustomSchemeRegistrationBase(LPCWSTR schemeName) { 105 | m_schemeName.Set(schemeName); 106 | } 107 | 108 | CoreWebView2CustomSchemeRegistrationBase( 109 | CoreWebView2CustomSchemeRegistrationBase&&) = default; 110 | ~CoreWebView2CustomSchemeRegistrationBase() { ReleaseAllowedOrigins(); } 111 | 112 | HRESULT STDMETHODCALLTYPE get_SchemeName(LPWSTR* schemeName) override { 113 | if (!schemeName) 114 | return E_POINTER; 115 | *schemeName = m_schemeName.Copy(); 116 | if ((*schemeName == nullptr) && (m_schemeName.Get() != nullptr)) 117 | return HRESULT_FROM_WIN32(GetLastError()); 118 | return S_OK; 119 | } 120 | 121 | HRESULT STDMETHODCALLTYPE 122 | GetAllowedOrigins(UINT32* allowedOriginsCount, 123 | LPWSTR** allowedOrigins) override { 124 | if (!allowedOrigins || !allowedOriginsCount) { 125 | return E_POINTER; 126 | } 127 | *allowedOriginsCount = 0; 128 | if (m_allowedOriginsCount == 0) { 129 | *allowedOrigins = nullptr; 130 | return S_OK; 131 | } else { 132 | *allowedOrigins = reinterpret_cast( 133 | allocate_fn(m_allowedOriginsCount * sizeof(LPWSTR))); 134 | if (!(*allowedOrigins)) { 135 | return HRESULT_FROM_WIN32(GetLastError()); 136 | } 137 | ZeroMemory(*allowedOrigins, m_allowedOriginsCount * sizeof(LPWSTR)); 138 | for (UINT32 i = 0; i < m_allowedOriginsCount; i++) { 139 | (*allowedOrigins)[i] = m_allowedOrigins[i].Copy(); 140 | if (!(*allowedOrigins)[i]) { 141 | HRESULT hr = HRESULT_FROM_WIN32(GetLastError()); 142 | for (UINT32 j = 0; j < i; j++) { 143 | deallocate_fn((*allowedOrigins)[j]); 144 | } 145 | deallocate_fn(*allowedOrigins); 146 | return hr; 147 | } 148 | } 149 | *allowedOriginsCount = m_allowedOriginsCount; 150 | return S_OK; 151 | } 152 | } 153 | 154 | HRESULT STDMETHODCALLTYPE 155 | SetAllowedOrigins(UINT32 allowedOriginsCount, 156 | LPCWSTR* allowedOrigins) override { 157 | ReleaseAllowedOrigins(); 158 | if (allowedOriginsCount == 0) { 159 | return S_OK; 160 | } else { 161 | m_allowedOrigins = new AutoCoMemString[allowedOriginsCount]; 162 | if (!m_allowedOrigins) { 163 | return HRESULT_FROM_WIN32(GetLastError()); 164 | } 165 | for (UINT32 i = 0; i < allowedOriginsCount; i++) { 166 | m_allowedOrigins[i].Set(allowedOrigins[i]); 167 | if (!m_allowedOrigins[i].Get()) { 168 | HRESULT hr = HRESULT_FROM_WIN32(GetLastError()); 169 | for (UINT32 j = 0; j < i; j++) { 170 | m_allowedOrigins[j].Release(); 171 | } 172 | m_allowedOriginsCount = 0; 173 | delete[] (m_allowedOrigins); 174 | return hr; 175 | } 176 | } 177 | m_allowedOriginsCount = allowedOriginsCount; 178 | return S_OK; 179 | } 180 | } 181 | 182 | protected: 183 | DEFINE_AUTO_COMEM_STRING(); 184 | 185 | void ReleaseAllowedOrigins() { 186 | if (m_allowedOrigins) { 187 | delete[] (m_allowedOrigins); 188 | m_allowedOrigins = nullptr; 189 | } 190 | } 191 | 192 | AutoCoMemString m_schemeName; 193 | COREWEBVIEW2ENVIRONMENTOPTIONS_BOOL_PROPERTY(TreatAsSecure, false); 194 | COREWEBVIEW2ENVIRONMENTOPTIONS_BOOL_PROPERTY(HasAuthorityComponent, false); 195 | AutoCoMemString* m_allowedOrigins = nullptr; 196 | unsigned int m_allowedOriginsCount = 0; 197 | }; 198 | 199 | // This is a base COM class that implements ICoreWebView2EnvironmentOptions. 200 | template 204 | class CoreWebView2EnvironmentOptionsBase 205 | : public Microsoft::WRL::Implements< 206 | Microsoft::WRL::RuntimeClassFlags, 207 | ICoreWebView2EnvironmentOptions, 208 | ICoreWebView2EnvironmentOptions2, 209 | ICoreWebView2EnvironmentOptions3, 210 | ICoreWebView2EnvironmentOptions4, 211 | ICoreWebView2EnvironmentOptions5, 212 | ICoreWebView2EnvironmentOptions6, 213 | ICoreWebView2EnvironmentOptions7, 214 | ICoreWebView2EnvironmentOptions8> { 215 | public: 216 | static const COREWEBVIEW2_RELEASE_CHANNELS kInternalChannel = 217 | static_cast(1 << 4); 218 | static const COREWEBVIEW2_RELEASE_CHANNELS kAllChannels = 219 | COREWEBVIEW2_RELEASE_CHANNELS_STABLE | 220 | COREWEBVIEW2_RELEASE_CHANNELS_BETA | COREWEBVIEW2_RELEASE_CHANNELS_DEV | 221 | COREWEBVIEW2_RELEASE_CHANNELS_CANARY | kInternalChannel; 222 | 223 | CoreWebView2EnvironmentOptionsBase() { 224 | // Initialize the target compatible browser version value to the version 225 | // of the browser binaries corresponding to this version of the SDK. 226 | m_TargetCompatibleBrowserVersion.Set(CORE_WEBVIEW_TARGET_PRODUCT_VERSION); 227 | } 228 | 229 | // ICoreWebView2EnvironmentOptions7 230 | HRESULT STDMETHODCALLTYPE 231 | get_ReleaseChannels(COREWEBVIEW2_RELEASE_CHANNELS* channels) override { 232 | if (!channels) { 233 | return E_POINTER; 234 | } 235 | *channels = m_releaseChannels; 236 | return S_OK; 237 | } 238 | 239 | HRESULT STDMETHODCALLTYPE 240 | put_ReleaseChannels(COREWEBVIEW2_RELEASE_CHANNELS channels) override { 241 | m_releaseChannels = channels; 242 | return S_OK; 243 | } 244 | 245 | HRESULT STDMETHODCALLTYPE 246 | get_ChannelSearchKind(COREWEBVIEW2_CHANNEL_SEARCH_KIND* value) override { 247 | if (!value) { 248 | return E_POINTER; 249 | } 250 | *value = m_channelSearchKind; 251 | return S_OK; 252 | } 253 | 254 | HRESULT STDMETHODCALLTYPE 255 | put_ChannelSearchKind(COREWEBVIEW2_CHANNEL_SEARCH_KIND value) override { 256 | m_channelSearchKind = value; 257 | return S_OK; 258 | } 259 | 260 | // ICoreWebView2EnvironmentOptions8 261 | HRESULT STDMETHODCALLTYPE 262 | get_ScrollBarStyle(COREWEBVIEW2_SCROLLBAR_STYLE* style) override { 263 | if (!style) { 264 | return E_POINTER; 265 | } 266 | *style = m_scrollbarStyle; 267 | return S_OK; 268 | } 269 | 270 | HRESULT STDMETHODCALLTYPE 271 | put_ScrollBarStyle(COREWEBVIEW2_SCROLLBAR_STYLE style) override { 272 | m_scrollbarStyle = style; 273 | return S_OK; 274 | } 275 | 276 | protected: 277 | ~CoreWebView2EnvironmentOptionsBase() { ReleaseCustomSchemeRegistrations(); }; 278 | 279 | void ReleaseCustomSchemeRegistrations() { 280 | if (m_customSchemeRegistrations) { 281 | for (UINT32 i = 0; i < m_customSchemeRegistrationsCount; i++) { 282 | m_customSchemeRegistrations[i]->Release(); 283 | } 284 | deallocate_fn(m_customSchemeRegistrations); 285 | m_customSchemeRegistrations = nullptr; 286 | m_customSchemeRegistrationsCount = 0; 287 | } 288 | } 289 | 290 | private: 291 | ICoreWebView2CustomSchemeRegistration** m_customSchemeRegistrations = nullptr; 292 | unsigned int m_customSchemeRegistrationsCount = 0; 293 | 294 | COREWEBVIEW2_RELEASE_CHANNELS m_releaseChannels = kAllChannels; 295 | COREWEBVIEW2_CHANNEL_SEARCH_KIND m_channelSearchKind = 296 | COREWEBVIEW2_CHANNEL_SEARCH_KIND_MOST_STABLE; 297 | 298 | // ICoreWebView2EnvironmentOptions8 299 | COREWEBVIEW2_SCROLLBAR_STYLE m_scrollbarStyle = 300 | COREWEBVIEW2_SCROLLBAR_STYLE_DEFAULT; 301 | 302 | DEFINE_AUTO_COMEM_STRING(); 303 | 304 | public: 305 | // ICoreWebView2EnvironmentOptions 306 | COREWEBVIEW2ENVIRONMENTOPTIONS_STRING_PROPERTY(AdditionalBrowserArguments) 307 | COREWEBVIEW2ENVIRONMENTOPTIONS_STRING_PROPERTY(Language) 308 | COREWEBVIEW2ENVIRONMENTOPTIONS_STRING_PROPERTY(TargetCompatibleBrowserVersion) 309 | COREWEBVIEW2ENVIRONMENTOPTIONS_BOOL_PROPERTY( 310 | AllowSingleSignOnUsingOSPrimaryAccount, 311 | false) 312 | 313 | // ICoreWebView2EnvironmentOptions2 314 | COREWEBVIEW2ENVIRONMENTOPTIONS_BOOL_PROPERTY(ExclusiveUserDataFolderAccess, 315 | false) 316 | 317 | // ICoreWebView2EnvironmentOptions3 318 | COREWEBVIEW2ENVIRONMENTOPTIONS_BOOL_PROPERTY(IsCustomCrashReportingEnabled, 319 | false) 320 | 321 | // ICoreWebView2EnvironmentOptions4 322 | public: 323 | HRESULT STDMETHODCALLTYPE GetCustomSchemeRegistrations( 324 | UINT32* count, 325 | ICoreWebView2CustomSchemeRegistration*** schemeRegistrations) override { 326 | if (!count || !schemeRegistrations) { 327 | return E_POINTER; 328 | } 329 | *count = 0; 330 | if (m_customSchemeRegistrationsCount == 0) { 331 | *schemeRegistrations = nullptr; 332 | return S_OK; 333 | } else { 334 | *schemeRegistrations = 335 | reinterpret_cast( 336 | allocate_fn(sizeof(ICoreWebView2CustomSchemeRegistration*) * 337 | m_customSchemeRegistrationsCount)); 338 | if (!*schemeRegistrations) { 339 | return HRESULT_FROM_WIN32(GetLastError()); 340 | } 341 | for (UINT32 i = 0; i < m_customSchemeRegistrationsCount; i++) { 342 | (*schemeRegistrations)[i] = m_customSchemeRegistrations[i]; 343 | (*schemeRegistrations)[i]->AddRef(); 344 | } 345 | *count = m_customSchemeRegistrationsCount; 346 | return S_OK; 347 | } 348 | } 349 | 350 | HRESULT STDMETHODCALLTYPE SetCustomSchemeRegistrations( 351 | UINT32 count, 352 | ICoreWebView2CustomSchemeRegistration** schemeRegistrations) override { 353 | ReleaseCustomSchemeRegistrations(); 354 | m_customSchemeRegistrations = 355 | reinterpret_cast(allocate_fn( 356 | sizeof(ICoreWebView2CustomSchemeRegistration*) * count)); 357 | if (!m_customSchemeRegistrations) { 358 | return GetLastError(); 359 | } 360 | for (UINT32 i = 0; i < count; i++) { 361 | m_customSchemeRegistrations[i] = schemeRegistrations[i]; 362 | m_customSchemeRegistrations[i]->AddRef(); 363 | } 364 | m_customSchemeRegistrationsCount = count; 365 | return S_OK; 366 | } 367 | 368 | // ICoreWebView2EnvironmentOptions5 369 | COREWEBVIEW2ENVIRONMENTOPTIONS_BOOL_PROPERTY(EnableTrackingPrevention, true) 370 | 371 | // ICoreWebView2EnvironmentOptions6 372 | COREWEBVIEW2ENVIRONMENTOPTIONS_BOOL_PROPERTY(AreBrowserExtensionsEnabled, 373 | false) 374 | }; 375 | 376 | template 380 | class CoreWebView2EnvironmentOptionsBaseClass 381 | : public Microsoft::WRL::RuntimeClass< 382 | Microsoft::WRL::RuntimeClassFlags, 383 | CoreWebView2EnvironmentOptionsBase> { 387 | public: 388 | CoreWebView2EnvironmentOptionsBaseClass() {} 389 | 390 | protected: 391 | ~CoreWebView2EnvironmentOptionsBaseClass() override {} 392 | }; 393 | 394 | typedef CoreWebView2CustomSchemeRegistrationBase 398 | CoreWebView2CustomSchemeRegistration; 399 | 400 | typedef CoreWebView2EnvironmentOptionsBaseClass 404 | CoreWebView2EnvironmentOptions; 405 | 406 | #endif // __core_webview2_environment_options_h__ 407 | -------------------------------------------------------------------------------- /libwebview2/build/native/x64/WebView2Loader.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polevpn/webview/bfb768203fa26a6c38f23db7d3b60cffcc52129e/libwebview2/build/native/x64/WebView2Loader.dll -------------------------------------------------------------------------------- /main.cc: -------------------------------------------------------------------------------- 1 | //bin/echo; [ $(uname) = "Darwin" ] && FLAGS="-framework Webkit" || FLAGS="$(pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0)" ; c++ "$0" $FLAGS -std=c++11 -g -o webview && ./webview ; exit 2 | // +build ignore 3 | 4 | #include "webview.h" 5 | 6 | #include 7 | 8 | #ifdef _WIN32 9 | int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 10 | LPSTR lpCmdLine, int nCmdShow) 11 | #else 12 | int main() 13 | #endif 14 | { 15 | webview::webview w(300,600,true, true); 16 | w.set_title("Example"); 17 | w.set_size(480, 320, WEBVIEW_HINT_NONE); 18 | w.set_size(180, 120, WEBVIEW_HINT_MIN); 19 | w.bind("noop", [](std::string s) -> std::string { 20 | std::cout << s << std::endl; 21 | return s; 22 | }); 23 | w.bind("add", [](std::string s) -> std::string { 24 | auto a = std::stoi(webview::json_parse(s, "", 0)); 25 | auto b = std::stoi(webview::json_parse(s, "", 1)); 26 | return std::to_string(a + b); 27 | }); 28 | w.navigate(R"(data:text/html, 29 | 30 | 31 | hello 32 | 43 | 44 | )"); 45 | w.run(); 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /webview.cc: -------------------------------------------------------------------------------- 1 | #include "webview.h" 2 | -------------------------------------------------------------------------------- /webview.go: -------------------------------------------------------------------------------- 1 | package webview 2 | 3 | /* 4 | #cgo linux openbsd freebsd CXXFLAGS: -DWEBVIEW_GTK -std=c++11 5 | #cgo linux openbsd freebsd pkg-config: gtk+-3.0 webkit2gtk-4.0 6 | 7 | #cgo darwin CXXFLAGS: -DWEBVIEW_COCOA -std=c++11 -mmacosx-version-min=12 8 | #cgo darwin LDFLAGS: -framework AppKit -framework WebKit 9 | 10 | #cgo windows CXXFLAGS: -std=c++11 -I./libwebview2/build/native/include 11 | #cgo windows,amd64 LDFLAGS:-Wl,-Bstatic -lstdc++ -lgcc_eh -lpthread -Wl,-Bdynamic -lole32 -lShlwapi -L./libwebview2/build/native/x64 -lWebView2Loader -mwindows 12 | 13 | #define WEBVIEW_HEADER 14 | #include "webview.h" 15 | 16 | #include 17 | #include 18 | 19 | extern void _webviewDispatchGoCallback(void *); 20 | static inline void _webview_dispatch_cb(webview_t w, void *arg) { 21 | _webviewDispatchGoCallback(arg); 22 | } 23 | static inline void CgoWebViewDispatch(webview_t w, uintptr_t arg) { 24 | webview_dispatch(w, _webview_dispatch_cb, (void *)arg); 25 | } 26 | 27 | struct binding_context { 28 | webview_t w; 29 | uintptr_t index; 30 | }; 31 | extern void _webviewBindingGoCallback(webview_t, char *, char *, uintptr_t); 32 | static inline void _webview_binding_cb(const char *id, const char *req, void *arg) { 33 | struct binding_context *ctx = (struct binding_context *) arg; 34 | _webviewBindingGoCallback(ctx->w, (char *)id, (char *)req, ctx->index); 35 | } 36 | static inline void CgoWebViewBind(webview_t w, const char *name, uintptr_t index) { 37 | struct binding_context *ctx = calloc(1, sizeof(struct binding_context)); 38 | ctx->w = w; 39 | ctx->index = index; 40 | webview_bind(w, name, _webview_binding_cb, (void *)ctx); 41 | } 42 | */ 43 | import "C" 44 | import ( 45 | "encoding/json" 46 | "errors" 47 | "reflect" 48 | "runtime" 49 | "sync" 50 | "unsafe" 51 | ) 52 | 53 | func init() { 54 | // Ensure that main.main is called from the main thread 55 | runtime.LockOSThread() 56 | } 57 | 58 | // Hints are used to configure window sizing and resizing 59 | type Hint int 60 | 61 | const ( 62 | // Width and height are default size 63 | HintNone = C.WEBVIEW_HINT_NONE 64 | 65 | // Window size can not be changed by a user 66 | HintFixed = C.WEBVIEW_HINT_FIXED 67 | 68 | // Width and height are minimum bounds 69 | HintMin = C.WEBVIEW_HINT_MIN 70 | 71 | // Width and height are maximum bounds 72 | HintMax = C.WEBVIEW_HINT_MAX 73 | ) 74 | 75 | type WebView interface { 76 | 77 | // Run runs the main loop until it's terminated. After this function exits - 78 | // you must destroy the webview. 79 | Run() 80 | 81 | // Terminate stops the main loop. It is safe to call this function from 82 | // a background thread. 83 | Terminate() 84 | 85 | //show webview windows 86 | Show() 87 | 88 | //hide webview windows 89 | Hide() 90 | 91 | // Dispatch posts a function to be executed on the main thread. You normally 92 | // do not need to call this function, unless you want to tweak the native 93 | // window. 94 | Dispatch(f func()) 95 | 96 | // Destroy destroys a webview and closes the native window. 97 | Destroy() 98 | 99 | // Window returns a native window handle pointer. When using GTK backend the 100 | // pointer is GtkWindow pointer, when using Cocoa backend the pointer is 101 | // NSWindow pointer, when using Win32 backend the pointer is HWND pointer. 102 | Window() unsafe.Pointer 103 | 104 | // SetTitle updates the title of the native window. Must be called from the UI 105 | // thread. 106 | SetTitle(title string) 107 | 108 | // SetSize updates native window size. See Hint constants. 109 | SetSize(w int, h int, hint Hint) 110 | 111 | // SetIcon. 112 | SetIcon(iconBytes []byte) 113 | 114 | // Navigate navigates webview to the given URL. URL may be a data URI, i.e. 115 | // "data:text/text,...". It is often ok not to url-encode it 116 | // properly, webview will re-encode it for you. 117 | Navigate(url string) 118 | 119 | // Init injects JavaScript code at the initialization of the new page. Every 120 | // time the webview will open a the new page - this initialization code will 121 | // be executed. It is guaranteed that code is executed before window.onload. 122 | Init(js string) 123 | 124 | // Eval evaluates arbitrary JavaScript code. Evaluation happens asynchronously, 125 | // also the result of the expression is ignored. Use RPC bindings if you want 126 | // to receive notifications about the results of the evaluation. 127 | Eval(js string) 128 | 129 | // Bind binds a callback function so that it will appear under the given name 130 | // as a global JavaScript function. Internally it uses webview_init(). 131 | // Callback receives a request string and a user-provided argument pointer. 132 | // Request string is a JSON array of all the arguments passed to the 133 | // JavaScript function. 134 | // 135 | // f must be a function 136 | // f must return either value and error or just error 137 | Bind(name string, f interface{}) error 138 | } 139 | 140 | type webview struct { 141 | w C.webview_t 142 | } 143 | 144 | var ( 145 | m sync.Mutex 146 | index uintptr 147 | dispatch = map[uintptr]func(){} 148 | bindings = map[uintptr]func(id, req string) (interface{}, error){} 149 | ) 150 | 151 | func boolToInt(b bool) C.int { 152 | if b { 153 | return 1 154 | } 155 | return 0 156 | } 157 | 158 | // New calls NewWindow to create a new window and a new webview instance. If debug 159 | // is non-zero - developer tools will be enabled (if the platform supports them). 160 | func New(width int, height int, hide bool, debug bool) WebView { 161 | 162 | w := &webview{} 163 | w.w = C.webview_create(C.int(width), C.int(height), boolToInt(hide), boolToInt(debug)) 164 | return w 165 | } 166 | 167 | func (w *webview) Destroy() { 168 | C.webview_destroy(w.w) 169 | } 170 | 171 | func (w *webview) Run() { 172 | C.webview_run(w.w) 173 | } 174 | 175 | func (w *webview) Terminate() { 176 | C.webview_terminate(w.w) 177 | } 178 | 179 | func (w *webview) Show() { 180 | C.webview_show(w.w) 181 | } 182 | 183 | func (w *webview) Hide() { 184 | C.webview_hide(w.w) 185 | } 186 | 187 | func (w *webview) Window() unsafe.Pointer { 188 | return C.webview_get_window(w.w) 189 | } 190 | 191 | func (w *webview) Navigate(url string) { 192 | s := C.CString(url) 193 | defer C.free(unsafe.Pointer(s)) 194 | C.webview_navigate(w.w, s) 195 | } 196 | 197 | func (w *webview) SetTitle(title string) { 198 | s := C.CString(title) 199 | defer C.free(unsafe.Pointer(s)) 200 | C.webview_set_title(w.w, s) 201 | } 202 | 203 | func (w *webview) SetSize(width int, height int, hint Hint) { 204 | C.webview_set_size(w.w, C.int(width), C.int(height), C.int(hint)) 205 | } 206 | 207 | func (w *webview) SetIcon(iconBytes []byte) { 208 | C.webview_set_icon(w.w, unsafe.Pointer(&iconBytes[0]), C.int(len(iconBytes))) 209 | } 210 | 211 | func (w *webview) Init(js string) { 212 | s := C.CString(js) 213 | defer C.free(unsafe.Pointer(s)) 214 | C.webview_init(w.w, s) 215 | } 216 | 217 | func (w *webview) Eval(js string) { 218 | s := C.CString(js) 219 | defer C.free(unsafe.Pointer(s)) 220 | C.webview_eval(w.w, s) 221 | } 222 | 223 | func (w *webview) Dispatch(f func()) { 224 | m.Lock() 225 | for ; dispatch[index] != nil; index++ { 226 | } 227 | dispatch[index] = f 228 | m.Unlock() 229 | C.CgoWebViewDispatch(w.w, C.uintptr_t(index)) 230 | } 231 | 232 | //export _webviewDispatchGoCallback 233 | func _webviewDispatchGoCallback(index unsafe.Pointer) { 234 | m.Lock() 235 | f := dispatch[uintptr(index)] 236 | delete(dispatch, uintptr(index)) 237 | m.Unlock() 238 | f() 239 | } 240 | 241 | //export _webviewBindingGoCallback 242 | func _webviewBindingGoCallback(w C.webview_t, id *C.char, req *C.char, index uintptr) { 243 | m.Lock() 244 | f := bindings[uintptr(index)] 245 | m.Unlock() 246 | jsString := func(v interface{}) string { b, _ := json.Marshal(v); return string(b) } 247 | status, result := 0, "" 248 | if res, err := f(C.GoString(id), C.GoString(req)); err != nil { 249 | status = -1 250 | result = jsString(err.Error()) 251 | } else if b, err := json.Marshal(res); err != nil { 252 | status = -1 253 | result = jsString(err.Error()) 254 | } else { 255 | status = 0 256 | result = string(b) 257 | } 258 | s := C.CString(result) 259 | defer C.free(unsafe.Pointer(s)) 260 | C.webview_return(w, id, C.int(status), s) 261 | } 262 | 263 | func (w *webview) Bind(name string, f interface{}) error { 264 | v := reflect.ValueOf(f) 265 | // f must be a function 266 | if v.Kind() != reflect.Func { 267 | return errors.New("only functions can be bound") 268 | } 269 | // f must return either value and error or just error 270 | if n := v.Type().NumOut(); n > 2 { 271 | return errors.New("function may only return a value or a value+error") 272 | } 273 | 274 | binding := func(id, req string) (interface{}, error) { 275 | raw := []json.RawMessage{} 276 | if err := json.Unmarshal([]byte(req), &raw); err != nil { 277 | return nil, err 278 | } 279 | 280 | isVariadic := v.Type().IsVariadic() 281 | numIn := v.Type().NumIn() 282 | if (isVariadic && len(raw) < numIn-1) || (!isVariadic && len(raw) != numIn) { 283 | return nil, errors.New("function arguments mismatch") 284 | } 285 | args := []reflect.Value{} 286 | for i := range raw { 287 | var arg reflect.Value 288 | if isVariadic && i >= numIn-1 { 289 | arg = reflect.New(v.Type().In(numIn - 1).Elem()) 290 | } else { 291 | arg = reflect.New(v.Type().In(i)) 292 | } 293 | if err := json.Unmarshal(raw[i], arg.Interface()); err != nil { 294 | return nil, err 295 | } 296 | args = append(args, arg.Elem()) 297 | } 298 | errorType := reflect.TypeOf((*error)(nil)).Elem() 299 | res := v.Call(args) 300 | switch len(res) { 301 | case 0: 302 | // No results from the function, just return nil 303 | return nil, nil 304 | case 1: 305 | // One result may be a value, or an error 306 | if res[0].Type().Implements(errorType) { 307 | if res[0].Interface() != nil { 308 | return nil, res[0].Interface().(error) 309 | } 310 | return nil, nil 311 | } 312 | return res[0].Interface(), nil 313 | case 2: 314 | // Two results: first one is value, second is error 315 | if !res[1].Type().Implements(errorType) { 316 | return nil, errors.New("second return value must be an error") 317 | } 318 | if res[1].Interface() == nil { 319 | return res[0].Interface(), nil 320 | } 321 | return res[0].Interface(), res[1].Interface().(error) 322 | default: 323 | return nil, errors.New("unexpected number of return values") 324 | } 325 | } 326 | 327 | m.Lock() 328 | for ; bindings[index] != nil; index++ { 329 | } 330 | bindings[index] = binding 331 | m.Unlock() 332 | cname := C.CString(name) 333 | defer C.free(unsafe.Pointer(cname)) 334 | C.CgoWebViewBind(w.w, cname, C.uintptr_t(index)) 335 | return nil 336 | } 337 | -------------------------------------------------------------------------------- /webview.h: -------------------------------------------------------------------------------- 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 | 36 | typedef void *webview_t; 37 | 38 | // Creates a new webview instance. If debug is non-zero - developer tools will 39 | // be enabled (if the platform supports them). Window parameter can be a 40 | // pointer to the native window handle. If it's non-null - then child WebView 41 | // is embedded into the given parent window. Otherwise a new window is created. 42 | // Depending on the platform, a GtkWindow, NSWindow or HWND pointer can be 43 | // passed here. 44 | WEBVIEW_API webview_t webview_create(int width,int height,int hide,int debug); 45 | 46 | // Destroys a webview and closes the native window. 47 | WEBVIEW_API void webview_destroy(webview_t w); 48 | 49 | // Runs the main loop until it's terminated. After this function exits - you 50 | // must destroy the webview. 51 | WEBVIEW_API void webview_run(webview_t w); 52 | 53 | // Stops the main loop. It is safe to call this function from another other 54 | // background thread. 55 | WEBVIEW_API void webview_terminate(webview_t w); 56 | 57 | //show webview window 58 | WEBVIEW_API void webview_show(webview_t w); 59 | 60 | //hide webview window 61 | WEBVIEW_API void webview_hide(webview_t w); 62 | 63 | // Posts a function to be executed on the main thread. You normally do not need 64 | // to call this function, unless you want to tweak the native window. 65 | WEBVIEW_API void 66 | webview_dispatch(webview_t w, void (*fn)(webview_t w, void *arg), void *arg); 67 | 68 | // Returns a native window handle pointer. When using GTK backend the pointer 69 | // is GtkWindow pointer, when using Cocoa backend the pointer is NSWindow 70 | // pointer, when using Win32 backend the pointer is HWND pointer. 71 | WEBVIEW_API void *webview_get_window(webview_t w); 72 | 73 | // Updates the title of the native window. Must be called from the UI thread. 74 | WEBVIEW_API void webview_set_title(webview_t w, const char *title); 75 | 76 | // Window size hints 77 | #define WEBVIEW_HINT_NONE 0 // Width and height are default size 78 | #define WEBVIEW_HINT_MIN 1 // Width and height are minimum bounds 79 | #define WEBVIEW_HINT_MAX 2 // Width and height are maximum bounds 80 | #define WEBVIEW_HINT_FIXED 3 // Window size can not be changed by a user 81 | // Updates native window size. See WEBVIEW_HINT constants. 82 | WEBVIEW_API void webview_set_size(webview_t w, int width, int height, 83 | int hints); 84 | 85 | //set window icon 86 | WEBVIEW_API void webview_set_icon(webview_t w, const void *icon,int size); 87 | 88 | // Navigates webview to the given URL. URL may be a data URI, i.e. 89 | // "data:text/text,...". It is often ok not to url-encode it 90 | // properly, webview will re-encode it for you. 91 | WEBVIEW_API void webview_navigate(webview_t w, const char *url); 92 | 93 | // Injects JavaScript code at the initialization of the new page. Every time 94 | // the webview will open a the new page - this initialization code will be 95 | // executed. It is guaranteed that code is executed before window.onload. 96 | WEBVIEW_API void webview_init(webview_t w, const char *js); 97 | 98 | // Evaluates arbitrary JavaScript code. Evaluation happens asynchronously, also 99 | // the result of the expression is ignored. Use RPC bindings if you want to 100 | // receive notifications about the results of the evaluation. 101 | WEBVIEW_API void webview_eval(webview_t w, const char *js); 102 | 103 | // Binds a native C callback so that it will appear under the given name as a 104 | // global JavaScript function. Internally it uses webview_init(). Callback 105 | // receives a request string and a user-provided argument pointer. Request 106 | // string is a JSON array of all the arguments passed to the JavaScript 107 | // function. 108 | WEBVIEW_API void webview_bind(webview_t w, const char *name, 109 | void (*fn)(const char *seq, const char *req, 110 | void *arg), 111 | void *arg); 112 | 113 | // Allows to return a value from the native binding. Original request pointer 114 | // must be provided to help internal RPC engine match requests with responses. 115 | // If status is zero - result is expected to be a valid JSON result value. 116 | // If status is not zero - result is an error JSON object. 117 | WEBVIEW_API void webview_return(webview_t w, const char *seq, int status, 118 | const char *result); 119 | 120 | #ifdef __cplusplus 121 | } 122 | #endif 123 | 124 | #ifndef WEBVIEW_HEADER 125 | 126 | #if !defined(WEBVIEW_GTK) && !defined(WEBVIEW_COCOA) && !defined(WEBVIEW_EDGE) 127 | #if defined(__linux__) 128 | #define WEBVIEW_GTK 129 | #elif defined(__APPLE__) 130 | #define WEBVIEW_COCOA 131 | #elif defined(_WIN32) 132 | #define WEBVIEW_EDGE 133 | #else 134 | #error "please, specify webview backend" 135 | #endif 136 | #endif 137 | 138 | #include 139 | #include 140 | #include 141 | #include 142 | #include 143 | #include 144 | #include 145 | 146 | #include 147 | 148 | namespace webview { 149 | using dispatch_fn_t = std::function; 150 | 151 | // Convert ASCII hex digit to a nibble (four bits, 0 - 15). 152 | // 153 | // Use unsigned to avoid signed overflow UB. 154 | static inline unsigned char hex2nibble(unsigned char c) { 155 | if (c >= '0' && c <= '9') { 156 | return c - '0'; 157 | } else if (c >= 'a' && c <= 'f') { 158 | return 10 + (c - 'a'); 159 | } else if (c >= 'A' && c <= 'F') { 160 | return 10 + (c - 'A'); 161 | } 162 | return 0; 163 | } 164 | 165 | // Convert ASCII hex string (two characters) to byte. 166 | // 167 | // E.g., "0B" => 0x0B, "af" => 0xAF. 168 | static inline char hex2char(const char *p) { 169 | return hex2nibble(p[0]) * 16 + hex2nibble(p[1]); 170 | } 171 | 172 | inline std::string url_encode(const std::string s) { 173 | std::string encoded; 174 | for (unsigned int i = 0; i < s.length(); i++) { 175 | auto c = s[i]; 176 | if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { 177 | encoded = encoded + c; 178 | } else { 179 | char hex[4]; 180 | snprintf(hex, sizeof(hex), "%%%02x", c); 181 | encoded = encoded + hex; 182 | } 183 | } 184 | return encoded; 185 | } 186 | 187 | inline std::string url_decode(const std::string st) { 188 | std::string decoded; 189 | const char *s = st.c_str(); 190 | size_t length = strlen(s); 191 | for (unsigned int i = 0; i < length; i++) { 192 | if (s[i] == '%') { 193 | decoded.push_back(hex2char(s + i + 1)); 194 | i = i + 2; 195 | } else if (s[i] == '+') { 196 | decoded.push_back(' '); 197 | } else { 198 | decoded.push_back(s[i]); 199 | } 200 | } 201 | return decoded; 202 | } 203 | 204 | inline std::string html_from_uri(const std::string s) { 205 | if (s.substr(0, 15) == "data:text/html,") { 206 | return url_decode(s.substr(15)); 207 | } 208 | return ""; 209 | } 210 | 211 | inline int json_parse_c(const char *s, size_t sz, const char *key, size_t keysz, 212 | const char **value, size_t *valuesz) { 213 | enum { 214 | JSON_STATE_VALUE, 215 | JSON_STATE_LITERAL, 216 | JSON_STATE_STRING, 217 | JSON_STATE_ESCAPE, 218 | JSON_STATE_UTF8 219 | } state = JSON_STATE_VALUE; 220 | const char *k = NULL; 221 | int index = 1; 222 | int depth = 0; 223 | int utf8_bytes = 0; 224 | 225 | if (key == NULL) { 226 | index = keysz; 227 | keysz = 0; 228 | } 229 | 230 | *value = NULL; 231 | *valuesz = 0; 232 | 233 | for (; sz > 0; s++, sz--) { 234 | enum { 235 | JSON_ACTION_NONE, 236 | JSON_ACTION_START, 237 | JSON_ACTION_END, 238 | JSON_ACTION_START_STRUCT, 239 | JSON_ACTION_END_STRUCT 240 | } action = JSON_ACTION_NONE; 241 | unsigned char c = *s; 242 | switch (state) { 243 | case JSON_STATE_VALUE: 244 | if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' || 245 | c == ':') { 246 | continue; 247 | } else if (c == '"') { 248 | action = JSON_ACTION_START; 249 | state = JSON_STATE_STRING; 250 | } else if (c == '{' || c == '[') { 251 | action = JSON_ACTION_START_STRUCT; 252 | } else if (c == '}' || c == ']') { 253 | action = JSON_ACTION_END_STRUCT; 254 | } else if (c == 't' || c == 'f' || c == 'n' || c == '-' || 255 | (c >= '0' && c <= '9')) { 256 | action = JSON_ACTION_START; 257 | state = JSON_STATE_LITERAL; 258 | } else { 259 | return -1; 260 | } 261 | break; 262 | case JSON_STATE_LITERAL: 263 | if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' || 264 | c == ']' || c == '}' || c == ':') { 265 | state = JSON_STATE_VALUE; 266 | s--; 267 | sz++; 268 | action = JSON_ACTION_END; 269 | } else if (c < 32 || c > 126) { 270 | return -1; 271 | } // fallthrough 272 | case JSON_STATE_STRING: 273 | if (c < 32 || (c > 126 && c < 192)) { 274 | return -1; 275 | } else if (c == '"') { 276 | action = JSON_ACTION_END; 277 | state = JSON_STATE_VALUE; 278 | } else if (c == '\\') { 279 | state = JSON_STATE_ESCAPE; 280 | } else if (c >= 192 && c < 224) { 281 | utf8_bytes = 1; 282 | state = JSON_STATE_UTF8; 283 | } else if (c >= 224 && c < 240) { 284 | utf8_bytes = 2; 285 | state = JSON_STATE_UTF8; 286 | } else if (c >= 240 && c < 247) { 287 | utf8_bytes = 3; 288 | state = JSON_STATE_UTF8; 289 | } else if (c >= 128 && c < 192) { 290 | return -1; 291 | } 292 | break; 293 | case JSON_STATE_ESCAPE: 294 | if (c == '"' || c == '\\' || c == '/' || c == 'b' || c == 'f' || 295 | c == 'n' || c == 'r' || c == 't' || c == 'u') { 296 | state = JSON_STATE_STRING; 297 | } else { 298 | return -1; 299 | } 300 | break; 301 | case JSON_STATE_UTF8: 302 | if (c < 128 || c > 191) { 303 | return -1; 304 | } 305 | utf8_bytes--; 306 | if (utf8_bytes == 0) { 307 | state = JSON_STATE_STRING; 308 | } 309 | break; 310 | default: 311 | return -1; 312 | } 313 | 314 | if (action == JSON_ACTION_END_STRUCT) { 315 | depth--; 316 | } 317 | 318 | if (depth == 1) { 319 | if (action == JSON_ACTION_START || action == JSON_ACTION_START_STRUCT) { 320 | if (index == 0) { 321 | *value = s; 322 | } else if (keysz > 0 && index == 1) { 323 | k = s; 324 | } else { 325 | index--; 326 | } 327 | } else if (action == JSON_ACTION_END || 328 | action == JSON_ACTION_END_STRUCT) { 329 | if (*value != NULL && index == 0) { 330 | *valuesz = (size_t)(s + 1 - *value); 331 | return 0; 332 | } else if (keysz > 0 && k != NULL) { 333 | if (keysz == (size_t)(s - k - 1) && memcmp(key, k + 1, keysz) == 0) { 334 | index = 0; 335 | } else { 336 | index = 2; 337 | } 338 | k = NULL; 339 | } 340 | } 341 | } 342 | 343 | if (action == JSON_ACTION_START_STRUCT) { 344 | depth++; 345 | } 346 | } 347 | return -1; 348 | } 349 | 350 | inline std::string json_escape(std::string s) { 351 | // TODO: implement 352 | return '"' + s + '"'; 353 | } 354 | 355 | inline int json_unescape(const char *s, size_t n, char *out) { 356 | int r = 0; 357 | if (*s++ != '"') { 358 | return -1; 359 | } 360 | while (n > 2) { 361 | char c = *s; 362 | if (c == '\\') { 363 | s++; 364 | n--; 365 | switch (*s) { 366 | case 'b': 367 | c = '\b'; 368 | break; 369 | case 'f': 370 | c = '\f'; 371 | break; 372 | case 'n': 373 | c = '\n'; 374 | break; 375 | case 'r': 376 | c = '\r'; 377 | break; 378 | case 't': 379 | c = '\t'; 380 | break; 381 | case '\\': 382 | c = '\\'; 383 | break; 384 | case '/': 385 | c = '/'; 386 | break; 387 | case '\"': 388 | c = '\"'; 389 | break; 390 | default: // TODO: support unicode decoding 391 | return -1; 392 | } 393 | } 394 | if (out != NULL) { 395 | *out++ = c; 396 | } 397 | s++; 398 | n--; 399 | r++; 400 | } 401 | if (*s != '"') { 402 | return -1; 403 | } 404 | if (out != NULL) { 405 | *out = '\0'; 406 | } 407 | return r; 408 | } 409 | 410 | inline std::string json_parse(const std::string s, const std::string key, 411 | const int index) { 412 | const char *value; 413 | size_t value_sz; 414 | if (key == "") { 415 | json_parse_c(s.c_str(), s.length(), nullptr, index, &value, &value_sz); 416 | } else { 417 | json_parse_c(s.c_str(), s.length(), key.c_str(), key.length(), &value, 418 | &value_sz); 419 | } 420 | if (value != nullptr) { 421 | if (value[0] != '"') { 422 | return std::string(value, value_sz); 423 | } 424 | int n = json_unescape(value, value_sz, nullptr); 425 | if (n > 0) { 426 | char *decoded = new char[n + 1]; 427 | json_unescape(value, value_sz, decoded); 428 | std::string result(decoded, n); 429 | delete[] decoded; 430 | return result; 431 | } 432 | } 433 | return ""; 434 | } 435 | 436 | } // namespace webview 437 | 438 | #if defined(WEBVIEW_GTK) 439 | 440 | #include "webview_linux.h" 441 | 442 | #elif defined(WEBVIEW_COCOA) 443 | 444 | #include "webview_darwin.h" 445 | 446 | #elif defined(WEBVIEW_EDGE) 447 | 448 | #include "webview_win.h" 449 | 450 | #endif /* WEBVIEW_GTK, WEBVIEW_COCOA, WEBVIEW_EDGE */ 451 | 452 | namespace webview { 453 | 454 | class webview : public browser_engine { 455 | public: 456 | webview(int width,int height,bool hide = false,bool debug = false) 457 | : browser_engine(width,height,hide,debug) {} 458 | 459 | void navigate(const std::string url) { 460 | if (url == "") { 461 | browser_engine::navigate("data:text/html," + 462 | url_encode("Hello")); 463 | return; 464 | } 465 | std::string html = html_from_uri(url); 466 | if (html != "") { 467 | browser_engine::navigate("data:text/html," + url_encode(html)); 468 | } else { 469 | browser_engine::navigate(url); 470 | } 471 | } 472 | 473 | using binding_t = std::function; 474 | using binding_ctx_t = std::pair; 475 | 476 | using sync_binding_t = std::function; 477 | using sync_binding_ctx_t = std::pair; 478 | 479 | void bind(const std::string name, sync_binding_t fn) { 480 | bind( 481 | name, 482 | [](std::string seq, std::string req, void *arg) { 483 | auto pair = static_cast(arg); 484 | pair->first->resolve(seq, 0, pair->second(req)); 485 | }, 486 | new sync_binding_ctx_t(this, fn)); 487 | } 488 | 489 | void bind(const std::string name, binding_t f, void *arg) { 490 | auto js = "(function() { var name = '" + name + "';" + R"( 491 | var RPC = window._rpc = (window._rpc || {nextSeq: 1}); 492 | window[name] = function() { 493 | var seq = RPC.nextSeq++; 494 | var promise = new Promise(function(resolve, reject) { 495 | RPC[seq] = { 496 | resolve: resolve, 497 | reject: reject, 498 | }; 499 | }); 500 | window.external.invoke(JSON.stringify({ 501 | id: seq, 502 | method: name, 503 | params: Array.prototype.slice.call(arguments), 504 | })); 505 | return promise; 506 | } 507 | })())"; 508 | init(js); 509 | bindings[name] = new binding_ctx_t(new binding_t(f), arg); 510 | } 511 | 512 | void resolve(const std::string seq, int status, const std::string result) { 513 | dispatch([=]() { 514 | if (status == 0) { 515 | eval("window._rpc[" + seq + "].resolve(" + result + "); window._rpc[" + 516 | seq + "] = undefined"); 517 | } else { 518 | eval("window._rpc[" + seq + "].reject(" + result + "); window._rpc[" + 519 | seq + "] = undefined"); 520 | } 521 | }); 522 | } 523 | 524 | private: 525 | void on_message(const std::string msg) { 526 | auto seq = json_parse(msg, "id", 0); 527 | auto name = json_parse(msg, "method", 0); 528 | auto args = json_parse(msg, "params", 0); 529 | if (bindings.find(name) == bindings.end()) { 530 | return; 531 | } 532 | auto fn = bindings[name]; 533 | (*fn->first)(seq, args, fn->second); 534 | } 535 | std::map bindings; 536 | }; 537 | } // namespace webview 538 | 539 | WEBVIEW_API webview_t webview_create(int width,int height,int hide,int debug) { 540 | return new webview::webview(width,height,hide,debug); 541 | } 542 | 543 | WEBVIEW_API void webview_destroy(webview_t w) { 544 | delete static_cast(w); 545 | } 546 | 547 | WEBVIEW_API void webview_run(webview_t w) { 548 | static_cast(w)->run(); 549 | } 550 | 551 | WEBVIEW_API void webview_terminate(webview_t w) { 552 | static_cast(w)->terminate(); 553 | } 554 | 555 | WEBVIEW_API void webview_show(webview_t w) { 556 | static_cast(w)->show(); 557 | } 558 | 559 | WEBVIEW_API void webview_hide(webview_t w) { 560 | static_cast(w)->hide(); 561 | } 562 | 563 | WEBVIEW_API void webview_dispatch(webview_t w, void (*fn)(webview_t, void *), 564 | void *arg) { 565 | static_cast(w)->dispatch([=]() { fn(w, arg); }); 566 | } 567 | 568 | WEBVIEW_API void *webview_get_window(webview_t w) { 569 | return static_cast(w)->window(); 570 | } 571 | 572 | WEBVIEW_API void webview_set_title(webview_t w, const char *title) { 573 | static_cast(w)->set_title(title); 574 | } 575 | 576 | WEBVIEW_API void webview_set_icon(webview_t w, const void *icon,int size) { 577 | static_cast(w)->set_icon(std::string((char*)icon,size)); 578 | } 579 | 580 | WEBVIEW_API void webview_set_size(webview_t w, int width, int height, 581 | int hints) { 582 | static_cast(w)->set_size(width, height, hints); 583 | } 584 | 585 | WEBVIEW_API void webview_navigate(webview_t w, const char *url) { 586 | static_cast(w)->navigate(url); 587 | } 588 | 589 | WEBVIEW_API void webview_init(webview_t w, const char *js) { 590 | static_cast(w)->init(js); 591 | } 592 | 593 | WEBVIEW_API void webview_eval(webview_t w, const char *js) { 594 | static_cast(w)->eval(js); 595 | } 596 | 597 | WEBVIEW_API void webview_bind(webview_t w, const char *name, 598 | void (*fn)(const char *seq, const char *req, 599 | void *arg), 600 | void *arg) { 601 | static_cast(w)->bind( 602 | name, 603 | [=](std::string seq, std::string req, void *arg) { 604 | fn(seq.c_str(), req.c_str(), arg); 605 | }, 606 | arg); 607 | } 608 | 609 | WEBVIEW_API void webview_return(webview_t w, const char *seq, int status, 610 | const char *result) { 611 | static_cast(w)->resolve(seq, status, result); 612 | } 613 | 614 | #endif /* WEBVIEW_HEADER */ 615 | 616 | #endif /* WEBVIEW_H */ 617 | -------------------------------------------------------------------------------- /webview_darwin.h: -------------------------------------------------------------------------------- 1 | // 2 | // ==================================================================== 3 | // 4 | // This implementation uses Cocoa WKWebView backend on macOS. It is 5 | // written using ObjC runtime and uses WKWebView class as a browser runtime. 6 | // You should pass "-framework Webkit" flag to the compiler. 7 | // 8 | // ==================================================================== 9 | // 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace webview { 16 | 17 | 18 | #define CLASS(clazz) (id)objc_getClass(clazz) 19 | #define METHOD(method) sel_registerName(method) 20 | #define NSTR(string) ((id(*)(id, SEL, const char *))objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithUTF8String:"), string) 21 | 22 | class cocoa_wkwebview_engine { 23 | public: 24 | cocoa_wkwebview_engine(int width,int height,bool hide,bool debug) { 25 | m_app = ((id(*)(id, SEL))objc_msgSend)(CLASS("WebViewApp"), METHOD("alloc")); 26 | m_app = ((id(*)(id, SEL,int,int,BOOL,BOOL))objc_msgSend)(m_app, METHOD("initApp:height:hide:debug:"),width,height,hide,debug); 27 | 28 | Class cls = objc_allocateClassPair((Class)objc_getClass("NSObject"), "NSWebviewResponder", 0); 29 | class_addProtocol(cls, objc_getProtocol("MessageDelegate")); 30 | 31 | class_addMethod(cls, METHOD("onMessage:"), 32 | (IMP)(+[](id self, SEL, id msg) { 33 | auto w = (cocoa_wkwebview_engine *)objc_getAssociatedObject(self, "webview"); 34 | assert(w); 35 | w->on_message(((const char *(*)(id, SEL))objc_msgSend)(((id(*)(id, SEL))objc_msgSend)(msg, METHOD("body")),METHOD("UTF8String"))); 36 | }), 37 | "v@:@@"); 38 | 39 | objc_registerClassPair(cls); 40 | 41 | id delegate = ((id(*)(id, SEL))objc_msgSend)((id)cls, METHOD("new")); 42 | objc_setAssociatedObject(delegate, "webview", (id)this,OBJC_ASSOCIATION_ASSIGN); 43 | ((void (*)(id, SEL, id))objc_msgSend)(m_app, METHOD("setDelegate:"),delegate); 44 | } 45 | ~cocoa_wkwebview_engine() { close(); } 46 | void *window() { return (void *)m_app; } 47 | 48 | void show() { 49 | ((void (*)(id, SEL))objc_msgSend)(m_app, METHOD("show")); 50 | } 51 | 52 | void hide() { 53 | ((void (*)(id, SEL))objc_msgSend)(m_app, METHOD("hide")); 54 | } 55 | void terminate() { 56 | close(); 57 | ((void (*)(id, SEL))objc_msgSend)(m_app, METHOD("terminate")); 58 | } 59 | void run() { 60 | ((void (*)(id, SEL))objc_msgSend)(m_app, METHOD("run")); 61 | } 62 | void dispatch(std::function f) { 63 | dispatch_async_f(dispatch_get_main_queue(), new dispatch_fn_t(f), 64 | (dispatch_function_t)([](void *arg) { 65 | auto f = static_cast(arg); 66 | (*f)(); 67 | delete f; 68 | })); 69 | } 70 | 71 | void set_icon(const std::string icon) { 72 | } 73 | 74 | void set_title(const std::string title) { 75 | ((void (*)(id, SEL, id))objc_msgSend)(m_app, METHOD("setTitle:"),NSTR(title.c_str())); 76 | } 77 | void set_size(int width, int height, int hints) { 78 | ((void (*)(id, SEL, int,int,int))objc_msgSend)(m_app, METHOD("setSize:height:hints:"),width,height,hints); 79 | 80 | } 81 | void navigate(const std::string url) { 82 | ((void (*)(id, SEL, id))objc_msgSend)(m_app, METHOD("navigate:"),NSTR(url.c_str())); 83 | } 84 | void init(const std::string js) { 85 | ((void (*)(id, SEL, id))objc_msgSend)(m_app, METHOD("initJS:"),NSTR(js.c_str())); 86 | 87 | } 88 | void eval(const std::string js) { 89 | ((void (*)(id, SEL, id))objc_msgSend)(m_app, METHOD("evalJS:"),NSTR(js.c_str())); 90 | } 91 | 92 | private: 93 | virtual void on_message(const std::string msg) = 0; 94 | void close() { ((void (*)(id, SEL))objc_msgSend)(m_app, METHOD("close")); } 95 | id m_app; 96 | 97 | }; 98 | 99 | using browser_engine = cocoa_wkwebview_engine; 100 | 101 | } // namespace webview -------------------------------------------------------------------------------- /webview_darwin.m: -------------------------------------------------------------------------------- 1 | #define WEBVIEW_HINT_NONE 0 // Width and height are default size 2 | #define WEBVIEW_HINT_MIN 1 // Width and height are minimum bounds 3 | #define WEBVIEW_HINT_MAX 2 // Width and height are maximum bounds 4 | #define WEBVIEW_HINT_FIXED 3 // Window size can not be changed by a user 5 | 6 | #import 7 | #import 8 | #import 9 | 10 | @interface AppWebView : WKWebView 11 | 12 | @end 13 | 14 | @implementation AppWebView 15 | 16 | - (BOOL)performKeyEquivalent:(NSEvent *)event 17 | { 18 | if ([event modifierFlags] & NSEventModifierFlagCommand) { 19 | // The command key is the ONLY modifier key being pressed. 20 | if ([[event charactersIgnoringModifiers] isEqualToString:@"x"]) { 21 | 22 | return [NSApp sendAction:@selector(cut:) to:[[self window] firstResponder] from:self]; 23 | } else if ([[event charactersIgnoringModifiers] isEqualToString:@"c"]) { 24 | return [NSApp sendAction:@selector(copy:) to:[[self window] firstResponder] from:self]; 25 | } else if ([[event charactersIgnoringModifiers] isEqualToString:@"v"]) { 26 | return [NSApp sendAction:@selector(paste:) to:[[self window] firstResponder] from:self]; 27 | } else if ([[event charactersIgnoringModifiers] isEqualToString:@"a"]) { 28 | return [NSApp sendAction:@selector(selectAll:) to:[[self window] firstResponder] from:self]; 29 | } 30 | } 31 | return [super performKeyEquivalent:event]; 32 | } 33 | 34 | @end 35 | 36 | 37 | @protocol MessageDelegate 38 | 39 | -(void)onMessage:(id)message; 40 | 41 | @end 42 | 43 | @interface WebViewApp : NSObject { 44 | NSWindow *_window; 45 | NSWindowController *_controller; 46 | AppWebView *_webview; 47 | WKUserContentController *_manager; 48 | BOOL _hide; 49 | id _delegate; 50 | } 51 | 52 | -(id) initApp:(NSInteger)width height:(NSInteger)height hide:(BOOL)hide debug:(BOOL)debug; 53 | -(void) close; 54 | -(void) hide; 55 | -(void) show; 56 | -(void) run; 57 | -(void) terminate; 58 | -(void)setDelegate:(id)delegate; 59 | -(void)setTitle:(NSString*)title; 60 | -(void) setSize:(NSInteger)width height:(NSInteger)height hints:(NSInteger)hints; 61 | -(void) initJS:(NSString*)js; 62 | -(void) evalJS:(NSString*)js; 63 | -(void) navigate:(NSString *)url; 64 | -(BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender; 65 | -(void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message; 66 | @end 67 | 68 | 69 | @implementation WebViewApp { 70 | } 71 | 72 | -(id) initApp:(NSInteger)width height:(NSInteger)height hide:(BOOL)hide debug:(BOOL)debug { 73 | 74 | id app = [NSApplication sharedApplication]; 75 | [app setActivationPolicy:YES]; 76 | 77 | _hide = hide; 78 | 79 | [app setDelegate:self]; 80 | 81 | _window = [[NSWindow alloc] initWithContentRect:CGRectMake(0, 0, width, height) styleMask:0 backing:NSBackingStoreBuffered defer:NO]; 82 | 83 | _controller = [[NSWindowController alloc]initWithWindow:_window]; 84 | 85 | WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; 86 | 87 | WKPreferences *preference = [[WKPreferences alloc]init]; 88 | 89 | if (debug) { 90 | [preference setValue:@YES forKey:@"developerExtrasEnabled"]; 91 | } 92 | [preference setValue:@YES forKey:@"fullScreenEnabled"]; 93 | [preference setValue:@YES forKey:@"javaScriptCanAccessClipboard"]; 94 | [preference setValue:@YES forKey:@"DOMPasteAllowed"]; 95 | config.preferences = preference; 96 | _manager = [[WKUserContentController alloc] init]; 97 | [_manager addScriptMessageHandler:self name:@"external"]; 98 | config.userContentController = _manager; 99 | 100 | _webview = [[AppWebView alloc] initWithFrame:CGRectMake(0, 0, 0, 0) configuration:config]; 101 | 102 | [self initJS:@"window.external = { invoke: function(s) {window.webkit.messageHandlers.external.postMessage(s)}}"]; 103 | 104 | [_window setContentView:_webview]; 105 | [_window makeKeyAndOrderFront:nil]; 106 | 107 | return self; 108 | 109 | // Application 110 | }; 111 | 112 | 113 | -(void) initJS:(NSString *)js { 114 | dispatch_async(dispatch_get_main_queue(),^{ 115 | [self->_manager addUserScript:[[WKUserScript alloc] initWithSource:js injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES]]; 116 | }); 117 | 118 | } 119 | 120 | -(void) evalJS:(NSString *)js{ 121 | dispatch_async(dispatch_get_main_queue(),^{ 122 | [self->_webview evaluateJavaScript:js completionHandler:nil]; 123 | }); 124 | } 125 | 126 | -(void) setDelegate:(id)delegate{ 127 | _delegate = delegate; 128 | } 129 | 130 | -(void) setTitle:(NSString *)title{ 131 | [_window setTitle:title]; 132 | 133 | } 134 | -(void) setSize:(NSInteger)width height:(NSInteger)height hints:(NSInteger)hints{ 135 | NSUInteger style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable; 136 | if (hints != WEBVIEW_HINT_FIXED) { 137 | style = style | NSWindowStyleMaskResizable; 138 | } 139 | 140 | [_window setStyleMask:style]; 141 | 142 | 143 | if (hints == WEBVIEW_HINT_MIN) { 144 | [_window setContentMinSize:CGSizeMake(width, height)]; 145 | }else if (hints == WEBVIEW_HINT_MAX){ 146 | [_window setContentMaxSize:CGSizeMake(width, height)]; 147 | }else{ 148 | [_window setFrame:CGRectMake(0, 0, width, height) display:YES animate:NO]; 149 | } 150 | 151 | [_window center]; 152 | } 153 | 154 | 155 | -(void) close { 156 | dispatch_async(dispatch_get_main_queue(),^{ 157 | [self->_window close]; 158 | }); 159 | } 160 | -(void) hide { 161 | dispatch_async(dispatch_get_main_queue(),^{ 162 | [self->_window orderOut:self->_window]; 163 | }); 164 | } 165 | 166 | -(void) show{ 167 | dispatch_async(dispatch_get_main_queue(),^{ 168 | id app = [NSApplication sharedApplication]; 169 | [app activateIgnoringOtherApps:YES]; 170 | [self->_controller showWindow:self->_window]; 171 | [self->_window makeKeyAndOrderFront:nil]; 172 | }); 173 | } 174 | -(void) navigate:(NSString *)url{ 175 | 176 | id request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; 177 | [_webview loadRequest:request]; 178 | } 179 | 180 | -(void) terminate { 181 | id app = [NSApplication sharedApplication]; 182 | [self close]; 183 | [app terminate:nil]; 184 | } 185 | -(void) run { 186 | 187 | id app = [NSApplication sharedApplication]; 188 | [app activateIgnoringOtherApps:YES]; 189 | [app run]; 190 | } 191 | 192 | 193 | -(BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender{ 194 | return !_hide; 195 | } 196 | 197 | - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { 198 | 199 | if (_delegate != nil){ 200 | [_delegate onMessage:message]; 201 | } 202 | } 203 | 204 | @end; 205 | -------------------------------------------------------------------------------- /webview_linux.h: -------------------------------------------------------------------------------- 1 | // 2 | // ==================================================================== 3 | // 4 | // This implementation uses webkit2gtk backend. It requires gtk+3.0 and 5 | // webkit2gtk-4.0 libraries. Proper compiler flags can be retrieved via: 6 | // 7 | // pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0 8 | // 9 | // ==================================================================== 10 | // 11 | #include 12 | #include 13 | #include 14 | 15 | namespace webview { 16 | 17 | class gtk_webkit_engine { 18 | public: 19 | gtk_webkit_engine(int width,int height,bool hide,bool debug) { 20 | m_hide = hide; 21 | gtk_init_check(0, NULL); 22 | m_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); 23 | gtk_window_resize(GTK_WINDOW(m_window), width, height); 24 | g_signal_connect(G_OBJECT(m_window), "destroy", 25 | G_CALLBACK(+[](GtkWidget *, gpointer arg) { 26 | static_cast(arg)->terminate(); 27 | }), 28 | this); 29 | 30 | if(m_hide){ 31 | g_signal_connect(G_OBJECT(m_window), "delete-event", G_CALLBACK(gtk_webkit_engine::hideWindow), this); 32 | } 33 | 34 | // Initialize webview widget 35 | m_webview = webkit_web_view_new(); 36 | WebKitUserContentManager *manager = 37 | webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview)); 38 | g_signal_connect(manager, "script-message-received::external", 39 | G_CALLBACK(+[](WebKitUserContentManager *, 40 | WebKitJavascriptResult *r, gpointer arg) { 41 | auto *w = static_cast(arg); 42 | #if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22 43 | JSCValue *value = 44 | webkit_javascript_result_get_js_value(r); 45 | char *s = jsc_value_to_string(value); 46 | #else 47 | JSGlobalContextRef ctx = 48 | webkit_javascript_result_get_global_context(r); 49 | JSValueRef value = webkit_javascript_result_get_value(r); 50 | JSStringRef js = JSValueToStringCopy(ctx, value, NULL); 51 | size_t n = JSStringGetMaximumUTF8CStringSize(js); 52 | char *s = g_new(char, n); 53 | JSStringGetUTF8CString(js, s, n); 54 | JSStringRelease(js); 55 | #endif 56 | w->on_message(s); 57 | g_free(s); 58 | }), 59 | this); 60 | webkit_user_content_manager_register_script_message_handler(manager, 61 | "external"); 62 | init("window.external={invoke:function(s){window.webkit.messageHandlers." 63 | "external.postMessage(s);}}"); 64 | 65 | gtk_container_add(GTK_CONTAINER(m_window), GTK_WIDGET(m_webview)); 66 | gtk_widget_grab_focus(GTK_WIDGET(m_webview)); 67 | 68 | WebKitSettings *settings = 69 | webkit_web_view_get_settings(WEBKIT_WEB_VIEW(m_webview)); 70 | webkit_settings_set_javascript_can_access_clipboard(settings, true); 71 | if (debug) { 72 | webkit_settings_set_enable_write_console_messages_to_stdout(settings, 73 | true); 74 | webkit_settings_set_enable_developer_extras(settings, true); 75 | } 76 | gtk_window_set_position( GTK_WINDOW(m_window), GTK_WIN_POS_CENTER_ALWAYS ); 77 | gtk_widget_show_all(m_window); 78 | } 79 | 80 | GdkPixbuf * create_pixbuf(const gchar *filename) 81 | { 82 | GdkPixbuf *pixbuf; 83 | GError *error = NULL; 84 | pixbuf = gdk_pixbuf_new_from_file(filename, &error); 85 | if(!pixbuf) 86 | { 87 | fprintf(stderr,"%s\n",error->message); 88 | g_error_free(error); 89 | } 90 | return pixbuf; 91 | } 92 | 93 | 94 | void set_icon(const std::string icon) { 95 | 96 | std::string temp_file_name ="/tmp/icon_xxxxxxxxxx";; 97 | FILE *fd = fopen((char*)temp_file_name.c_str(),"w+"); 98 | 99 | if (fd == NULL) { 100 | printf("failed to create icon file %s: %s\n", temp_file_name.c_str(), strerror(errno)); 101 | return; 102 | } 103 | printf("size=%d\n",(int)icon.size()); 104 | ssize_t written = fwrite(icon.data(), icon.size(),icon.size(),fd); 105 | fclose(fd); 106 | gtk_window_set_icon(GTK_WINDOW(m_window),create_pixbuf(temp_file_name.c_str())); 107 | 108 | } 109 | 110 | 111 | void *window() { return (void *)m_window; } 112 | void run() { gtk_main(); } 113 | void terminate() { gtk_main_quit(); } 114 | 115 | void hide(){ 116 | g_idle_add(GSourceFunc(hideWindowMain),this); 117 | } 118 | 119 | static gboolean hideWindowMain(gpointer arg){ 120 | auto *engine = static_cast(arg); 121 | gtk_widget_hide(engine->m_window); 122 | return false; 123 | } 124 | 125 | static gboolean hideWindow(GtkWidget *window, GdkEvent *event,gpointer arg){ 126 | 127 | gtk_widget_hide(window); 128 | return true; 129 | } 130 | 131 | static gboolean showWindowMain(gpointer arg){ 132 | auto *engine = static_cast(arg); 133 | gtk_window_set_position( GTK_WINDOW(engine->m_window), GTK_WIN_POS_CENTER_ALWAYS ); 134 | gtk_widget_show_all(engine->m_window); 135 | gtk_window_present(GTK_WINDOW(engine->m_window)); 136 | return false; 137 | } 138 | 139 | void show(){ 140 | g_idle_add(GSourceFunc(showWindowMain),this); 141 | } 142 | 143 | 144 | void dispatch(std::function f) { 145 | g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)([](void *f) -> int { 146 | (*static_cast(f))(); 147 | return G_SOURCE_REMOVE; 148 | }), 149 | new std::function(f), 150 | [](void *f) { delete static_cast(f); }); 151 | } 152 | 153 | void set_title(const std::string title) { 154 | gtk_window_set_title(GTK_WINDOW(m_window), title.c_str()); 155 | } 156 | 157 | void set_size(int width, int height, int hints) { 158 | gtk_window_set_resizable(GTK_WINDOW(m_window), hints != WEBVIEW_HINT_FIXED); 159 | if (hints == WEBVIEW_HINT_NONE) { 160 | gtk_window_resize(GTK_WINDOW(m_window), width, height); 161 | } else if (hints == WEBVIEW_HINT_FIXED) { 162 | gtk_widget_set_size_request(m_window, width, height); 163 | } else { 164 | GdkGeometry g; 165 | g.min_width = g.max_width = width; 166 | g.min_height = g.max_height = height; 167 | GdkWindowHints h = 168 | (hints == WEBVIEW_HINT_MIN ? GDK_HINT_MIN_SIZE : GDK_HINT_MAX_SIZE); 169 | // This defines either MIN_SIZE, or MAX_SIZE, but not both: 170 | gtk_window_set_geometry_hints(GTK_WINDOW(m_window), nullptr, &g, h); 171 | } 172 | } 173 | 174 | void navigate(const std::string url) { 175 | webkit_web_view_load_uri(WEBKIT_WEB_VIEW(m_webview), url.c_str()); 176 | } 177 | 178 | void init(const std::string js) { 179 | WebKitUserContentManager *manager = 180 | webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview)); 181 | webkit_user_content_manager_add_script( 182 | manager, webkit_user_script_new( 183 | js.c_str(), WEBKIT_USER_CONTENT_INJECT_TOP_FRAME, 184 | WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, NULL, NULL)); 185 | } 186 | 187 | void eval(const std::string js) { 188 | webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(m_webview), js.c_str(), NULL, 189 | NULL, NULL); 190 | } 191 | private: 192 | virtual void on_message(const std::string msg) = 0; 193 | GtkWidget *m_window; 194 | GtkWidget *m_webview; 195 | bool m_hide; 196 | }; 197 | 198 | using browser_engine = gtk_webkit_engine; 199 | 200 | } // namespace webview 201 | -------------------------------------------------------------------------------- /webview_test.cc: -------------------------------------------------------------------------------- 1 | //bin/echo; [ $(uname) = "Darwin" ] && FLAGS="-framework Webkit" || FLAGS="$(pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0)" ; c++ "$0" $FLAGS -std=c++11 -Wall -Wextra -pedantic -g -o webview_test && ./webview_test ; exit 2 | // +build ignore 3 | 4 | #include "webview.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | // ================================================================= 13 | // TEST: start app loop and terminate it. 14 | // ================================================================= 15 | static void test_terminate() { 16 | webview::webview w(false, nullptr); 17 | w.dispatch([&]() { w.terminate(); }); 18 | w.run(); 19 | } 20 | 21 | // ================================================================= 22 | // TEST: use C API to create a window, run app and terminate it. 23 | // ================================================================= 24 | static void cb_assert_arg(webview_t w, void *arg) { 25 | assert(w != nullptr); 26 | assert(memcmp(arg, "arg", 3) == 0); 27 | } 28 | static void cb_terminate(webview_t w, void *arg) { 29 | assert(arg == nullptr); 30 | webview_terminate(w); 31 | } 32 | static void test_c_api() { 33 | webview_t w; 34 | w = webview_create(false, nullptr); 35 | webview_set_size(w, 480, 320, 0); 36 | webview_set_title(w, "Test"); 37 | webview_navigate(w, "https://github.com/zserge/webview"); 38 | webview_dispatch(w, cb_assert_arg, (void *)"arg"); 39 | webview_dispatch(w, cb_terminate, nullptr); 40 | webview_run(w); 41 | webview_destroy(w); 42 | } 43 | 44 | // ================================================================= 45 | // TEST: ensure that JS code can call native code and vice versa. 46 | // ================================================================= 47 | struct test_webview : webview::browser_engine { 48 | using cb_t = std::function; 49 | test_webview(cb_t cb) : webview::browser_engine(true, nullptr), m_cb(cb) {} 50 | void on_message(const std::string msg) override { m_cb(this, i++, msg); } 51 | int i = 0; 52 | cb_t m_cb; 53 | }; 54 | 55 | static void test_bidir_comms() { 56 | test_webview browser([](test_webview *w, int i, const std::string msg) { 57 | std::cout << msg << std::endl; 58 | switch (i) { 59 | case 0: 60 | assert(msg == "loaded"); 61 | w->eval("window.external.invoke('exiting ' + window.x)"); 62 | break; 63 | case 1: 64 | assert(msg == "exiting 42"); 65 | w->terminate(); 66 | break; 67 | default: 68 | assert(0); 69 | } 70 | }); 71 | browser.init(R"( 72 | window.x = 42; 73 | window.onload = () => { 74 | window.external.invoke('loaded'); 75 | }; 76 | )"); 77 | browser.navigate("data:text/html,%3Chtml%3Ehello%3C%2Fhtml%3E"); 78 | browser.run(); 79 | } 80 | 81 | // ================================================================= 82 | // TEST: ensure that JSON parsing works. 83 | // ================================================================= 84 | static void test_json() { 85 | auto J = webview::json_parse; 86 | assert(J(R"({"foo":"bar"})", "foo", -1) == "bar"); 87 | assert(J(R"({"foo":""})", "foo", -1) == ""); 88 | assert(J(R"({"foo": {"bar": 1}})", "foo", -1) == R"({"bar": 1})"); 89 | assert(J(R"(["foo", "bar", "baz"])", "", 0) == "foo"); 90 | assert(J(R"(["foo", "bar", "baz"])", "", 2) == "baz"); 91 | } 92 | 93 | static void run_with_timeout(std::function fn, int timeout_ms) { 94 | std::atomic_flag flag_running = ATOMIC_FLAG_INIT; 95 | flag_running.test_and_set(); 96 | std::thread timeout_thread([&]() { 97 | for (int i = 0; i < timeout_ms / 100; i++) { 98 | if (!flag_running.test_and_set()) { 99 | return; 100 | } 101 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 102 | } 103 | std::cout << "Exiting due to a timeout." << std::endl; 104 | exit(1); 105 | }); 106 | fn(); 107 | flag_running.clear(); 108 | timeout_thread.join(); 109 | } 110 | 111 | int main(int argc, char *argv[]) { 112 | std::unordered_map> all_tests = { 113 | {"terminate", test_terminate}, 114 | {"c_api", test_c_api}, 115 | {"bidir_comms", test_bidir_comms}, 116 | {"json", test_json}, 117 | }; 118 | // Without arguments run all tests, one-by-one by forking itself. 119 | // With a single argument - run the requested test 120 | if (argc == 1) { 121 | int failed = 0; 122 | for (auto test : all_tests) { 123 | std::cout << "TEST: " << test.first << std::endl; 124 | int status = system((std::string(argv[0]) + " " + test.first).c_str()); 125 | if (status == 0) { 126 | std::cout << " PASS " << std::endl; 127 | } else { 128 | std::cout << " FAIL: " << status << std::endl; 129 | failed = 1; 130 | } 131 | } 132 | return failed; 133 | } 134 | 135 | if (argc == 2) { 136 | auto it = all_tests.find(argv[1]); 137 | if (it != all_tests.end()) { 138 | run_with_timeout(it->second, 5000); 139 | return 0; 140 | } 141 | } 142 | std::cout << "USAGE: " << argv[0] << " [test name]" << std::endl; 143 | std::cout << "Tests: " << std::endl; 144 | for (auto test : all_tests) { 145 | std::cout << " " << test.first << std::endl; 146 | } 147 | return 1; 148 | } 149 | -------------------------------------------------------------------------------- /webview_test.go: -------------------------------------------------------------------------------- 1 | package webview 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func Example() { 11 | w := New(300, 600, true, true) 12 | defer w.Destroy() 13 | w.SetTitle("Hello") 14 | w.Bind("noop", func() string { 15 | log.Println("hello") 16 | return "hello" 17 | }) 18 | w.Bind("add", func(a, b int) int { 19 | return a + b 20 | }) 21 | w.Bind("quit", func() { 22 | w.Terminate() 23 | }) 24 | w.Navigate(`data:text/html, 25 | 26 | 27 | hello 28 | 40 | 41 | )`) 42 | w.Run() 43 | } 44 | 45 | func TestMain(m *testing.M) { 46 | flag.Parse() 47 | if testing.Verbose() { 48 | Example() 49 | } 50 | os.Exit(m.Run()) 51 | } 52 | -------------------------------------------------------------------------------- /webview_win.h: -------------------------------------------------------------------------------- 1 | // 2 | // ==================================================================== 3 | // 4 | // This implementation uses Win32 API to create a native window. It can 5 | // use either EdgeHTML or Edge/Chromium backend as a browser engine. 6 | // 7 | // ==================================================================== 8 | // 9 | 10 | #define WIN32_LEAN_AND_MEAN 11 | //#define WINVER 0x0605 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "webview2.h" 23 | 24 | 25 | namespace webview { 26 | 27 | using msg_cb_t = std::function; 28 | 29 | // Common interface for EdgeHTML and Edge/Chromium 30 | class browser { 31 | public: 32 | virtual ~browser() = default; 33 | virtual bool embed(HWND, bool, msg_cb_t) = 0; 34 | virtual void navigate(const std::string url) = 0; 35 | virtual void eval(const std::string js) = 0; 36 | virtual void init(const std::string js) = 0; 37 | virtual void resize(HWND) = 0; 38 | }; 39 | 40 | // 41 | // Edge/Chromium browser engine 42 | // 43 | class edge_chromium : public browser { 44 | public: 45 | bool embed(HWND wnd, bool debug, msg_cb_t cb) override { 46 | m_debug = debug; 47 | CoInitializeEx(0,COINIT_APARTMENTTHREADED); 48 | std::atomic_flag flag = ATOMIC_FLAG_INIT; 49 | flag.test_and_set(); 50 | wchar_t currentExePath[MAX_PATH]; 51 | GetModuleFileNameW(nullptr, currentExePath, MAX_PATH); 52 | wchar_t *currentExeName = PathFindFileNameW(currentExePath); 53 | std::wstring_convert> wideCharConverter; 54 | 55 | wchar_t dataPath[MAX_PATH]; 56 | 57 | HRESULT ret = SHGetFolderPathW(nullptr,CSIDL_APPDATA,nullptr,0,dataPath); 58 | 59 | if (ret != S_OK){ 60 | CoUninitialize(); 61 | return false; 62 | } 63 | 64 | std::wstring userDataFolder = dataPath; 65 | std::wstring currentExeNameW = currentExeName; 66 | HRESULT res = CreateCoreWebView2EnvironmentWithOptions( 67 | nullptr, (userDataFolder + L"/" + currentExeNameW).c_str(), nullptr, 68 | new webview2_com_handler(wnd, cb, 69 | [&](ICoreWebView2Controller *controller) { 70 | m_controller = controller; 71 | m_controller->get_CoreWebView2(&m_webview); 72 | m_webview->AddRef(); 73 | flag.clear(); 74 | })); 75 | 76 | if (res != S_OK) { 77 | CoUninitialize(); 78 | return false; 79 | } 80 | MSG msg = {}; 81 | while (flag.test_and_set() && GetMessage(&msg, NULL, 0, 0)) { 82 | TranslateMessage(&msg); 83 | DispatchMessage(&msg); 84 | } 85 | 86 | ICoreWebView2Settings* Settings; 87 | m_webview->get_Settings(&Settings); 88 | Settings->put_AreDevToolsEnabled(m_debug); 89 | 90 | init("window.external={invoke:s=>window.chrome.webview.postMessage(s)}"); 91 | return true; 92 | } 93 | 94 | void resize(HWND wnd) override { 95 | if (m_controller == nullptr) { 96 | return; 97 | } 98 | RECT bounds; 99 | GetClientRect(wnd, &bounds); 100 | m_controller->put_Bounds(bounds); 101 | } 102 | 103 | void navigate(const std::string url) override { 104 | auto wurl = to_lpwstr(url); 105 | m_webview->Navigate(wurl); 106 | delete[] wurl; 107 | } 108 | 109 | void init(const std::string js) override { 110 | LPCWSTR wjs = to_lpwstr(js); 111 | m_webview->AddScriptToExecuteOnDocumentCreated(wjs, nullptr); 112 | delete[] wjs; 113 | } 114 | 115 | void eval(const std::string js) override { 116 | LPCWSTR wjs = to_lpwstr(js); 117 | m_webview->ExecuteScript(wjs, nullptr); 118 | delete[] wjs; 119 | } 120 | 121 | private: 122 | LPWSTR to_lpwstr(const std::string s) { 123 | int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); 124 | wchar_t *ws = new wchar_t[n]; 125 | MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, ws, n); 126 | return ws; 127 | } 128 | bool m_debug; 129 | ICoreWebView2 *m_webview = nullptr; 130 | ICoreWebView2Controller *m_controller = nullptr; 131 | 132 | class webview2_com_handler 133 | : public ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler, 134 | public ICoreWebView2CreateCoreWebView2ControllerCompletedHandler, 135 | public ICoreWebView2WebMessageReceivedEventHandler, 136 | public ICoreWebView2PermissionRequestedEventHandler { 137 | using webview2_com_handler_cb_t = std::function; 138 | 139 | public: 140 | webview2_com_handler(HWND hwnd, msg_cb_t msgCb, 141 | webview2_com_handler_cb_t cb) 142 | : m_window(hwnd), m_msgCb(msgCb), m_cb(cb) {} 143 | ULONG STDMETHODCALLTYPE AddRef() { return 1; } 144 | ULONG STDMETHODCALLTYPE Release() { return 1; } 145 | HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppv) { 146 | return S_OK; 147 | } 148 | HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, ICoreWebView2Environment *env) { 149 | if(res != 0x0){ 150 | return res; 151 | } 152 | env->CreateCoreWebView2Controller(m_window, this); 153 | return S_OK; 154 | } 155 | HRESULT STDMETHODCALLTYPE Invoke(HRESULT res,ICoreWebView2Controller *controller) { 156 | 157 | if(res != 0x0){ 158 | return res; 159 | } 160 | controller->AddRef(); 161 | 162 | ICoreWebView2 *webview; 163 | ::EventRegistrationToken token; 164 | controller->get_CoreWebView2(&webview); 165 | webview->add_WebMessageReceived(this, &token); 166 | webview->add_PermissionRequested(this, &token); 167 | 168 | m_cb(controller); 169 | return S_OK; 170 | } 171 | HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2 *sender, ICoreWebView2WebMessageReceivedEventArgs *args) { 172 | LPWSTR message; 173 | args->TryGetWebMessageAsString(&message); 174 | 175 | std::wstring_convert> wideCharConverter; 176 | m_msgCb(wideCharConverter.to_bytes(message)); 177 | sender->PostWebMessageAsString(message); 178 | 179 | CoTaskMemFree(message); 180 | return S_OK; 181 | } 182 | HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2 *sender,ICoreWebView2PermissionRequestedEventArgs *args) { 183 | COREWEBVIEW2_PERMISSION_KIND kind; 184 | args->get_PermissionKind(&kind); 185 | if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ) { 186 | args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); 187 | } 188 | return S_OK; 189 | } 190 | 191 | private: 192 | HWND m_window; 193 | msg_cb_t m_msgCb; 194 | webview2_com_handler_cb_t m_cb; 195 | }; 196 | }; 197 | 198 | class win32_edge_engine { 199 | public: 200 | win32_edge_engine(int width,int height,bool hide,bool debug) { 201 | m_hide = hide; 202 | HINSTANCE hInstance = GetModuleHandle(nullptr); 203 | HICON icon = (HICON)LoadImage(hInstance, IDI_APPLICATION, IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CXICON), LR_DEFAULTCOLOR); 204 | WNDCLASSEX wc; 205 | ZeroMemory(&wc, sizeof(WNDCLASSEX)); 206 | wc.cbSize = sizeof(WNDCLASSEX); 207 | wc.hInstance = hInstance; 208 | wc.lpszClassName = "webview"; 209 | wc.hIcon = icon; 210 | wc.hIconSm = icon; 211 | wc.hbrBackground = CreateSolidBrush(RGB(255,255,255)); 212 | wc.lpfnWndProc = 213 | (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> LRESULT { 214 | auto w = (win32_edge_engine *)GetWindowLongPtrW(hwnd, GWLP_USERDATA); 215 | switch (msg) { 216 | case WM_SIZE: 217 | w->m_browser->resize(hwnd); 218 | break; 219 | case WM_CLOSE: 220 | if(w->m_hide){ 221 | ShowWindow(w->m_window, SW_HIDE); 222 | }else{ 223 | DestroyWindow(hwnd); 224 | } 225 | break; 226 | case WM_DESTROY: 227 | w->terminate(); 228 | break; 229 | case WM_GETMINMAXINFO: { 230 | auto lpmmi = (LPMINMAXINFO)lp; 231 | if (w == nullptr) { 232 | return 0; 233 | } 234 | if (w->m_maxsz.x > 0 && w->m_maxsz.y > 0) { 235 | lpmmi->ptMaxSize = w->m_maxsz; 236 | lpmmi->ptMaxTrackSize = w->m_maxsz; 237 | } 238 | if (w->m_minsz.x > 0 && w->m_minsz.y > 0) { 239 | lpmmi->ptMinTrackSize = w->m_minsz; 240 | } 241 | } break; 242 | default: 243 | return DefWindowProc(hwnd, msg, wp, lp); 244 | } 245 | return 0; 246 | }); 247 | 248 | RegisterClassEx(&wc); 249 | 250 | m_window = CreateWindowExW(WS_EX_APPWINDOW,L"webview", L"", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 251 | CW_USEDEFAULT, width, height, nullptr, nullptr,hInstance, this); 252 | 253 | SetWindowLongPtrW(m_window, GWLP_USERDATA, (LONG_PTR)this); 254 | 255 | int scrWidth = GetSystemMetrics(SM_CXSCREEN); 256 | int scrHeight = GetSystemMetrics(SM_CYSCREEN); 257 | RECT rect; 258 | rect.right = width;// 设置窗口宽高 259 | rect.bottom = height; 260 | rect.left = (scrWidth - rect.right) / 2; 261 | rect.top = (scrHeight - rect.bottom) / 2; 262 | 263 | AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, 0); 264 | MoveWindow(m_window,rect.left, rect.top, rect.right, rect.bottom, 1);// 居中 265 | ShowWindow(m_window, SW_SHOW); 266 | UpdateWindow(m_window); 267 | SetFocus(m_window); 268 | 269 | auto cb = std::bind(&win32_edge_engine::on_message, this, std::placeholders::_1); 270 | bool flag = m_browser->embed(m_window, debug, cb); 271 | if(!flag){ 272 | MessageBox(NULL,TEXT("can't load webview2,please install webveiw2 runtime"),TEXT("Alert"),MB_OK); 273 | exit(0); 274 | } 275 | m_browser->resize(m_window); 276 | } 277 | 278 | void run() { 279 | MSG msg; 280 | BOOL res; 281 | while ((res = GetMessage(&msg, nullptr, 0, 0)) != -1) { 282 | 283 | if (msg.hwnd) { 284 | TranslateMessage(&msg); 285 | DispatchMessage(&msg); 286 | continue; 287 | } 288 | if (msg.message == WM_APP) { 289 | auto f = (dispatch_fn_t *)(msg.lParam); 290 | (*f)(); 291 | delete f; 292 | } else if (msg.message == WM_QUIT) { 293 | return; 294 | } 295 | } 296 | } 297 | 298 | LPWSTR to_lpwstr(const std::string s) { 299 | int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); 300 | wchar_t *ws = new wchar_t[n]; 301 | MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, ws, n); 302 | return ws; 303 | } 304 | 305 | void *window() { return (void *)m_window; } 306 | void hide() { 307 | ShowWindow(m_window, SW_HIDE); 308 | } 309 | void show() { 310 | ShowWindow(m_window, SW_SHOW); 311 | } 312 | void terminate() { PostQuitMessage(0); } 313 | void dispatch(dispatch_fn_t f) { 314 | PostThreadMessage(m_main_thread, WM_APP, 0, (LPARAM) new dispatch_fn_t(f)); 315 | } 316 | 317 | void set_icon(const std::string icon) { 318 | } 319 | 320 | void set_title(const std::string title) { 321 | SetWindowTextW(m_window, to_lpwstr(title)); 322 | } 323 | 324 | void set_size(int width, int height, int hints) { 325 | auto style = GetWindowLong(m_window, GWL_STYLE); 326 | if (hints == WEBVIEW_HINT_FIXED) { 327 | style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX); 328 | } else { 329 | style |= (WS_THICKFRAME | WS_MAXIMIZEBOX); 330 | } 331 | SetWindowLong(m_window, GWL_STYLE, style); 332 | 333 | if (hints == WEBVIEW_HINT_MAX) { 334 | m_maxsz.x = width; 335 | m_maxsz.y = height; 336 | } else if (hints == WEBVIEW_HINT_MIN) { 337 | m_minsz.x = width; 338 | m_minsz.y = height; 339 | } else { 340 | 341 | int scrWidth = GetSystemMetrics(SM_CXSCREEN); 342 | int scrHeight = GetSystemMetrics(SM_CYSCREEN); 343 | RECT rect; 344 | rect.right = width;// 设置窗口宽高 345 | rect.bottom = height; 346 | rect.left = (scrWidth - rect.right) / 2; 347 | rect.top = (scrHeight - rect.bottom) / 2; 348 | 349 | AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, 0); 350 | MoveWindow(m_window,rect.left, rect.top, rect.right, rect.bottom, 1);// 居中 351 | m_browser->resize(m_window); 352 | } 353 | } 354 | 355 | void navigate(const std::string url) { m_browser->navigate(url); } 356 | void eval(const std::string js) { m_browser->eval(js); } 357 | void init(const std::string js) { m_browser->init(js); } 358 | 359 | private: 360 | virtual void on_message(const std::string msg) = 0; 361 | bool m_hide; 362 | HWND m_window; 363 | POINT m_minsz = POINT{0, 0}; 364 | POINT m_maxsz = POINT{0, 0}; 365 | DWORD m_main_thread = GetCurrentThreadId(); 366 | std::unique_ptr m_browser = std::unique_ptr(new webview::edge_chromium()); 367 | }; 368 | 369 | using browser_engine = win32_edge_engine; 370 | } // namespace webview --------------------------------------------------------------------------------