From 0fd0d2cf486d06b334b1db122d3f967a8659d212 Mon Sep 17 00:00:00 2001 From: Tensor-Programming Date: Mon, 7 Oct 2019 14:42:25 -0400 Subject: [PATCH] Feature/edge(ui) (#40) * add webview-x code * add logic for json_escape function * fix warning * merge edge changes --- ui/tauri-edge.cc | 32 ++ ui/tauri-edge.h | 1173 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1205 insertions(+) create mode 100644 ui/tauri-edge.cc create mode 100644 ui/tauri-edge.h diff --git a/ui/tauri-edge.cc b/ui/tauri-edge.cc new file mode 100644 index 000000000..b5fa8711b --- /dev/null +++ b/ui/tauri-edge.cc @@ -0,0 +1,32 @@ +#include "webview-edge.h" +#ifdef __cplusplus +extern "C" +{ +#endif + + void wrapper_webview_free(webview_t w) + { + webview_destroy(w); + } + + webview_t wrapper_webview_new(const char *title, const char *url, int width, int height, int resizable, int debug, webview_external_invoke_cb_t external_invoke_cb, void *userdata) + { + webview_t w = webview_create(external_invoke_cb, title, width, height, resizable, debug); + webview_set_userdata(w, userdata); + webview_navigate(w, url); + return w; + } + + void *wrapper_webview_get_userdata(webview_t w) + { + return webview_get_userdata(w); + } + + void webview_exit(webview_t w) + { + webview_terminate(w); + } + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/ui/tauri-edge.h b/ui/tauri-edge.h new file mode 100644 index 000000000..9c145c251 --- /dev/null +++ b/ui/tauri-edge.h @@ -0,0 +1,1173 @@ +/* + * MIT License + * + * Copyright (c) 2017 Serge Zaitsev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef WEBVIEW_H +#define WEBVIEW_H + +#ifndef WEBVIEW_API +#define WEBVIEW_API extern +#endif + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef void *webview_t; + + typedef void (*webview_external_invoke_cb_t)(webview_t w, const char *arg); + + // Create a new webview instance + WEBVIEW_API webview_t webview_create(webview_external_invoke_cb_t invoke_cb, int width, int height, int resizable, int debug); + + // Destroy a webview + WEBVIEW_API void webview_destroy(webview_t w); + + // Run the main loop + WEBVIEW_API void webview_run(webview_t w); + + // Stop the main loop + WEBVIEW_API void webview_terminate(webview_t w); + + // Post a function to be executed on the main thread + WEBVIEW_API void webview_dispatch( + webview_t w, void (*fn)(webview_t w, void *arg), void *arg); + + WEBVIEW_API void *webview_get_window(webview_t w); + + WEBVIEW_API void webview_set_title(webview_t w, const char *title); + + WEBVIEW_API void webview_navigate(webview_t w, const char *url); + WEBVIEW_API void webview_init(webview_t w, const char *js); + WEBVIEW_API int webview_eval(webview_t w, const char *js); + + WEBVIEW_API int webview_loop(webview_t w, int blocking); + + WEBVIEW_API void *webview_get_userdata(webview_t w); + WEBVIEW_API void webview_set_userdata(webview_t w, void *user_data); + + // Enable or disable window fullscreen + WEBVIEW_API void webview_set_fullscreen(webview_t w, int fullscreen); + + // Set rgba color of the window's title bar + WEBVIEW_API void webview_set_color(webview_t w, uint8_t r, uint8_t g, uint8_t b, uint8_t a); + + // Inject css into webview's page + WEBVIEW_API int webview_inject_css(webview_t w, const char *css); + + WEBVIEW_API void webview_dialog(webview_t w, + enum webview_dialog_type dlgtype, int flags, + const char *title, const char *arg, + char *result, size_t resultsz); + +#ifdef __cplusplus +} +#endif + +enum webview_dialog_type +{ + WEBVIEW_DIALOG_TYPE_OPEN = 0, + WEBVIEW_DIALOG_TYPE_SAVE = 1, + WEBVIEW_DIALOG_TYPE_ALERT = 2 +}; + +#define WEBVIEW_DIALOG_FLAG_FILE (0 << 0) +#define WEBVIEW_DIALOG_FLAG_DIRECTORY (1 << 0) + +#define WEBVIEW_DIALOG_FLAG_INFO (1 << 1) +#define WEBVIEW_DIALOG_FLAG_WARNING (2 << 1) +#define WEBVIEW_DIALOG_FLAG_ERROR (3 << 1) +#define WEBVIEW_DIALOG_FLAG_ALERT_MASK (3 << 1) + +#ifndef WEBVIEW_HEADER + +#include +#include +#include +#include +#include +#include +#include + +// +// ==================================================================== +// +// This implementation uses Win32 API to create a native window. It can +// use either MSHTML or EdgeHTML backend as a browser engine. +// +// ==================================================================== +// + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include + +#pragma comment(lib, "windowsapp") +#pragma comment(lib, "user32.lib") +#pragma comment(lib, "gdi32") + +namespace webview +{ +using dispatch_fn_t = std::function; +using msg_cb_t = std::function; + +inline std::string url_encode(std::string s) +{ + std::string encoded; + for (unsigned int i = 0; i < s.length(); i++) + { + auto c = s[i]; + if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') + { + encoded = encoded + c; + } + else + { + char hex[4]; + snprintf(hex, sizeof(hex), "%%%02x", c); + encoded = encoded + hex; + } + } + return encoded; +} + +inline std::string url_decode(std::string s) +{ + std::string decoded; + for (unsigned int i = 0; i < s.length(); i++) + { + if (s[i] == '%') + { + int n; + sscanf(s.substr(i + 1, 2).c_str(), "%x", &n); + decoded = decoded + static_cast(n); + i = i + 2; + } + else if (s[i] == '+') + { + decoded = decoded + ' '; + } + else + { + decoded = decoded + s[i]; + } + } + return decoded; +} + +inline std::string html_from_uri(std::string s) +{ + if (s.substr(0, 15) == "data:text/html,") + { + return url_decode(s.substr(15)); + } + return ""; +} + +inline int json_parse_c(const char *s, size_t sz, const char *key, size_t keysz, + const char **value, size_t *valuesz) +{ + enum + { + JSON_STATE_VALUE, + JSON_STATE_LITERAL, + JSON_STATE_STRING, + JSON_STATE_ESCAPE, + JSON_STATE_UTF8 + } state = JSON_STATE_VALUE; + const char *k = NULL; + int index = 1; + int depth = 0; + int utf8_bytes = 0; + + if (key == NULL) + { + index = keysz; + keysz = 0; + } + + *value = NULL; + *valuesz = 0; + + for (; sz > 0; s++, sz--) + { + enum + { + JSON_ACTION_NONE, + JSON_ACTION_START, + JSON_ACTION_END, + JSON_ACTION_START_STRUCT, + JSON_ACTION_END_STRUCT + } action = JSON_ACTION_NONE; + unsigned char c = *s; + switch (state) + { + case JSON_STATE_VALUE: + if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' || c == ':') + { + continue; + } + else if (c == '"') + { + action = JSON_ACTION_START; + state = JSON_STATE_STRING; + } + else if (c == '{' || c == '[') + { + action = JSON_ACTION_START_STRUCT; + } + else if (c == '}' || c == ']') + { + action = JSON_ACTION_END_STRUCT; + } + else if (c == 't' || c == 'f' || c == 'n' || c == '-' || (c >= '0' && c <= '9')) + { + action = JSON_ACTION_START; + state = JSON_STATE_LITERAL; + } + else + { + return -1; + } + break; + case JSON_STATE_LITERAL: + if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' || c == ']' || c == '}' || c == ':') + { + state = JSON_STATE_VALUE; + s--; + sz++; + action = JSON_ACTION_END; + } + else if (c < 32 || c > 126) + { + return -1; + } // fallthrough + case JSON_STATE_STRING: + if (c < 32 || (c > 126 && c < 192)) + { + return -1; + } + else if (c == '"') + { + action = JSON_ACTION_END; + state = JSON_STATE_VALUE; + } + else if (c == '\\') + { + state = JSON_STATE_ESCAPE; + } + else if (c >= 192 && c < 224) + { + utf8_bytes = 1; + state = JSON_STATE_UTF8; + } + else if (c >= 224 && c < 240) + { + utf8_bytes = 2; + state = JSON_STATE_UTF8; + } + else if (c >= 240 && c < 247) + { + utf8_bytes = 3; + state = JSON_STATE_UTF8; + } + else if (c >= 128 && c < 192) + { + return -1; + } + break; + case JSON_STATE_ESCAPE: + if (c == '"' || c == '\\' || c == '/' || c == 'b' || c == 'f' || c == 'n' || c == 'r' || c == 't' || c == 'u') + { + state = JSON_STATE_STRING; + } + else + { + return -1; + } + break; + case JSON_STATE_UTF8: + if (c < 128 || c > 191) + { + return -1; + } + utf8_bytes--; + if (utf8_bytes == 0) + { + state = JSON_STATE_STRING; + } + break; + default: + return -1; + } + + if (action == JSON_ACTION_END_STRUCT) + { + depth--; + } + + if (depth == 1) + { + if (action == JSON_ACTION_START || action == JSON_ACTION_START_STRUCT) + { + if (index == 0) + { + *value = s; + } + else if (keysz > 0 && index == 1) + { + k = s; + } + else + { + index--; + } + } + else if (action == JSON_ACTION_END || action == JSON_ACTION_END_STRUCT) + { + if (*value != NULL && index == 0) + { + *valuesz = (size_t)(s + 1 - *value); + return 0; + } + else if (keysz > 0 && k != NULL) + { + if (keysz == (size_t)(s - k - 1) && memcmp(key, k + 1, keysz) == 0) + { + index = 0; + } + else + { + index = 2; + } + k = NULL; + } + } + } + + if (action == JSON_ACTION_START_STRUCT) + { + depth++; + } + } + return -1; +} + +inline std::string json_escape(std::string s) +{ + std::string r = "\""; + + r.reserve(s.size() + 4); + + static const char *h = "0123456789abcdef"; + + const unsigned char *d = reinterpret_cast(s.data()); + + for (size_t i = 0; i < s.size(); ++i) + { + switch (const auto c = d[i]) + { + case '\b': + r += "\\b"; + break; + case '\f': + r += "\\f"; + break; + case '\n': + r += "\\n"; + break; + case '\r': + r += "\\r"; + break; + case '\t': + r += "\\t"; + break; + case '\\': + r += "\\\\"; + break; + case '\"': + r += "\\\""; + break; + default: + if ((c < 32) || (c == 127)) + { + r += "\\u00"; + r += h[(c & 0xf0) >> 4]; + r += h[c & 0x0f]; + continue; + } + r += c; // Assume valid UTF-8. + break; + } + } + r += '"'; + return r; +} + +inline int json_unescape(const char *s, size_t n, char *out) +{ + int r = 0; + if (*s++ != '"') + { + return -1; + } + while (n > 2) + { + char c = *s; + if (c == '\\') + { + s++; + n--; + switch (*s) + { + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case '\\': + c = '\\'; + break; + case '/': + c = '/'; + break; + case '\"': + c = '\"'; + break; + default: // TODO: support unicode decoding + return -1; + } + } + if (out != NULL) + { + *out++ = c; + } + s++; + n--; + r++; + } + if (*s != '"') + { + return -1; + } + if (out != NULL) + { + *out = '\0'; + } + return r; +} + +inline std::string json_parse(std::string s, std::string key, int index) +{ + const char *value; + size_t value_sz; + if (key == "") + { + json_parse_c(s.c_str(), s.length(), nullptr, index, &value, &value_sz); + } + else + { + json_parse_c(s.c_str(), s.length(), key.c_str(), key.length(), &value, &value_sz); + } + if (value != nullptr) + { + if (value[0] != '"') + { + return std::string(value, value_sz); + } + int n = json_unescape(value, value_sz, nullptr); + if (n > 0) + { + char *decoded = new char[n]; + json_unescape(value, value_sz, decoded); + auto result = std::string(decoded, n); + delete[] decoded; + return result; + } + } + return ""; +} + +LRESULT CALLBACK WebviewWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp); +class browser_window +{ +public: + browser_window(msg_cb_t cb, const char *title, int width, int height, bool resizable) + : m_cb(cb) + { + HINSTANCE hInstance = GetModuleHandle(nullptr); + + WNDCLASSEX wc; + ZeroMemory(&wc, sizeof(WNDCLASSEX)); + wc.cbSize = sizeof(WNDCLASSEX); + wc.hInstance = hInstance; + wc.lpfnWndProc = WebviewWndProc; + wc.lpszClassName = "webview"; + RegisterClassEx(&wc); + + DWORD style = WS_OVERLAPPEDWINDOW; + if (!resizable) + { + style = WS_OVERLAPPED | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU; + } + + RECT clientRect; + RECT rect; + rect.left = 0; + rect.top = 0; + rect.right = width; + rect.bottom = height; + AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, 0); + + GetClientRect(GetDesktopWindow(), &clientRect); + int left = (clientRect.right / 2) - ((rect.right - rect.left) / 2); + int top = (clientRect.bottom / 2) - ((rect.bottom - rect.top) / 2); + rect.right = rect.right - rect.left + left; + rect.left = left; + rect.bottom = rect.bottom - rect.top + top; + rect.top = top; + + m_window = CreateWindowEx(0, "webview", title, style, rect.left, rect.top, + rect.right - rect.left, rect.bottom - rect.top, + HWND_DESKTOP, NULL, hInstance, (void *)this); + + SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this); + + ShowWindow(m_window, SW_SHOW); + UpdateWindow(m_window); + SetFocus(m_window); + } + + void run() + { + while (this->loop(true) == 0) + { + } + } + + int loop(int blocking) + { + MSG msg; + + if (blocking) + { + if (GetMessage(&msg, nullptr, 0, 0) < 0) + return 0; + } + else + { + if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE) == 0) + return 0; + } + + if (msg.hwnd) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + return 0; + } + if (msg.message == WM_APP) + { + auto f = (dispatch_fn_t *)(msg.lParam); + (*f)(); + delete f; + } + else if (msg.message == WM_QUIT) + { + return -1; + } + + return 0; + } + + void terminate() { PostQuitMessage(0); } + void dispatch(dispatch_fn_t f) + { + PostThreadMessage(m_main_thread, WM_APP, 0, (LPARAM) new dispatch_fn_t(f)); + } + + void set_title(const char *title) { SetWindowText(m_window, title); } + + void set_size(int width, int height) + { + RECT r; + r.left = 50; + r.top = 50; + r.right = width; + r.bottom = height; + AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, 0); + SetWindowPos(m_window, NULL, r.left, r.top, r.right - r.left, r.bottom - r.top, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); + } + + void set_fullscreen(bool fullscreen) + { + if (this->is_fullscreen == fullscreen) + { + return; + } + if (!this->is_fullscreen) + { + this->saved_style = GetWindowLong(this->m_window, GWL_STYLE); + this->saved_ex_style = GetWindowLong(this->m_window, GWL_EXSTYLE); + GetWindowRect(this->m_window, &this->saved_rect); + } + this->is_fullscreen = !!fullscreen; + if (fullscreen) + { + MONITORINFO monitor_info; + SetWindowLong(this->m_window, GWL_STYLE, + this->saved_style & ~(WS_CAPTION | WS_THICKFRAME)); + SetWindowLong(this->m_window, GWL_EXSTYLE, + this->saved_ex_style & + ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | + WS_EX_CLIENTEDGE | WS_EX_STATICEDGE)); + monitor_info.cbSize = sizeof(monitor_info); + GetMonitorInfo(MonitorFromWindow(this->m_window, MONITOR_DEFAULTTONEAREST), + &monitor_info); + RECT r; + r.left = monitor_info.rcMonitor.left; + r.top = monitor_info.rcMonitor.top; + r.right = monitor_info.rcMonitor.right; + r.bottom = monitor_info.rcMonitor.bottom; + SetWindowPos(this->m_window, NULL, r.left, r.top, r.right - r.left, + r.bottom - r.top, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); + } + else + { + SetWindowLong(this->m_window, GWL_STYLE, this->saved_style); + SetWindowLong(this->m_window, GWL_EXSTYLE, this->saved_ex_style); + SetWindowPos(this->m_window, NULL, this->saved_rect.left, + this->saved_rect.top, + this->saved_rect.right - this->saved_rect.left, + this->saved_rect.bottom - this->saved_rect.top, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); + } + } + + void set_color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) + { + HBRUSH brush = CreateSolidBrush(RGB(r, g, b)); + SetClassLongPtr(this->m_window, GCLP_HBRBACKGROUND, (LONG_PTR)brush); + } + + // protected: + virtual void resize() {} + HWND m_window; + DWORD m_main_thread = GetCurrentThreadId(); + msg_cb_t m_cb; + + bool is_fullscreen = false; + DWORD saved_style = 0; + DWORD saved_ex_style = 0; + RECT saved_rect; +}; + +LRESULT CALLBACK WebviewWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) +{ + auto w = (browser_window *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + switch (msg) + { + case WM_SIZE: + w->resize(); + break; + case WM_CLOSE: + DestroyWindow(hwnd); + break; + case WM_DESTROY: + w->terminate(); + break; + default: + return DefWindowProc(hwnd, msg, wp, lp); + } + return 0; +} + +using namespace winrt; +using namespace Windows::Foundation; +using namespace Windows::Web::UI; +using namespace Windows::Web::UI::Interop; + +class webview : public browser_window +{ +public: + webview(webview_external_invoke_cb_t invoke_cb, const char *title, int width, int height, bool resizable, bool debug) + : browser_window(std::bind(&webview::on_message, this, std::placeholders::_1), title, width, height, resizable), invoke_cb(invoke_cb) + { + init_apartment(winrt::apartment_type::single_threaded); + WebViewControlProcessOptions options; + options.PrivateNetworkClientServerCapability(WebViewControlProcessCapabilityState::Enabled); + m_process = WebViewControlProcess(options); + auto op = m_process.CreateWebViewControlAsync( + reinterpret_cast(m_window), Rect()); + if (op.Status() != AsyncStatus::Completed) + { + handle h(CreateEvent(nullptr, false, false, nullptr)); + op.Completed([h = h.get()](auto, auto) { SetEvent(h); }); + HANDLE hs[] = {h.get()}; + DWORD i; + CoWaitForMultipleHandles(COWAIT_DISPATCH_WINDOW_MESSAGES | COWAIT_DISPATCH_CALLS | COWAIT_INPUTAVAILABLE, + INFINITE, 1, hs, &i); + } + m_webview = op.GetResults(); + m_webview.Settings().IsScriptNotifyAllowed(true); + m_webview.IsVisible(true); + m_webview.ScriptNotify([=](auto const &sender, auto const &args) { + std::string s = winrt::to_string(args.Value()); + m_cb(s.c_str()); + }); + m_webview.NavigationStarting([=](auto const &sender, auto const &args) { + m_webview.AddInitializeScript(winrt::to_hstring(init_js)); + }); + init("window.external.invoke = s => window.external.notify(s)"); + resize(); + } + + void navigate(const char *url) + { + std::string html = html_from_uri(url); + if (html != "") + { + m_webview.NavigateToString(winrt::to_hstring(html.c_str())); + } + else + { + Uri uri(winrt::to_hstring(url)); + m_webview.Navigate(uri); + } + } + void init(const char *js) { init_js = init_js + "(function(){" + js + "})();"; } + void eval(const char *js) + { + m_webview.InvokeScriptAsync( + L"eval", single_threaded_vector({winrt::to_hstring(js)})); + } + + void *window() { return (void *)m_window; } + + void *get_user_data() { return this->user_data; } + void set_user_data(void *user_data) { this->user_data = user_data; } + +private: + void on_message(const char *msg) + { + this->invoke_cb(this, msg); + } + + void resize() + { + RECT r; + GetClientRect(m_window, &r); + Rect bounds(r.left, r.top, r.right - r.left, r.bottom - r.top); + m_webview.Bounds(bounds); + } + WebViewControlProcess m_process; + WebViewControl m_webview = nullptr; + std::string init_js = ""; + + void *user_data = nullptr; + webview_external_invoke_cb_t invoke_cb; +}; + +} // namespace webview + +static inline char *webview_from_utf16(WCHAR *ws) +{ + int n = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL); + char *s = (char *)GlobalAlloc(GMEM_FIXED, n); + if (s == NULL) + { + return NULL; + } + WideCharToMultiByte(CP_UTF8, 0, ws, -1, s, n, NULL, NULL); + return s; +} + +WEBVIEW_API webview_t webview_create(webview_external_invoke_cb_t invoke_cb, const char *title, int width, int height, int resizable, int debug) +{ + return new webview::webview(invoke_cb, title, width, height, resizable, debug); +} + +WEBVIEW_API void webview_destroy(webview_t w) +{ + delete static_cast(w); +} + +WEBVIEW_API void webview_run(webview_t w) { static_cast(w)->run(); } + +WEBVIEW_API void webview_terminate(webview_t w) +{ + static_cast(w)->terminate(); +} + +WEBVIEW_API void webview_dispatch( + webview_t w, void (*fn)(webview_t w, void *arg), void *arg) +{ + static_cast(w)->dispatch([=]() { fn(w, arg); }); +} + +WEBVIEW_API void *webview_get_window(webview_t w) +{ + return static_cast(w)->window(); +} + +WEBVIEW_API void webview_set_title(webview_t w, const char *title) +{ + static_cast(w)->set_title(title); +} + +WEBVIEW_API void webview_navigate(webview_t w, const char *url) +{ + static_cast(w)->navigate(url); +} + +WEBVIEW_API void webview_init(webview_t w, const char *js) +{ + static_cast(w)->init(js); +} + +WEBVIEW_API int webview_eval(webview_t w, const char *js) +{ + static_cast(w)->eval(js); + return 0; +} + +WEBVIEW_API int webview_loop(webview_t w, int blocking) +{ + return static_cast(w)->loop(blocking); +} + +WEBVIEW_API void *webview_get_userdata(webview_t w) +{ + return static_cast(w)->get_user_data(); +} + +WEBVIEW_API void webview_set_userdata(webview_t w, void *user_data) +{ + static_cast(w)->set_user_data(user_data); +} + +WEBVIEW_API void webview_set_fullscreen(webview_t w, int fullscreen) +{ + static_cast(w)->set_fullscreen(fullscreen); +} + +WEBVIEW_API void webview_set_color(webview_t w, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + static_cast(w)->set_color(r, g, b, a); +} + +#define CSS_INJECT_FUNCTION \ + "(function(e){var " \ + "t=document.createElement('style'),d=document.head||document." \ + "getElementsByTagName('head')[0];t.setAttribute('type','text/" \ + "css'),t.styleSheet?t.styleSheet.cssText=e:t.appendChild(document." \ + "createTextNode(e)),d.appendChild(t)})" + +static int webview_js_encode(const char *s, char *esc, size_t n) +{ + int r = 1; /* At least one byte for trailing zero */ + for (; *s; s++) + { + const unsigned char c = *s; + if (c >= 0x20 && c < 0x80 && strchr("<>\\'\"", c) == NULL) + { + if (n > 0) + { + *esc++ = c; + n--; + } + r++; + } + else + { + if (n > 0) + { + snprintf(esc, n, "\\x%02x", (int)c); + esc += 4; + n -= 4; + } + r += 4; + } + } + return r; +} + +WEBVIEW_API int webview_inject_css(webview_t w, const char *css) +{ + int n = webview_js_encode(css, NULL, 0); + char *esc = (char *)calloc(1, sizeof(CSS_INJECT_FUNCTION) + n + 4); + if (esc == NULL) + { + return -1; + } + char *js = (char *)calloc(1, n); + webview_js_encode(css, js, n); + snprintf(esc, sizeof(CSS_INJECT_FUNCTION) + n + 4, "%s(\"%s\")", + CSS_INJECT_FUNCTION, js); + int r = webview_eval(w, esc); + free(js); + free(esc); + return r; +} + +#include + +#ifdef __cplusplus +#define iid_ref(x) &(x) +#define iid_unref(x) *(x) +#else +#define iid_ref(x) (x) +#define iid_unref(x) (x) +#endif + +/* These are missing parts from MinGW */ +#ifndef __IFileDialog_INTERFACE_DEFINED__ +#define __IFileDialog_INTERFACE_DEFINED__ +enum _FILEOPENDIALOGOPTIONS +{ + FOS_OVERWRITEPROMPT = 0x2, + FOS_STRICTFILETYPES = 0x4, + FOS_NOCHANGEDIR = 0x8, + FOS_PICKFOLDERS = 0x20, + FOS_FORCEFILESYSTEM = 0x40, + FOS_ALLNONSTORAGEITEMS = 0x80, + FOS_NOVALIDATE = 0x100, + FOS_ALLOWMULTISELECT = 0x200, + FOS_PATHMUSTEXIST = 0x800, + FOS_FILEMUSTEXIST = 0x1000, + FOS_CREATEPROMPT = 0x2000, + FOS_SHAREAWARE = 0x4000, + FOS_NOREADONLYRETURN = 0x8000, + FOS_NOTESTFILECREATE = 0x10000, + FOS_HIDEMRUPLACES = 0x20000, + FOS_HIDEPINNEDPLACES = 0x40000, + FOS_NODEREFERENCELINKS = 0x100000, + FOS_DONTADDTORECENT = 0x2000000, + FOS_FORCESHOWHIDDEN = 0x10000000, + FOS_DEFAULTNOMINIMODE = 0x20000000, + FOS_FORCEPREVIEWPANEON = 0x40000000 +}; +typedef DWORD FILEOPENDIALOGOPTIONS; +typedef enum FDAP +{ + FDAP_BOTTOM = 0, + FDAP_TOP = 1 +} FDAP; +DEFINE_GUID(IID_IFileDialog, 0x42f85136, 0xdb7e, 0x439c, 0x85, 0xf1, 0xe4, 0x07, + 0x5d, 0x13, 0x5f, 0xc8); +typedef struct IFileDialogVtbl +{ + BEGIN_INTERFACE + HRESULT(STDMETHODCALLTYPE *QueryInterface) + (IFileDialog *This, REFIID riid, void **ppvObject); + ULONG(STDMETHODCALLTYPE *AddRef) + (IFileDialog *This); + ULONG(STDMETHODCALLTYPE *Release) + (IFileDialog *This); + HRESULT(STDMETHODCALLTYPE *Show) + (IFileDialog *This, HWND hwndOwner); + HRESULT(STDMETHODCALLTYPE *SetFileTypes) + (IFileDialog *This, UINT cFileTypes, const COMDLG_FILTERSPEC *rgFilterSpec); + HRESULT(STDMETHODCALLTYPE *SetFileTypeIndex) + (IFileDialog *This, UINT iFileType); + HRESULT(STDMETHODCALLTYPE *GetFileTypeIndex) + (IFileDialog *This, UINT *piFileType); + HRESULT(STDMETHODCALLTYPE *Advise) + (IFileDialog *This, IFileDialogEvents *pfde, DWORD *pdwCookie); + HRESULT(STDMETHODCALLTYPE *Unadvise) + (IFileDialog *This, DWORD dwCookie); + HRESULT(STDMETHODCALLTYPE *SetOptions) + (IFileDialog *This, FILEOPENDIALOGOPTIONS fos); + HRESULT(STDMETHODCALLTYPE *GetOptions) + (IFileDialog *This, FILEOPENDIALOGOPTIONS *pfos); + HRESULT(STDMETHODCALLTYPE *SetDefaultFolder) + (IFileDialog *This, IShellItem *psi); + HRESULT(STDMETHODCALLTYPE *SetFolder) + (IFileDialog *This, IShellItem *psi); + HRESULT(STDMETHODCALLTYPE *GetFolder) + (IFileDialog *This, IShellItem **ppsi); + HRESULT(STDMETHODCALLTYPE *GetCurrentSelection) + (IFileDialog *This, IShellItem **ppsi); + HRESULT(STDMETHODCALLTYPE *SetFileName) + (IFileDialog *This, LPCWSTR pszName); + HRESULT(STDMETHODCALLTYPE *GetFileName) + (IFileDialog *This, LPWSTR *pszName); + HRESULT(STDMETHODCALLTYPE *SetTitle) + (IFileDialog *This, LPCWSTR pszTitle); + HRESULT(STDMETHODCALLTYPE *SetOkButtonLabel) + (IFileDialog *This, LPCWSTR pszText); + HRESULT(STDMETHODCALLTYPE *SetFileNameLabel) + (IFileDialog *This, LPCWSTR pszLabel); + HRESULT(STDMETHODCALLTYPE *GetResult) + (IFileDialog *This, IShellItem **ppsi); + HRESULT(STDMETHODCALLTYPE *AddPlace) + (IFileDialog *This, IShellItem *psi, FDAP fdap); + HRESULT(STDMETHODCALLTYPE *SetDefaultExtension) + (IFileDialog *This, LPCWSTR pszDefaultExtension); + HRESULT(STDMETHODCALLTYPE *Close) + (IFileDialog *This, HRESULT hr); + HRESULT(STDMETHODCALLTYPE *SetClientGuid) + (IFileDialog *This, REFGUID guid); + HRESULT(STDMETHODCALLTYPE *ClearClientData) + (IFileDialog *This); + HRESULT(STDMETHODCALLTYPE *SetFilter) + (IFileDialog *This, IShellItemFilter *pFilter); + END_INTERFACE +} IFileDialogVtbl; +interface IFileDialog +{ + CONST_VTBL IFileDialogVtbl *lpVtbl; +}; +DEFINE_GUID(IID_IFileOpenDialog, 0xd57c7288, 0xd4ad, 0x4768, 0xbe, 0x02, 0x9d, + 0x96, 0x95, 0x32, 0xd9, 0x60); +DEFINE_GUID(IID_IFileSaveDialog, 0x84bccd23, 0x5fde, 0x4cdb, 0xae, 0xa4, 0xaf, + 0x64, 0xb8, 0x3d, 0x78, 0xab); +#endif + +WEBVIEW_API void webview_dialog(webview_t w, + enum webview_dialog_type dlgtype, int flags, + const char *title, const char *arg, + char *result, size_t resultsz) +{ + HWND hwnd = static_cast(w)->m_window; + if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN || + dlgtype == WEBVIEW_DIALOG_TYPE_SAVE) + { + IFileDialog *dlg = NULL; + IShellItem *res = NULL; + WCHAR *ws = NULL; + char *s = NULL; + FILEOPENDIALOGOPTIONS opts = 0, add_opts = 0; + if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN) + { + if (CoCreateInstance( + iid_unref(&CLSID_FileOpenDialog), NULL, CLSCTX_INPROC_SERVER, + iid_unref(&IID_IFileOpenDialog), (void **)&dlg) != S_OK) + { + goto error_dlg; + } + if (flags & WEBVIEW_DIALOG_FLAG_DIRECTORY) + { + add_opts |= FOS_PICKFOLDERS; + } + add_opts |= FOS_NOCHANGEDIR | FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE | + FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_SHAREAWARE | + FOS_NOTESTFILECREATE | FOS_NODEREFERENCELINKS | + FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE; + } + else + { + if (CoCreateInstance( + iid_unref(&CLSID_FileSaveDialog), NULL, CLSCTX_INPROC_SERVER, + iid_unref(&IID_IFileSaveDialog), (void **)&dlg) != S_OK) + { + goto error_dlg; + } + add_opts |= FOS_OVERWRITEPROMPT | FOS_NOCHANGEDIR | + FOS_ALLNONSTORAGEITEMS | FOS_NOVALIDATE | FOS_SHAREAWARE | + FOS_NOTESTFILECREATE | FOS_NODEREFERENCELINKS | + FOS_FORCESHOWHIDDEN | FOS_DEFAULTNOMINIMODE; + } + if (dlg->GetOptions(&opts) != S_OK) + { + goto error_dlg; + } + opts &= ~FOS_NOREADONLYRETURN; + opts |= add_opts; + if (dlg->SetOptions(opts) != S_OK) + { + goto error_dlg; + } + if (dlg->Show(hwnd) != S_OK) + { + goto error_dlg; + } + if (dlg->GetResult(&res) != S_OK) + { + goto error_dlg; + } + if (res->GetDisplayName(SIGDN_FILESYSPATH, &ws) != S_OK) + { + goto error_result; + } + s = webview_from_utf16(ws); + CoTaskMemFree(ws); + if (!s) + goto error_result; + strncpy(result, s, resultsz); + GlobalFree(s); + result[resultsz - 1] = '\0'; + error_result: + res->Release(); + error_dlg: + dlg->Release(); + return; + } + else if (dlgtype == WEBVIEW_DIALOG_TYPE_ALERT) + { +#if 0 + /* MinGW often doesn't contain TaskDialog, we'll use MessageBox for now */ + WCHAR *wtitle = webview_to_utf16(title); + WCHAR *warg = webview_to_utf16(arg); + TaskDialog(hwnd, NULL, NULL, wtitle, warg, 0, NULL, NULL); + GlobalFree(warg); + GlobalFree(wtitle); +#else + UINT type = MB_OK; + switch (flags & WEBVIEW_DIALOG_FLAG_ALERT_MASK) + { + case WEBVIEW_DIALOG_FLAG_INFO: + type |= MB_ICONINFORMATION; + break; + case WEBVIEW_DIALOG_FLAG_WARNING: + type |= MB_ICONWARNING; + break; + case WEBVIEW_DIALOG_FLAG_ERROR: + type |= MB_ICONERROR; + break; + } + MessageBox(hwnd, arg, title, type); +#endif + } +} + +#endif /* WEBVIEW_HEADER */ + +#endif /* WEBVIEW_H */