diff --git a/README.md b/README.md index 9e9b5f1..60c3538 100644 --- a/README.md +++ b/README.md @@ -29,14 +29,11 @@ Be sure to explore the provided [examples](examples). - [x] webview_eval - [x] webview_inject_css - [x] webview_set_title -- [x] webview_set_fullscreen -- [x] webview_set_color -- [x] webview_dialog - [x] webview_dispatch - [x] webview_terminate - [x] webview_exit -- [x] webview_debug -- [x] webview_print_log +- [x] webview_bind +- [x] webview_return ## How do I install webviewhs? @@ -109,150 +106,47 @@ main = do } ``` -If you want more control over the native desktop window, you could do something like this: +You can also bind callbacks to communicate with HTML/JS: ```haskell {-# LANGUAGE OverloadedStrings - , QuasiQuotes #-} -import Control.Monad -import Control.Concurrent -import Control.Concurrent.BoundedChan as CCBC -import Data.Maybe -import Data.Text -import qualified Data.Text.Lazy as DTL -import Data.Text.Format.Heavy -import Language.Javascript.JMacro -import qualified Clay import qualified Graphics.UI.Webviewhs as WHS main :: IO () main = do - -- Create a channel to communicate between the main thread and another thread you'll create. - -- This isn't necessary but it's a great way to communicate between threads. - channel <- newBoundedChan 1 - - -- withWindowLoop handles the creation, iteration, and deletion of the window. - WHS.withWindowLoop - - -- Set the window creation params. - WHS.WindowParams + eitherWindow <- WHS.createWindow windowParams + case eitherWindow of + Left _ -> pure () + Right window -> do + WHS.bindCallback window "callback1" callback "custom data" + WHS.bindCallback window "callback2" callbackWithResponse () + WHS.iterateWindowLoop window + WHS.terminateWindowLoop window + WHS.destroyWindow window + where + windowParams = WHS.WindowParams { WHS.windowParamsTitle = "Test" - -- This could be a localhost URL to your single-page application (SPA). - , WHS.windowParamsUri = "https://lettier.github.com" + , WHS.windowParamsUri = "https://lettier.github.io" , WHS.windowParamsWidth = 800 , WHS.windowParamsHeight = 600 , WHS.windowParamsResizable = True - , WHS.windowParamsDebuggable = True -- Enables the Web Inspector if using WebKit. + , WHS.windowParamsDebuggable = True } - -- webview allows you to specify a callback function that can be - -- called from the JavaScript side. - -- The callback receives a single string parameter. - -- This could be unstructured text or unparsed JSON for example. - -- You can just print what was received for now. - (\ _window stringFromJavaScript -> print stringFromJavaScript) - - -- This function runs before the loop. - (WHS.WithWindowLoopSetUp (\ _window -> print "Setting up.")) - - -- This function runs after the loop. - (WHS.WithWindowLoopTearDown (\ _window -> print "Tearing down.")) - - -- If you don't need to set up and/or tear down anything, you can do this. - -- (WHS.WithWindowLoopSetUp (void . return . const)) - -- (WHS.WithWindowLoopTearDown (void . return . const)) - - -- This function is called continuously. - -- Return True to continue the window loop or - -- return False to exit the loop and destroy the window. - $ \ window -> do - - -- webviewhs provides log and log'. - -- log uses text-format-heavy which provides a - -- "full-featured string formatting function, similar to Python's string.format." - -- log' takes a simple Text string. - -- According to webview, logging will print to - -- "stderr, MacOS Console or [Windows] DebugView." - let string = "world" :: Text - WHS.log "Hello {string}!" [("string" :: DTL.Text, Variable string)] - - -- webview allows you to run JS inside the window. - -- webviewhs comes with runJavaScript and runJavaScript'. - -- runJavaScript uses JMacro which is a - -- "simple DSL for lightweight (untyped) programmatic generation of Javascript." - -- runJavaScript' takes a Text string which may or may not be valid JavaScript. - let red = "red" :: Text - _ <- WHS.runJavaScript - window - - -- This changes the web page background color to red. - -- Notice that you can use Haskell values inside the JavaScript and - -- even use Haskell like syntax. - [jmacro| - fun setBackgroundColor color { document.body.style.backgroundColor = color; } - setTimeout( - \ -> setBackgroundColor `(red)`, - 5000 - ); - |] - - -- webview allows you to inject CSS into the window. - -- webviewhs offers injectCss and injectCss'. - -- injectCss uses Clay "a CSS preprocessor like LESS and Sass, - -- but implemented as an embedded domain specific language (EDSL) in Haskell." - -- injectCss' takes a Text string which may or may not be valid CSS. - _ <- WHS.injectCss - window - - -- This turns all
text blue. - $ Clay.div Clay.? - Clay.color "#0000ff" - - -- Inside the window loop, create a thread. - _ <- forkIO $ do - WHS.log' "Hello from inside a thread." - - -- When you're not in the main window UI thread, you'll need to call - -- dispatchToMain if you want to interact with the window. - -- dispatchToMain will run the given function in the main UI thread. - -- Note that dispatchToMain runs the function asynchronously with no guarantee - -- as to when it will run. - WHS.dispatchToMain - window - $ \ window' -> do - result <- - WHS.runJavaScript - window' - - -- This will randomly scroll the web page up and down. - [jmacro| - if (Math.random() < 0.1) { - setTimeout( - function() { - window.scrollTo(0, Math.random() * window.innerHeight); - }, - 10000 - ); - } - |] - - -- runJavaScript returns True if it was successful and - -- False if something went wrong. - -- Here is an attempt to write the result to the channel. - void $ CCBC.tryWriteChan channel result - - -- Exit the loop if you read False from the channel. - -- Note that tryReadChan does not block which is - -- important when inside the window loop. - fromMaybe True <$> tryReadChan channel - - -- At this point, - -- the loop has been exited, - -- the window has been destroyed, - -- and the program will now exit. + -- This can be called from Html/JS as "window.callback1(msg)" + -- userData was given to bindCallback as parameter + callback window _ req userData = do + print req + print userData + + -- This can be called from Html/JS as "let p = window.callback2(msg)" + -- where p is a Promise which can be rejected, resolved, or ignored + callbackWithResponse window reqId req _ = do + print req + WHS.respondRequest reqId WHS.RequestResolve "'some data'" ``` For more ways to use webviewhs, diff --git a/deps/webview/webview.cpp b/deps/webview/webview.cpp new file mode 100644 index 0000000..a38b0b6 --- /dev/null +++ b/deps/webview/webview.cpp @@ -0,0 +1,1149 @@ +#include "webview.h" + +#if !defined(WEBVIEW_GTK) && !defined(WEBVIEW_COCOA) && !defined(WEBVIEW_EDGE) +#if defined(__linux__) +#define WEBVIEW_GTK +#elif defined(__APPLE__) +#define WEBVIEW_COCOA +#elif defined(_WIN32) +#define WEBVIEW_EDGE +#else +#error "please, specify webview backend" +#endif +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace webview { +using dispatch_fn_t = std::function; + +inline std::string url_encode(const 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(const std::string s) { + std::string decoded; + for (unsigned int i = 0; i < s.length(); i++) { + if (s[i] == '%') { + int n; + n = std::stoul(s.substr(i + 1, 2), nullptr, 16); + 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(const 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) { + // TODO: implement + return '"' + s + '"'; +} + +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(const std::string s, const std::string key, + const 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 + 1]; + json_unescape(value, value_sz, decoded); + std::string result(decoded, n); + delete[] decoded; + return result; + } + } + return ""; +} + +} // namespace webview + +#if defined(WEBVIEW_GTK) +// +// ==================================================================== +// +// This implementation uses webkit2gtk backend. It requires gtk+3.0 and +// webkit2gtk-4.0 libraries. Proper compiler flags can be retrieved via: +// +// pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0 +// +// ==================================================================== +// +#include +#include +#include + +namespace webview { + +class gtk_webkit_engine { +public: + gtk_webkit_engine(bool debug, void *window) + : m_window(static_cast(window)) { + gtk_init_check(0, NULL); + m_window = static_cast(window); + if (m_window == nullptr) { + m_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + } + g_signal_connect(G_OBJECT(m_window), "destroy", + G_CALLBACK(+[](GtkWidget *, gpointer arg) { + static_cast(arg)->terminate(); + }), + this); + // Initialize webview widget + m_webview = webkit_web_view_new(); + WebKitUserContentManager *manager = + webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview)); + g_signal_connect(manager, "script-message-received::external", + G_CALLBACK(+[](WebKitUserContentManager *, + WebKitJavascriptResult *r, gpointer arg) { + auto *w = static_cast(arg); +#if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22 + JSCValue *value = + webkit_javascript_result_get_js_value(r); + char *s = jsc_value_to_string(value); +#else + JSGlobalContextRef ctx = + webkit_javascript_result_get_global_context(r); + JSValueRef value = webkit_javascript_result_get_value(r); + JSStringRef js = JSValueToStringCopy(ctx, value, NULL); + size_t n = JSStringGetMaximumUTF8CStringSize(js); + char *s = g_new(char, n); + JSStringGetUTF8CString(js, s, n); + JSStringRelease(js); +#endif + w->on_message(s); + g_free(s); + }), + this); + webkit_user_content_manager_register_script_message_handler(manager, + "external"); + init("window.external={invoke:function(s){window.webkit.messageHandlers." + "external.postMessage(s);}}"); + + gtk_container_add(GTK_CONTAINER(m_window), GTK_WIDGET(m_webview)); + gtk_widget_grab_focus(GTK_WIDGET(m_webview)); + + if (debug) { + WebKitSettings *settings = + webkit_web_view_get_settings(WEBKIT_WEB_VIEW(m_webview)); + webkit_settings_set_enable_write_console_messages_to_stdout(settings, + true); + webkit_settings_set_enable_developer_extras(settings, true); + } + + gtk_widget_show_all(m_window); + } + void *window() { return (void *)m_window; } + void run() { gtk_main(); } + void terminate() { gtk_main_quit(); } + void dispatch(std::function f) { + g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)([](void *f) -> int { + (*static_cast(f))(); + return G_SOURCE_REMOVE; + }), + new std::function(f), + [](void *f) { delete static_cast(f); }); + } + + void set_title(const std::string title) { + gtk_window_set_title(GTK_WINDOW(m_window), title.c_str()); + } + + void set_size(int width, int height, int hints) { + gtk_window_set_resizable(GTK_WINDOW(m_window), hints != WEBVIEW_HINT_FIXED); + if (hints == WEBVIEW_HINT_NONE) { + gtk_window_resize(GTK_WINDOW(m_window), width, height); + } else if (hints == WEBVIEW_HINT_FIXED) { + gtk_widget_set_size_request(m_window, width, height); + } else { + GdkGeometry g; + g.min_width = g.max_width = width; + g.min_height = g.max_height = height; + GdkWindowHints h = + (hints == WEBVIEW_HINT_MIN ? GDK_HINT_MIN_SIZE : GDK_HINT_MAX_SIZE); + // This defines either MIN_SIZE, or MAX_SIZE, but not both: + gtk_window_set_geometry_hints(GTK_WINDOW(m_window), nullptr, &g, h); + } + } + + void navigate(const std::string url) { + webkit_web_view_load_uri(WEBKIT_WEB_VIEW(m_webview), url.c_str()); + } + + void init(const std::string js) { + WebKitUserContentManager *manager = + webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview)); + webkit_user_content_manager_add_script( + manager, webkit_user_script_new( + js.c_str(), WEBKIT_USER_CONTENT_INJECT_TOP_FRAME, + WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, NULL, NULL)); + } + + void eval(const std::string js) { + webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(m_webview), js.c_str(), NULL, + NULL, NULL); + } + +private: + virtual void on_message(const std::string msg) = 0; + GtkWidget *m_window; + GtkWidget *m_webview; +}; + +using browser_engine = gtk_webkit_engine; + +} // namespace webview + +#elif defined(WEBVIEW_COCOA) + +// +// ==================================================================== +// +// This implementation uses Cocoa WKWebView backend on macOS. It is +// written using ObjC runtime and uses WKWebView class as a browser runtime. +// You should pass "-framework Webkit" flag to the compiler. +// +// ==================================================================== +// + +#define OBJC_OLD_DISPATCH_PROTOTYPES 1 +#include +#include + +#define NSBackingStoreBuffered 2 + +#define NSWindowStyleMaskResizable 8 +#define NSWindowStyleMaskMiniaturizable 4 +#define NSWindowStyleMaskTitled 1 +#define NSWindowStyleMaskClosable 2 + +#define NSApplicationActivationPolicyRegular 0 + +#define WKUserScriptInjectionTimeAtDocumentStart 0 + +namespace webview { + +// Helpers to avoid too much typing +id operator"" _cls(const char *s, std::size_t) { return (id)objc_getClass(s); } +SEL operator"" _sel(const char *s, std::size_t) { return sel_registerName(s); } +id operator"" _str(const char *s, std::size_t) { + return objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, s); +} + +class cocoa_wkwebview_engine { +public: + cocoa_wkwebview_engine(bool debug, void *window) { + // Application + id app = objc_msgSend("NSApplication"_cls, "sharedApplication"_sel); + objc_msgSend(app, "setActivationPolicy:"_sel, + NSApplicationActivationPolicyRegular); + + // Delegate + auto cls = objc_allocateClassPair((Class) "NSResponder"_cls, "AppDelegate", 0); + class_addProtocol(cls, objc_getProtocol("NSTouchBarProvider")); + class_addMethod(cls, "applicationShouldTerminateAfterLastWindowClosed:"_sel, + (IMP)(+[](id, SEL, id) -> BOOL { return 1; }), "c@:@"); + class_addMethod(cls, "userContentController:didReceiveScriptMessage:"_sel, + (IMP)(+[](id self, SEL, id, id msg) { + auto w = + (cocoa_wkwebview_engine *)objc_getAssociatedObject( + self, "webview"); + assert(w); + w->on_message((const char *)objc_msgSend( + objc_msgSend(msg, "body"_sel), "UTF8String"_sel)); + }), + "v@:@@"); + objc_registerClassPair(cls); + + auto delegate = objc_msgSend((id)cls, "new"_sel); + objc_setAssociatedObject(delegate, "webview", (id)this, + OBJC_ASSOCIATION_ASSIGN); + objc_msgSend(app, sel_registerName("setDelegate:"), delegate); + + // Main window + if (window == nullptr) { + m_window = objc_msgSend("NSWindow"_cls, "alloc"_sel); + m_window = objc_msgSend( + m_window, "initWithContentRect:styleMask:backing:defer:"_sel, + CGRectMake(0, 0, 0, 0), 0, NSBackingStoreBuffered, 0); + } else { + m_window = (id)window; + } + + // Webview + auto config = objc_msgSend("WKWebViewConfiguration"_cls, "new"_sel); + m_manager = objc_msgSend(config, "userContentController"_sel); + m_webview = objc_msgSend("WKWebView"_cls, "alloc"_sel); + if (debug) { + objc_msgSend(objc_msgSend(config, "preferences"_sel), + "setValue:forKey:"_sel, + objc_msgSend("NSNumber"_cls, "numberWithBool:"_sel, 1), + "developerExtrasEnabled"_str); + } + objc_msgSend(m_webview, "initWithFrame:configuration:"_sel, + CGRectMake(0, 0, 0, 0), config); + objc_msgSend(m_manager, "addScriptMessageHandler:name:"_sel, delegate, + "external"_str); + init(R"script( + window.external = { + invoke: function(s) { + window.webkit.messageHandlers.external.postMessage(s); + }, + }; + )script"); + objc_msgSend(m_window, "setContentView:"_sel, m_webview); + objc_msgSend(m_window, "makeKeyAndOrderFront:"_sel, nullptr); + } + ~cocoa_wkwebview_engine() { close(); } + void *window() { return (void *)m_window; } + void terminate() { + close(); + objc_msgSend("NSApp"_cls, "terminate:"_sel, nullptr); + } + void run() { + id app = objc_msgSend("NSApplication"_cls, "sharedApplication"_sel); + dispatch([&]() { objc_msgSend(app, "activateIgnoringOtherApps:"_sel, 1); }); + objc_msgSend(app, "run"_sel); + } + void dispatch(std::function f) { + dispatch_async_f(dispatch_get_main_queue(), new dispatch_fn_t(f), + (dispatch_function_t)([](void *arg) { + auto f = static_cast(arg); + (*f)(); + delete f; + })); + } + void set_title(const std::string title) { + objc_msgSend(m_window, "setTitle:"_sel, + objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, + title.c_str())); + } + void set_size(int width, int height, int hints) { + auto style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | + NSWindowStyleMaskMiniaturizable; + if (hints != WEBVIEW_HINT_FIXED) { + style = style | NSWindowStyleMaskResizable; + } + objc_msgSend(m_window, "setStyleMask:"_sel, style); + + if (hints == WEBVIEW_HINT_MIN) { + objc_msgSend(m_window, "setContentMinSize:"_sel, + CGSizeMake(width, height)); + } else if (hints == WEBVIEW_HINT_MAX) { + objc_msgSend(m_window, "setContentMaxSize:"_sel, + CGSizeMake(width, height)); + } else { + objc_msgSend(m_window, "setFrame:display:animate:"_sel, + CGRectMake(0, 0, width, height), 1, 0); + } + } + void navigate(const std::string url) { + auto nsurl = objc_msgSend( + "NSURL"_cls, "URLWithString:"_sel, + objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, url.c_str())); + objc_msgSend( + m_webview, "loadRequest:"_sel, + objc_msgSend("NSURLRequest"_cls, "requestWithURL:"_sel, nsurl)); + } + void init(const std::string js) { + objc_msgSend( + m_manager, "addUserScript:"_sel, + objc_msgSend(objc_msgSend("WKUserScript"_cls, "alloc"_sel), + "initWithSource:injectionTime:forMainFrameOnly:"_sel, + objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, + js.c_str()), + WKUserScriptInjectionTimeAtDocumentStart, 1)); + } + void eval(const std::string js) { + objc_msgSend( + m_webview, "evaluateJavaScript:completionHandler:"_sel, + objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, js.c_str()), + nullptr); + } + +private: + virtual void on_message(const std::string msg) = 0; + void close() { objc_msgSend(m_window, "close"_sel); } + id m_window; + id m_webview; + id m_manager; +}; + +using browser_engine = cocoa_wkwebview_engine; + +} // namespace webview + +#elif defined(WEBVIEW_EDGE) + +// +// ==================================================================== +// +// This implementation uses Win32 API to create a native window. It can +// use either EdgeHTML or Edge/Chromium backend as a browser engine. +// +// ==================================================================== +// + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include + +#pragma comment(lib, "user32.lib") +#pragma comment(lib, "Shlwapi.lib") + +// EdgeHTML headers and libs +#include +#include +#include +#pragma comment(lib, "windowsapp") + +// Edge/Chromium headers and libs +#include "webview2.h" +#pragma comment(lib, "ole32.lib") +#pragma comment(lib, "oleaut32.lib") + +namespace webview { + +using msg_cb_t = std::function; + +// Common interface for EdgeHTML and Edge/Chromium +class browser { +public: + virtual ~browser() = default; + virtual bool embed(HWND, bool, msg_cb_t) = 0; + virtual void navigate(const std::string url) = 0; + virtual void eval(const std::string js) = 0; + virtual void init(const std::string js) = 0; + virtual void resize(HWND) = 0; +}; + +// +// EdgeHTML browser engine +// +using namespace winrt; +using namespace Windows::Foundation; +using namespace Windows::Web::UI; +using namespace Windows::Web::UI::Interop; + +class edge_html : public browser { +public: + bool embed(HWND wnd, bool debug, msg_cb_t cb) override { + init_apartment(winrt::apartment_type::single_threaded); + auto process = WebViewControlProcess(); + auto op = process.CreateWebViewControlAsync(reinterpret_cast(wnd), + 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()); + 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)"); + return true; + } + + void navigate(const std::string url) override { + std::string html = html_from_uri(url); + if (html != "") { + m_webview.NavigateToString(winrt::to_hstring(html)); + } else { + Uri uri(winrt::to_hstring(url)); + m_webview.Navigate(uri); + } + } + + void init(const std::string js) override { + init_js = init_js + "(function(){" + js + "})();"; + } + + void eval(const std::string js) override { + m_webview.InvokeScriptAsync( + L"eval", single_threaded_vector({winrt::to_hstring(js)})); + } + + void resize(HWND wnd) override { + if (m_webview == nullptr) { + return; + } + RECT r; + GetClientRect(wnd, &r); + Rect bounds(r.left, r.top, r.right - r.left, r.bottom - r.top); + m_webview.Bounds(bounds); + } + +private: + WebViewControl m_webview = nullptr; + std::string init_js = ""; +}; + +// +// Edge/Chromium browser engine +// +class edge_chromium : public browser { +public: + bool embed(HWND wnd, bool debug, msg_cb_t cb) override { + CoInitializeEx(nullptr, 0); + std::atomic_flag flag = ATOMIC_FLAG_INIT; + flag.test_and_set(); + + char currentExePath[MAX_PATH]; + GetModuleFileNameA(NULL, currentExePath, MAX_PATH); + char* currentExeName = PathFindFileNameA(currentExePath); + + std::wstring_convert> wideCharConverter; + std::wstring userDataFolder = wideCharConverter.from_bytes(std::getenv("APPDATA")); + std::wstring currentExeNameW = wideCharConverter.from_bytes(currentExeName); + + HRESULT res = CreateCoreWebView2EnvironmentWithOptions( + nullptr, (userDataFolder + L"/" + currentExeNameW).c_str(), nullptr, + new webview2_com_handler(wnd, cb, [&](ICoreWebView2Controller *controller) { + m_controller = controller; + m_controller->get_CoreWebView2(&m_webview); + m_webview->AddRef(); + flag.clear(); + })); + if (res != S_OK) { + CoUninitialize(); + return false; + } + MSG msg = {}; + while (flag.test_and_set() && GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + init("window.external={invoke:s=>window.chrome.webview.postMessage(s)}"); + return true; + } + + void resize(HWND wnd) override { + if (m_controller == nullptr) { + return; + } + RECT bounds; + GetClientRect(wnd, &bounds); + m_controller->put_Bounds(bounds); + } + + void navigate(const std::string url) override { + auto wurl = to_lpwstr(url); + m_webview->Navigate(wurl); + delete[] wurl; + } + + void init(const std::string js) override { + LPCWSTR wjs = to_lpwstr(js); + m_webview->AddScriptToExecuteOnDocumentCreated(wjs, nullptr); + delete[] wjs; + } + + void eval(const std::string js) override { + LPCWSTR wjs = to_lpwstr(js); + m_webview->ExecuteScript(wjs, nullptr); + delete[] wjs; + } + +private: + LPWSTR to_lpwstr(const std::string s) { + int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, NULL, 0); + wchar_t *ws = new wchar_t[n]; + MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, ws, n); + return ws; + } + + ICoreWebView2 *m_webview = nullptr; + ICoreWebView2Controller *m_controller = nullptr; + + class webview2_com_handler + : public ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler, + public ICoreWebView2CreateCoreWebView2ControllerCompletedHandler, + public ICoreWebView2WebMessageReceivedEventHandler { + using webview2_com_handler_cb_t = std::function; + + public: + webview2_com_handler(HWND hwnd, msg_cb_t msgCb, webview2_com_handler_cb_t cb) + : m_window(hwnd), m_msgCb(msgCb), m_cb(cb) {} + ULONG STDMETHODCALLTYPE AddRef() { return 1; } + ULONG STDMETHODCALLTYPE Release() { return 1; } + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppv) { + return S_OK; + } + HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, ICoreWebView2Environment *env) { + env->CreateCoreWebView2Controller(m_window, this); + return S_OK; + } + HRESULT STDMETHODCALLTYPE Invoke(HRESULT res, ICoreWebView2Controller *controller) { + controller->AddRef(); + + ICoreWebView2 *webview; + EventRegistrationToken token; + controller->get_CoreWebView2(&webview); + webview->add_WebMessageReceived(this, &token); + + m_cb(controller); + return S_OK; + } + HRESULT STDMETHODCALLTYPE Invoke(ICoreWebView2* sender, ICoreWebView2WebMessageReceivedEventArgs* args) { + LPWSTR message; + args->TryGetWebMessageAsString(&message); + + std::wstring_convert> wideCharConverter; + m_msgCb(wideCharConverter.to_bytes(message)); + sender->PostWebMessageAsString(message); + + CoTaskMemFree(message); + return S_OK; + } + + private: + HWND m_window; + msg_cb_t m_msgCb; + webview2_com_handler_cb_t m_cb; + }; +}; + +class win32_edge_engine { +public: + win32_edge_engine(bool debug, void *window) { + if (window == nullptr) { + HINSTANCE hInstance = GetModuleHandle(nullptr); + HICON icon = (HICON) LoadImage( + hInstance, IDI_APPLICATION, IMAGE_ICON, + GetSystemMetrics(SM_CXSMICON), + GetSystemMetrics(SM_CYSMICON), + LR_DEFAULTCOLOR); + + WNDCLASSEX wc; + ZeroMemory(&wc, sizeof(WNDCLASSEX)); + wc.cbSize = sizeof(WNDCLASSEX); + wc.hInstance = hInstance; + wc.lpszClassName = "webview"; + wc.hIcon = icon; + wc.hIconSm = icon; + wc.lpfnWndProc = + (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> int { + auto w = (win32_edge_engine *)GetWindowLongPtr(hwnd, GWLP_USERDATA); + switch (msg) { + case WM_SIZE: + w->m_browser->resize(hwnd); + break; + case WM_CLOSE: + DestroyWindow(hwnd); + break; + case WM_DESTROY: + w->terminate(); + break; + case WM_GETMINMAXINFO: { + auto lpmmi = (LPMINMAXINFO)lp; + if (w == nullptr) { + return 0; + } + if (w->m_maxsz.x > 0 && w->m_maxsz.y > 0) { + lpmmi->ptMaxSize = w->m_maxsz; + lpmmi->ptMaxTrackSize = w->m_maxsz; + } + if (w->m_minsz.x > 0 && w->m_minsz.y > 0) { + lpmmi->ptMinTrackSize = w->m_minsz; + } + } break; + default: + return DefWindowProc(hwnd, msg, wp, lp); + } + return 0; + }); + RegisterClassEx(&wc); + m_window = CreateWindow("webview", "", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, + CW_USEDEFAULT, 640, 480, nullptr, nullptr, + GetModuleHandle(nullptr), nullptr); + SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this); + } else { + m_window = *(static_cast(window)); + } + + ShowWindow(m_window, SW_SHOW); + UpdateWindow(m_window); + SetFocus(m_window); + + auto cb = + std::bind(&win32_edge_engine::on_message, this, std::placeholders::_1); + + if (!m_browser->embed(m_window, debug, cb)) { + m_browser = std::make_unique(); + m_browser->embed(m_window, debug, cb); + } + + m_browser->resize(m_window); + } + + void run() { + MSG msg; + BOOL res; + while ((res = GetMessage(&msg, nullptr, 0, 0)) != -1) { + if (msg.hwnd) { + TranslateMessage(&msg); + DispatchMessage(&msg); + continue; + } + if (msg.message == WM_APP) { + auto f = (dispatch_fn_t *)(msg.lParam); + (*f)(); + delete f; + } else if (msg.message == WM_QUIT) { + return; + } + } + } + void *window() { return (void *)m_window; } + 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 std::string title) { + SetWindowText(m_window, title.c_str()); + } + + void set_size(int width, int height, int hints) { + auto style = GetWindowLong(m_window, GWL_STYLE); + if (hints == WEBVIEW_HINT_FIXED) { + style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX); + } else { + style |= (WS_THICKFRAME | WS_MAXIMIZEBOX); + } + SetWindowLong(m_window, GWL_STYLE, style); + + if (hints == WEBVIEW_HINT_MAX) { + m_maxsz.x = width; + m_maxsz.y = height; + } else if (hints == WEBVIEW_HINT_MIN) { + m_minsz.x = width; + m_minsz.y = height; + } else { + RECT r; + r.left = r.top = 0; + 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_NOMOVE | SWP_FRAMECHANGED); + m_browser->resize(m_window); + } + } + + void navigate(const std::string url) { m_browser->navigate(url); } + void eval(const std::string js) { m_browser->eval(js); } + void init(const std::string js) { m_browser->init(js); } + +private: + virtual void on_message(const std::string msg) = 0; + + HWND m_window; + POINT m_minsz = POINT { 0, 0 }; + POINT m_maxsz = POINT { 0, 0 }; + DWORD m_main_thread = GetCurrentThreadId(); + std::unique_ptr m_browser = + std::make_unique(); +}; + +using browser_engine = win32_edge_engine; +} // namespace webview + +#endif /* WEBVIEW_GTK, WEBVIEW_COCOA, WEBVIEW_EDGE */ + +namespace webview { + +class webview : public browser_engine { +public: + webview(bool debug = false, void *wnd = nullptr) + : browser_engine(debug, wnd) {} + + void navigate(const std::string url) { + if (url == "") { + browser_engine::navigate("data:text/html," + + url_encode("Hello")); + return; + } + std::string html = html_from_uri(url); + if (html != "") { + browser_engine::navigate("data:text/html," + url_encode(html)); + } else { + browser_engine::navigate(url); + } + } + + using binding_t = std::function; + using binding_ctx_t = std::pair; + + using sync_binding_t = std::function; + using sync_binding_ctx_t = std::pair; + + void bind(const std::string name, sync_binding_t fn) { + bind(name, + [](std::string seq, std::string req, void *arg) { + auto pair = static_cast(arg); + pair->first->resolve(seq, 0, pair->second(req)); + }, + new sync_binding_ctx_t(this, fn)); + } + + void bind(const std::string name, binding_t f, void *arg) { + auto js = "(function() { var name = '" + name + "';" + R"( + var RPC = window._rpc = (window._rpc || {nextSeq: 1}); + window[name] = function() { + var seq = RPC.nextSeq++; + var promise = new Promise(function(resolve, reject) { + RPC[seq] = { + resolve: resolve, + reject: reject, + }; + }); + window.external.invoke(JSON.stringify({ + id: seq, + method: name, + params: Array.prototype.slice.call(arguments), + })); + return promise; + } + })())"; + init(js); + bindings[name] = new binding_ctx_t(new binding_t(f), arg); + } + + void resolve(const std::string seq, int status, const std::string result) { + dispatch([=]() { + if (status == 0) { + eval("window._rpc[" + seq + "].resolve(" + result + "); window._rpc[" + + seq + "] = undefined"); + } else { + eval("window._rpc[" + seq + "].reject(" + result + "); window._rpc[" + + seq + "] = undefined"); + } + }); + } + +private: + void on_message(const std::string msg) { + auto seq = json_parse(msg, "id", 0); + auto name = json_parse(msg, "method", 0); + auto args = json_parse(msg, "params", 0); + if (bindings.find(name) == bindings.end()) { + return; + } + auto fn = bindings[name]; + (*fn->first)(seq, args, fn->second); + } + std::map bindings; +}; +} // namespace webview + +WEBVIEW_API webview_t webview_create(int debug, void *wnd) { + return new webview::webview(debug, wnd); +} + +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, void *), + 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_set_size(webview_t w, int width, int height, + int hints) { + static_cast(w)->set_size(width, height, hints); +} + +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 void webview_eval(webview_t w, const char *js) { + static_cast(w)->eval(js); +} + +WEBVIEW_API void webview_bind(webview_t w, const char *name, + void (*fn)(const char *seq, const char *req, + void *arg), + void *arg) { + static_cast(w)->bind( + name, + [=](std::string seq, std::string req, void *arg) { + fn(seq.c_str(), req.c_str(), arg); + }, + arg); +} + +WEBVIEW_API void webview_return(webview_t w, const char *seq, int status, + const char *result) { + static_cast(w)->resolve(seq, status, result); +} diff --git a/deps/webview/webview.h b/deps/webview/webview.h index 0e41237..a89aa28 100644 --- a/deps/webview/webview.h +++ b/deps/webview/webview.h @@ -24,2262 +24,88 @@ #ifndef WEBVIEW_H #define WEBVIEW_H -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef WEBVIEW_STATIC -#define WEBVIEW_API static -#else +#ifndef WEBVIEW_API #define WEBVIEW_API extern #endif -#include -#include -#include - -#if defined(WEBVIEW_GTK) -#include -#include -#include - -struct webview_priv { - GtkWidget *window; - GtkWidget *scroller; - GtkWidget *webview; - GtkWidget *inspector_window; - GAsyncQueue *queue; - int ready; - int js_busy; - int should_exit; -}; -#elif defined(WEBVIEW_WINAPI) -#define CINTERFACE -#include - -#include -#include -#include -#include -#include - -#include - -struct webview_priv { - HWND hwnd; - IOleObject **browser; - BOOL is_fullscreen; - DWORD saved_style; - DWORD saved_ex_style; - RECT saved_rect; -}; -#elif defined(WEBVIEW_COCOA) -#include -#include -#include - -struct webview_priv { - id pool; - id window; - id webview; - id windowDelegate; - int should_exit; -}; -#else -#error "Define one of: WEBVIEW_GTK, WEBVIEW_COCOA or WEBVIEW_WINAPI" -#endif - -struct webview; - -typedef void (*webview_external_invoke_cb_t)(struct webview *w, - const char *arg); - -struct webview { - const char *url; - const char *title; - int width; - int height; - int resizable; - int debug; - webview_external_invoke_cb_t external_invoke_cb; - struct webview_priv priv; - void *userdata; -}; - -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) - -typedef void (*webview_dispatch_fn)(struct webview *w, void *arg); - -struct webview_dispatch_arg { - webview_dispatch_fn fn; - struct webview *w; - void *arg; -}; - -#define DEFAULT_URL \ - "data:text/" \ - "html,%3C%21DOCTYPE%20html%3E%0A%3Chtml%20lang=%22en%22%3E%0A%3Chead%3E%" \ - "3Cmeta%20charset=%22utf-8%22%3E%3Cmeta%20http-equiv=%22X-UA-Compatible%22%" \ - "20content=%22IE=edge%22%3E%3C%2Fhead%3E%0A%3Cbody%3E%3Cdiv%20id=%22app%22%" \ - "3E%3C%2Fdiv%3E%3Cscript%20type=%22text%2Fjavascript%22%3E%3C%2Fscript%3E%" \ - "3C%2Fbody%3E%0A%3C%2Fhtml%3E" - -#define CSS_INJECT_FUNCTION \ - "(function(e){var " \ - "t=document.createElement('style'),d=document.head||document." \ - "getElementsByTagName('head')[0];t.setAttribute('type','text/" \ - "css'),t.styleSheet?t.styleSheet.cssText=e:t.appendChild(document." \ - "createTextNode(e)),d.appendChild(t)})" - -static const char *webview_check_url(const char *url) { - if (url == NULL || strlen(url) == 0) { - return DEFAULT_URL; - } - return url; -} - -WEBVIEW_API int webview(const char *title, const char *url, int width, - int height, int resizable, -/* Start David Lettier Edit */ - int debug); -/* End David Lettier Edit */ - -WEBVIEW_API int webview_init(struct webview *w); -WEBVIEW_API int webview_loop(struct webview *w, int blocking); -WEBVIEW_API int webview_eval(struct webview *w, const char *js); -WEBVIEW_API int webview_inject_css(struct webview *w, const char *css); -WEBVIEW_API void webview_set_title(struct webview *w, const char *title); -WEBVIEW_API void webview_set_fullscreen(struct webview *w, int fullscreen); -WEBVIEW_API void webview_set_color(struct webview *w, uint8_t r, uint8_t g, - uint8_t b, uint8_t a); -WEBVIEW_API void webview_dialog(struct webview *w, - enum webview_dialog_type dlgtype, int flags, - const char *title, const char *arg, - char *result, size_t resultsz); -WEBVIEW_API void webview_dispatch(struct webview *w, webview_dispatch_fn fn, - void *arg); -WEBVIEW_API void webview_terminate(struct webview *w); -WEBVIEW_API void webview_exit(struct webview *w); -WEBVIEW_API void webview_debug(const char *format, ...); -WEBVIEW_API void webview_print_log(const char *s); - -#ifdef WEBVIEW_IMPLEMENTATION -#undef WEBVIEW_IMPLEMENTATION - -WEBVIEW_API int webview(const char *title, const char *url, int width, - int height, int resizable, - /* Start David Lettier Edit */ - int debug) { - /* End David Lettier Edit */ - struct webview webview; - memset(&webview, 0, sizeof(webview)); - webview.title = title; - webview.url = url; - webview.width = width; - webview.height = height; - webview.resizable = resizable; - /* Start David Lettier Edit */ - webview.debug = debug; - /* End David Lettier Edit */ - int r = webview_init(&webview); - if (r != 0) { - return r; - } - while (webview_loop(&webview, 1) == 0) { - } - webview_exit(&webview); - return 0; -} - -WEBVIEW_API void webview_debug(const char *format, ...) { - char buf[4096]; - va_list ap; - va_start(ap, format); - vsnprintf(buf, sizeof(buf), format, ap); - webview_print_log(buf); - va_end(ap); -} - -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(struct webview *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; -} - -#if defined(WEBVIEW_GTK) -static void external_message_received_cb(WebKitUserContentManager *m, - WebKitJavascriptResult *r, - gpointer arg) { - (void)m; - struct webview *w = (struct webview *)arg; - if (w->external_invoke_cb == NULL) { - return; - } - JSGlobalContextRef context = webkit_javascript_result_get_global_context(r); - JSValueRef value = webkit_javascript_result_get_value(r); - JSStringRef js = JSValueToStringCopy(context, value, NULL); - size_t n = JSStringGetMaximumUTF8CStringSize(js); - char *s = g_new(char, n); - JSStringGetUTF8CString(js, s, n); - w->external_invoke_cb(w, s); - JSStringRelease(js); - g_free(s); -} - -static void webview_load_changed_cb(WebKitWebView *webview, - WebKitLoadEvent event, gpointer arg) { - (void)webview; - struct webview *w = (struct webview *)arg; - if (event == WEBKIT_LOAD_FINISHED) { - w->priv.ready = 1; - } -} - -static void webview_destroy_cb(GtkWidget *widget, gpointer arg) { - (void)widget; - struct webview *w = (struct webview *)arg; - webview_terminate(w); -} - -static gboolean webview_context_menu_cb(WebKitWebView *webview, - GtkWidget *default_menu, - WebKitHitTestResult *hit_test_result, - gboolean triggered_with_keyboard, - gpointer userdata) { - (void)webview; - (void)default_menu; - (void)hit_test_result; - (void)triggered_with_keyboard; - (void)userdata; - return TRUE; -} - -WEBVIEW_API int webview_init(struct webview *w) { - if (gtk_init_check(0, NULL) == FALSE) { - return -1; - } - - w->priv.ready = 0; - w->priv.should_exit = 0; - w->priv.queue = g_async_queue_new(); - w->priv.window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_title(GTK_WINDOW(w->priv.window), w->title); - - if (w->resizable) { - gtk_window_set_default_size(GTK_WINDOW(w->priv.window), w->width, - w->height); - } else { - gtk_widget_set_size_request(w->priv.window, w->width, w->height); - } - gtk_window_set_resizable(GTK_WINDOW(w->priv.window), !!w->resizable); - gtk_window_set_position(GTK_WINDOW(w->priv.window), GTK_WIN_POS_CENTER); - - w->priv.scroller = gtk_scrolled_window_new(NULL, NULL); - gtk_container_add(GTK_CONTAINER(w->priv.window), w->priv.scroller); - - WebKitUserContentManager *m = webkit_user_content_manager_new(); - webkit_user_content_manager_register_script_message_handler(m, "external"); - g_signal_connect(m, "script-message-received::external", - G_CALLBACK(external_message_received_cb), w); - - w->priv.webview = webkit_web_view_new_with_user_content_manager(m); - webkit_web_view_load_uri(WEBKIT_WEB_VIEW(w->priv.webview), - webview_check_url(w->url)); - g_signal_connect(G_OBJECT(w->priv.webview), "load-changed", - G_CALLBACK(webview_load_changed_cb), w); - gtk_container_add(GTK_CONTAINER(w->priv.scroller), w->priv.webview); - - /* Begin David Lettier Edit */ - WebKitSettings* settings = webkit_web_view_get_settings( - WEBKIT_WEB_VIEW(w->priv.webview) - ); - webkit_settings_set_enable_webgl(settings, true); - webkit_settings_set_javascript_can_access_clipboard(settings, true); - webkit_settings_set_allow_file_access_from_file_urls(settings, true); - /* End David Lettier Edit */ - if (w->debug) { - webkit_settings_set_enable_write_console_messages_to_stdout(settings, true); - webkit_settings_set_enable_developer_extras(settings, true); - } else { - g_signal_connect(G_OBJECT(w->priv.webview), "context-menu", - G_CALLBACK(webview_context_menu_cb), w); - } - - gtk_widget_show_all(w->priv.window); - - webkit_web_view_run_javascript( - WEBKIT_WEB_VIEW(w->priv.webview), - "window.external={invoke:function(x){" - "window.webkit.messageHandlers.external.postMessage(x);}}", - NULL, NULL, NULL); - - g_signal_connect(G_OBJECT(w->priv.window), "destroy", - G_CALLBACK(webview_destroy_cb), w); - return 0; -} - -WEBVIEW_API int webview_loop(struct webview *w, int blocking) { - gtk_main_iteration_do(blocking); - return w->priv.should_exit; -} - -WEBVIEW_API void webview_set_title(struct webview *w, const char *title) { - gtk_window_set_title(GTK_WINDOW(w->priv.window), title); -} - -WEBVIEW_API void webview_set_fullscreen(struct webview *w, int fullscreen) { - if (fullscreen) { - gtk_window_fullscreen(GTK_WINDOW(w->priv.window)); - } else { - gtk_window_unfullscreen(GTK_WINDOW(w->priv.window)); - } -} - -WEBVIEW_API void webview_set_color(struct webview *w, uint8_t r, uint8_t g, - uint8_t b, uint8_t a) { - GdkRGBA color = {r / 255.0, g / 255.0, b / 255.0, a / 255.0}; - webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(w->priv.webview), - &color); -} - -WEBVIEW_API void webview_dialog(struct webview *w, - enum webview_dialog_type dlgtype, int flags, - const char *title, const char *arg, - char *result, size_t resultsz) { - GtkWidget *dlg; - if (result != NULL) { - result[0] = '\0'; - } - if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN || - dlgtype == WEBVIEW_DIALOG_TYPE_SAVE) { - dlg = gtk_file_chooser_dialog_new( - title, GTK_WINDOW(w->priv.window), - (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN - ? (flags & WEBVIEW_DIALOG_FLAG_DIRECTORY - ? GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER - : GTK_FILE_CHOOSER_ACTION_OPEN) - : GTK_FILE_CHOOSER_ACTION_SAVE), - "_Cancel", GTK_RESPONSE_CANCEL, - (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN ? "_Open" : "_Save"), - GTK_RESPONSE_ACCEPT, NULL); - gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dlg), FALSE); - gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dlg), FALSE); - gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dlg), TRUE); - gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dlg), TRUE); - gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER(dlg), TRUE); - gint response = gtk_dialog_run(GTK_DIALOG(dlg)); - if (response == GTK_RESPONSE_ACCEPT) { - gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dlg)); - g_strlcpy(result, filename, resultsz); - g_free(filename); - } - gtk_widget_destroy(dlg); - } else if (dlgtype == WEBVIEW_DIALOG_TYPE_ALERT) { - GtkMessageType type = GTK_MESSAGE_OTHER; - switch (flags & WEBVIEW_DIALOG_FLAG_ALERT_MASK) { - case WEBVIEW_DIALOG_FLAG_INFO: - type = GTK_MESSAGE_INFO; - break; - case WEBVIEW_DIALOG_FLAG_WARNING: - type = GTK_MESSAGE_WARNING; - break; - case WEBVIEW_DIALOG_FLAG_ERROR: - type = GTK_MESSAGE_ERROR; - break; - } - dlg = gtk_message_dialog_new(GTK_WINDOW(w->priv.window), GTK_DIALOG_MODAL, - type, GTK_BUTTONS_OK, "%s", title); - gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dlg), "%s", - arg); - gtk_dialog_run(GTK_DIALOG(dlg)); - gtk_widget_destroy(dlg); - } -} - -static void webview_eval_finished(GObject *object, GAsyncResult *result, - gpointer userdata) { - (void)object; - (void)result; - struct webview *w = (struct webview *)userdata; - w->priv.js_busy = 0; -} - -WEBVIEW_API int webview_eval(struct webview *w, const char *js) { - /* Start David Lettier Edit */ - if (NULL == w || NULL == w->priv.webview) { return -1; } - g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(w->priv.webview), -1); - /* End David Lettier Edit */ - while (w->priv.ready == 0) { - g_main_context_iteration(NULL, TRUE); - } - w->priv.js_busy = 1; - webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(w->priv.webview), js, NULL, - webview_eval_finished, w); - while (w->priv.js_busy) { - g_main_context_iteration(NULL, TRUE); - } - return 0; -} - -static gboolean webview_dispatch_wrapper(gpointer userdata) { - struct webview *w = (struct webview *)userdata; - for (;;) { - struct webview_dispatch_arg *arg = - (struct webview_dispatch_arg *)g_async_queue_try_pop(w->priv.queue); - if (arg == NULL) { - break; - } - (arg->fn)(w, arg->arg); - g_free(arg); - } - return FALSE; -} - -WEBVIEW_API void webview_dispatch(struct webview *w, webview_dispatch_fn fn, - void *arg) { - struct webview_dispatch_arg *context = - (struct webview_dispatch_arg *)g_new(struct webview_dispatch_arg, 1); - context->w = w; - context->arg = arg; - context->fn = fn; - g_async_queue_lock(w->priv.queue); - g_async_queue_push_unlocked(w->priv.queue, context); - if (g_async_queue_length_unlocked(w->priv.queue) == 1) { - gdk_threads_add_idle(webview_dispatch_wrapper, w); - } - g_async_queue_unlock(w->priv.queue); -} - -WEBVIEW_API void webview_terminate(struct webview *w) { - w->priv.should_exit = 1; -} - -WEBVIEW_API void webview_exit(struct webview *w) { (void)w; } -WEBVIEW_API void webview_print_log(const char *s) { - fprintf(stderr, "%s\n", s); -} - -#endif /* WEBVIEW_GTK */ - -#if defined(WEBVIEW_WINAPI) - -#pragma comment(lib, "user32.lib") -#pragma comment(lib, "ole32.lib") -#pragma comment(lib, "oleaut32.lib") - -#define WM_WEBVIEW_DISPATCH (WM_APP + 1) - -typedef struct { - IOleInPlaceFrame frame; - HWND window; -} _IOleInPlaceFrameEx; - -typedef struct { - IOleInPlaceSite inplace; - _IOleInPlaceFrameEx frame; -} _IOleInPlaceSiteEx; - -typedef struct { - IDocHostUIHandler ui; -} _IDocHostUIHandlerEx; - -typedef struct { - IInternetSecurityManager mgr; -} _IInternetSecurityManagerEx; - -typedef struct { - IServiceProvider provider; - _IInternetSecurityManagerEx mgr; -} _IServiceProviderEx; - -typedef struct { - IOleClientSite client; - _IOleInPlaceSiteEx inplace; - _IDocHostUIHandlerEx ui; - IDispatch external; - _IServiceProviderEx provider; -} _IOleClientSiteEx; - #ifdef __cplusplus -#define iid_ref(x) &(x) -#define iid_unref(x) *(x) -#else -#define iid_ref(x) (x) -#define iid_unref(x) (x) -#endif - -static inline WCHAR *webview_to_utf16(const char *s) { - DWORD size = MultiByteToWideChar(CP_UTF8, 0, s, -1, 0, 0); - WCHAR *ws = (WCHAR *)GlobalAlloc(GMEM_FIXED, sizeof(WCHAR) * size); - if (ws == NULL) { - return NULL; - } - MultiByteToWideChar(CP_UTF8, 0, s, -1, ws, size); - return ws; -} - -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; -} - -static int iid_eq(REFIID a, const IID *b) { - return memcmp((const void *)iid_ref(a), (const void *)b, sizeof(GUID)) == 0; -} - -static HRESULT STDMETHODCALLTYPE JS_QueryInterface(IDispatch FAR *This, - REFIID riid, - LPVOID FAR *ppvObj) { - if (iid_eq(riid, &IID_IDispatch)) { - *ppvObj = This; - return S_OK; - } - *ppvObj = 0; - return E_NOINTERFACE; -} -static ULONG STDMETHODCALLTYPE JS_AddRef(IDispatch FAR *This) { return 1; } -static ULONG STDMETHODCALLTYPE JS_Release(IDispatch FAR *This) { return 1; } -static HRESULT STDMETHODCALLTYPE JS_GetTypeInfoCount(IDispatch FAR *This, - UINT *pctinfo) { - return S_OK; -} -static HRESULT STDMETHODCALLTYPE JS_GetTypeInfo(IDispatch FAR *This, - UINT iTInfo, LCID lcid, - ITypeInfo **ppTInfo) { - return S_OK; -} -#define WEBVIEW_JS_INVOKE_ID 0x1000 -static HRESULT STDMETHODCALLTYPE JS_GetIDsOfNames(IDispatch FAR *This, - REFIID riid, - LPOLESTR *rgszNames, - UINT cNames, LCID lcid, - DISPID *rgDispId) { - if (cNames != 1) { - return S_FALSE; - } - if (wcscmp(rgszNames[0], L"invoke") == 0) { - rgDispId[0] = WEBVIEW_JS_INVOKE_ID; - return S_OK; - } - return S_FALSE; -} - -static HRESULT STDMETHODCALLTYPE -JS_Invoke(IDispatch FAR *This, DISPID dispIdMember, REFIID riid, LCID lcid, - WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, - EXCEPINFO *pExcepInfo, UINT *puArgErr) { - size_t offset = (size_t) & ((_IOleClientSiteEx *)NULL)->external; - _IOleClientSiteEx *ex = (_IOleClientSiteEx *)((char *)(This)-offset); - struct webview *w = (struct webview *)GetWindowLongPtr( - ex->inplace.frame.window, GWLP_USERDATA); - if (pDispParams->cArgs == 1 && pDispParams->rgvarg[0].vt == VT_BSTR) { - BSTR bstr = pDispParams->rgvarg[0].bstrVal; - char *s = webview_from_utf16(bstr); - if (s != NULL) { - if (dispIdMember == WEBVIEW_JS_INVOKE_ID) { - if (w->external_invoke_cb != NULL) { - w->external_invoke_cb(w, s); - } - } else { - return S_FALSE; - } - GlobalFree(s); - } - } - return S_OK; -} - -static IDispatchVtbl ExternalDispatchTable = { - JS_QueryInterface, JS_AddRef, JS_Release, JS_GetTypeInfoCount, - JS_GetTypeInfo, JS_GetIDsOfNames, JS_Invoke}; - -static ULONG STDMETHODCALLTYPE Site_AddRef(IOleClientSite FAR *This) { - return 1; -} -static ULONG STDMETHODCALLTYPE Site_Release(IOleClientSite FAR *This) { - return 1; -} -static HRESULT STDMETHODCALLTYPE Site_SaveObject(IOleClientSite FAR *This) { - return E_NOTIMPL; -} -static HRESULT STDMETHODCALLTYPE Site_GetMoniker(IOleClientSite FAR *This, - DWORD dwAssign, - DWORD dwWhichMoniker, - IMoniker **ppmk) { - return E_NOTIMPL; -} -static HRESULT STDMETHODCALLTYPE -Site_GetContainer(IOleClientSite FAR *This, LPOLECONTAINER FAR *ppContainer) { - *ppContainer = 0; - return E_NOINTERFACE; -} -static HRESULT STDMETHODCALLTYPE Site_ShowObject(IOleClientSite FAR *This) { - return NOERROR; -} -static HRESULT STDMETHODCALLTYPE Site_OnShowWindow(IOleClientSite FAR *This, - BOOL fShow) { - return E_NOTIMPL; -} -static HRESULT STDMETHODCALLTYPE -Site_RequestNewObjectLayout(IOleClientSite FAR *This) { - return E_NOTIMPL; -} -static HRESULT STDMETHODCALLTYPE Site_QueryInterface(IOleClientSite FAR *This, - REFIID riid, - void **ppvObject) { - if (iid_eq(riid, &IID_IUnknown) || iid_eq(riid, &IID_IOleClientSite)) { - *ppvObject = &((_IOleClientSiteEx *)This)->client; - } else if (iid_eq(riid, &IID_IOleInPlaceSite)) { - *ppvObject = &((_IOleClientSiteEx *)This)->inplace; - } else if (iid_eq(riid, &IID_IDocHostUIHandler)) { - *ppvObject = &((_IOleClientSiteEx *)This)->ui; - } else if (iid_eq(riid, &IID_IServiceProvider)) { - *ppvObject = &((_IOleClientSiteEx *)This)->provider; - } else { - *ppvObject = 0; - return (E_NOINTERFACE); - } - return S_OK; -} -static HRESULT STDMETHODCALLTYPE InPlace_QueryInterface( - IOleInPlaceSite FAR *This, REFIID riid, LPVOID FAR *ppvObj) { - return (Site_QueryInterface( - (IOleClientSite *)((char *)This - sizeof(IOleClientSite)), riid, ppvObj)); -} -static ULONG STDMETHODCALLTYPE InPlace_AddRef(IOleInPlaceSite FAR *This) { - return 1; -} -static ULONG STDMETHODCALLTYPE InPlace_Release(IOleInPlaceSite FAR *This) { - return 1; -} -static HRESULT STDMETHODCALLTYPE InPlace_GetWindow(IOleInPlaceSite FAR *This, - HWND FAR *lphwnd) { - *lphwnd = ((_IOleInPlaceSiteEx FAR *)This)->frame.window; - return S_OK; -} -static HRESULT STDMETHODCALLTYPE -InPlace_ContextSensitiveHelp(IOleInPlaceSite FAR *This, BOOL fEnterMode) { - return E_NOTIMPL; -} -static HRESULT STDMETHODCALLTYPE -InPlace_CanInPlaceActivate(IOleInPlaceSite FAR *This) { - return S_OK; -} -static HRESULT STDMETHODCALLTYPE -InPlace_OnInPlaceActivate(IOleInPlaceSite FAR *This) { - return S_OK; -} -static HRESULT STDMETHODCALLTYPE -InPlace_OnUIActivate(IOleInPlaceSite FAR *This) { - return S_OK; -} -static HRESULT STDMETHODCALLTYPE InPlace_GetWindowContext( - IOleInPlaceSite FAR *This, LPOLEINPLACEFRAME FAR *lplpFrame, - LPOLEINPLACEUIWINDOW FAR *lplpDoc, LPRECT lprcPosRect, LPRECT lprcClipRect, - LPOLEINPLACEFRAMEINFO lpFrameInfo) { - *lplpFrame = (LPOLEINPLACEFRAME) & ((_IOleInPlaceSiteEx *)This)->frame; - *lplpDoc = 0; - lpFrameInfo->fMDIApp = FALSE; - lpFrameInfo->hwndFrame = ((_IOleInPlaceFrameEx *)*lplpFrame)->window; - lpFrameInfo->haccel = 0; - lpFrameInfo->cAccelEntries = 0; - return S_OK; -} -static HRESULT STDMETHODCALLTYPE InPlace_Scroll(IOleInPlaceSite FAR *This, - SIZE scrollExtent) { - return E_NOTIMPL; -} -static HRESULT STDMETHODCALLTYPE -InPlace_OnUIDeactivate(IOleInPlaceSite FAR *This, BOOL fUndoable) { - return S_OK; -} -static HRESULT STDMETHODCALLTYPE -InPlace_OnInPlaceDeactivate(IOleInPlaceSite FAR *This) { - return S_OK; -} -static HRESULT STDMETHODCALLTYPE -InPlace_DiscardUndoState(IOleInPlaceSite FAR *This) { - return E_NOTIMPL; -} -static HRESULT STDMETHODCALLTYPE -InPlace_DeactivateAndUndo(IOleInPlaceSite FAR *This) { - return E_NOTIMPL; -} -static HRESULT STDMETHODCALLTYPE -InPlace_OnPosRectChange(IOleInPlaceSite FAR *This, LPCRECT lprcPosRect) { - IOleObject *browserObject; - IOleInPlaceObject *inplace; - browserObject = *((IOleObject **)((char *)This - sizeof(IOleObject *) - - sizeof(IOleClientSite))); - if (!browserObject->lpVtbl->QueryInterface(browserObject, - iid_unref(&IID_IOleInPlaceObject), - (void **)&inplace)) { - inplace->lpVtbl->SetObjectRects(inplace, lprcPosRect, lprcPosRect); - inplace->lpVtbl->Release(inplace); - } - return S_OK; -} -static HRESULT STDMETHODCALLTYPE Frame_QueryInterface( - IOleInPlaceFrame FAR *This, REFIID riid, LPVOID FAR *ppvObj) { - return E_NOTIMPL; -} -static ULONG STDMETHODCALLTYPE Frame_AddRef(IOleInPlaceFrame FAR *This) { - return 1; -} -static ULONG STDMETHODCALLTYPE Frame_Release(IOleInPlaceFrame FAR *This) { - return 1; -} -static HRESULT STDMETHODCALLTYPE Frame_GetWindow(IOleInPlaceFrame FAR *This, - HWND FAR *lphwnd) { - *lphwnd = ((_IOleInPlaceFrameEx *)This)->window; - return S_OK; -} -static HRESULT STDMETHODCALLTYPE -Frame_ContextSensitiveHelp(IOleInPlaceFrame FAR *This, BOOL fEnterMode) { - return E_NOTIMPL; -} -static HRESULT STDMETHODCALLTYPE Frame_GetBorder(IOleInPlaceFrame FAR *This, - LPRECT lprectBorder) { - return E_NOTIMPL; -} -static HRESULT STDMETHODCALLTYPE Frame_RequestBorderSpace( - IOleInPlaceFrame FAR *This, LPCBORDERWIDTHS pborderwidths) { - return E_NOTIMPL; -} -static HRESULT STDMETHODCALLTYPE Frame_SetBorderSpace( - IOleInPlaceFrame FAR *This, LPCBORDERWIDTHS pborderwidths) { - return E_NOTIMPL; -} -static HRESULT STDMETHODCALLTYPE Frame_SetActiveObject( - IOleInPlaceFrame FAR *This, IOleInPlaceActiveObject *pActiveObject, - LPCOLESTR pszObjName) { - return S_OK; -} -static HRESULT STDMETHODCALLTYPE -Frame_InsertMenus(IOleInPlaceFrame FAR *This, HMENU hmenuShared, - LPOLEMENUGROUPWIDTHS lpMenuWidths) { - return E_NOTIMPL; -} -static HRESULT STDMETHODCALLTYPE Frame_SetMenu(IOleInPlaceFrame FAR *This, - HMENU hmenuShared, - HOLEMENU holemenu, - HWND hwndActiveObject) { - return S_OK; -} -static HRESULT STDMETHODCALLTYPE Frame_RemoveMenus(IOleInPlaceFrame FAR *This, - HMENU hmenuShared) { - return E_NOTIMPL; -} -static HRESULT STDMETHODCALLTYPE Frame_SetStatusText(IOleInPlaceFrame FAR *This, - LPCOLESTR pszStatusText) { - return S_OK; -} -static HRESULT STDMETHODCALLTYPE -Frame_EnableModeless(IOleInPlaceFrame FAR *This, BOOL fEnable) { - return S_OK; -} -static HRESULT STDMETHODCALLTYPE -Frame_TranslateAccelerator(IOleInPlaceFrame FAR *This, LPMSG lpmsg, WORD wID) { - return E_NOTIMPL; -} -static HRESULT STDMETHODCALLTYPE UI_QueryInterface(IDocHostUIHandler FAR *This, - REFIID riid, - LPVOID FAR *ppvObj) { - return (Site_QueryInterface((IOleClientSite *)((char *)This - - sizeof(IOleClientSite) - - sizeof(_IOleInPlaceSiteEx)), - riid, ppvObj)); -} -static ULONG STDMETHODCALLTYPE UI_AddRef(IDocHostUIHandler FAR *This) { - return 1; -} -static ULONG STDMETHODCALLTYPE UI_Release(IDocHostUIHandler FAR *This) { - return 1; -} -static HRESULT STDMETHODCALLTYPE UI_ShowContextMenu( - IDocHostUIHandler FAR *This, DWORD dwID, POINT __RPC_FAR *ppt, - IUnknown __RPC_FAR *pcmdtReserved, IDispatch __RPC_FAR *pdispReserved) { - return S_OK; -} -static HRESULT STDMETHODCALLTYPE -UI_GetHostInfo(IDocHostUIHandler FAR *This, DOCHOSTUIINFO __RPC_FAR *pInfo) { - pInfo->cbSize = sizeof(DOCHOSTUIINFO); - pInfo->dwFlags = DOCHOSTUIFLAG_NO3DBORDER; - pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT; - return S_OK; -} -static HRESULT STDMETHODCALLTYPE UI_ShowUI( - IDocHostUIHandler FAR *This, DWORD dwID, - IOleInPlaceActiveObject __RPC_FAR *pActiveObject, - IOleCommandTarget __RPC_FAR *pCommandTarget, - IOleInPlaceFrame __RPC_FAR *pFrame, IOleInPlaceUIWindow __RPC_FAR *pDoc) { - return S_OK; -} -static HRESULT STDMETHODCALLTYPE UI_HideUI(IDocHostUIHandler FAR *This) { - return S_OK; -} -static HRESULT STDMETHODCALLTYPE UI_UpdateUI(IDocHostUIHandler FAR *This) { - return S_OK; -} -static HRESULT STDMETHODCALLTYPE UI_EnableModeless(IDocHostUIHandler FAR *This, - BOOL fEnable) { - return S_OK; -} -static HRESULT STDMETHODCALLTYPE -UI_OnDocWindowActivate(IDocHostUIHandler FAR *This, BOOL fActivate) { - return S_OK; -} -static HRESULT STDMETHODCALLTYPE -UI_OnFrameWindowActivate(IDocHostUIHandler FAR *This, BOOL fActivate) { - return S_OK; -} -static HRESULT STDMETHODCALLTYPE -UI_ResizeBorder(IDocHostUIHandler FAR *This, LPCRECT prcBorder, - IOleInPlaceUIWindow __RPC_FAR *pUIWindow, BOOL fRameWindow) { - return S_OK; -} -static HRESULT STDMETHODCALLTYPE -UI_TranslateAccelerator(IDocHostUIHandler FAR *This, LPMSG lpMsg, - const GUID __RPC_FAR *pguidCmdGroup, DWORD nCmdID) { - return S_FALSE; -} -static HRESULT STDMETHODCALLTYPE UI_GetOptionKeyPath( - IDocHostUIHandler FAR *This, LPOLESTR __RPC_FAR *pchKey, DWORD dw) { - return S_FALSE; -} -static HRESULT STDMETHODCALLTYPE UI_GetDropTarget( - IDocHostUIHandler FAR *This, IDropTarget __RPC_FAR *pDropTarget, - IDropTarget __RPC_FAR *__RPC_FAR *ppDropTarget) { - return S_FALSE; -} -static HRESULT STDMETHODCALLTYPE UI_GetExternal( - IDocHostUIHandler FAR *This, IDispatch __RPC_FAR *__RPC_FAR *ppDispatch) { - *ppDispatch = (IDispatch *)(This + 1); - return S_OK; -} -static HRESULT STDMETHODCALLTYPE UI_TranslateUrl( - IDocHostUIHandler FAR *This, DWORD dwTranslate, OLECHAR __RPC_FAR *pchURLIn, - OLECHAR __RPC_FAR *__RPC_FAR *ppchURLOut) { - *ppchURLOut = 0; - return S_FALSE; -} -static HRESULT STDMETHODCALLTYPE -UI_FilterDataObject(IDocHostUIHandler FAR *This, IDataObject __RPC_FAR *pDO, - IDataObject __RPC_FAR *__RPC_FAR *ppDORet) { - *ppDORet = 0; - return S_FALSE; -} - -static const TCHAR *classname = "WebView"; -static const SAFEARRAYBOUND ArrayBound = {1, 0}; - -static IOleClientSiteVtbl MyIOleClientSiteTable = { - Site_QueryInterface, Site_AddRef, Site_Release, - Site_SaveObject, Site_GetMoniker, Site_GetContainer, - Site_ShowObject, Site_OnShowWindow, Site_RequestNewObjectLayout}; -static IOleInPlaceSiteVtbl MyIOleInPlaceSiteTable = { - InPlace_QueryInterface, - InPlace_AddRef, - InPlace_Release, - InPlace_GetWindow, - InPlace_ContextSensitiveHelp, - InPlace_CanInPlaceActivate, - InPlace_OnInPlaceActivate, - InPlace_OnUIActivate, - InPlace_GetWindowContext, - InPlace_Scroll, - InPlace_OnUIDeactivate, - InPlace_OnInPlaceDeactivate, - InPlace_DiscardUndoState, - InPlace_DeactivateAndUndo, - InPlace_OnPosRectChange}; - -static IOleInPlaceFrameVtbl MyIOleInPlaceFrameTable = { - Frame_QueryInterface, - Frame_AddRef, - Frame_Release, - Frame_GetWindow, - Frame_ContextSensitiveHelp, - Frame_GetBorder, - Frame_RequestBorderSpace, - Frame_SetBorderSpace, - Frame_SetActiveObject, - Frame_InsertMenus, - Frame_SetMenu, - Frame_RemoveMenus, - Frame_SetStatusText, - Frame_EnableModeless, - Frame_TranslateAccelerator}; - -static IDocHostUIHandlerVtbl MyIDocHostUIHandlerTable = { - UI_QueryInterface, - UI_AddRef, - UI_Release, - UI_ShowContextMenu, - UI_GetHostInfo, - UI_ShowUI, - UI_HideUI, - UI_UpdateUI, - UI_EnableModeless, - UI_OnDocWindowActivate, - UI_OnFrameWindowActivate, - UI_ResizeBorder, - UI_TranslateAccelerator, - UI_GetOptionKeyPath, - UI_GetDropTarget, - UI_GetExternal, - UI_TranslateUrl, - UI_FilterDataObject}; - - - -static HRESULT STDMETHODCALLTYPE IS_QueryInterface(IInternetSecurityManager FAR *This, REFIID riid, void **ppvObject) { - return E_NOTIMPL; -} -static ULONG STDMETHODCALLTYPE IS_AddRef(IInternetSecurityManager FAR *This) { return 1; } -static ULONG STDMETHODCALLTYPE IS_Release(IInternetSecurityManager FAR *This) { return 1; } -static HRESULT STDMETHODCALLTYPE IS_SetSecuritySite(IInternetSecurityManager FAR *This, IInternetSecurityMgrSite *pSited) { - return INET_E_DEFAULT_ACTION; -} -static HRESULT STDMETHODCALLTYPE IS_GetSecuritySite(IInternetSecurityManager FAR *This, IInternetSecurityMgrSite **ppSite) { - return INET_E_DEFAULT_ACTION; -} -static HRESULT STDMETHODCALLTYPE IS_MapUrlToZone(IInternetSecurityManager FAR *This, LPCWSTR pwszUrl, DWORD *pdwZone, DWORD dwFlags) { - *pdwZone = URLZONE_LOCAL_MACHINE; - return S_OK; -} -static HRESULT STDMETHODCALLTYPE IS_GetSecurityId(IInternetSecurityManager FAR *This, LPCWSTR pwszUrl, BYTE *pbSecurityId, DWORD *pcbSecurityId, DWORD_PTR dwReserved) { - return INET_E_DEFAULT_ACTION; -} -static HRESULT STDMETHODCALLTYPE IS_ProcessUrlAction(IInternetSecurityManager FAR *This, LPCWSTR pwszUrl, DWORD dwAction, BYTE *pPolicy, DWORD cbPolicy, BYTE *pContext, DWORD cbContext, DWORD dwFlags, DWORD dwReserved) { - return INET_E_DEFAULT_ACTION; -} -static HRESULT STDMETHODCALLTYPE IS_QueryCustomPolicy(IInternetSecurityManager FAR *This, LPCWSTR pwszUrl, REFGUID guidKey, BYTE **ppPolicy, DWORD *pcbPolicy, BYTE *pContext, DWORD cbContext, DWORD dwReserved) { - return INET_E_DEFAULT_ACTION; -} -static HRESULT STDMETHODCALLTYPE IS_SetZoneMapping(IInternetSecurityManager FAR *This, DWORD dwZone, LPCWSTR lpszPattern, DWORD dwFlags) { - return INET_E_DEFAULT_ACTION; -} -static HRESULT STDMETHODCALLTYPE IS_GetZoneMappings(IInternetSecurityManager FAR *This, DWORD dwZone, IEnumString **ppenumString, DWORD dwFlags) { - return INET_E_DEFAULT_ACTION; -} -static IInternetSecurityManagerVtbl MyInternetSecurityManagerTable = {IS_QueryInterface, IS_AddRef, IS_Release, IS_SetSecuritySite, IS_GetSecuritySite, IS_MapUrlToZone, IS_GetSecurityId, IS_ProcessUrlAction, IS_QueryCustomPolicy, IS_SetZoneMapping, IS_GetZoneMappings}; - -static HRESULT STDMETHODCALLTYPE SP_QueryInterface(IServiceProvider FAR *This, REFIID riid, void **ppvObject) { - return (Site_QueryInterface( - (IOleClientSite *)((char *)This - sizeof(IOleClientSite) - sizeof(_IOleInPlaceSiteEx) - sizeof(_IDocHostUIHandlerEx) - sizeof(IDispatch)), riid, ppvObject)); -} -static ULONG STDMETHODCALLTYPE SP_AddRef(IServiceProvider FAR *This) { return 1; } -static ULONG STDMETHODCALLTYPE SP_Release(IServiceProvider FAR *This) { return 1; } -static HRESULT STDMETHODCALLTYPE SP_QueryService(IServiceProvider FAR *This, REFGUID siid, REFIID riid, void **ppvObject) { - if (iid_eq(siid, &IID_IInternetSecurityManager) && iid_eq(riid, &IID_IInternetSecurityManager)) { - *ppvObject = &((_IServiceProviderEx *)This)->mgr; - } else { - *ppvObject = 0; - return (E_NOINTERFACE); - } - return S_OK; -} -static IServiceProviderVtbl MyServiceProviderTable = {SP_QueryInterface, SP_AddRef, SP_Release, SP_QueryService}; - -static void UnEmbedBrowserObject(struct webview *w) { - if (w->priv.browser != NULL) { - (*w->priv.browser)->lpVtbl->Close(*w->priv.browser, OLECLOSE_NOSAVE); - (*w->priv.browser)->lpVtbl->Release(*w->priv.browser); - GlobalFree(w->priv.browser); - w->priv.browser = NULL; - } -} - -static int EmbedBrowserObject(struct webview *w) { - RECT rect; - IWebBrowser2 *webBrowser2 = NULL; - LPCLASSFACTORY pClassFactory = NULL; - _IOleClientSiteEx *_iOleClientSiteEx = NULL; - IOleObject **browser = (IOleObject **)GlobalAlloc( - GMEM_FIXED, sizeof(IOleObject *) + sizeof(_IOleClientSiteEx)); - if (browser == NULL) { - goto error; - } - w->priv.browser = browser; - - _iOleClientSiteEx = (_IOleClientSiteEx *)(browser + 1); - _iOleClientSiteEx->client.lpVtbl = &MyIOleClientSiteTable; - _iOleClientSiteEx->inplace.inplace.lpVtbl = &MyIOleInPlaceSiteTable; - _iOleClientSiteEx->inplace.frame.frame.lpVtbl = &MyIOleInPlaceFrameTable; - _iOleClientSiteEx->inplace.frame.window = w->priv.hwnd; - _iOleClientSiteEx->ui.ui.lpVtbl = &MyIDocHostUIHandlerTable; - _iOleClientSiteEx->external.lpVtbl = &ExternalDispatchTable; - _iOleClientSiteEx->provider.provider.lpVtbl = &MyServiceProviderTable; - _iOleClientSiteEx->provider.mgr.mgr.lpVtbl = &MyInternetSecurityManagerTable; - - if (CoGetClassObject(iid_unref(&CLSID_WebBrowser), - CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, NULL, - iid_unref(&IID_IClassFactory), - (void **)&pClassFactory) != S_OK) { - goto error; - } - - if (pClassFactory == NULL) { - goto error; - } - - if (pClassFactory->lpVtbl->CreateInstance(pClassFactory, 0, - iid_unref(&IID_IOleObject), - (void **)browser) != S_OK) { - goto error; - } - pClassFactory->lpVtbl->Release(pClassFactory); - if ((*browser)->lpVtbl->SetClientSite( - *browser, (IOleClientSite *)_iOleClientSiteEx) != S_OK) { - goto error; - } - (*browser)->lpVtbl->SetHostNames(*browser, L"My Host Name", 0); - - if (OleSetContainedObject((struct IUnknown *)(*browser), TRUE) != S_OK) { - goto error; - } - GetClientRect(w->priv.hwnd, &rect); - if ((*browser)->lpVtbl->DoVerb((*browser), OLEIVERB_SHOW, NULL, - (IOleClientSite *)_iOleClientSiteEx, -1, - w->priv.hwnd, &rect) != S_OK) { - goto error; - } - if ((*browser)->lpVtbl->QueryInterface((*browser), - iid_unref(&IID_IWebBrowser2), - (void **)&webBrowser2) != S_OK) { - goto error; - } - - webBrowser2->lpVtbl->put_Left(webBrowser2, 0); - webBrowser2->lpVtbl->put_Top(webBrowser2, 0); - webBrowser2->lpVtbl->put_Width(webBrowser2, rect.right); - webBrowser2->lpVtbl->put_Height(webBrowser2, rect.bottom); - webBrowser2->lpVtbl->Release(webBrowser2); - - return 0; -error: - UnEmbedBrowserObject(w); - if (pClassFactory != NULL) { - pClassFactory->lpVtbl->Release(pClassFactory); - } - if (browser != NULL) { - GlobalFree(browser); - } - return -1; -} - -#define WEBVIEW_DATA_URL_PREFIX "data:text/html," -static int DisplayHTMLPage(struct webview *w) { - IWebBrowser2 *webBrowser2; - VARIANT myURL; - LPDISPATCH lpDispatch; - IHTMLDocument2 *htmlDoc2; - BSTR bstr; - IOleObject *browserObject; - SAFEARRAY *sfArray; - VARIANT *pVar; - browserObject = *w->priv.browser; - int isDataURL = 0; - const char *webview_url = webview_check_url(w->url); - if (!browserObject->lpVtbl->QueryInterface( - browserObject, iid_unref(&IID_IWebBrowser2), (void **)&webBrowser2)) { - LPCSTR webPageName; - isDataURL = (strncmp(webview_url, WEBVIEW_DATA_URL_PREFIX, - strlen(WEBVIEW_DATA_URL_PREFIX)) == 0); - if (isDataURL) { - webPageName = "about:blank"; - } else { - webPageName = (LPCSTR)webview_url; - } - VariantInit(&myURL); - myURL.vt = VT_BSTR; -#ifndef UNICODE - { - wchar_t *buffer = webview_to_utf16(webPageName); - if (buffer == NULL) { - goto badalloc; - } - myURL.bstrVal = SysAllocString(buffer); - GlobalFree(buffer); - } -#else - myURL.bstrVal = SysAllocString(webPageName); -#endif - if (!myURL.bstrVal) { - badalloc: - webBrowser2->lpVtbl->Release(webBrowser2); - return (-6); - } - webBrowser2->lpVtbl->Navigate2(webBrowser2, &myURL, 0, 0, 0, 0); - VariantClear(&myURL); - if (!isDataURL) { - return 0; - } - - char *url = (char *)calloc(1, strlen(webview_url) + 1); - char *q = url; - for (const char *p = webview_url + strlen(WEBVIEW_DATA_URL_PREFIX); *q = *p; - p++, q++) { - if (*q == '%' && *(p + 1) && *(p + 2)) { - sscanf(p + 1, "%02x", q); - p = p + 2; - } - } - - if (webBrowser2->lpVtbl->get_Document(webBrowser2, &lpDispatch) == S_OK) { - if (lpDispatch->lpVtbl->QueryInterface(lpDispatch, - iid_unref(&IID_IHTMLDocument2), - (void **)&htmlDoc2) == S_OK) { - if ((sfArray = SafeArrayCreate(VT_VARIANT, 1, - (SAFEARRAYBOUND *)&ArrayBound))) { - if (!SafeArrayAccessData(sfArray, (void **)&pVar)) { - pVar->vt = VT_BSTR; -#ifndef UNICODE - { - wchar_t *buffer = webview_to_utf16(url); - if (buffer == NULL) { - goto release; - } - bstr = SysAllocString(buffer); - GlobalFree(buffer); - } -#else - bstr = SysAllocString(string); -#endif - if ((pVar->bstrVal = bstr)) { - htmlDoc2->lpVtbl->write(htmlDoc2, sfArray); - htmlDoc2->lpVtbl->close(htmlDoc2); - } - } - SafeArrayDestroy(sfArray); - } - release: - free(url); - htmlDoc2->lpVtbl->Release(htmlDoc2); - } - lpDispatch->lpVtbl->Release(lpDispatch); - } - webBrowser2->lpVtbl->Release(webBrowser2); - return (0); - } - return (-5); -} - -static LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, - LPARAM lParam) { - struct webview *w = (struct webview *)GetWindowLongPtr(hwnd, GWLP_USERDATA); - switch (uMsg) { - case WM_CREATE: - w = (struct webview *)((CREATESTRUCT *)lParam)->lpCreateParams; - w->priv.hwnd = hwnd; - return EmbedBrowserObject(w); - case WM_DESTROY: - UnEmbedBrowserObject(w); - PostQuitMessage(0); - return TRUE; - case WM_SIZE: { - IWebBrowser2 *webBrowser2; - IOleObject *browser = *w->priv.browser; - if (browser->lpVtbl->QueryInterface(browser, iid_unref(&IID_IWebBrowser2), - (void **)&webBrowser2) == S_OK) { - RECT rect; - GetClientRect(hwnd, &rect); - webBrowser2->lpVtbl->put_Width(webBrowser2, rect.right); - webBrowser2->lpVtbl->put_Height(webBrowser2, rect.bottom); - } - return TRUE; - } - case WM_WEBVIEW_DISPATCH: { - webview_dispatch_fn f = (webview_dispatch_fn)wParam; - void *arg = (void *)lParam; - (*f)(w, arg); - return TRUE; - } - } - return DefWindowProc(hwnd, uMsg, wParam, lParam); -} - -#define WEBVIEW_KEY_FEATURE_BROWSER_EMULATION \ - "Software\\Microsoft\\Internet " \ - "Explorer\\Main\\FeatureControl\\FEATURE_BROWSER_EMULATION" - -static int webview_fix_ie_compat_mode() { - HKEY hKey; - DWORD ie_version = 11000; - TCHAR appname[MAX_PATH + 1]; - TCHAR *p; - if (GetModuleFileName(NULL, appname, MAX_PATH + 1) == 0) { - return -1; - } - for (p = &appname[strlen(appname) - 1]; p != appname && *p != '\\'; p--) { - } - p++; - if (RegCreateKey(HKEY_CURRENT_USER, WEBVIEW_KEY_FEATURE_BROWSER_EMULATION, - &hKey) != ERROR_SUCCESS) { - return -1; - } - if (RegSetValueEx(hKey, p, 0, REG_DWORD, (BYTE *)&ie_version, - sizeof(ie_version)) != ERROR_SUCCESS) { - RegCloseKey(hKey); - return -1; - } - RegCloseKey(hKey); - return 0; -} - -WEBVIEW_API int webview_init(struct webview *w) { - WNDCLASSEX wc; - HINSTANCE hInstance; - DWORD style; - RECT clientRect; - RECT rect; - - if (webview_fix_ie_compat_mode() < 0) { - return -1; - } - - hInstance = GetModuleHandle(NULL); - if (hInstance == NULL) { - return -1; - } - if (OleInitialize(NULL) != S_OK) { - return -1; - } - ZeroMemory(&wc, sizeof(WNDCLASSEX)); - wc.cbSize = sizeof(WNDCLASSEX); - wc.hInstance = hInstance; - wc.lpfnWndProc = wndproc; - wc.lpszClassName = classname; - RegisterClassEx(&wc); - - style = WS_OVERLAPPEDWINDOW; - if (!w->resizable) { - style = WS_OVERLAPPED | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU; - } - - rect.left = 0; - rect.top = 0; - rect.right = w->width; - rect.bottom = w->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; - - w->priv.hwnd = - CreateWindowEx(0, classname, w->title, style, rect.left, rect.top, - rect.right - rect.left, rect.bottom - rect.top, - HWND_DESKTOP, NULL, hInstance, (void *)w); - if (w->priv.hwnd == 0) { - OleUninitialize(); - return -1; - } - - SetWindowLongPtr(w->priv.hwnd, GWLP_USERDATA, (LONG_PTR)w); - - DisplayHTMLPage(w); - - SetWindowText(w->priv.hwnd, w->title); - ShowWindow(w->priv.hwnd, SW_SHOWDEFAULT); - UpdateWindow(w->priv.hwnd); - SetFocus(w->priv.hwnd); - - return 0; -} - -WEBVIEW_API int webview_loop(struct webview *w, int blocking) { - MSG msg; - if (blocking) { - GetMessage(&msg, 0, 0, 0); - } else { - PeekMessage(&msg, 0, 0, 0, PM_REMOVE); - } - switch (msg.message) { - case WM_QUIT: - return -1; - case WM_COMMAND: - case WM_KEYDOWN: - case WM_KEYUP: { - HRESULT r = S_OK; - IWebBrowser2 *webBrowser2; - IOleObject *browser = *w->priv.browser; - if (browser->lpVtbl->QueryInterface(browser, iid_unref(&IID_IWebBrowser2), - (void **)&webBrowser2) == S_OK) { - IOleInPlaceActiveObject *pIOIPAO; - if (browser->lpVtbl->QueryInterface( - browser, iid_unref(&IID_IOleInPlaceActiveObject), - (void **)&pIOIPAO) == S_OK) { - r = pIOIPAO->lpVtbl->TranslateAccelerator(pIOIPAO, &msg); - pIOIPAO->lpVtbl->Release(pIOIPAO); - } - webBrowser2->lpVtbl->Release(webBrowser2); - } - if (r != S_FALSE) { - break; - } - } - default: - TranslateMessage(&msg); - DispatchMessage(&msg); - } - return 0; -} - -WEBVIEW_API int webview_eval(struct webview *w, const char *js) { - IWebBrowser2 *webBrowser2; - IHTMLDocument2 *htmlDoc2; - IDispatch *docDispatch; - IDispatch *scriptDispatch; - if ((*w->priv.browser) - ->lpVtbl->QueryInterface((*w->priv.browser), - iid_unref(&IID_IWebBrowser2), - (void **)&webBrowser2) != S_OK) { - return -1; - } - - if (webBrowser2->lpVtbl->get_Document(webBrowser2, &docDispatch) != S_OK) { - return -1; - } - if (docDispatch->lpVtbl->QueryInterface(docDispatch, - iid_unref(&IID_IHTMLDocument2), - (void **)&htmlDoc2) != S_OK) { - return -1; - } - if (htmlDoc2->lpVtbl->get_Script(htmlDoc2, &scriptDispatch) != S_OK) { - return -1; - } - DISPID dispid; - BSTR evalStr = SysAllocString(L"eval"); - if (scriptDispatch->lpVtbl->GetIDsOfNames( - scriptDispatch, iid_unref(&IID_NULL), &evalStr, 1, - LOCALE_SYSTEM_DEFAULT, &dispid) != S_OK) { - SysFreeString(evalStr); - return -1; - } - SysFreeString(evalStr); - - DISPPARAMS params; - VARIANT arg; - VARIANT result; - EXCEPINFO excepInfo; - UINT nArgErr = (UINT)-1; - params.cArgs = 1; - params.cNamedArgs = 0; - params.rgvarg = &arg; - arg.vt = VT_BSTR; - static const char *prologue = "(function(){"; - static const char *epilogue = ";})();"; - int n = strlen(prologue) + strlen(epilogue) + strlen(js) + 1; - char *eval = (char *)malloc(n); - snprintf(eval, n, "%s%s%s", prologue, js, epilogue); - wchar_t *buf = webview_to_utf16(eval); - if (buf == NULL) { - return -1; - } - arg.bstrVal = SysAllocString(buf); - if (scriptDispatch->lpVtbl->Invoke( - scriptDispatch, dispid, iid_unref(&IID_NULL), 0, DISPATCH_METHOD, - ¶ms, &result, &excepInfo, &nArgErr) != S_OK) { - return -1; - } - SysFreeString(arg.bstrVal); - free(eval); - scriptDispatch->lpVtbl->Release(scriptDispatch); - htmlDoc2->lpVtbl->Release(htmlDoc2); - docDispatch->lpVtbl->Release(docDispatch); - return 0; -} - -WEBVIEW_API void webview_dispatch(struct webview *w, webview_dispatch_fn fn, - void *arg) { - PostMessageW(w->priv.hwnd, WM_WEBVIEW_DISPATCH, (WPARAM)fn, (LPARAM)arg); -} - -WEBVIEW_API void webview_set_title(struct webview *w, const char *title) { - SetWindowText(w->priv.hwnd, title); -} - -WEBVIEW_API void webview_set_fullscreen(struct webview *w, int fullscreen) { - if (w->priv.is_fullscreen == !!fullscreen) { - return; - } - if (w->priv.is_fullscreen == 0) { - w->priv.saved_style = GetWindowLong(w->priv.hwnd, GWL_STYLE); - w->priv.saved_ex_style = GetWindowLong(w->priv.hwnd, GWL_EXSTYLE); - GetWindowRect(w->priv.hwnd, &w->priv.saved_rect); - } - w->priv.is_fullscreen = !!fullscreen; - if (fullscreen) { - MONITORINFO monitor_info; - SetWindowLong(w->priv.hwnd, GWL_STYLE, - w->priv.saved_style & ~(WS_CAPTION | WS_THICKFRAME)); - SetWindowLong(w->priv.hwnd, GWL_EXSTYLE, - w->priv.saved_ex_style & - ~(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE | - WS_EX_CLIENTEDGE | WS_EX_STATICEDGE)); - monitor_info.cbSize = sizeof(monitor_info); - GetMonitorInfo(MonitorFromWindow(w->priv.hwnd, 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(w->priv.hwnd, NULL, r.left, r.top, r.right - r.left, - r.bottom - r.top, - SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); - } else { - SetWindowLong(w->priv.hwnd, GWL_STYLE, w->priv.saved_style); - SetWindowLong(w->priv.hwnd, GWL_EXSTYLE, w->priv.saved_ex_style); - SetWindowPos(w->priv.hwnd, NULL, w->priv.saved_rect.left, - w->priv.saved_rect.top, - w->priv.saved_rect.right - w->priv.saved_rect.left, - w->priv.saved_rect.bottom - w->priv.saved_rect.top, - SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); - } -} - -WEBVIEW_API void webview_set_color(struct webview *w, uint8_t r, uint8_t g, - uint8_t b, uint8_t a) { - HBRUSH brush = CreateSolidBrush(RGB(r, g, b)); - SetClassLongPtr(w->priv.hwnd, GCLP_HBRBACKGROUND, (LONG_PTR)brush); -} - -/* 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(struct webview *w, - enum webview_dialog_type dlgtype, int flags, - const char *title, const char *arg, - char *result, size_t resultsz) { - 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, add_opts; - 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->lpVtbl->GetOptions(dlg, &opts) != S_OK) { - goto error_dlg; - } - opts &= ~FOS_NOREADONLYRETURN; - opts |= add_opts; - if (dlg->lpVtbl->SetOptions(dlg, opts) != S_OK) { - goto error_dlg; - } - if (dlg->lpVtbl->Show(dlg, w->priv.hwnd) != S_OK) { - goto error_dlg; - } - if (dlg->lpVtbl->GetResult(dlg, &res) != S_OK) { - goto error_dlg; - } - if (res->lpVtbl->GetDisplayName(res, SIGDN_FILESYSPATH, &ws) != S_OK) { - goto error_result; - } - s = webview_from_utf16(ws); - strncpy(result, s, resultsz); - result[resultsz - 1] = '\0'; - CoTaskMemFree(ws); - error_result: - res->lpVtbl->Release(res); - error_dlg: - dlg->lpVtbl->Release(dlg); - 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(w->priv.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(w->priv.hwnd, arg, title, type); +extern "C" { #endif - } -} - -WEBVIEW_API void webview_terminate(struct webview *w) { PostQuitMessage(0); } - -WEBVIEW_API void webview_exit(struct webview *w) { - DestroyWindow(w->priv.hwnd); - OleUninitialize(); -} - -WEBVIEW_API void webview_print_log(const char *s) { OutputDebugString(s); } - -#endif /* WEBVIEW_WINAPI */ - -#if defined(WEBVIEW_COCOA) -#define NSAlertStyleWarning 0 -#define NSAlertStyleCritical 2 -#define NSWindowStyleMaskResizable 8 -#define NSWindowStyleMaskMiniaturizable 4 -#define NSWindowStyleMaskTitled 1 -#define NSWindowStyleMaskClosable 2 -#define NSWindowStyleMaskFullScreen (1 << 14) -#define NSViewWidthSizable 2 -#define NSViewHeightSizable 16 -#define NSBackingStoreBuffered 2 -#define NSEventMaskAny ULONG_MAX -#define NSEventModifierFlagCommand (1 << 20) -#define NSEventModifierFlagOption (1 << 19) -#define NSAlertStyleInformational 1 -#define NSAlertFirstButtonReturn 1000 -#define WKNavigationActionPolicyDownload 2 -#define NSModalResponseOK 1 -#define WKNavigationActionPolicyDownload 2 -#define WKNavigationResponsePolicyAllow 1 -#define WKUserScriptInjectionTimeAtDocumentStart 0 -#define NSApplicationActivationPolicyRegular 0 - -static id get_nsstring(const char *c_str) { - return objc_msgSend((id)objc_getClass("NSString"), - sel_registerName("stringWithUTF8String:"), c_str); -} - -static id create_menu_item(id title, const char *action, const char *key) { - id item = - objc_msgSend((id)objc_getClass("NSMenuItem"), sel_registerName("alloc")); - objc_msgSend(item, sel_registerName("initWithTitle:action:keyEquivalent:"), - title, sel_registerName(action), get_nsstring(key)); - objc_msgSend(item, sel_registerName("autorelease")); - - return item; -} - -static void webview_window_will_close(id self, SEL cmd, id notification) { - struct webview *w = - (struct webview *)objc_getAssociatedObject(self, "webview"); - webview_terminate(w); -} - -static void webview_external_invoke(id self, SEL cmd, id contentController, - id message) { - struct webview *w = - (struct webview *)objc_getAssociatedObject(contentController, "webview"); - if (w == NULL || w->external_invoke_cb == NULL) { - return; - } - - w->external_invoke_cb(w, (const char *)objc_msgSend( - objc_msgSend(message, sel_registerName("body")), - sel_registerName("UTF8String"))); -} - -static void run_open_panel(id self, SEL cmd, id webView, id parameters, - id frame, void (^completionHandler)(id)) { - - id openPanel = objc_msgSend((id)objc_getClass("NSOpenPanel"), - sel_registerName("openPanel")); - - objc_msgSend( - openPanel, sel_registerName("setAllowsMultipleSelection:"), - objc_msgSend(parameters, sel_registerName("allowsMultipleSelection"))); - - objc_msgSend(openPanel, sel_registerName("setCanChooseFiles:"), 1); - objc_msgSend( - openPanel, sel_registerName("beginWithCompletionHandler:"), ^(id result) { - if (result == (id)NSModalResponseOK) { - completionHandler(objc_msgSend(openPanel, sel_registerName("URLs"))); - } else { - completionHandler(nil); - } - }); -} - -static void run_save_panel(id self, SEL cmd, id download, id filename, - void (^completionHandler)(int allowOverwrite, - id destination)) { - id savePanel = objc_msgSend((id)objc_getClass("NSSavePanel"), - sel_registerName("savePanel")); - objc_msgSend(savePanel, sel_registerName("setCanCreateDirectories:"), 1); - objc_msgSend(savePanel, sel_registerName("setNameFieldStringValue:"), - filename); - objc_msgSend(savePanel, sel_registerName("beginWithCompletionHandler:"), - ^(id result) { - if (result == (id)NSModalResponseOK) { - id url = objc_msgSend(savePanel, sel_registerName("URL")); - id path = objc_msgSend(url, sel_registerName("path")); - completionHandler(1, path); - } else { - completionHandler(NO, nil); - } - }); -} - -static void run_confirmation_panel(id self, SEL cmd, id webView, id message, - id frame, void (^completionHandler)(bool)) { - - id alert = - objc_msgSend((id)objc_getClass("NSAlert"), sel_registerName("new")); - objc_msgSend(alert, sel_registerName("setIcon:"), - objc_msgSend((id)objc_getClass("NSImage"), - sel_registerName("imageNamed:"), - get_nsstring("NSCaution"))); - objc_msgSend(alert, sel_registerName("setShowsHelp:"), 0); - objc_msgSend(alert, sel_registerName("setInformativeText:"), message); - objc_msgSend(alert, sel_registerName("addButtonWithTitle:"), - get_nsstring("OK")); - objc_msgSend(alert, sel_registerName("addButtonWithTitle:"), - get_nsstring("Cancel")); - if (objc_msgSend(alert, sel_registerName("runModal")) == - (id)NSAlertFirstButtonReturn) { - completionHandler(true); - } else { - completionHandler(false); - } - objc_msgSend(alert, sel_registerName("release")); -} - -static void run_alert_panel(id self, SEL cmd, id webView, id message, id frame, - void (^completionHandler)(void)) { - id alert = - objc_msgSend((id)objc_getClass("NSAlert"), sel_registerName("new")); - objc_msgSend(alert, sel_registerName("setIcon:"), - objc_msgSend((id)objc_getClass("NSImage"), - sel_registerName("imageNamed:"), - get_nsstring("NSCaution"))); - objc_msgSend(alert, sel_registerName("setShowsHelp:"), 0); - objc_msgSend(alert, sel_registerName("setInformativeText:"), message); - objc_msgSend(alert, sel_registerName("addButtonWithTitle:"), - get_nsstring("OK")); - objc_msgSend(alert, sel_registerName("runModal")); - objc_msgSend(alert, sel_registerName("release")); - completionHandler(); -} - -static void download_failed(id self, SEL cmd, id download, id error) { - printf("%s", - (const char *)objc_msgSend( - objc_msgSend(error, sel_registerName("localizedDescription")), - sel_registerName("UTF8String"))); -} - -static void make_nav_policy_decision(id self, SEL cmd, id webView, id response, - void (^decisionHandler)(int)) { - if (objc_msgSend(response, sel_registerName("canShowMIMEType")) == 0) { - decisionHandler(WKNavigationActionPolicyDownload); - } else { - decisionHandler(WKNavigationResponsePolicyAllow); - } -} - -WEBVIEW_API int webview_init(struct webview *w) { - w->priv.pool = objc_msgSend((id)objc_getClass("NSAutoreleasePool"), - sel_registerName("new")); - objc_msgSend((id)objc_getClass("NSApplication"), - sel_registerName("sharedApplication")); - - Class __WKScriptMessageHandler = objc_allocateClassPair( - objc_getClass("NSObject"), "__WKScriptMessageHandler", 0); - class_addMethod( - __WKScriptMessageHandler, - sel_registerName("userContentController:didReceiveScriptMessage:"), - (IMP)webview_external_invoke, "v@:@@"); - objc_registerClassPair(__WKScriptMessageHandler); - - id scriptMessageHandler = - objc_msgSend((id)__WKScriptMessageHandler, sel_registerName("new")); - - /*** - _WKDownloadDelegate is an undocumented/private protocol with methods called - from WKNavigationDelegate - References: - https://github.com/WebKit/webkit/blob/master/Source/WebKit/UIProcess/API/Cocoa/_WKDownload.h - https://github.com/WebKit/webkit/blob/master/Source/WebKit/UIProcess/API/Cocoa/_WKDownloadDelegate.h - https://github.com/WebKit/webkit/blob/master/Tools/TestWebKitAPI/Tests/WebKitCocoa/Download.mm - ***/ - - Class __WKDownloadDelegate = objc_allocateClassPair( - objc_getClass("NSObject"), "__WKDownloadDelegate", 0); - class_addMethod( - __WKDownloadDelegate, - sel_registerName("_download:decideDestinationWithSuggestedFilename:" - "completionHandler:"), - (IMP)run_save_panel, "v@:@@?"); - class_addMethod(__WKDownloadDelegate, - sel_registerName("_download:didFailWithError:"), - (IMP)download_failed, "v@:@@"); - objc_registerClassPair(__WKDownloadDelegate); - id downloadDelegate = - objc_msgSend((id)__WKDownloadDelegate, sel_registerName("new")); - - Class __WKPreferences = objc_allocateClassPair(objc_getClass("WKPreferences"), - "__WKPreferences", 0); - objc_property_attribute_t type = {"T", "c"}; - objc_property_attribute_t ownership = {"N", ""}; - objc_property_attribute_t attrs[] = {type, ownership}; - class_replaceProperty(__WKPreferences, "developerExtrasEnabled", attrs, 2); - objc_registerClassPair(__WKPreferences); - id wkPref = objc_msgSend((id)__WKPreferences, sel_registerName("new")); - objc_msgSend(wkPref, sel_registerName("setValue:forKey:"), - objc_msgSend((id)objc_getClass("NSNumber"), - sel_registerName("numberWithBool:"), !!w->debug), - objc_msgSend((id)objc_getClass("NSString"), - sel_registerName("stringWithUTF8String:"), - "developerExtrasEnabled")); - - id userController = objc_msgSend((id)objc_getClass("WKUserContentController"), - sel_registerName("new")); - objc_setAssociatedObject(userController, "webview", (id)(w), - OBJC_ASSOCIATION_ASSIGN); - objc_msgSend( - userController, sel_registerName("addScriptMessageHandler:name:"), - scriptMessageHandler, - objc_msgSend((id)objc_getClass("NSString"), - sel_registerName("stringWithUTF8String:"), "invoke")); - - /*** - In order to maintain compatibility with the other 'webviews' we need to - override window.external.invoke to call - webkit.messageHandlers.invoke.postMessage - ***/ - - id windowExternalOverrideScript = objc_msgSend( - (id)objc_getClass("WKUserScript"), sel_registerName("alloc")); - objc_msgSend( - windowExternalOverrideScript, - sel_registerName("initWithSource:injectionTime:forMainFrameOnly:"), - get_nsstring("window.external = this; invoke = function(arg){ " - "webkit.messageHandlers.invoke.postMessage(arg); };"), - WKUserScriptInjectionTimeAtDocumentStart, 0); - - objc_msgSend(userController, sel_registerName("addUserScript:"), - windowExternalOverrideScript); - - id config = objc_msgSend((id)objc_getClass("WKWebViewConfiguration"), - sel_registerName("new")); - id processPool = objc_msgSend(config, sel_registerName("processPool")); - objc_msgSend(processPool, sel_registerName("_setDownloadDelegate:"), - downloadDelegate); - objc_msgSend(config, sel_registerName("setProcessPool:"), processPool); - objc_msgSend(config, sel_registerName("setUserContentController:"), - userController); - objc_msgSend(config, sel_registerName("setPreferences:"), wkPref); - - Class __NSWindowDelegate = objc_allocateClassPair(objc_getClass("NSObject"), - "__NSWindowDelegate", 0); - class_addProtocol(__NSWindowDelegate, objc_getProtocol("NSWindowDelegate")); - class_replaceMethod(__NSWindowDelegate, sel_registerName("windowWillClose:"), - (IMP)webview_window_will_close, "v@:@"); - objc_registerClassPair(__NSWindowDelegate); - - w->priv.windowDelegate = - objc_msgSend((id)__NSWindowDelegate, sel_registerName("new")); - - objc_setAssociatedObject(w->priv.windowDelegate, "webview", (id)(w), - OBJC_ASSOCIATION_ASSIGN); - - id nsTitle = - objc_msgSend((id)objc_getClass("NSString"), - sel_registerName("stringWithUTF8String:"), w->title); - - CGRect r = CGRectMake(0, 0, w->width, w->height); - - unsigned int style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | - NSWindowStyleMaskMiniaturizable; - if (w->resizable) { - style = style | NSWindowStyleMaskResizable; - } - - w->priv.window = - objc_msgSend((id)objc_getClass("NSWindow"), sel_registerName("alloc")); - objc_msgSend(w->priv.window, - sel_registerName("initWithContentRect:styleMask:backing:defer:"), - r, style, NSBackingStoreBuffered, 0); - - objc_msgSend(w->priv.window, sel_registerName("autorelease")); - objc_msgSend(w->priv.window, sel_registerName("setTitle:"), nsTitle); - objc_msgSend(w->priv.window, sel_registerName("setDelegate:"), - w->priv.windowDelegate); - objc_msgSend(w->priv.window, sel_registerName("center")); - - Class __WKUIDelegate = - objc_allocateClassPair(objc_getClass("NSObject"), "__WKUIDelegate", 0); - class_addProtocol(__WKUIDelegate, objc_getProtocol("WKUIDelegate")); - class_addMethod(__WKUIDelegate, - sel_registerName("webView:runOpenPanelWithParameters:" - "initiatedByFrame:completionHandler:"), - (IMP)run_open_panel, "v@:@@@?"); - class_addMethod(__WKUIDelegate, - sel_registerName("webView:runJavaScriptAlertPanelWithMessage:" - "initiatedByFrame:completionHandler:"), - (IMP)run_alert_panel, "v@:@@@?"); - class_addMethod( - __WKUIDelegate, - sel_registerName("webView:runJavaScriptConfirmPanelWithMessage:" - "initiatedByFrame:completionHandler:"), - (IMP)run_confirmation_panel, "v@:@@@?"); - objc_registerClassPair(__WKUIDelegate); - id uiDel = objc_msgSend((id)__WKUIDelegate, sel_registerName("new")); - - Class __WKNavigationDelegate = objc_allocateClassPair( - objc_getClass("NSObject"), "__WKNavigationDelegate", 0); - class_addProtocol(__WKNavigationDelegate, - objc_getProtocol("WKNavigationDelegate")); - class_addMethod( - __WKNavigationDelegate, - sel_registerName( - "webView:decidePolicyForNavigationResponse:decisionHandler:"), - (IMP)make_nav_policy_decision, "v@:@@?"); - objc_registerClassPair(__WKNavigationDelegate); - id navDel = objc_msgSend((id)__WKNavigationDelegate, sel_registerName("new")); - - w->priv.webview = - objc_msgSend((id)objc_getClass("WKWebView"), sel_registerName("alloc")); - objc_msgSend(w->priv.webview, - sel_registerName("initWithFrame:configuration:"), r, config); - objc_msgSend(w->priv.webview, sel_registerName("setUIDelegate:"), uiDel); - objc_msgSend(w->priv.webview, sel_registerName("setNavigationDelegate:"), - navDel); - - id nsURL = objc_msgSend((id)objc_getClass("NSURL"), - sel_registerName("URLWithString:"), - get_nsstring(webview_check_url(w->url))); - - objc_msgSend(w->priv.webview, sel_registerName("loadRequest:"), - objc_msgSend((id)objc_getClass("NSURLRequest"), - sel_registerName("requestWithURL:"), nsURL)); - objc_msgSend(w->priv.webview, sel_registerName("setAutoresizesSubviews:"), 1); - objc_msgSend(w->priv.webview, sel_registerName("setAutoresizingMask:"), - (NSViewWidthSizable | NSViewHeightSizable)); - objc_msgSend(objc_msgSend(w->priv.window, sel_registerName("contentView")), - sel_registerName("addSubview:"), w->priv.webview); - objc_msgSend(w->priv.window, sel_registerName("orderFrontRegardless")); - - objc_msgSend(objc_msgSend((id)objc_getClass("NSApplication"), - sel_registerName("sharedApplication")), - sel_registerName("setActivationPolicy:"), - NSApplicationActivationPolicyRegular); - - objc_msgSend(objc_msgSend((id)objc_getClass("NSApplication"), - sel_registerName("sharedApplication")), - sel_registerName("finishLaunching")); - - objc_msgSend(objc_msgSend((id)objc_getClass("NSApplication"), - sel_registerName("sharedApplication")), - sel_registerName("activateIgnoringOtherApps:"), 1); - - id menubar = - objc_msgSend((id)objc_getClass("NSMenu"), sel_registerName("alloc")); - objc_msgSend(menubar, sel_registerName("initWithTitle:"), get_nsstring("")); - objc_msgSend(menubar, sel_registerName("autorelease")); - - id appName = objc_msgSend(objc_msgSend((id)objc_getClass("NSProcessInfo"), - sel_registerName("processInfo")), - sel_registerName("processName")); - - id appMenuItem = - objc_msgSend((id)objc_getClass("NSMenuItem"), sel_registerName("alloc")); - objc_msgSend(appMenuItem, - sel_registerName("initWithTitle:action:keyEquivalent:"), appName, - NULL, get_nsstring("")); - - id appMenu = - objc_msgSend((id)objc_getClass("NSMenu"), sel_registerName("alloc")); - objc_msgSend(appMenu, sel_registerName("initWithTitle:"), appName); - objc_msgSend(appMenu, sel_registerName("autorelease")); - - objc_msgSend(appMenuItem, sel_registerName("setSubmenu:"), appMenu); - objc_msgSend(menubar, sel_registerName("addItem:"), appMenuItem); - - id title = - objc_msgSend(get_nsstring("Hide "), - sel_registerName("stringByAppendingString:"), appName); - id item = create_menu_item(title, "hide:", "h"); - objc_msgSend(appMenu, sel_registerName("addItem:"), item); - - item = create_menu_item(get_nsstring("Hide Others"), - "hideOtherApplications:", "h"); - objc_msgSend(item, sel_registerName("setKeyEquivalentModifierMask:"), - (NSEventModifierFlagOption | NSEventModifierFlagCommand)); - objc_msgSend(appMenu, sel_registerName("addItem:"), item); - - item = - create_menu_item(get_nsstring("Show All"), "unhideAllApplications:", ""); - objc_msgSend(appMenu, sel_registerName("addItem:"), item); - - objc_msgSend(appMenu, sel_registerName("addItem:"), - objc_msgSend((id)objc_getClass("NSMenuItem"), - sel_registerName("separatorItem"))); - - title = objc_msgSend(get_nsstring("Quit "), - sel_registerName("stringByAppendingString:"), appName); - item = create_menu_item(title, "terminate:", "q"); - objc_msgSend(appMenu, sel_registerName("addItem:"), item); - - objc_msgSend(objc_msgSend((id)objc_getClass("NSApplication"), - sel_registerName("sharedApplication")), - sel_registerName("setMainMenu:"), menubar); - - w->priv.should_exit = 0; - return 0; -} - -WEBVIEW_API int webview_loop(struct webview *w, int blocking) { - id until = (blocking ? objc_msgSend((id)objc_getClass("NSDate"), - sel_registerName("distantFuture")) - : objc_msgSend((id)objc_getClass("NSDate"), - sel_registerName("distantPast"))); - - id event = objc_msgSend( - objc_msgSend((id)objc_getClass("NSApplication"), - sel_registerName("sharedApplication")), - sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"), - ULONG_MAX, until, - objc_msgSend((id)objc_getClass("NSString"), - sel_registerName("stringWithUTF8String:"), - "kCFRunLoopDefaultMode"), - true); - - if (event) { - objc_msgSend(objc_msgSend((id)objc_getClass("NSApplication"), - sel_registerName("sharedApplication")), - sel_registerName("sendEvent:"), event); - } - - return w->priv.should_exit; -} - -WEBVIEW_API int webview_eval(struct webview *w, const char *js) { - objc_msgSend(w->priv.webview, - sel_registerName("evaluateJavaScript:completionHandler:"), - get_nsstring(js), NULL); - - return 0; -} - -WEBVIEW_API void webview_set_title(struct webview *w, const char *title) { - objc_msgSend(w->priv.window, sel_registerName( - /* Start David Lettier Edit */ - "setTitle:" - /* End David Lettier Edit */ - ), - get_nsstring(title)); -} - -WEBVIEW_API void webview_set_fullscreen(struct webview *w, int fullscreen) { - unsigned long windowStyleMask = (unsigned long)objc_msgSend( - w->priv.window, sel_registerName("styleMask")); - int b = (((windowStyleMask & NSWindowStyleMaskFullScreen) == - NSWindowStyleMaskFullScreen) - ? 1 - : 0); - if (b != fullscreen) { - objc_msgSend(w->priv.window, sel_registerName("toggleFullScreen:"), NULL); - } -} - -WEBVIEW_API void webview_set_color(struct webview *w, uint8_t r, uint8_t g, - uint8_t b, uint8_t a) { - - id color = objc_msgSend((id)objc_getClass("NSColor"), - sel_registerName("colorWithRed:green:blue:alpha:"), - (float)r / 255.0, (float)g / 255.0, (float)b / 255.0, - (float)a / 255.0); - - objc_msgSend(w->priv.window, sel_registerName("setBackgroundColor:"), color); - - if (0.5 >= ((r / 255.0 * 299.0) + (g / 255.0 * 587.0) + (b / 255.0 * 114.0)) / - 1000.0) { - objc_msgSend(w->priv.window, sel_registerName("setAppearance:"), - objc_msgSend((id)objc_getClass("NSAppearance"), - sel_registerName("appearanceNamed:"), - get_nsstring("NSAppearanceNameVibrantDark"))); - } else { - objc_msgSend(w->priv.window, sel_registerName("setAppearance:"), - objc_msgSend((id)objc_getClass("NSAppearance"), - sel_registerName("appearanceNamed:"), - get_nsstring("NSAppearanceNameVibrantLight"))); - } - objc_msgSend(w->priv.window, sel_registerName("setOpaque:"), 0); - objc_msgSend(w->priv.window, - sel_registerName("setTitlebarAppearsTransparent:"), 1); -} - -WEBVIEW_API void webview_dialog(struct webview *w, - enum webview_dialog_type dlgtype, int flags, - const char *title, const char *arg, - char *result, size_t resultsz) { - if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN || - dlgtype == WEBVIEW_DIALOG_TYPE_SAVE) { - id panel = (id)objc_getClass("NSSavePanel"); - if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN) { - id openPanel = objc_msgSend((id)objc_getClass("NSOpenPanel"), - sel_registerName("openPanel")); - if (flags & WEBVIEW_DIALOG_FLAG_DIRECTORY) { - objc_msgSend(openPanel, sel_registerName("setCanChooseFiles:"), 0); - objc_msgSend(openPanel, sel_registerName("setCanChooseDirectories:"), - 1); - } else { - objc_msgSend(openPanel, sel_registerName("setCanChooseFiles:"), 1); - objc_msgSend(openPanel, sel_registerName("setCanChooseDirectories:"), - 0); - } - objc_msgSend(openPanel, sel_registerName("setResolvesAliases:"), 0); - objc_msgSend(openPanel, sel_registerName("setAllowsMultipleSelection:"), - 0); - panel = openPanel; - } else { - panel = objc_msgSend((id)objc_getClass("NSSavePanel"), - sel_registerName("savePanel")); - } - - objc_msgSend(panel, sel_registerName("setCanCreateDirectories:"), 1); - objc_msgSend(panel, sel_registerName("setShowsHiddenFiles:"), 1); - objc_msgSend(panel, sel_registerName("setExtensionHidden:"), 0); - objc_msgSend(panel, sel_registerName("setCanSelectHiddenExtension:"), 0); - objc_msgSend(panel, sel_registerName("setTreatsFilePackagesAsDirectories:"), - 1); - objc_msgSend( - panel, sel_registerName("beginSheetModalForWindow:completionHandler:"), - w->priv.window, ^(id result) { - objc_msgSend(objc_msgSend((id)objc_getClass("NSApplication"), - sel_registerName("sharedApplication")), - sel_registerName("stopModalWithCode:"), result); - }); - - if (objc_msgSend(objc_msgSend((id)objc_getClass("NSApplication"), - sel_registerName("sharedApplication")), - sel_registerName("runModalForWindow:"), - panel) == (id)NSModalResponseOK) { - id url = objc_msgSend(panel, sel_registerName("URL")); - id path = objc_msgSend(url, sel_registerName("path")); - const char *filename = - (const char *)objc_msgSend(path, sel_registerName("UTF8String")); - strlcpy(result, filename, resultsz); - } - } else if (dlgtype == WEBVIEW_DIALOG_TYPE_ALERT) { - id a = objc_msgSend((id)objc_getClass("NSAlert"), sel_registerName("new")); - switch (flags & WEBVIEW_DIALOG_FLAG_ALERT_MASK) { - case WEBVIEW_DIALOG_FLAG_INFO: - objc_msgSend(a, sel_registerName("setAlertStyle:"), - NSAlertStyleInformational); - break; - case WEBVIEW_DIALOG_FLAG_WARNING: - printf("Warning\n"); - objc_msgSend(a, sel_registerName("setAlertStyle:"), NSAlertStyleWarning); - break; - case WEBVIEW_DIALOG_FLAG_ERROR: - printf("Error\n"); - objc_msgSend(a, sel_registerName("setAlertStyle:"), NSAlertStyleCritical); - break; - } - objc_msgSend(a, sel_registerName("setShowsHelp:"), 0); - objc_msgSend(a, sel_registerName("setShowsSuppressionButton:"), 0); - objc_msgSend(a, sel_registerName("setMessageText:"), get_nsstring(title)); - objc_msgSend(a, sel_registerName("setInformativeText:"), get_nsstring(arg)); - objc_msgSend(a, sel_registerName("addButtonWithTitle:"), - get_nsstring("OK")); - objc_msgSend(a, sel_registerName("runModal")); - objc_msgSend(a, sel_registerName("release")); - } -} - -static void webview_dispatch_cb(void *arg) { - struct webview_dispatch_arg *context = (struct webview_dispatch_arg *)arg; - (context->fn)(context->w, context->arg); - free(context); -} - -WEBVIEW_API void webview_dispatch(struct webview *w, webview_dispatch_fn fn, - void *arg) { - struct webview_dispatch_arg *context = (struct webview_dispatch_arg *)malloc( - sizeof(struct webview_dispatch_arg)); - context->w = w; - context->arg = arg; - context->fn = fn; - dispatch_async_f(dispatch_get_main_queue(), context, webview_dispatch_cb); -} - -WEBVIEW_API void webview_terminate(struct webview *w) { - w->priv.should_exit = 1; -} - -WEBVIEW_API void webview_exit(struct webview *w) { - id app = objc_msgSend((id)objc_getClass("NSApplication"), - sel_registerName("sharedApplication")); - objc_msgSend(app, sel_registerName("terminate:"), app); -} - -WEBVIEW_API void webview_print_log(const char *s) { printf("%s\n", s); } - -#endif /* WEBVIEW_COCOA */ -#endif /* WEBVIEW_IMPLEMENTATION */ +typedef void *webview_t; + +// Creates a new webview instance. If debug is non-zero - developer tools will +// be enabled (if the platform supports them). Window parameter can be a +// pointer to the native window handle. If it's non-null - then child WebView +// is embedded into the given parent window. Otherwise a new window is created. +// Depending on the platform, a GtkWindow, NSWindow or HWND pointer can be +// passed here. +WEBVIEW_API webview_t webview_create(int debug, void *window); + +// Destroys a webview and closes the native window. +WEBVIEW_API void webview_destroy(webview_t w); + +// Runs the main loop until it's terminated. After this function exits - you +// must destroy the webview. +WEBVIEW_API void webview_run(webview_t w); + +// Stops the main loop. It is safe to call this function from another other +// background thread. +WEBVIEW_API void webview_terminate(webview_t w); + +// Posts a function to be executed on the main thread. You normally do not need +// to call this function, unless you want to tweak the native window. +WEBVIEW_API void +webview_dispatch(webview_t w, void (*fn)(webview_t w, void *arg), void *arg); + +// Returns a native window handle pointer. When using GTK backend the pointer +// is GtkWindow pointer, when using Cocoa backend the pointer is NSWindow +// pointer, when using Win32 backend the pointer is HWND pointer. +WEBVIEW_API void *webview_get_window(webview_t w); + +// Updates the title of the native window. Must be called from the UI thread. +WEBVIEW_API void webview_set_title(webview_t w, const char *title); + +// Window size hints +#define WEBVIEW_HINT_NONE 0 // Width and height are default size +#define WEBVIEW_HINT_MIN 1 // Width and height are minimum bounds +#define WEBVIEW_HINT_MAX 2 // Width and height are maximum bounds +#define WEBVIEW_HINT_FIXED 3 // Window size can not be changed by a user +// Updates native window size. See WEBVIEW_HINT constants. +WEBVIEW_API void webview_set_size(webview_t w, int width, int height, + int hints); + +// Navigates webview to the given URL. URL may be a data URI, i.e. +// "data:text/text,...". It is often ok not to url-encode it +// properly, webview will re-encode it for you. +WEBVIEW_API void webview_navigate(webview_t w, const char *url); + +// Injects JavaScript code at the initialization of the new page. Every time +// the webview will open a the new page - this initialization code will be +// executed. It is guaranteed that code is executed before window.onload. +WEBVIEW_API void webview_init(webview_t w, const char *js); + +// Evaluates arbitrary JavaScript code. Evaluation happens asynchronously, also +// the result of the expression is ignored. Use RPC bindings if you want to +// receive notifications about the results of the evaluation. +WEBVIEW_API void webview_eval(webview_t w, const char *js); + +// Binds a native C callback so that it will appear under the given name as a +// global JavaScript function. Internally it uses webview_init(). Callback +// receives a request string and a user-provided argument pointer. Request +// string is a JSON array of all the arguments passed to the JavaScript +// function. +WEBVIEW_API void webview_bind(webview_t w, const char *name, + void (*fn)(const char *seq, const char *req, + void *arg), + void *arg); + +// Allows to return a value from the native binding. Original request pointer +// must be provided to help internal RPC engine match requests with responses. +// If status is zero - result is expected to be a valid JSON result value. +// If status is not zero - result is an error JSON object. +WEBVIEW_API void webview_return(webview_t w, const char *seq, int status, + const char *result); #ifdef __cplusplus } diff --git a/examples-light/how-do-i-communicate-with-haskell-from-javascript.hs b/examples-light/how-do-i-communicate-with-haskell-from-javascript.hs index db9b7d4..433f3cf 100644 --- a/examples-light/how-do-i-communicate-with-haskell-from-javascript.hs +++ b/examples-light/how-do-i-communicate-with-haskell-from-javascript.hs @@ -36,27 +36,34 @@ main = , WHS.windowParamsResizable = True , WHS.windowParamsDebuggable = True } - -- This is the callback JavaScript can execute. - -- Inside JavaScript, you call "window.external.invoke". - (\ _window stringFromJavaScript -> do - print stringFromJavaScript - print (decode (fromStrict $ encodeUtf8 stringFromJavaScript) :: Maybe JsonMessage) - ) -- This function runs before the loop. - (WHS.WithWindowLoopSetUp (\ _window -> print ("Setting up." :: Data.Text.Text))) + (WHS.WithWindowLoopSetUp setUp) -- This function runs after the loop. - (WHS.WithWindowLoopTearDown (\ _window -> print ("Tearing down." :: Data.Text.Text))) + (WHS.WithWindowLoopTearDown tearDown) + where + callback _window _ dataFromJavaScript () = do + print dataFromJavaScript + print (decode (fromStrict $ encodeUtf8 dataFromJavaScript) :: Maybe JsonMessage) -- This function runs every window loop. - -- Return True to continue the loop or False to exit the loop. - $ \ window -> do + foo _window _ dataFromJavaScript () = do + print dataFromJavaScript let message' = "Hello from JavaScript." :: Text -- runJavaScript' returns either True on success or False on failure. - success <- - WHS.runJavaScript' - window - $ Data.Text.concat - [ "window.external.invoke(JSON.stringify({ message: \"" - , message' - , "\" }));" - ] - return success + WHS.runJavaScript' + _window + $ Data.Text.concat + [ "window.callback(JSON.stringify({ message: \"" + , message' + , "\" }));" + ] + pure () + + setUp _window = do + print ("Setting up." :: Data.Text.Text) + -- This is a callback JavaScript can execute. + -- Inside JavaScript, you call "window.callback". + WHS.bindCallback _window "callback" callback () + WHS.bindCallback _window "foo" foo () + WHS.runJavaScript' _window "setTimeout(function f(){ console.log('called f'); window.foo(''); setTimeout(f, 0); }, 1000)" + pure () + tearDown _window = print ("Tearing down." :: Data.Text.Text) \ No newline at end of file diff --git a/examples-light/how-do-i-inject-some-custom-css-into-the-window.hs b/examples-light/how-do-i-inject-some-custom-css-into-the-window.hs index 2dd8fb7..7a5406c 100644 --- a/examples-light/how-do-i-inject-some-custom-css-into-the-window.hs +++ b/examples-light/how-do-i-inject-some-custom-css-into-the-window.hs @@ -23,24 +23,25 @@ main = , WHS.windowParamsResizable = True , WHS.windowParamsDebuggable = True } - -- This is the callback JavaScript can execute. - (\ _window text -> print text) -- This function runs before the loop. - (WHS.WithWindowLoopSetUp (\ _window -> print ("Setting up." :: Data.Text.Text))) + (WHS.WithWindowLoopSetUp setUp) -- This function runs after the loop. - (WHS.WithWindowLoopTearDown (\ _window -> print ("Tearing down." :: Data.Text.Text))) - -- This function runs every window loop. - -- Return True to continue the loop or False to exit the loop. - $ \ window -> do + (WHS.WithWindowLoopTearDown tearDown) + where + setUp _window = do + print ("Setting up." :: Data.Text.Text) + -- This is a callback JavaScript can execute. + -- Inside JavaScript, you call "window.callback". + WHS.bindCallback _window "callback" (\ _window _ reqData () -> print reqData) () let color = "#0000ff" -- injectCss' returns either True on success or False on failure. -- If you rather not use Clay, you can use injectCss'. success <- - WHS.injectCss' - window + WHS.injectCss' _window $ Data.Text.concat - [ "div { color: " - , color - , "; }" - ] - return success + [ "div { color: " + , color + , "; }" + ] + pure () + tearDown _window = print ("Tearing down." :: Data.Text.Text) \ No newline at end of file diff --git a/examples-light/how-do-i-log-some-debug-information.hs b/examples-light/how-do-i-log-some-debug-information.hs deleted file mode 100644 index ae7ba4a..0000000 --- a/examples-light/how-do-i-log-some-debug-information.hs +++ /dev/null @@ -1,44 +0,0 @@ -{- - webviewhs - (C) 2018 David Lettier - lettier.com --} - -{-# LANGUAGE - OverloadedStrings -#-} - -import Data.Text -import qualified Graphics.UI.Webviewhs as WHS - -main :: IO () -main = - WHS.withWindowLoop - WHS.WindowParams - { WHS.windowParamsTitle = "webviewhs - How do I log some debug information?" - -- This could be a localhost URL to your single-page application (SPA). - , WHS.windowParamsUri = "https://lettier.github.com" - , WHS.windowParamsWidth = 800 - , WHS.windowParamsHeight = 600 - , WHS.windowParamsResizable = True - , WHS.windowParamsDebuggable = True - } - -- This is the callback JavaScript can execute. - (\ _window text -> print text) - -- This function runs before the loop. - (WHS.WithWindowLoopSetUp (\ _window -> print ("Setting up." :: Data.Text.Text))) - -- This function runs after the loop. - (WHS.WithWindowLoopTearDown (\ _window -> print ("Tearing down." :: Data.Text.Text))) - -- This function runs every window loop. - -- Return True to continue the loop or False to exit the loop. - $ \ _window -> do - -- webviewhs provides log'. - let string = "world" :: Text - -- log' takes a simple Text string. - WHS.log' - $ Data.Text.concat - [ "Hello " - , string - , "!" - ] - return True diff --git a/examples-light/webviewhs-examples-light.cabal b/examples-light/webviewhs-examples-light.cabal index d2ff4ab..48232b2 100644 --- a/examples-light/webviewhs-examples-light.cabal +++ b/examples-light/webviewhs-examples-light.cabal @@ -51,19 +51,3 @@ executable how-do-i-inject-some-custom-css-into-the-window , webviewhs , text default-language: Haskell2010 - -executable how-do-i-log-some-debug-information - main-is: how-do-i-log-some-debug-information.hs - hs-source-dirs: - . - ghc-options: - -Wall - -freverse-errors - -threaded - -rtsopts - -with-rtsopts=-N - build-depends: - base >= 4.7 && < 5 - , webviewhs - , text - default-language: Haskell2010 diff --git a/src/Graphics/UI/Webviewhs.hs b/src/Graphics/UI/Webviewhs.hs index 7380161..e14b54b 100644 --- a/src/Graphics/UI/Webviewhs.hs +++ b/src/Graphics/UI/Webviewhs.hs @@ -39,33 +39,27 @@ -} module Graphics.UI.Webviewhs - ( WindowParams(..) - , WindowBackgroundColor(..) - , WindowAlertDialogType(..) + ( RequestResponseType(..) + , WindowParams(..) , Window , WithWindowLoopSetUp(..) , WithWindowLoopTearDown(..) , createWindowAndBlock , createWindow , setWindowTitle - , setWindowFullscreen - , setWindowBackgroundColor , withWindowLoop , iterateWindowLoop , runJavaScript' #ifndef LIGHT , runJavaScript , injectCss - , Graphics.UI.Webviewhs.log #endif , injectCss' - , openWindowAlertDialog - , withWindowOpenDialog - , withWindowSaveDialog , dispatchToMain - , Graphics.UI.Webviewhs.log' , terminateWindowLoop , destroyWindow + , bindCallback + , respondRequest ) where @@ -116,27 +110,13 @@ data CWindowParams = , cWindowParamsDebuggable :: CInt } --- | Specifies the RGBA for the window background color. -data WindowBackgroundColor = - WindowBackgroundColor - { windowBackgroundColorRed :: Word8 - , windowBackgroundColorGreen :: Word8 - , windowBackgroundColorBlue :: Word8 - , windowBackgroundColorAlpha :: Word8 - } - --- | Specifies the window alert dialog type. -data WindowAlertDialogType = - WindowAlertDialogTypeInfo - | WindowAlertDialogTypeWarning - | WindowAlertDialogTypeError +data RequestResponseType + = RequestReject + | RequestResolve newtype WithWindowLoopSetUp a = WithWindowLoopSetUp (Window a -> IO ()) newtype WithWindowLoopTearDown a = WithWindowLoopTearDown (Window a -> IO ()) -windowDialogTypeAlert :: CInt -windowDialogTypeAlert = 2 - foreign import ccall "webview-ffi.h c_create_window_and_block" c_create_window_and_block :: CString @@ -155,7 +135,6 @@ foreign import ccall "webview-ffi.h c_create_window" -> CInt -> CInt -> CInt - -> FunPtr (Window a -> CString -> IO ()) -> IO (Window a) foreign import ccall "webview-ffi.h c_set_window_title" @@ -164,26 +143,10 @@ foreign import ccall "webview-ffi.h c_set_window_title" -> CString -> IO () -foreign import ccall "webview-ffi.h c_set_window_fullscreen" - c_set_window_fullscreen - :: Window a - -> CInt - -> IO () - -foreign import ccall "webview-ffi.h c_set_window_background_color" - c_set_window_background_color - :: Window a - -> CUChar - -> CUChar - -> CUChar - -> CUChar - -> IO () - foreign import ccall "webview-ffi.h c_iterate_window" c_iterate_window :: Window a - -> CInt - -> IO CInt + -> IO () foreign import ccall "webview-ffi.h c_run_javascript" c_run_javascript @@ -197,17 +160,6 @@ foreign import ccall "webview-ffi.h c_inject_css" -> CString -> IO CInt -foreign import ccall "webview-ffi.h c_open_window_dialog" - c_open_window_dialog - :: Window a - -> CInt -- Type - -> CInt -- Flags - -> CString -- Primary Text / Title - -> CString -- Secondary Text - -> CString -- Buffer to store result - -> CUInt -- Result buffer size - -> IO () - foreign import ccall safe "webview-ffi.h c_dispatch_to_main" c_dispatch_to_main :: Window a @@ -215,11 +167,6 @@ foreign import ccall safe "webview-ffi.h c_dispatch_to_main" -> Ptr () -> IO () -foreign import ccall "webview-ffi.h c_log" - c_log - :: CString - -> IO () - foreign import ccall "webview-ffi.h c_terminate_window_loop" c_terminate_window_loop :: Window a @@ -230,6 +177,22 @@ foreign import ccall "webview-ffi.h c_destroy_window" :: Window a -> IO () +foreign import ccall "webview-ffi.h c_bind_callback" + c_bind_callback + :: Window a + -> CString + -> FunPtr (CString -> CString -> Ptr b -> IO ()) + -> Ptr b + -> IO () + +foreign import ccall "webview-ffi.c c_return_response" + c_return_response + :: Window a + -> CString + -> CInt + -> CString + -> IO () + foreign import ccall "wrapper" makeDispatchCallback :: (Window a -> Ptr () -> IO ()) @@ -237,8 +200,8 @@ foreign import ccall "wrapper" foreign import ccall "wrapper" makeCallback - :: (Window a -> CString -> IO ()) - -> IO (FunPtr (Window a -> CString -> IO ())) + :: (CString -> CString -> Ptr b -> IO ()) + -> IO (FunPtr (CString -> CString-> Ptr b -> IO ())) -- | Creates a window and runs the main loop unless the window is destroyed. -- Useful for loading a web page and not having to manage the loop. @@ -266,16 +229,13 @@ createWindowAndBlock free cWindowParamsTitle free cWindowParamsUri --- | Creates a window giving you the chance to changes its properties, run its loop, etc. +-- | Creates a window giving you the chance to changes its properties. -- Returns 'Left' on failure and 'Right' 'Window' on success. createWindow :: WindowParams - -> (Window a -> Text -> IO ()) -- ^ A callback that JavaScript can use to - -- communicate with the Haskell side. -> IO (Either Text (Window a)) createWindow windowParams - callback = do CWindowParams { cWindowParamsTitle @@ -285,10 +245,6 @@ createWindow , cWindowParamsResizable , cWindowParamsDebuggable } <- windowParamsToC windowParams - let callback' window cString = do - string <- peekCString cString - callback window (Data.Text.pack string) - funPtr <- makeCallback callback' result <- c_create_window cWindowParamsTitle @@ -297,18 +253,54 @@ createWindow cWindowParamsHeight cWindowParamsResizable cWindowParamsDebuggable - funPtr if result == nullPtr then do free cWindowParamsTitle free cWindowParamsUri - freeHaskellFunPtr funPtr return $ Left "[WEBVIEWHS:ERROR] Could not create window." else do free cWindowParamsTitle free cWindowParamsUri return $ Right result +-- | Expose a callback as global JavaScript function. +bindCallback + :: Window a + -> Text -- ^ Callback name + -> (Window a -> Text -> Text -> b -> IO ()) + -- ^ A callback that JavaScript can use to + -- communicate with the Haskell side. + -> b -- ^ User defined data + -> IO () +bindCallback + window + callbackName + callback + userData + = do + callbackName' <- newCString $ Data.Text.unpack callbackName + let callback' cSeq cReq _ = do + seq <- peekCString cSeq + req <- peekCString cReq + callback window (Data.Text.pack seq) (Data.Text.pack req) userData + funPtr <- makeCallback callback' + c_bind_callback window callbackName' funPtr nullPtr + +-- | Send a value to answer request from webview +respondRequest + :: Window a + -> Text -- ^ request id + -> RequestResponseType + -> Text -- ^ json encoded result + -> IO () +respondRequest window reqId responseType result = do + reqId' <- newCString $ Data.Text.unpack reqId + status' <- case responseType of + RequestReject -> pure 1 + RequestResolve -> pure 0 + result' <- newCString $ Data.Text.unpack result + c_return_response window reqId' status' result' + -- | Changes the window title. setWindowTitle :: Window a @@ -322,59 +314,14 @@ setWindowTitle c_set_window_title window newTitle' free newTitle' --- | Sets the window's fullscreen state. --- Pass 'True' to put the window into fullscreen mode. --- Pass 'False' to take the window out of fullscreen mode. -setWindowFullscreen - :: Window a - -> Bool - -> IO () -setWindowFullscreen - window - fullscreen - = do - let fullscreen' = if fullscreen then 1 else 0 - c_set_window_fullscreen window fullscreen' - --- | If the loaded web page does not specify a background color, --- this sets the window's background color. -setWindowBackgroundColor - :: Window a - -> WindowBackgroundColor - -> IO () -setWindowBackgroundColor - window - WindowBackgroundColor - { windowBackgroundColorRed - , windowBackgroundColorGreen - , windowBackgroundColorBlue - , windowBackgroundColorAlpha - } - = do - let red' = fromIntegral windowBackgroundColorRed :: CUChar - let green' = fromIntegral windowBackgroundColorGreen :: CUChar - let blue' = fromIntegral windowBackgroundColorBlue :: CUChar - let alpha' = fromIntegral windowBackgroundColorAlpha :: CUChar - c_set_window_background_color window red' green' blue' alpha' - --- | Iterates the window loop. --- If 'True', runs the window loop continuously—blocking until the window exits. --- If 'False', runs one iteration of the window loop --- and releases control back to the caller. +-- | Runs the window loop continuously — blocking until the window exits. iterateWindowLoop :: Window a - -> Bool -- ^ Pass 'True' to iterate until the window exits. - -- Pass 'False' to run one iteration. - -> IO Bool + -> IO () iterateWindowLoop window - block = do - result <- - c_iterate_window - window - (if block then 1 else 0) - return (result == 0) + c_iterate_window window -- | Runs the given JavaScript inside the window. -- The given JavaScript is not checked for validity. @@ -407,109 +354,6 @@ injectCss' free css' return (result /= -1) --- | Opens a window alert dialog. -openWindowAlertDialog - :: Window a - -> WindowAlertDialogType - -> Text -- ^ This is the primary message. - -> Text -- ^ This is the secondary message. - -> IO () -openWindowAlertDialog - window - windowAlertDialogType - primaryMessage - secondaryMessage - = do - primaryMessage' <- newCString $ Data.Text.unpack primaryMessage - secondaryMessage' <- newCString $ Data.Text.unpack secondaryMessage - result <- newCString "" - c_open_window_dialog - window - windowDialogTypeAlert - (dialogType windowAlertDialogType) - primaryMessage' - secondaryMessage' - result - 0 - free primaryMessage' - free secondaryMessage' - free result - where - dialogType :: WindowAlertDialogType -> CInt - dialogType WindowAlertDialogTypeInfo = 2 - dialogType WindowAlertDialogTypeWarning = 4 - dialogType WindowAlertDialogTypeError = 6 - --- | Opens a native file chooser dialog. --- Accepts a callback that receives the selection. -withWindowOpenDialog - :: Window a - -> Text -- ^ The open dialog window title. - -> Bool -- ^ Pass 'True' to disable selecting files. - -- Pass 'False' to allow selecting both files and directories. - -> (Text -> IO ()) -- ^ A callback that accepts the result of the dialog. - -> IO () -withWindowOpenDialog - window - title - = - withWindowFileDialog - window - title - True - --- | Opens a native file saving dialog. --- Accepts a callback that receives the selection. --- Does not actually save the file. --- Save the file inside the provided callback. -withWindowSaveDialog - :: Window a - -> Text -- ^ The save dialog window title. - -> (Text -> IO ()) -- ^ A callback that accepts the result of the dialog. - -> IO () -withWindowSaveDialog - window - title - = - withWindowFileDialog - window - title - False - False - -withWindowFileDialog - :: Window a - -> Text - -> Bool - -> Bool - -> (Text -> IO ()) - -> IO () -withWindowFileDialog - window - title - open - disableOpeningFiles - callback - = do - title' <- newCString $ Data.Text.unpack title - message' <- newCString "" - let bufferSize = 1024 - let bufferSize' = fromIntegral bufferSize :: CUInt - result <- callocBytes bufferSize - c_open_window_dialog - window - (if open then 0 else 1) - (if disableOpeningFiles then 1 else 0) - title' - message' - result - bufferSize' - free title' - free message' - result' <- peekCString result - callback $ Data.Text.pack result' - free result - -- | Runs the given function in the main window UI thread. -- Use this function whenever you wish to interact with the -- window but you're not running in the main window UI thread. @@ -532,19 +376,6 @@ dispatchToMain freeHaskellFunPtr funPtr return () --- | Logs the given input to stderr, macOS console, --- or Windows DebugView depending on the build platform. -log' - :: Text - -> IO () -log' - entry - = do - entry' <- newCString $ Data.Text.unpack entry - c_log entry' - free entry' - return () - -- | Terminates the window's loop. terminateWindowLoop :: Window a @@ -561,9 +392,6 @@ destroyWindow = c_destroy_window -- It accepts a JavaScript callback, setup, teardown, and iteration function. withWindowLoop :: WindowParams - -> (Window a -> Text -> IO ()) -- ^ A callback function that can be invoked from the JavaScript side. - -- The callback must accept a 'Window' and the JavaScript sent 'Text'. - -- The JavaScript sent 'Text' could be unstructured or structured like JSON. -> WithWindowLoopSetUp a -- ^ A function that is called before iterating. -- It must accept a 'Window' and return 'IO' (). -- Use it to set up before entering the main loop. @@ -576,32 +404,21 @@ withWindowLoop -- don't have a teardown function. -- Note, do not terminate the window loop and/or -- destroy the window as this is done for you. - -> (Window a -> IO Bool) -- ^ A function that is called each iteration. - -- Return 'True' to continue or 'False' to stop iterating. -> IO () withWindowLoop windowParams - callback (WithWindowLoopSetUp setUp) (WithWindowLoopTearDown tearDown) - iteration = do - eitherWindow <- createWindow windowParams callback + eitherWindow <- createWindow windowParams case eitherWindow of Left e -> putStrLn $ Data.Text.unpack e Right window -> do setUp window - loop window iteration + iterateWindowLoop window tearDown window terminateWindowLoop window destroyWindow window - where - loop :: Window a -> (Window a -> IO Bool) -> IO () - loop window iteration' = do - continue <- iteration' window - shouldContinue <- iterateWindowLoop window False - when (continue && shouldContinue) $ - loop window iteration' #ifndef LIGHT -- | Runs the given JavaScript inside the window. @@ -634,22 +451,6 @@ injectCss window $ DTL.toStrict $ Clay.render css - --- | Logs the given formatted input to stderr, macOS console, --- or Windows DebugView depending on the build platform. --- Uses [Data.Text.Format.Heavy](https://hackage.haskell.org/package/text-format-heavy). --- Note, this function is not available when using the `light` cabal build flag. -log - :: VarContainer vars - => Format - -> vars - -> IO () -log - fmt - vars - = do - let entry = format fmt vars - log' $ DTL.toStrict entry #endif windowParamsToC diff --git a/src/c/webview-ffi.c b/src/c/webview-ffi.c index bf68620..602d820 100644 --- a/src/c/webview-ffi.c +++ b/src/c/webview-ffi.c @@ -9,6 +9,39 @@ #include "webview.h" #include "webview-ffi.h" +#include "stdio.h" +#include "stdlib.h" +#include "string.h" + +#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; +} + void c_create_window_and_block( const char* title, const char* uri, @@ -17,138 +50,118 @@ void c_create_window_and_block( int resizable, int debuggable ) { - webview(title, uri, width, height, resizable, debuggable); + webview_t w = c_create_window(title, uri, width, height, resizable, debuggable); + c_iterate_window(w); + c_destroy_window(w); } -struct webview* c_create_window( +webview_t c_create_window( const char* title, const char* uri, int width, int height, int resizable, - int debuggable, - void (*webview_callback_fn)(struct webview* w, const char* arg) + int debuggable ) { - struct webview* w; - w = malloc(sizeof(struct webview)); + webview_t w; + w = malloc(sizeof(webview_t)); if (NULL == w) { printf("[WEBVIEWHS:ERROR] Could not create a window!\n"); return NULL; } - w->title = title; - w->url = uri; - w->width = width; - w->height = height; - w->debug = debuggable; - w->resizable = resizable; - w->external_invoke_cb = webview_callback_fn; - webview_init(w); + w = webview_create(debuggable, NULL); + c_set_window_title(w, title); + webview_set_size(w, width, height, + resizable ? WEBVIEW_HINT_NONE : WEBVIEW_HINT_FIXED); + webview_navigate(w, uri); return w; } -void c_set_window_title( - struct webview* w, - const char* newTitle +void c_bind_callback( + webview_t w, + const char *name, + void (*webview_callback_fn)(const char *seq, const char *req, void *arg), + void *arg ) { if (NULL == w) { - printf("[WEBVIEWHS:ERROR] Could not set the window title!\n"); + printf("[WEBVIEWHS:ERROR] Could not bind the callback!\n"); return; } - webview_set_title(w, newTitle); + webview_bind(w, name, webview_callback_fn, arg); } -void c_set_window_fullscreen( - struct webview* w, - int fullscreen +void c_return_response( + webview_t w, + const char *seq, + int status, + const char *result ) { - if (NULL == w) { - printf("[WEBVIEWHS:ERROR] Could not set the window fullscreen!\n"); + if (NULL == w) { + printf("[WEBVIEWHS:ERROR] Could not return the response!\n"); return; } - webview_set_fullscreen(w, fullscreen); + webview_return(w, seq, status, result); } -void c_set_window_background_color( - struct webview* w, - uint8_t red, - uint8_t green, - uint8_t blue, - uint8_t alpha +void c_set_window_title( + webview_t w, + const char* newTitle ) { if (NULL == w) { - printf("[WEBVIEWHS:ERROR] Could not set the window background color!\n"); + printf("[WEBVIEWHS:ERROR] Could not set the window title!\n"); return; } - webview_set_color(w, red, green, blue, alpha); + webview_set_title(w, newTitle); } -int c_iterate_window( - struct webview* w, - int block +void c_iterate_window( + webview_t w ) { if (NULL == w) { printf("[WEBVIEWHS:ERROR] Could not iterate the window!\n"); - return -1; - } - if (1 == block) { - int should_exit = 0; - do { - should_exit = webview_loop(w, block); - } while (0 == should_exit); - return should_exit; + return; } - return webview_loop(w, block); + webview_run(w); } int c_run_javascript( - struct webview* w, + webview_t w, char* javascript ) { if (NULL == w) { printf("[WEBVIEWHS:ERROR] Could not run JavaScript!\n"); return -1; } - return webview_eval(w, javascript); + webview_eval(w, javascript); + return 1; } int c_inject_css( - struct webview* w, + webview_t w, const char* css ) { if (NULL == w) { printf("[WEBVIEWHS:ERROR] Could not inject CSS!\n"); return -1; } - return webview_inject_css(w, css); -} - -void c_open_window_dialog( - struct webview* w, - enum webview_dialog_type dlgtype, - int flags, - const char* primary_text, - const char* secondary_text, - char* result, - size_t result_buffer_size -) { - if (NULL == w) { - printf("[WEBVIEWHS:ERROR] Could not open window dialog!\n"); - return; + int n = webview_js_encode(css, NULL, 0); + char *esc = (char *)calloc(1, sizeof(CSS_INJECT_FUNCTION) + n + 4); + if (esc == NULL) { + return -1; } - webview_dialog( - w, - dlgtype, - flags, - primary_text, - secondary_text, - result, - result_buffer_size - ); + 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); + webview_eval(w, esc); + free(js); + free(esc); + return 1; } void c_dispatch_to_main( - struct webview* w, - void (*webview_dispatch_fn)(struct webview *ww, void *arg), + webview_t w, + void (*webview_dispatch_fn)(webview_t w, void *arg), void* arg ) { if (NULL == w) { @@ -158,14 +171,8 @@ void c_dispatch_to_main( webview_dispatch(w, webview_dispatch_fn, arg); } -void c_log( - char* message -) { - webview_debug(message); -} - void c_terminate_window_loop( - struct webview* w + webview_t w ) { if (NULL == w) { printf("[WEBVIEWHS:ERROR] Could not terminate the window loop!\n"); @@ -175,13 +182,12 @@ void c_terminate_window_loop( } void c_destroy_window( - struct webview* w + webview_t w ) { if (NULL == w) { printf("[WEBVIEWHS:ERROR] Could not destroy the window!\n"); return; } - webview_exit(w); - free(w); + webview_destroy(w); w = NULL; } diff --git a/src/c/webview-ffi.h b/src/c/webview-ffi.h index 595755e..3db8fed 100644 --- a/src/c/webview-ffi.h +++ b/src/c/webview-ffi.h @@ -19,28 +19,41 @@ void c_create_window_and_block( int debuggable ); -struct webview* c_create_window( +webview_t c_create_window( const char* title, const char* uri, int width, int height, int resizable, - int debuggable, - void (*webview_callback_fn)(struct webview* w, const char* arg) + int debuggable +); + +void c_bind_callback( + webview_t w, + const char *name, + void (*webview_callback_fn)(const char *seq, const char *req, void *arg), + void *arg +); + +void c_return_response( + webview_t w, + const char *seq, + int status, + const char *result ); void c_set_window_title( - struct webview* w, + webview_t w, const char* newTitle ); void c_set_window_fullscreen( - struct webview* w, + webview_t w, int fullscreen ); void c_set_window_background_color( - struct webview* w, + webview_t w, uint8_t red, uint8_t blue, uint8_t green, @@ -48,46 +61,31 @@ void c_set_window_background_color( ); int c_run_javascript( - struct webview* w, + webview_t w, char* javascript ); int c_inject_css( - struct webview* w, + webview_t w, const char* css ); -void c_open_dialog( - struct webview* w, - enum webview_dialog_type dlgtype, - int flags, - const char* primary_text, - const char* secondary_text, - char* result, - size_t resultsz -); - void c_dispatch_to_main( - struct webview* w, - void (*webview_dispatch_fn)(struct webview* w, void* arg), + webview_t w, + void (*webview_dispatch_fn)(webview_t w, void* arg), void* arg ); -void c_log( - char* message -); - -int c_iterate_window( - struct webview* w, - int block +void c_iterate_window( + webview_t w ); void c_terminate_window_loop( - struct webview* w + webview_t w ); void c_destroy_window( - struct webview* w + webview_t w ); #endif diff --git a/webviewhs.cabal b/webviewhs.cabal index 6503d52..611461f 100644 --- a/webviewhs.cabal +++ b/webviewhs.cabal @@ -1,3 +1,4 @@ +cabal-version: 2.2 name: webviewhs version: 0.1.0.0 synopsis: Create native dialogs and windows that run web pages. @@ -11,13 +12,13 @@ bug-reports: https://github.com/lettier/webviewhs/issues author: David Lettier maintainer: David Lettier copyright: (C) 2018 David Lettier -license: BSD3 +license: BSD-3-Clause license-file: LICENSE build-type: Simple -cabal-version: >= 1.10 extra-source-files: ./CHANGELOG.md + , ./deps/webview/webview.cpp , ./deps/webview/webview.h , ./deps/webview/LICENSE , ./src/c/webview-ffi.h @@ -42,6 +43,8 @@ library , src/c/webview-ffi.h c-sources: src/c/webview-ffi.c + cxx-sources: + deps/webview/webview.cpp build-depends: base >= 4.7 && < 5 , text @@ -75,3 +78,4 @@ library if flag(light) cpp-options: -DLIGHT + extra-libraries: stdc++