diff --git a/go.mod b/go.mod index a78d23f41..01e6b0c51 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,6 @@ require ( github.com/pkg/errors v0.8.1 // indirect github.com/sirupsen/logrus v1.4.1 github.com/stretchr/testify v1.3.0 // indirect - github.com/wailsapp/webview v0.2.7 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 // indirect golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5 // indirect golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 diff --git a/go.sum b/go.sum index 370cbd8ce..f3d52753e 100644 --- a/go.sum +++ b/go.sum @@ -68,9 +68,6 @@ github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/wailsapp/webview v0.2.7 h1:fN5L5H9Oivg9IJPk7uaXQnjqB68Fny11ZWkIaTIZHmk= -github.com/wailsapp/webview v0.2.7/go.mod h1:XO9HJbKWokDxUYTWQEBCYg95n/To1v7PxvanDNVf8hY= -github.com/zserge/webview v0.0.0-20190123072648-16c93bcaeaeb/go.mod h1:a1CV8KR4Dd1eP2g+mEijGOp+HKczwdKHWyx0aPHKvo4= golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk= diff --git a/lib/renderer/webview.go b/lib/renderer/webview.go index f921e8ad5..71bed315d 100644 --- a/lib/renderer/webview.go +++ b/lib/renderer/webview.go @@ -13,7 +13,7 @@ import ( "github.com/wailsapp/wails/lib/interfaces" "github.com/wailsapp/wails/lib/logger" "github.com/wailsapp/wails/lib/messages" - "github.com/wailsapp/webview" + "github.com/wailsapp/wails/lib/renderer/webview" ) // WebView defines the main webview application window diff --git a/lib/renderer/webview/LICENSE b/lib/renderer/webview/LICENSE new file mode 100644 index 000000000..b18604bf4 --- /dev/null +++ b/lib/renderer/webview/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Serge Zaitsev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/renderer/webview/webview.go b/lib/renderer/webview/webview.go new file mode 100755 index 000000000..2ae4ef422 --- /dev/null +++ b/lib/renderer/webview/webview.go @@ -0,0 +1,372 @@ +// Package wails implements Go bindings to https://github.com/zserge/webview C library. +// It is a modified version of webview.go from that repository + +// Bindings closely repeat the C APIs and include both, a simplified +// single-function API to just open a full-screen webview window, and a more +// advanced and featureful set of APIs, including Go-to-JavaScript bindings. +// +// The library uses gtk-webkit, Cocoa/Webkit and MSHTML (IE8..11) as a browser +// engine and supports Linux, MacOS and Windows 7..10 respectively. +// +package webview + +/* +#cgo linux openbsd freebsd CFLAGS: -DWEBVIEW_GTK=1 +#cgo linux openbsd freebsd pkg-config: gtk+-3.0 webkit2gtk-4.0 + +#cgo windows CFLAGS: -DWEBVIEW_WINAPI=1 +#cgo windows LDFLAGS: -lole32 -lcomctl32 -loleaut32 -luuid -lgdi32 + +#cgo darwin CFLAGS: -DWEBVIEW_COCOA=1 -x objective-c +#cgo darwin LDFLAGS: -framework Cocoa -framework WebKit + +#include +#include +#define WEBVIEW_STATIC +#define WEBVIEW_IMPLEMENTATION +#include "webview.h" + +extern void _webviewExternalInvokeCallback(void *, void *); + +static inline void CgoWebViewFree(void *w) { + free((void *)((struct webview *)w)->title); + free((void *)((struct webview *)w)->url); + free(w); +} + +static inline void *CgoWebViewCreate(int width, int height, char *title, char *url, int resizable, int debug) { + struct webview *w = (struct webview *) calloc(1, sizeof(*w)); + w->width = width; + w->height = height; + w->title = title; + w->url = url; + w->resizable = resizable; + w->debug = debug; + w->external_invoke_cb = (webview_external_invoke_cb_t) _webviewExternalInvokeCallback; + if (webview_init(w) != 0) { + CgoWebViewFree(w); + return NULL; + } + return (void *)w; +} + +static inline int CgoWebViewLoop(void *w, int blocking) { + return webview_loop((struct webview *)w, blocking); +} + +static inline void CgoWebViewTerminate(void *w) { + webview_terminate((struct webview *)w); +} + +static inline void CgoWebViewExit(void *w) { + webview_exit((struct webview *)w); +} + +static inline void CgoWebViewSetTitle(void *w, char *title) { + webview_set_title((struct webview *)w, title); +} + +static inline void CgoWebViewSetFullscreen(void *w, int fullscreen) { + webview_set_fullscreen((struct webview *)w, fullscreen); +} + +static inline void CgoWebViewSetColor(void *w, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + webview_set_color((struct webview *)w, r, g, b, a); +} + +static inline void CgoDialog(void *w, int dlgtype, int flags, + char *title, char *arg, char *res, size_t ressz) { + webview_dialog(w, dlgtype, flags, + (const char*)title, (const char*) arg, res, ressz); +} + +static inline int CgoWebViewEval(void *w, char *js) { + return webview_eval((struct webview *)w, js); +} + +static inline void CgoWebViewInjectCSS(void *w, char *css) { + webview_inject_css((struct webview *)w, css); +} + +extern void _webviewDispatchGoCallback(void *); +static inline void _webview_dispatch_cb(struct webview *w, void *arg) { + _webviewDispatchGoCallback(arg); +} +static inline void CgoWebViewDispatch(void *w, uintptr_t arg) { + webview_dispatch((struct webview *)w, _webview_dispatch_cb, (void *)arg); +} +*/ +import "C" +import ( + "errors" + "runtime" + "sync" + "unsafe" +) + +func init() { + // Ensure that main.main is called from the main thread + runtime.LockOSThread() +} + +// Open is a simplified API to open a single native window with a full-size webview in +// it. It can be helpful if you want to communicate with the core app using XHR +// or WebSockets (as opposed to using JavaScript bindings). +// +// Window appearance can be customized using title, width, height and resizable parameters. +// URL must be provided and can user either a http or https protocol, or be a +// local file:// URL. On some platforms "data:" URLs are also supported +// (Linux/MacOS). +func Open(title, url string, w, h int, resizable bool) error { + titleStr := C.CString(title) + defer C.free(unsafe.Pointer(titleStr)) + urlStr := C.CString(url) + defer C.free(unsafe.Pointer(urlStr)) + resize := C.int(0) + if resizable { + resize = C.int(1) + } + + r := C.webview(titleStr, urlStr, C.int(w), C.int(h), resize) + if r != 0 { + return errors.New("failed to create webview") + } + return nil +} + +// ExternalInvokeCallbackFunc is a function type that is called every time +// "window.external.invoke()" is called from JavaScript. Data is the only +// obligatory string parameter passed into the "invoke(data)" function from +// JavaScript. To pass more complex data serialized JSON or base64 encoded +// string can be used. +type ExternalInvokeCallbackFunc func(w WebView, data string) + +// Settings is a set of parameters to customize the initial WebView appearance +// and behavior. It is passed into the webview.New() constructor. +type Settings struct { + // WebView main window title + Title string + // URL to open in a webview + URL string + // Window width in pixels + Width int + // Window height in pixels + Height int + // Allows/disallows window resizing + Resizable bool + // Enable debugging tools (Linux/BSD/MacOS, on Windows use Firebug) + Debug bool + // A callback that is executed when JavaScript calls "window.external.invoke()" + ExternalInvokeCallback ExternalInvokeCallbackFunc +} + +// WebView is an interface that wraps the basic methods for controlling the UI +// loop, handling multithreading and providing JavaScript bindings. +type WebView interface { + // Run() starts the main UI loop until the user closes the webview window or + // Terminate() is called. + Run() + // Loop() runs a single iteration of the main UI. + Loop(blocking bool) bool + // SetTitle() changes window title. This method must be called from the main + // thread only. See Dispatch() for more details. + SetTitle(title string) + // SetFullscreen() controls window full-screen mode. This method must be + // called from the main thread only. See Dispatch() for more details. + SetFullscreen(fullscreen bool) + // SetColor() changes window background color. This method must be called from + // the main thread only. See Dispatch() for more details. + SetColor(r, g, b, a uint8) + // Eval() evaluates an arbitrary JS code inside the webview. This method must + // be called from the main thread only. See Dispatch() for more details. + Eval(js string) error + // InjectJS() injects an arbitrary block of CSS code using the JS API. This + // method must be called from the main thread only. See Dispatch() for more + // details. + InjectCSS(css string) + // Dialog() opens a system dialog of the given type and title. String + // argument can be provided for certain dialogs, such as alert boxes. For + // alert boxes argument is a message inside the dialog box. + Dialog(dlgType DialogType, flags int, title string, arg string) string + // Terminate() breaks the main UI loop. This method must be called from the main thread + // only. See Dispatch() for more details. + Terminate() + // Dispatch() schedules some arbitrary function to be executed on the main UI + // thread. This may be helpful if you want to run some JavaScript from + // background threads/goroutines, or to terminate the app. + Dispatch(func()) + // Exit() closes the window and cleans up the resources. Use Terminate() to + // forcefully break out of the main UI loop. + Exit() +} + +// DialogType is an enumeration of all supported system dialog types +type DialogType int + +const ( + // DialogTypeOpen is a system file open dialog + DialogTypeOpen DialogType = iota + // DialogTypeSave is a system file save dialog + DialogTypeSave + // DialogTypeAlert is a system alert dialog (message box) + DialogTypeAlert +) + +const ( + // DialogFlagFile is a normal file picker dialog + DialogFlagFile = C.WEBVIEW_DIALOG_FLAG_FILE + // DialogFlagDirectory is an open directory dialog + DialogFlagDirectory = C.WEBVIEW_DIALOG_FLAG_DIRECTORY + // DialogFlagInfo is an info alert dialog + DialogFlagInfo = C.WEBVIEW_DIALOG_FLAG_INFO + // DialogFlagWarning is a warning alert dialog + DialogFlagWarning = C.WEBVIEW_DIALOG_FLAG_WARNING + // DialogFlagError is an error dialog + DialogFlagError = C.WEBVIEW_DIALOG_FLAG_ERROR +) + +var ( + m sync.Mutex + index uintptr + fns = map[uintptr]func(){} + cbs = map[WebView]ExternalInvokeCallbackFunc{} +) + +type webview struct { + w unsafe.Pointer +} + +var _ WebView = &webview{} + +func boolToInt(b bool) int { + if b { + return 1 + } + return 0 +} + +// NewWebview creates and opens a new webview window using the given settings. The +// returned object implements the WebView interface. This function returns nil +// if a window can not be created. +func NewWebview(settings Settings) WebView { + if settings.Width == 0 { + settings.Width = 640 + } + if settings.Height == 0 { + settings.Height = 480 + } + if settings.Title == "" { + settings.Title = "WebView" + } + w := &webview{} + w.w = C.CgoWebViewCreate(C.int(settings.Width), C.int(settings.Height), + C.CString(settings.Title), C.CString(settings.URL), + C.int(boolToInt(settings.Resizable)), C.int(boolToInt(settings.Debug))) + m.Lock() + if settings.ExternalInvokeCallback != nil { + cbs[w] = settings.ExternalInvokeCallback + } else { + cbs[w] = func(w WebView, data string) {} + } + m.Unlock() + return w +} + +func (w *webview) Loop(blocking bool) bool { + block := C.int(0) + if blocking { + block = 1 + } + return C.CgoWebViewLoop(w.w, block) == 0 +} + +func (w *webview) Run() { + for w.Loop(true) { + } +} + +func (w *webview) Exit() { + C.CgoWebViewExit(w.w) +} + +func (w *webview) Dispatch(f func()) { + m.Lock() + for ; fns[index] != nil; index++ { + } + fns[index] = f + m.Unlock() + C.CgoWebViewDispatch(w.w, C.uintptr_t(index)) +} + +func (w *webview) SetTitle(title string) { + p := C.CString(title) + defer C.free(unsafe.Pointer(p)) + C.CgoWebViewSetTitle(w.w, p) +} + +func (w *webview) SetColor(r, g, b, a uint8) { + C.CgoWebViewSetColor(w.w, C.uint8_t(r), C.uint8_t(g), C.uint8_t(b), C.uint8_t(a)) +} + +func (w *webview) SetFullscreen(fullscreen bool) { + C.CgoWebViewSetFullscreen(w.w, C.int(boolToInt(fullscreen))) +} + +func (w *webview) Dialog(dlgType DialogType, flags int, title string, arg string) string { + const maxPath = 4096 + titlePtr := C.CString(title) + defer C.free(unsafe.Pointer(titlePtr)) + argPtr := C.CString(arg) + defer C.free(unsafe.Pointer(argPtr)) + resultPtr := (*C.char)(C.calloc((C.size_t)(unsafe.Sizeof((*C.char)(nil))), (C.size_t)(maxPath))) + defer C.free(unsafe.Pointer(resultPtr)) + C.CgoDialog(w.w, C.int(dlgType), C.int(flags), titlePtr, + argPtr, resultPtr, C.size_t(maxPath)) + return C.GoString(resultPtr) +} + +func (w *webview) Eval(js string) error { + p := C.CString(js) + defer C.free(unsafe.Pointer(p)) + switch C.CgoWebViewEval(w.w, p) { + case -1: + return errors.New("evaluation failed") + } + return nil +} + +func (w *webview) InjectCSS(css string) { + p := C.CString(css) + defer C.free(unsafe.Pointer(p)) + C.CgoWebViewInjectCSS(w.w, p) +} + +func (w *webview) Terminate() { + C.CgoWebViewTerminate(w.w) +} + +//export _webviewDispatchGoCallback +func _webviewDispatchGoCallback(index unsafe.Pointer) { + var f func() + m.Lock() + f = fns[uintptr(index)] + delete(fns, uintptr(index)) + m.Unlock() + f() +} + +//export _webviewExternalInvokeCallback +func _webviewExternalInvokeCallback(w unsafe.Pointer, data unsafe.Pointer) { + m.Lock() + var ( + cb ExternalInvokeCallbackFunc + wv WebView + ) + for wv, cb = range cbs { + if wv.(*webview).w == w { + break + } + } + m.Unlock() + cb(wv, C.GoString((*C.char)(data))) +} diff --git a/lib/renderer/webview/webview.h b/lib/renderer/webview/webview.h new file mode 100644 index 000000000..dee1cb353 --- /dev/null +++ b/lib/renderer/webview/webview.h @@ -0,0 +1,2244 @@ +/* + * MIT License + * + * Copyright (c) 2017 Serge Zaitsev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef WEBVIEW_H +#define WEBVIEW_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef WEBVIEW_STATIC +#define WEBVIEW_API static +#else +#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) +#import +#import +#import + +struct webview_priv +{ + NSAutoreleasePool *pool; + NSWindow *window; + WebView *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 transparentTitlebar; + 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=%22IE=edge%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); + + 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) + { + struct webview webview; + memset(&webview, 0, sizeof(webview)); + webview.title = title; + webview.url = url; + webview.width = width; + webview.height = height; + webview.resizable = resizable; + 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); + + if (w->debug) + { + WebKitSettings *settings = + webkit_web_view_get_settings(WEBKIT_WEB_VIEW(w->priv.webview)); + webkit_settings_set_enable_write_console_messages_to_stdout(settings, true); + webkit_settings_set_enable_developer_extras(settings, true); + } + else + { + g_signal_connect(G_OBJECT(w->priv.webview), "context-menu", + G_CALLBACK(webview_context_menu_cb), w); + } + + gtk_widget_show_all(w->priv.window); + + webkit_web_view_run_javascript( + WEBKIT_WEB_VIEW(w->priv.webview), + "window.external={invoke:function(x){" + "window.webkit.messageHandlers.external.postMessage(x);}}", + NULL, NULL, NULL); + + g_signal_connect(G_OBJECT(w->priv.window), "destroy", + G_CALLBACK(webview_destroy_cb), w); + return 0; + } + + WEBVIEW_API int webview_loop(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) + { + 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 + { + IOleClientSite client; + _IOleInPlaceSiteEx inplace; + _IDocHostUIHandlerEx ui; + IDispatch external; + } _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 + { + *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 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; + + 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); +#endif + } + } + + WEBVIEW_API void webview_terminate(struct webview *w) { PostQuitMessage(0); } + WEBVIEW_API void webview_exit(struct webview *w) { OleUninitialize(); } + WEBVIEW_API void webview_print_log(const char *s) { OutputDebugString(s); } + +#endif /* WEBVIEW_WINAPI */ + +#if defined(WEBVIEW_COCOA) +#if (!defined MAC_OS_X_VERSION_10_12) || \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12 +#define NSAlertStyleWarning NSWarningAlertStyle +#define NSAlertStyleCritical NSCriticalAlertStyle +#define NSWindowStyleMaskResizable NSResizableWindowMask +#define NSWindowStyleMaskMiniaturizable NSMiniaturizableWindowMask +#define NSWindowStyleMaskTitled NSTitledWindowMask +#define NSWindowStyleMaskClosable NSClosableWindowMask +#define NSWindowStyleMaskFullScreen NSFullScreenWindowMask +#define NSEventMaskAny NSAnyEventMask +#define NSEventModifierFlagCommand NSCommandKeyMask +#define NSEventModifierFlagOption NSAlternateKeyMask +#define NSAlertStyleInformational NSInformationalAlertStyle +#endif /* MAC_OS_X_VERSION_10_12 */ +#if (!defined MAC_OS_X_VERSION_10_13) || \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13 +#define NSModalResponseOK NSFileHandlingPanelOKButton +#endif /* MAC_OS_X_VERSION_10_12, MAC_OS_X_VERSION_10_13 */ + 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 BOOL webview_is_selector_excluded_from_web_script(id self, SEL cmd, + SEL selector) + { + return selector != @selector(invoke:); + } + + static NSString *webview_webscript_name_for_selector(id self, SEL cmd, + SEL selector) + { + return selector == @selector(invoke:) ? @"invoke" : nil; + } + + static void webview_did_clear_window_object(id self, SEL cmd, id webview, + id script, id frame) + { + [script setValue:self forKey:@"external"]; + } + + static void webview_external_invoke(id self, SEL cmd, id arg) + { + struct webview *w = + (struct webview *)objc_getAssociatedObject(self, "webview"); + if (w == NULL || w->external_invoke_cb == NULL) + { + return; + } + if ([arg isKindOfClass:[NSString class]] == NO) + { + return; + } + w->external_invoke_cb(w, [(NSString *)(arg)UTF8String]); + } + + WEBVIEW_API int webview_init(struct webview *w) + { + w->priv.pool = [[NSAutoreleasePool alloc] init]; + [NSApplication sharedApplication]; + + Class webViewDelegateClass = + objc_allocateClassPair([NSObject class], "WebViewDelegate", 0); + class_addMethod(webViewDelegateClass, sel_registerName("windowWillClose:"), + (IMP)webview_window_will_close, "v@:@"); + class_addMethod(object_getClass(webViewDelegateClass), + sel_registerName("isSelectorExcludedFromWebScript:"), + (IMP)webview_is_selector_excluded_from_web_script, "c@::"); + class_addMethod(object_getClass(webViewDelegateClass), + sel_registerName("webScriptNameForSelector:"), + (IMP)webview_webscript_name_for_selector, "c@::"); + class_addMethod(webViewDelegateClass, + sel_registerName("webView:didClearWindowObject:forFrame:"), + (IMP)webview_did_clear_window_object, "v@:@@@"); + class_addMethod(webViewDelegateClass, sel_registerName("invoke:"), + (IMP)webview_external_invoke, "v@:@"); + objc_registerClassPair(webViewDelegateClass); + + w->priv.windowDelegate = [[webViewDelegateClass alloc] init]; + objc_setAssociatedObject(w->priv.windowDelegate, "webview", (id)(w), + OBJC_ASSOCIATION_ASSIGN); + + NSRect r = NSMakeRect(0, 0, w->width, w->height); + NSUInteger style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | + NSWindowStyleMaskMiniaturizable; + if (w->resizable) + { + style = style | NSWindowStyleMaskResizable; + // style = style | NSTexturedBackgroundWindowMask; + // style = style | NSUnifiedTitleAndToolbarWindowMask; + } + + // Transparent title bar + // if (w->transparentTitlebar) { + // style = style | NSFullSizeContentViewWindowMask | NSUnifiedTitleAndToolbarWindowMask | NSTexturedBackgroundWindowMask; + // } + + w->priv.window = [[NSWindow alloc] initWithContentRect:r + styleMask:style + backing:NSBackingStoreBuffered + defer:NO]; + [w->priv.window autorelease]; + + // Title + NSString *nsTitle = [NSString stringWithUTF8String:w->title]; + [w->priv.window setTitle:nsTitle]; + + [w->priv.window setDelegate:w->priv.windowDelegate]; + [w->priv.window center]; + + // NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"wat"]; + // toolbar.showsBaselineSeparator = NO; + // [w->priv.window setToolbar:toolbar]; + + // if (w->transparentTitlebar) { + + // // Configure window look with hidden toolbar + // [w->priv.window setTitlebarAppearsTransparent:YES]; + // [w->priv.window setTitleVisibility:NSWindowTitleHidden]; + // // w->priv.window.isMovableByWindowBackground = true; + // } + + [[NSUserDefaults standardUserDefaults] setBool:!!w->debug + forKey:@"WebKitDeveloperExtras"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + w->priv.webview = + [[WebView alloc] initWithFrame:r + frameName:@"WebView" + groupName:nil]; + NSURL *nsURL = [NSURL + URLWithString:[NSString stringWithUTF8String:webview_check_url(w->url)]]; + [[w->priv.webview mainFrame] loadRequest:[NSURLRequest requestWithURL:nsURL]]; + + [w->priv.webview setAutoresizesSubviews:YES]; + [w->priv.webview + setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; + w->priv.webview.frameLoadDelegate = w->priv.windowDelegate; + [[w->priv.window contentView] addSubview:w->priv.webview]; + [w->priv.window orderFrontRegardless]; + + // Disable scrolling - make this configurable + // [[[w->priv.webview mainFrame] frameView] setAllowsScrolling:NO]; + + // ----> Enables WebGL but won't pass the app store guidelines + // + // WebPreferences *p = [w->priv.webview preferences]; + // if ([p respondsToSelector:@selector(setWebGLEnabled:)]) { + // [p setWebGLEnabled:YES]; + // } + + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + [NSApp finishLaunching]; + [NSApp activateIgnoringOtherApps:YES]; + + NSMenu *menubar = [[[NSMenu alloc] initWithTitle:@""] autorelease]; + + NSMenuItem *appMenuItem = + [[[NSMenuItem alloc] initWithTitle:@"wails app" action:NULL keyEquivalent:@""] + autorelease]; + NSMenu *appMenu = [[[NSMenu alloc] initWithTitle:@"wails app"] autorelease]; + [appMenuItem setSubmenu:appMenu]; + [menubar addItem:appMenuItem]; + + NSMenuItem *item = [[[NSMenuItem alloc] initWithTitle:@"Hide" + action:@selector(hide:) + keyEquivalent:@"h"] autorelease]; + [appMenu addItem:item]; + item = [[[NSMenuItem alloc] initWithTitle:@"Hide Others" + action:@selector(hideOtherApplications:) + keyEquivalent:@"h"] autorelease]; + [item setKeyEquivalentModifierMask:(NSEventModifierFlagOption | + NSEventModifierFlagCommand)]; + [appMenu addItem:item]; + item = [[[NSMenuItem alloc] initWithTitle:@"Show All" + action:@selector(unhideAllApplications:) + keyEquivalent:@""] autorelease]; + [appMenu addItem:item]; + [appMenu addItem:[NSMenuItem separatorItem]]; + + NSMenuItem *editMenuItem = + [[[NSMenuItem alloc] initWithTitle:@"Edit" action:NULL keyEquivalent:@""] + autorelease]; + NSMenu *editMenu = [[[NSMenu alloc] initWithTitle:@"Edit"] autorelease]; + [editMenuItem setSubmenu:editMenu]; + [menubar addItem:editMenuItem]; + + item = [[[NSMenuItem alloc] initWithTitle:@"Cut" + action:@selector(cut:) + keyEquivalent:@"x"] autorelease]; + [editMenu addItem:item]; + + item = [[[NSMenuItem alloc] initWithTitle:@"Copy" + action:@selector(copy:) + keyEquivalent:@"c"] autorelease]; + [editMenu addItem:item]; + + item = [[[NSMenuItem alloc] initWithTitle:@"Paste" + action:@selector(paste:) + keyEquivalent:@"v"] autorelease]; + [editMenu addItem:item]; + + item = [[[NSMenuItem alloc] initWithTitle:@"Select All" + action:@selector(selectAll:) + keyEquivalent:@"a"] autorelease]; + [editMenu addItem:item]; + + [appMenu addItem:[NSMenuItem separatorItem]]; + + item = [[[NSMenuItem alloc] initWithTitle:@"Quit" + action:@selector(terminate:) + keyEquivalent:@"q"] autorelease]; + [appMenu addItem:item]; + + [NSApp setMainMenu:menubar]; + + w->priv.should_exit = 0; + return 0; + } + + WEBVIEW_API int webview_loop(struct webview *w, int blocking) + { + NSDate *until = (blocking ? [NSDate distantFuture] : [NSDate distantPast]); + NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny + untilDate:until + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (event) + { + [NSApp sendEvent:event]; + } + return w->priv.should_exit; + } + + WEBVIEW_API int webview_eval(struct webview *w, const char *js) + { + NSString *nsJS = [NSString stringWithUTF8String:js]; + [[w->priv.webview windowScriptObject] evaluateWebScript:nsJS]; + return 0; + } + + WEBVIEW_API void webview_set_title(struct webview *w, const char *title) + { + NSString *nsTitle = [NSString stringWithUTF8String:title]; + [w->priv.window setTitle:nsTitle]; + } + + WEBVIEW_API void webview_set_fullscreen(struct webview *w, int fullscreen) + { + int b = ((([w->priv.window styleMask] & NSWindowStyleMaskFullScreen) == + NSWindowStyleMaskFullScreen) + ? 1 + : 0); + if (b != fullscreen) + { + [w->priv.window toggleFullScreen:nil]; + } + } + + WEBVIEW_API void webview_set_color(struct webview *w, uint8_t r, uint8_t g, + uint8_t b, uint8_t a) + { + [w->priv.window setBackgroundColor:[NSColor colorWithRed:(CGFloat)r / 255.0 + green:(CGFloat)g / 255.0 + blue:(CGFloat)b / 255.0 + alpha:(CGFloat)a / 255.0]]; + if (0.5 >= ((r / 255.0 * 299.0) + (g / 255.0 * 587.0) + (b / 255.0 * 114.0)) / + 1000.0) + { + [w->priv.window + setAppearance:[NSAppearance + appearanceNamed:NSAppearanceNameVibrantDark]]; + } + else + { + [w->priv.window + setAppearance:[NSAppearance + appearanceNamed:NSAppearanceNameVibrantLight]]; + } + [w->priv.window setOpaque:NO]; + [w->priv.window setTitlebarAppearsTransparent:YES]; + [w->priv.webview setDrawsBackground:NO]; + } + + 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) + { + NSSavePanel *panel; + if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN) + { + NSOpenPanel *openPanel = [NSOpenPanel openPanel]; + if (flags & WEBVIEW_DIALOG_FLAG_DIRECTORY) + { + [openPanel setCanChooseFiles:NO]; + [openPanel setCanChooseDirectories:YES]; + } + else + { + [openPanel setCanChooseFiles:YES]; + [openPanel setCanChooseDirectories:NO]; + } + [openPanel setResolvesAliases:NO]; + [openPanel setAllowsMultipleSelection:NO]; + panel = openPanel; + } + else + { + panel = [NSSavePanel savePanel]; + } + [panel setCanCreateDirectories:YES]; + [panel setShowsHiddenFiles:YES]; + [panel setExtensionHidden:NO]; + [panel setCanSelectHiddenExtension:NO]; + [panel setTreatsFilePackagesAsDirectories:YES]; + [panel beginSheetModalForWindow:w->priv.window + completionHandler:^(NSInteger result) { + [NSApp stopModalWithCode:result]; + }]; + if ([NSApp runModalForWindow:panel] == NSModalResponseOK) + { + const char *filename = [[[panel URL] path] UTF8String]; + strlcpy(result, filename, resultsz); + } + } + else if (dlgtype == WEBVIEW_DIALOG_TYPE_ALERT) + { + NSAlert *a = [NSAlert new]; + switch (flags & WEBVIEW_DIALOG_FLAG_ALERT_MASK) + { + case WEBVIEW_DIALOG_FLAG_INFO: + [a setAlertStyle:NSAlertStyleInformational]; + break; + case WEBVIEW_DIALOG_FLAG_WARNING: + NSLog(@"warning"); + [a setAlertStyle:NSAlertStyleWarning]; + break; + case WEBVIEW_DIALOG_FLAG_ERROR: + NSLog(@"error"); + [a setAlertStyle:NSAlertStyleCritical]; + break; + } + [a setShowsHelp:NO]; + [a setShowsSuppressionButton:NO]; + [a setMessageText:[NSString stringWithUTF8String:title]]; + [a setInformativeText:[NSString stringWithUTF8String:arg]]; + [a addButtonWithTitle:@"OK"]; + [a runModal]; + [a 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) { [NSApp terminate:NSApp]; } + WEBVIEW_API void webview_print_log(const char *s) { NSLog(@"%s", s); } + +#endif /* WEBVIEW_COCOA */ + +#endif /* WEBVIEW_IMPLEMENTATION */ + +#ifdef __cplusplus +} +#endif + +#endif /* WEBVIEW_H */