Compare commits

..

1 Commits

Author SHA1 Message Date
Travis McLane
ddcaf58a90 runtime.Store => wails.Store 2020-09-04 13:02:11 -05:00
30 changed files with 1105 additions and 1878 deletions

View File

@@ -33,4 +33,3 @@ Wails is what it is because of the time and effort given by these great people.
* [artem](https://github.com/Unix4ever) * [artem](https://github.com/Unix4ever)
* [Tim Kipp](https://github.com/timkippdev) * [Tim Kipp](https://github.com/timkippdev)
* [Dmitry Gomzyakov](https://github.com/kyoto44) * [Dmitry Gomzyakov](https://github.com/kyoto44)
* [Arthur Wiebe](https://github.com/artooro)

View File

@@ -12,6 +12,7 @@
<a href="https://houndci.com"><img src="https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg"/></a> <a href="https://houndci.com"><img src="https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg"/></a>
<a href="https://github.com/avelino/awesome-go" rel="nofollow"><img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg" alt="Awesome"></a> <a href="https://github.com/avelino/awesome-go" rel="nofollow"><img src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg" alt="Awesome"></a>
<a href="https://github.com/wailsapp/wails/workflows/release/badge.svg?branch=master" rel="nofollow"><img src="https://github.com/wailsapp/wails/workflows/release/badge.svg?branch=master" alt="Release Pipelines"></a> <a href="https://github.com/wailsapp/wails/workflows/release/badge.svg?branch=master" rel="nofollow"><img src="https://github.com/wailsapp/wails/workflows/release/badge.svg?branch=master" alt="Release Pipelines"></a>
<a href="https://github.com/wailsapp/wails/workflows/latest-pre/badge.svg?branch=masterr" rel="nofollow"><img src="https://github.com/wailsapp/wails/workflows/latest-pre/badge.svg?branch=master" alt="Pre-Release Pipelines"></a>
</p> </p>
The traditional method of providing web interfaces to Go programs is via a built-in web server. Wails offers a different approach: it provides the ability to wrap both Go code and a web frontend into a single binary. Tools are provided to make this easy for you by handling project creation, compilation and bundling. All you have to do is get creative! The traditional method of providing web interfaces to Go programs is via a built-in web server. Wails offers a different approach: it provides the ability to wrap both Go code and a web frontend into a single binary. Tools are provided to make this easy for you by handling project creation, compilation and bundling. All you have to do is get creative!

View File

@@ -5,7 +5,7 @@ const routes: Routes = [];
@NgModule({ @NgModule({
imports: [ imports: [
RouterModule.forRoot(routes,{useHash:true}) RouterModule.forRoot(routes)
], ],
exports: [RouterModule] exports: [RouterModule]
}) })

View File

@@ -1,4 +0,0 @@
/node_modules/
/public/build/
.DS_Store

View File

@@ -1,90 +0,0 @@
*Looking for a shareable component template? Go here --> [sveltejs/component-template](https://github.com/sveltejs/component-template)*
---
# svelte app
This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template.
To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
```bash
npx degit sveltejs/template svelte-app
cd svelte-app
```
*Note that you will need to have [Node.js](https://nodejs.org) installed.*
## Get started
Install the dependencies...
```bash
cd svelte-app
npm install
```
...then start [Rollup](https://rollupjs.org):
```bash
npm run dev
```
Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`.
## Building and running in production mode
To create an optimised version of the app:
```bash
npm run build
```
You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com).
## Single-page app mode
By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere.
If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json:
```js
"start": "sirv public --single"
```
## Deploying to the web
### With [Vercel](https://vercel.com)
Install `vercel` if you haven't already:
```bash
npm install -g vercel
```
Then, from within your project folder:
```bash
cd public
vercel deploy --name my-project
```
### With [surge](https://surge.sh/)
Install `surge` if you haven't already:
```bash
npm install -g surge
```
Then, from within your project folder:
```bash
npm run build
surge public my-project.surge.sh
```

View File

@@ -1,31 +0,0 @@
{
"name": "{{.NPMProjectName}}",
"author": "{{.Author.Name}}<{{.Author.Email}}>",
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w",
"start": "sirv public"
},
"devDependencies": {
"@babel/core": "^7.11.6",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.11.5",
"@babel/preset-env": "^7.11.5",
"@rollup/plugin-commonjs": "^14.0.0",
"@rollup/plugin-image": "^2.0.5",
"@rollup/plugin-node-resolve": "^8.0.0",
"core-js": "^3.6.5",
"rollup": "^2.3.4",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-polyfill": "^3.0.0",
"rollup-plugin-svelte": "^6.0.0",
"rollup-plugin-terser": "^7.0.0",
"svelte": "^3.0.0"
},
"dependencies": {
"sirv-cli": "^1.0.0",
"@wailsapp/runtime": "^1.0.10",
"svelte-simple-modal": "^0.6.0"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>Svelte app</title>
<link rel='icon' type='image/png' href='/favicon.png'>
<link rel='stylesheet' href='/build/bundle.css'>
<script defer src='/build/bundle.js'></script>
</head>
<body>
</body>
</html>

View File

@@ -1,110 +0,0 @@
import svelte from 'rollup-plugin-svelte';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import image from '@rollup/plugin-image';
import babel from 'rollup-plugin-babel';
import polyfill from 'rollup-plugin-polyfill';
const production = !process.env.ROLLUP_WATCH;
function serve() {
let server;
function toExit() {
if (server) server.kill(0);
}
return {
writeBundle() {
if (server) return;
server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
stdio: ['ignore', 'inherit', 'inherit'],
shell: true
});
process.on('SIGTERM', toExit);
process.on('exit', toExit);
}
};
}
export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/build/bundle.js'
},
plugins: [
image(),
svelte({
// enable run-time checks when not in production
dev: !production,
// we'll extract any component CSS out into
// a separate file - better for performance
css: css => {
css.write('bundle.css');
}
}),
// If you have external dependencies installed from
// npm, you'll most likely need these plugins. In
// some cases you'll need additional configuration -
// consult the documentation for details:
// https://github.com/rollup/plugins/tree/master/packages/commonjs
resolve({
browser: true,
dedupe: ['svelte', 'svelte/transition', 'svelte/internal']
}),
commonjs(),
// In dev mode, call `npm run start` once
// the bundle has been generated
!production && serve(),
// Watch the `public` directory and refresh the
// browser on changes when not in production
!production && livereload('public'),
// Credit: https://blog.az.sg/posts/svelte-and-ie11/
babel({
extensions: [ '.js', '.jsx', '.es6', '.es', '.mjs', '.svelte', '.html' ],
runtimeHelpers: true,
exclude: [ 'node_modules/@babel/**', 'node_modules/core-js/**' ],
presets: [
[
'@babel/preset-env',
{
targets: '> 0.25%, not dead, IE 11',
modules: false,
spec: true,
useBuiltIns: 'usage',
forceAllTransforms: true,
corejs: 3,
},
]
],
plugins: [
'@babel/plugin-syntax-dynamic-import',
[
'@babel/plugin-transform-runtime',
{
useESModules: true
}
]
]
}),
polyfill(['@webcomponents/webcomponentsjs']),
// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser()
],
watch: {
clearScreen: false
}
};

View File

@@ -1,69 +0,0 @@
<script>
import Modal from 'svelte-simple-modal';
import HelloWorld from './components/HelloWorld.svelte'
import logo from './logo.png';
</script>
<main>
<div class="App">
<header class="App-header">
<Modal>
<img src={logo} class="App-logo" alt="logo" />
<p>Welcome to your new <code>wails/svelte</code> project.</p>
<HelloWorld/>
</Modal>
</header>
</div>
</main>
<style>
:global(body) {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
}
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>

View File

@@ -1,18 +0,0 @@
<script>
import { getContext } from 'svelte';
import ModalContent from './ModalContent.svelte'
const { open } = getContext('simple-modal');
const handleOpenModal = () => {
window.backend.basic().then((result) => {
open(ModalContent, { message: result });
});
};
</script>
<main>
<p><button on:click={handleOpenModal}>Hello</button></p>
</main>
<style></style>

View File

@@ -1,7 +0,0 @@
<script>
export let message;
</script>
<p>
{message}
</p>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 KiB

View File

@@ -1,13 +0,0 @@
import App from './App.svelte';
import * as Wails from '@wailsapp/runtime';
let app;
Wails.Init(() => {
app = new App({
target: document.body,
});
});
export default app;

View File

@@ -1,5 +0,0 @@
module {{.BinaryName}}
require (
github.com/wailsapp/wails {{.WailsVersion}}
)

View File

@@ -1,27 +0,0 @@
package main
import (
"github.com/leaanthony/mewn"
"github.com/wailsapp/wails"
)
func basic() string {
return "World!"
}
func main() {
js := mewn.String("./frontend/public/build/bundle.js")
css := mewn.String("./frontend/public/build/bundle.css")
app := wails.CreateApp(&wails.AppConfig{
Width: 1024,
Height: 768,
Title: "{{.Name}}",
JS: js,
CSS: css,
Colour: "#131313",
})
app.Bind(basic)
app.Run()
}

View File

@@ -1,14 +0,0 @@
{
"name": "Svelte",
"version": "1.0.0",
"shortdescription": "A basic Svelte template",
"description": "A basic Svelte template",
"install": "npm install",
"build": "npm run build",
"author": "Tim Kipp <timkipp.22.developer@gmail.com>",
"created": "2020-09-06 13:06:10.469848 -0700 PDT m=+213.578828559",
"frontenddir": "frontend",
"serve": "npm run dev",
"bridge": "src",
"wailsdir": ""
}

View File

@@ -2,33 +2,29 @@
html, html,
body { body {
background-color: white; background-color: white;
color: black;
width: 100%; width: 100%;
height: 100%; height: 100%;
margin: 0; margin: 0;
} }
input {
background-color: rgb(254,254,254);
color: black;
}
.container { .container {
display: block; display: block;
width:100%; width:100%;
text-align: center; text-align: center;
margin-top: 1rem; margin-top: 3rem;
font-size: 2rem; font-size: 2rem;
} }
button { button {
font-size: 1rem; font-size: 1rem;
background-color: white; }
color: black;
#newCounter {
color: white;
} }
.result { .result {
margin-top: 1rem; margin-top: 3rem;
text-align: center; text-align: center;
font-size: 2rem; font-size: 2rem;
} }

View File

@@ -4,7 +4,7 @@ const runtime = require('@wailsapp/runtime');
// Main entry point // Main entry point
function start() { function start() {
var mystore = runtime.Store.New('Counter'); var mystore = wails.Store.New('Counter');
// Ensure the default app div is 100% wide/high // Ensure the default app div is 100% wide/high
var app = document.getElementById('app'); var app = document.getElementById('app');
@@ -36,7 +36,7 @@ function start() {
mystore.set(newValue); mystore.set(newValue);
}; };
mystore.subscribe( function(state) { mystore.subscribe( (state) => {
document.getElementById('counter').innerText = state; document.getElementById('counter').innerText = state;
}); });

View File

@@ -1,4 +1,4 @@
package cmd package cmd
// Version - Wails version // Version - Wails version
const Version = "v1.8.0" const Version = "v1.7.2-pre5"

View File

@@ -1,27 +0,0 @@
package webview
/*
#include <stdlib.h>
*/
import "C"
import "unsafe"
// This is our allocated C Memory
var memory []unsafe.Pointer
func saveMemoryReference(mem unsafe.Pointer) {
memory = append(memory, mem)
}
func freeMemory() {
for _, mem := range memory {
C.free(mem)
}
}
func string2CString(str string) *C.char {
result := C.CString(str)
saveMemoryReference(unsafe.Pointer(result))
return result
}

356
lib/renderer/webview/webview.go Normal file → Executable file
View File

@@ -1,20 +1,166 @@
// Package webview 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 package webview
/*
#cgo linux openbsd freebsd CFLAGS: -DWEBVIEW_GTK=1 -Wno-deprecated-declarations
#cgo linux openbsd freebsd pkg-config: gtk+-3.0 webkit2gtk-4.0
#cgo windows CFLAGS: -DWEBVIEW_WINAPI=1 -std=c99
#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 <stdlib.h>
#include <stdint.h>
#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, char *filter) {
webview_dialog(w, dlgtype, flags,
(const char*)title, (const char*) arg, res, ressz, filter);
}
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 "C"
import (
// DialogType is an enumeration of all supported system dialog types "errors"
type DialogType int "runtime"
"sync"
const ( "unsafe"
// 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
) )
// WebView is our webview interface 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 { type WebView interface {
// Run() starts the main UI loop until the user closes the webview window or // Run() starts the main UI loop until the user closes the webview window or
// Terminate() is called. // Terminate() is called.
@@ -53,21 +199,175 @@ type WebView interface {
Exit() Exit()
} }
type ExternalInvokeCallbackFunc func(w WebView, data string) // DialogType is an enumeration of all supported system dialog types
type DialogType int
type Settings struct { const (
// WebView main window title // DialogTypeOpen is a system file open dialog
Title string DialogTypeOpen DialogType = iota
// URL to open in a webview // DialogTypeSave is a system file save dialog
URL string DialogTypeSave
// Window width in pixels // DialogTypeAlert is a system alert dialog (message box)
Width int DialogTypeAlert
// Window height in pixels )
Height int
// Allows/disallows window resizing const (
Resizable bool // DialogFlagFile is a normal file picker dialog
// Enable debugging tools (Linux/BSD/MacOS, on Windows use Firebug) DialogFlagFile = C.WEBVIEW_DIALOG_FLAG_FILE
Debug bool // DialogFlagDirectory is an open directory dialog
// A callback that is executed when JavaScript calls "window.external.invoke()" DialogFlagDirectory = C.WEBVIEW_DIALOG_FLAG_DIRECTORY
ExternalInvokeCallback ExternalInvokeCallbackFunc // 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, filter 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))
filterPtr := C.CString(filter)
defer C.free(unsafe.Pointer(filterPtr))
C.CgoDialog(w.w, C.int(dlgType), C.int(flags), titlePtr,
argPtr, resultPtr, C.size_t(maxPath), filterPtr)
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)))
} }

View File

@@ -39,6 +39,23 @@ extern "C"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#if defined(WEBVIEW_GTK)
#include <JavaScriptCore/JavaScript.h>
#include <gtk/gtk.h>
#include <webkit2/webkit2.h>
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 #define CINTERFACE
#include <windows.h> #include <windows.h>
@@ -59,6 +76,22 @@ struct webview_priv
DWORD saved_ex_style; DWORD saved_ex_style;
RECT saved_rect; RECT saved_rect;
}; };
#elif defined(WEBVIEW_COCOA)
#import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>
#import <objc/runtime.h>
struct webview_priv
{
NSAutoreleasePool *pool;
NSWindow *window;
WebView *webview;
id delegate;
int should_exit;
};
#else
#error "Define one of: WEBVIEW_GTK, WEBVIEW_COCOA or WEBVIEW_WINAPI"
#endif
struct webview; struct webview;
@@ -149,6 +182,9 @@ struct webview_priv
WEBVIEW_API void webview_debug(const char *format, ...); WEBVIEW_API void webview_debug(const char *format, ...);
WEBVIEW_API void webview_print_log(const char *s); 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, WEBVIEW_API int webview(const char *title, const char *url, int width,
int height, int resizable) int height, int resizable)
{ {
@@ -228,6 +264,301 @@ struct webview_priv
return r; 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, char *filter)
{
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);
if (filter[0] != '\0') {
GtkFileFilter *file_filter = gtk_file_filter_new();
gchar **filters = g_strsplit(filter, ",", -1);
gint i;
for(i = 0; filters && filters[i]; i++) {
gtk_file_filter_add_pattern(file_filter, filters[i]);
}
gtk_file_filter_set_name(file_filter, filter);
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dlg), file_filter);
g_strfreev(filters);
}
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) #if defined(WEBVIEW_WINAPI)
#pragma comment(lib, "user32.lib") #pragma comment(lib, "user32.lib")
@@ -1100,13 +1431,6 @@ struct webview_priv
style = WS_OVERLAPPED | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU; style = WS_OVERLAPPED | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU;
} }
// Scale
// Credit: https://github.com/webview/webview/issues/54#issuecomment-379528243
HDC hDC = GetDC(NULL);
w->width = GetDeviceCaps(hDC, 88)*w->width/96.0;
w->height = GetDeviceCaps(hDC, 90)*w->height/96.0;
ReleaseDC(NULL, hDC);
rect.left = 0; rect.left = 0;
rect.top = 0; rect.top = 0;
rect.right = w->width; rect.right = w->width;
@@ -1599,6 +1923,433 @@ struct webview_priv
WEBVIEW_API void webview_exit(struct webview *w) { OleUninitialize(); } WEBVIEW_API void webview_exit(struct webview *w) { OleUninitialize(); }
WEBVIEW_API void webview_print_log(const char *s) { OutputDebugString(s); } 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_run_input_open_panel(id self, SEL cmd, id webview,
id listener, BOOL allowMultiple)
{
char filename[256] = "";
struct webview *w =
(struct webview *)objc_getAssociatedObject(self, "webview");
webview_dialog(w, WEBVIEW_DIALOG_TYPE_OPEN, WEBVIEW_DIALOG_FLAG_FILE, "", "",
filename, 255, "");
filename[255] = '\0';
if (strlen(filename) > 0)
{
[listener chooseFilename:[NSString stringWithUTF8String:filename]];
}
else
{
[listener cancel];
}
}
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("webView:runOpenPanelForFileButtonWithResultListener:"
"allowMultipleFiles:"),
(IMP)webview_run_input_open_panel, "v@:@@c");
class_addMethod(webViewDelegateClass, sel_registerName("invoke:"),
(IMP)webview_external_invoke, "v@:@");
objc_registerClassPair(webViewDelegateClass);
w->priv.delegate = [[webViewDelegateClass alloc] init];
objc_setAssociatedObject(w->priv.delegate, "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.delegate];
[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.delegate;
w->priv.webview.UIDelegate = w->priv.delegate;
[[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, char *filter)
{
if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN ||
dlgtype == WEBVIEW_DIALOG_TYPE_SAVE)
{
NSSavePanel *panel;
NSString *filter_str = [NSString stringWithUTF8String:filter];
filter_str = [filter_str stringByReplacingOccurrencesOfString:@"*."
withString:@""];
NSArray *fileTypes = [filter_str componentsSeparatedByString:@","];
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];
if(filter[0] != NULL)
{
[openPanel setAllowedFileTypes:fileTypes];
}
}
[openPanel setResolvesAliases:NO];
[openPanel setAllowsMultipleSelection:NO];
panel = openPanel;
}
else
{
panel = [NSSavePanel savePanel];
}
[panel setCanCreateDirectories:YES];
[panel setShowsHiddenFiles:YES];
[panel setExtensionHidden:NO];
[panel setCanSelectHiddenExtension:NO];
if(filter[0] != NULL)
{
[panel setAllowedFileTypes:fileTypes];
}
[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 #ifdef __cplusplus
} }
#endif #endif

View File

@@ -1,57 +0,0 @@
package webview
/*
#include "webview_darwin.h"
*/
import "C"
import "unsafe"
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
)
type MacWebView struct {
app unsafe.Pointer
}
func NewWebview(settings Settings) WebView {
return &MacWebView{}
}
func (w *MacWebView) Dialog(dlgType DialogType, flags int, title string, arg string, filter string) string {
// TBD
return ""
}
func (w *MacWebView) Dispatch(func()) {
// TBD
}
func (w *MacWebView) Eval(js string) error {
// TBD
return nil
}
func (w *MacWebView) Exit() {
// TBD
}
func (w *MacWebView) Run() {}
func (w *MacWebView) Loop(blocking bool) bool {
return true
}
func (w *MacWebView) SetTitle(title string) {}
func (w *MacWebView) SetFullscreen(fullscreen bool) {}
func (w *MacWebView) SetColor(r, g, b, a uint8) {}
func (w *MacWebView) InjectCSS(css string) {}
func (w *MacWebView) Terminate() {}

View File

@@ -1,13 +0,0 @@
#ifndef WEBVIEW_DARWIN
#define WEBVIEW_DARWIN
#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)
#endif

View File

@@ -1,386 +0,0 @@
// Package webview 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 -Wno-deprecated-declarations
#cgo linux openbsd freebsd pkg-config: gtk+-3.0 webkit2gtk-4.0
#cgo windows CFLAGS: -DWEBVIEW_WINAPI=1 -std=c99
#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 <stdlib.h>
#include <stdint.h>
#define WEBVIEW_STATIC
#define WEBVIEW_IMPLEMENTATION
#ifdef WEBVIEW_GTK
#include "webview_linux.h"
#endif
#ifdef WEBVIEW_COCOA
#include "webview_darwin.h"
#endif
#ifdef WEBVIEW_COCOA
#include "webview_darwin.h"
#endif
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, char *filter) {
webview_dialog(w, dlgtype, flags,
(const char*)title, (const char*) arg, res, ressz, filter);
}
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, filter 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, filter 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))
filterPtr := C.CString(filter)
defer C.free(unsafe.Pointer(filterPtr))
C.CgoDialog(w.w, C.int(dlgType), C.int(flags), titlePtr,
argPtr, resultPtr, C.size_t(maxPath), filterPtr)
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)))
}

View File

@@ -1,522 +0,0 @@
/*
* 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 <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <JavaScriptCore/JavaScript.h>
#include <gtk/gtk.h>
#include <webkit2/webkit2.h>
struct webview_priv
{
GtkWidget *window;
GtkWidget *scroller;
GtkWidget *webview;
GtkWidget *inspector_window;
GAsyncQueue *queue;
int ready;
int js_busy;
int should_exit;
};
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, char *filter);
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);
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;
}
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, char *filter)
{
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);
if (filter[0] != '\0') {
GtkFileFilter *file_filter = gtk_file_filter_new();
gchar **filters = g_strsplit(filter, ",", -1);
gint i;
for(i = 0; filters && filters[i]; i++) {
gtk_file_filter_add_pattern(file_filter, filters[i]);
}
gtk_file_filter_set_name(file_filter, filter);
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dlg), file_filter);
g_strfreev(filters);
}
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);
}
#ifdef __cplusplus
}
#endif
#endif /* WEBVIEW_H */

View File

@@ -1,368 +0,0 @@
// Package webview 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 windows CFLAGS: -DWEBVIEW_WINAPI=1 -std=c99
#cgo windows LDFLAGS: -lole32 -lcomctl32 -loleaut32 -luuid -lgdi32
#include <stdlib.h>
#include <stdint.h>
#define WEBVIEW_STATIC
#define WEBVIEW_IMPLEMENTATION
#include "webview_windows.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, char *filter) {
webview_dialog(w, dlgtype, flags,
(const char*)title, (const char*) arg, res, ressz, filter);
}
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, filter 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, filter 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))
filterPtr := C.CString(filter)
defer C.free(unsafe.Pointer(filterPtr))
C.CgoDialog(w.w, C.int(dlgType), C.int(flags), titlePtr,
argPtr, resultPtr, C.size_t(maxPath), filterPtr)
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)))
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "@wailsapp/runtime", "name": "@wailsapp/runtime",
"version": "1.1.1", "version": "1.1.0",
"description": "Wails Javascript runtime library", "description": "Wails Javascript runtime library",
"main": "main.js", "main": "main.js",
"types": "runtime.d.ts", "types": "runtime.d.ts",

View File

@@ -45,7 +45,7 @@ type Store struct {
data reflect.Value data reflect.Value
dataType reflect.Type dataType reflect.Type
eventPrefix string eventPrefix string
callbacks []reflect.Value callbacks []func(interface{})
runtime *Runtime runtime *Runtime
notifySynchronously bool notifySynchronously bool
@@ -143,13 +143,10 @@ func (s *Store) notify() {
// Execute callbacks // Execute callbacks
for _, callback := range s.callbacks { for _, callback := range s.callbacks {
// Build args
args := []reflect.Value{s.data}
if s.notifySynchronously { if s.notifySynchronously {
callback.Call(args) callback(s.data)
} else { } else {
go callback.Call(args) go callback(s.data)
} }
} }
@@ -187,50 +184,11 @@ func (s *Store) Set(data interface{}) error {
return nil return nil
} }
// callbackCheck ensures the given function to Subscribe() is
// of the correct signature. Absolutely cannot wait for
// generics to land rather than writing this nonsense.
func (s *Store) callbackCheck(callback interface{}) error {
// Get type
callbackType := reflect.TypeOf(callback)
// Check callback is a function
if callbackType.Kind() != reflect.Func {
return fmt.Errorf("invalid value given to store.Subscribe(). Expected 'func(%s)'", s.dataType.String())
}
// Check input param
if callbackType.NumIn() != 1 {
return fmt.Errorf("invalid number of parameters given in callback function. Expected 1")
}
// Check input data type
if callbackType.In(0) != s.dataType {
return fmt.Errorf("invalid type for input parameter given in callback function. Expected %s, got %s", s.dataType.String(), callbackType.In(0))
}
// Check output param
if callbackType.NumOut() != 0 {
return fmt.Errorf("invalid number of return parameters given in callback function. Expected 0")
}
return nil
}
// Subscribe will subscribe to updates to the store by // Subscribe will subscribe to updates to the store by
// providing a callback. Any updates to the store are sent // providing a callback. Any updates to the store are sent
// to the callback // to the callback
func (s *Store) Subscribe(callback interface{}) { func (s *Store) Subscribe(callback func(interface{})) {
s.callbacks = append(s.callbacks, callback)
err := s.callbackCheck(callback)
if err != nil {
log.Fatal(err)
}
callbackFunc := reflect.ValueOf(callback)
s.callbacks = append(s.callbacks, callbackFunc)
} }
// updaterCheck ensures the given function to Update() is // updaterCheck ensures the given function to Update() is