Compare commits

...

55 Commits

Author SHA1 Message Date
Lea Anthony
f73506c496 Skel code in place 2020-09-11 11:45:58 +10:00
Lea Anthony
cf18086b84 Split out webview Go files 2020-09-11 10:57:03 +10:00
Lea Anthony
91269bc4a0 Trim MacOS implementation 2020-09-11 10:49:19 +10:00
Lea Anthony
b3061156a8 Trim windows version 2020-09-11 07:26:16 +10:00
Lea Anthony
7df86cc8e6 Initial split 2020-09-11 07:23:27 +10:00
Lea Anthony
422ee22d0c Updated contributors 2020-09-11 06:40:36 +10:00
Lea Anthony
22f94cfdb6 Merge branch 'artooro-angular-routing-fix' into develop 2020-09-11 06:39:15 +10:00
Arthur Wiebe
5d754f40de resolve angular routing broken when app is built 2020-09-09 22:06:14 -04:00
Lea Anthony
c9b26c6352 Merge branch 'develop' 2020-09-10 06:55:39 +10:00
Lea Anthony
6f0696631f v1.8.0 2020-09-10 06:45:42 +10:00
Lea Anthony
fefb54de12 v1.7.2-pre12 2020-09-10 06:40:13 +10:00
Lea Anthony
b4224066f7 css fix for vanilla 2020-09-10 06:39:36 +10:00
Lea Anthony
3d9ef75488 v1.7.2-pre11 2020-09-10 06:19:07 +10:00
Lea Anthony
79ecb0704e test fix 2020-09-10 06:17:17 +10:00
Lea Anthony
8154887824 v1.7.2-pre10 2020-09-09 06:58:30 +10:00
Lea Anthony
39ae91a250 Simplify Subscribe() 2020-09-09 06:27:57 +10:00
Lea Anthony
861f5f2a1c v1.7.2-pre9 2020-09-08 07:06:27 +10:00
Lea Anthony
89ed00d6ed Fix svelte for Windows 2020-09-08 07:04:01 +10:00
Lea Anthony
1e1834158b fix vanilla template for windows 2020-09-07 20:45:20 +10:00
Lea Anthony
214fcf03b9 v1.7.2-pre8 2020-09-07 20:28:29 +10:00
Tim Kipp
82ac4f1358 chore: removed Svelte TypeScript support for now 2020-09-07 17:35:12 +10:00
Tim Kipp
8b7cd03428 chore: removed Svelte template link 2020-09-07 17:35:12 +10:00
Tim Kipp
4d2b4fec45 feat: added Svelte template 2020-09-07 17:35:12 +10:00
Lea Anthony
bc0478b2b2 Update README.md 2020-09-06 15:45:37 +10:00
Lea Anthony
d59849b952 v1.7.2-pre7 2020-09-05 07:11:28 +10:00
Lea Anthony
806821ad49 v1.7.2-pre6 2020-09-05 07:01:15 +10:00
Lea Anthony
a4cc95351f Updated runtime package version 2020-09-05 06:52:39 +10:00
Lea Anthony
f851b89350 v1.7.2-pre5 2020-09-04 20:23:26 +10:00
Lea Anthony
7f72189231 updates 2020-09-04 20:20:21 +10:00
Lea Anthony
ef79dd95cf Merge branch 'sync-store' into develop 2020-09-04 20:19:20 +10:00
Lea Anthony
d49b146eaa Add mutex to store 2020-09-04 20:08:31 +10:00
Lea Anthony
8c051e004d Release v1.7.2-pre4 2020-09-03 20:13:55 +10:00
Lea Anthony
c02b9ac032 Update contributors 2020-09-03 20:12:57 +10:00
Dmitry
35f839ae65 Support Ctlos Linux 2020-09-03 20:11:39 +10:00
Lea Anthony
0474f15c05 Simplify Store type conversions 2020-09-01 20:36:18 +10:00
Lea Anthony
ba0af0c16d Lint fix! 2020-08-31 21:17:04 +10:00
Lea Anthony
c9f1247284 Updated to use specialised update fuctions 2020-08-31 21:10:32 +10:00
Lea Anthony
baa661532d Now using reflection
Update() can now take specialised functions
2020-08-31 21:10:20 +10:00
Lea Anthony
43a5f410d9 Major updates
Automatic type conversion
Support struct data
Custom error handler
2020-08-31 20:00:39 +10:00
Lea Anthony
0ab6a93e0c Update runtime defs
Use runtime.Store instead of wails.Store
2020-08-30 16:07:53 +10:00
Lea Anthony
485df87560 Update vanilla template 2020-08-30 15:49:37 +10:00
Lea Anthony
176c447e87 Update to latest assets 2020-08-30 15:49:29 +10:00
Lea Anthony
aa9cb5e58e Add store to js runtime 2020-08-30 15:49:12 +10:00
Lea Anthony
9a7be38462 Update WailsInit checker 2020-08-30 15:47:32 +10:00
Lea Anthony
678c2194db Add Sync Store 2020-08-30 15:46:50 +10:00
Lea Anthony
9f9c9e27de Move runtime out to package 2020-08-30 15:46:31 +10:00
Lea Anthony
f86996705b v1.7.1 2020-07-03 20:07:02 +10:00
Lea Anthony
254aa664d7 Fix contributors 2020-07-03 20:07:02 +10:00
Lea Anthony
c8371ee824 v1.7.0-pre2 2020-07-03 20:07:02 +10:00
Lea Anthony
02e0250555 fix: vanilla template for windows 2020-07-03 20:07:02 +10:00
Lea Anthony
e1b025cab6 fix: windows icon name 2020-07-03 20:07:02 +10:00
Lea Anthony
626854f1b7 free filter memory 2020-07-03 20:07:02 +10:00
Lea Anthony
98468d1c4d v1.7.0-pre1 2020-07-03 20:07:02 +10:00
Lea Anthony
1062aeb136 Add ldflags option to build 2020-07-03 20:07:02 +10:00
Lea Anthony
aa93e5d8d2 Merge branch 'develop' 2020-06-19 09:42:15 +10:00
49 changed files with 3768 additions and 1352 deletions

View File

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

View File

@@ -12,7 +12,6 @@
<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/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>
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!
@@ -57,7 +56,7 @@ _Ubuntu: 16.04, 18.04, 19.04_
_Also succesfully tested on: Zorin 15, Parrot 4.7, Linuxmint 19, Elementary 5, Kali, Neon_, Pop!_OS
#### Arch Linux / ArchLabs
#### Arch Linux / ArchLabs / Ctlos Linux
`sudo pacman -S webkit2gtk gtk3`

13
app.go
View File

@@ -13,6 +13,7 @@ import (
"github.com/wailsapp/wails/lib/ipc"
"github.com/wailsapp/wails/lib/logger"
"github.com/wailsapp/wails/lib/renderer"
wailsruntime "github.com/wailsapp/wails/runtime"
)
// -------------------------------- Compile time Flags ------------------------------
@@ -20,6 +21,16 @@ import (
// BuildMode indicates what mode we are in
var BuildMode = cmd.BuildModeProd
// Runtime is the Go Runtime struct
type Runtime = wailsruntime.Runtime
// Store is a state store used for syncing with
// the front end
type Store = wailsruntime.Store
// CustomLogger is a specialised logger
type CustomLogger = logger.CustomLogger
// ----------------------------------------------------------------------------------
// App defines the main application struct
@@ -125,7 +136,7 @@ func (a *App) start() error {
a.ipc.Start(a.eventManager, a.bindingManager)
// Create the runtime
a.runtime = NewRuntime(a.eventManager, a.renderer)
a.runtime = wailsruntime.NewRuntime(a.eventManager, a.renderer)
// Start binding manager and give it our renderer
err = a.bindingManager.Start(a.renderer, a.runtime)

View File

@@ -63,6 +63,8 @@ const (
PopOS
// Solus distribution
Solus
// Ctlos Linux distribution
Ctlos
)
// DistroInfo contains all the information relating to a linux distribution
@@ -129,6 +131,8 @@ func parseOsRelease(osRelease string) *DistroInfo {
result.Distribution = Arch
case "archlabs":
result.Distribution = ArchLabs
case "ctlos":
result.Distribution = Ctlos
case "debian":
result.Distribution = Debian
case "ubuntu":

View File

@@ -193,7 +193,16 @@ distributions:
name: ArchLabs
gccversioncommand: *gccdumpversion
programs: *archdefaultprograms
libraries: *archdefaultlibraries
libraries: *archdefaultlibraries
ctlos:
id: ctlos
releases:
default:
version: default
name: Ctlos Linux
gccversioncommand: *gccdumpversion
programs: *archdefaultprograms
libraries: *archdefaultlibraries
manjaro:
id: manjaro
releases:

View File

@@ -276,7 +276,7 @@ func CheckDependencies(logger *Logger) (bool, error) {
switch distroInfo.Distribution {
case Ubuntu, Debian, Zorin, Parrot, Linuxmint, Elementary, Kali, Neon, Deepin, Raspbian, PopOS:
libraryChecker = DpkgInstalled
case Arch, ArcoLinux, ArchLabs, Manjaro, ManjaroARM:
case Arch, ArcoLinux, ArchLabs, Ctlos, Manjaro, ManjaroARM:
libraryChecker = PacmanInstalled
case CentOS, Fedora, Tumbleweed, Leap:
libraryChecker = RpmInstalled

View File

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

View File

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

View File

@@ -0,0 +1,90 @@
*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

@@ -0,0 +1,31 @@
{
"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.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,17 @@
<!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

@@ -0,0 +1,110 @@
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

@@ -0,0 +1,69 @@
<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

@@ -0,0 +1,18 @@
<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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

View File

@@ -0,0 +1,13 @@
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

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

View File

@@ -0,0 +1,27 @@
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

@@ -0,0 +1,14 @@
{
"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

@@ -0,0 +1,46 @@
package main
import (
"math/rand"
"github.com/wailsapp/wails"
)
// Counter is what we use for counting
type Counter struct {
r *wails.Runtime
store *wails.Store
}
// WailsInit is called when the component is being initialised
func (c *Counter) WailsInit(runtime *wails.Runtime) error {
c.r = runtime
c.store = runtime.Store.New("Counter", 0)
return nil
}
// RandomValue sets the counter to a random value
func (c *Counter) RandomValue() {
c.store.Set(rand.Intn(1000))
}
// Increment will increment the counter
func (c *Counter) Increment() {
increment := func(data int) int {
return data + 1
}
// Update the store using the increment function
c.store.Update(increment)
}
// Decrement will decrement the counter
func (c *Counter) Decrement() {
decrement := func(data int) int {
return data - 1
}
// Update the store using the decrement function
c.store.Update(decrement)
}

View File

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

View File

@@ -4,6 +4,8 @@ const runtime = require('@wailsapp/runtime');
// Main entry point
function start() {
var mystore = runtime.Store.New('Counter');
// Ensure the default app div is 100% wide/high
var app = document.getElementById('app');
app.style.width = '100%';
@@ -13,17 +15,32 @@ function start() {
app.innerHTML = `
<div class='logo'></div>
<div class='container'>
<button id='button'>Click Me!</button>
<div id='result'/>
<button onClick='window.backend.Counter.Increment()'>
Increment Counter
</button>
<button onClick='window.backend.Counter.Decrement()'>
Decrement Counter
</button>
</div>
<div class='result'>Counter: <span id='counter'></span></div>
<div class='container'>
<input id='newCounter' type="number" value="0"/>
<button id='setvalue'>Set Counter Value</button>
<button onclick='window.backend.Counter.RandomValue()'>Set to Random Value</button>
</div>
`;
// Connect button to Go method
document.getElementById('button').onclick = function() {
window.backend.basic().then( function(result) {
document.getElementById('result').innerText = result;
});
// Connect counter value button to Go method
document.getElementById('setvalue').onclick = function() {
let newValue = parseInt(document.getElementById('newCounter').value,10);
mystore.set(newValue);
};
mystore.subscribe( function(state) {
document.getElementById('counter').innerText = state;
});
mystore.set(0);
};
// We provide our entrypoint as a callback for runtime.Init

View File

@@ -5,10 +5,6 @@ import (
"github.com/wailsapp/wails"
)
func basic() string {
return "Hello World!"
}
func main() {
js := mewn.String("./frontend/build/main.js")
@@ -22,6 +18,6 @@ func main() {
CSS: css,
Colour: "#131313",
})
app.Bind(basic)
app.Bind(&Counter{})
app.Run()
}

View File

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

View File

@@ -196,7 +196,7 @@ func (b *boundMethod) processWailsInit() error {
// It must be *wails.Runtime
inputName := b.inputs[0].String()
b.log.Debugf("WailsInit input type: %s", inputName)
if inputName != "*wails.Runtime" {
if inputName != "*runtime.Runtime" {
return fmt.Errorf("Invalid WailsInit() definition. Expected input to be wails.Runtime, but got %s", inputName)
}
@@ -219,7 +219,7 @@ func (b *boundMethod) processWailsInit() error {
}
func (b *boundMethod) processWailsShutdown() error {
// We must have only 1 input, it must be *wails.Runtime
// We must not have any inputs
if len(b.inputs) != 0 {
return fmt.Errorf("Invalid WailsShutdown() definition. Expected 0 inputs, but got %d", len(b.inputs))
}

View File

@@ -5,5 +5,5 @@ package renderer
import "github.com/leaanthony/mewn"
func init() {
mewn.AddAsset(".", "../../runtime/assets/wails.js", "1f8b08000000000000ff94586d8f1bb711fe2b2ba255481f8f91dc14a977c318cee582a838df053ebbfda00a01b53b92e85b915b2e758a20f1bf17b3efba97a2050c7bc519728633cf3c33f468b533a9d7d650c38e8fca45208f216917234f1d3bea1585b95b30077ee74c84df02fe28acf365825bacc42579d4b1e3793c9af246181f43489a4d0637a52acfa96df772cbfb6fcfb815b91c4dfab5e0c5561aee452a817b91c9de550edcb1a317163fd9e974b7fc0aa91719acb481df9c2dc0f943a57604b3db8253cb1ce2d184afc1c72eb0c0bd70727875b233f5ee8c8ca43f146057d1fd61bbb4f9785cff2bbcbdf74e9bf567b51e8f5fb3f85c971f1f55be83987cb4d92e0712187f6d33f9fd77281bb576db6852bbebcfae5f25653a86f1981ae9a9618cff6d0c6d864ca257f43b94125b9922b2bd93198ff18fe82df59b30974e36cea50e94076a7679cef0382f1c75afb9ee38c960a576b9274f235edfc204c6df560e95555cfa201bb6b28e56308ab4890cf322a38e5bde5d17d8b103112c82586a93557e71cb588b2f873132f2399a9fdcf67da7d19f2a1adf43fc82b04330fa059c28c281714073f6494a1ac5264485b3dee225c54695777bd306abae02dc8067149210eea917a59cb040e76718f788cb12228c59ea49823900c61b4f66467bc2e9739f6781b12699c7903489abc34a7e86e56efde2ae5d609dd6ccacec8b4af940e99fca194ce54b7aab81deb573d6bda8950db47e515ee52f6a95ed756c7b1d5befb29cdc1560be7cba7971dfa13dbdd1fb456359bda0b86c0d68395ff4dca7da1c34a1c45cc68663fa962a7d98fd1c7b5ea8436e55164348ce9854afe85e9bcceec55ee9bc5c3a9dade1fdf325b1876569d307f0a20484358b1b25f8c383332a17da3cda07a086712d72306bbff971d2d50cc849023fb482042e2e989ec3821a16e8dfefef6e455d6e7a75a08eb1d0dd2dad11ab28c9ed9af0630e8f90c7866fa12cd51a620803e51dde28a524abb16306a2bc11e90a3043c9aa91ec5b940c855923841a1a4351d98856351e0c0b78cf3d2fcefad2639b9bb64c77793e927e3c6e3e4e27eae5847103fbe83767b7ba04da67de715b675527993d6a692ec825b9d85316f61b9d032de67a5193de8f13867aa92cc17fd65bb03b4f8708b2b4423725572acf236f2372612e48e4f516b2c8eebc883ec1bf7750fa68f6731c910bcd5860dcb3044dc8a3af8ffc55992c8738e50e903a62cb1d94367f84d885c4bb03e6096147f811c94f9bf5adda221633e555fc24cfc82b9a8554f97453c5d29ad2e620aa582330c21008eb33ea7aa4a446a720171543757a073caad15a53f293b3fb129ce8ea6f98c2e5ebba750d1a16f6b2c179ea0e85b7435ac6801b8999fba28dffcbdb0fcea9039d76547fb651acc17f5226b3db7f60a341f4cc278b9798fcdd64f2fdf4ddbbb77ffdeefbef26efde4ddf7c547e235cb597b25011c0b663f4c8d00a5fa3675d77444da44de99549b17f01637ee3ec3e42873f1f0ae800618cf511a62d52519aabb28c5419a9a83d90b040fd4697dc30ee11b197538ebfc555433067cdac9dbd842a8afc50b73fc3f8e5742411f6132925f59772ca42e09bb352b96b4b6533370b897f9d4ef345d31fd0e96d75d30425a2d8951bea06b9bcee3be9139ccd17a2cc75da7aa4dc7ab705e34b562fd329b2af3c9a015221248a127804e309f77565df9f39fb80e610f16d5dc33efaa50d03417d7261181f4d7a7cd79aa369e89dbeeab24eafda794695a55e9bd36918d49e45a709fcd0dd60c8a6d5ddbdec64735824ed3687038b67ff53bff7dc311cd67050f6384c876ec0604d042b30f4610c0dce110a6032790c7cd83fe4f0c7e9d44b7bfd2ac75fe5f116f63fd5b4113f9f8fe60b915a932a4f8d288b5c7b4a0461d56797c4f3939118a16d45d3ae15393949dc0fade0729ab80b39adadd40f04dc377aa096b141729b6a21ad139693489711968e8a1e55aeb3e8ab7a5465ea74e1239d81f17aa5c109c258e2e776814c3fb70b790c5519cded22d4ad1c44610bca1aabfabf5ad5ff9f55bd904ff80afbf0e0d1d4c2a6ab910a037d6e938e6f0df7c8b3dd8cd9f799b3f20769705a5cbf20edb921701f280bbc2590e7d94e8ccc20b5197cf934bbb2dbc21a3098780745ae52a0dffeabbcf876cd0961fdd27c72f94e5dae16c7b701457ffed3b80a823b1c1b4a28942b713e694a12dacb9399a943895a51a1ca12326c91ed0015475dfc413483072722fad8cc2011611d34595293ec8efabaa9d7196c38c4c93ac278aaceb895450bb7b6cf4bd20625faa6b3ea38f9a6cab983b52e3d64a3d188b496cedba61e5ac5ee9a8372ed4060c5591f673c831c3c44e8056f4e786f45dddc69b3c0625ca9ba3c0581f4c802bfb55eaf0ef1b39e83dcdcd395af58bca159c6ebc2ab961ae21a561eaea31fd56c8b95cb30757a983a18a42ea3e76943c7a2128cc7cc99ca3d115d238347c8ec91ac0839d8ae6b51cdc663dff287e35316aac6e343e01fb2ecbe2aa92737ac2f95d9b42a8fe6e9799d03fea2a4ae424c8cf0f0879786779a4b9b1d903dc164571b9d67880e188fafab21686630de57f7f72fb0deabb6fc2107c212c042fce0bdd3cb9d074a90d7092768ffdbb42c09e3202addfb0d807f3ffc21d2b2fc5cf919c3996f4f6ca2ceadcda07abcf7c20da8ec74ea7eaec137ce953f1d3eab6af0a30495080e3aeccc427d6bede319867af6dbd50dc2da803b8b80aebb3c4e82fc561e6fec3a76bc19d262cbabe496f1f1ce3cc9d25df368bd331f77b9d7450ef11dbfde6a1f5ff35f4139bf04759ed9fef184f34a728f3818f056554433e3c13daabc7aa3227b399cb5bbd5e1b08db34860950f1fd20763f739646b889fbcbb466866309365dd2ca6fa4dd1cee0b78936ade3035e309c7c831c8707518cd2eff1d7c18c32434386b27075f6c6e3b7ac6dc1d654257efe1f569e57cf8ee1167163d7a2e9456fdebc79135da9dd7ae3a32f6653314956134e8432c2f86b5b7bc2c4d9e835ad2f9f6e50035ed7b8d106a25b8b5afe75ad2b9befb6a6d173afeb5521401dcb02bfc687a0cecb185fcb901116162cf94f000000ffff04075a367c140000")
mewn.AddAsset(".", "../../runtime/assets/wails.js", "1f8b08000000000000ff94587f6f1b37d2fe2a2be27d5532a6b752ae875e56dd06a9eba23e38761127d73f744241ed8e24262b52478eac0812bffb61f6b76cb9770704b1440ec9e13ccfcc3cd460b135196a6bb8118747e5224c0f61d20c46c09d38e805c7a99b0907b87526a2cf317cdd58877e424b6c4a43e941274e16c9602cebc9e410c2a45e646851a68a82db66adb4b2fb0c42dab84807a36e2c40bc4e8d84384b51429ca79dab12a51307882d7d14c7e3fdfc336418e7b0d0067e7376030ef7a5d901cc760d4ecd0b480623b9044c5c104142ecd2fed5d9d654ab73364871bf01bb881ef6ebb92d86c3ea6f8cf6019d36cb8f6a391cbe74e2735b797854c51612f6dee6db025810f2a5c5ec8f3fc0d766cdb2c1a872174fae5f82321ee270c84d0adc0821ff36c4062133d10bfe1dcd325b1ec5d2e64e6638a47f717752b788b07469ed5ce6402170b32d0a41db41ecb87bc97527590e0bb52d903d8d78750b13847c5d3ae4cbb874413662611d2f69146913190171ce9db4b2bd2e8a434b229c8578ae4d5efa25ad100dbf1cc5c8a4cfd9fce4b66f5b8b6ed7b8f63d2467265b06935f2899621285443ace3e81a436ac43b471162d5d325e297fbf334db0aa2ca005b4c726654c0287d8a72311f8f484e340bcf41051cc326413c20085ac3db9311a99e4cf7dfe1a84a8c13c84490d5c1556f633ccb7cbb3ab8a205aab1bb3b0678d7ccfe877e50c4179ce6ed1b3bb76cebab35679cfea1785aa386bb56bae639bebd86a9595ec7e03e6d387dbb3ebf6cdeeb5dd2f9ad2ea8ce1bc39403707e86aa196ec0e7667d75c356b543a9d75f5326b70abc34ff8274612e473957db9f93901b951fbc2aa3cc13039a9be7ac177dae47617ef942efcdce97c096f9f0fc53b987b9b7d018c3d502a88a43682af08cea822d6e6d17ea1b220555c8059e2eac7519b67988e26f8433331c18b0ba1a638e34604fef787fbbbb84a51bdd873274468efb6ad589e7156d8259387021ea1488c5c83f76a0909869e714137da7296577c33bd295f4fe99264fd99453db36b98d59fcceb49a8e8d49fdad5538b8a434604bae7463e9ef4b265834d93dadba218a4301cd61f8e470ee9484803bbe83767d7da03ef9077d256a8ea496e0f3a3517ec925d6cb808bb952e803f4ef5ac2a943f8e44450c0ff851afc16e91f719647999119c5da9a288d046ecc25cb008f51af2c86e318e3ec0bfb6e031baf93989d88516220809624247a407acb6fc5599bc80444907546e122b1d785b3c42e2c204dd9e7022da3179a082a9cdf24ead898bb942953cc1996a9116215398ad28969935de161097b12662843e115627e56ec959c5ce985d9455adb5dbd356b5d58ab39f9cdd7970719bb37d08e72fdb56796b44d8a435cf33b7dfa0ed97720ab84909b94fdae05f5ebf734eedf9b86d0f270be325e0076572bbfe073527e2e374343b57fddf8c46df8fdfbc79fdd7efbeff6ef4e6cdf8d57b85abd8956bb908650158b75d2032bce4d7e059a71e701369e351998c7a1e0a812b67771139fc71bf819610c6588c08b6484559a1bc8f948f54d46cc844e0b8d25e1a2181187b3996f43dbeaa0bcc49036cf45aac369b625fb54c23e4e5789012ed47699a72b84cc72204797d922af74daa5c4fcd2ca5ff8ec7e9acee29e4f4babce98466e2cdd6afb8eb61f9d075df273c9bce625fe80c6a8f945b6ed760d08b6a988fa962a707d3632a8649c6193c824126a1caecbb1367bfd071c4f826af6117fdd28481913dbb30420e461dbf2bcbc138744e5f5560913948975254299b07fdfa5ba356a3f53b0d45da478499361ab52ab4879c35dd6a3a6be85762e4b7739f393d8713906c153f234285a4879ed24371801465df87f89a42e1e3ebb546aa94baf089df9b2cf1681d24db4dae10f2f97ee1ac4130794259f9040610a4b617d65dab6cd5ab4d461c0c0741c95e3953ed764e5571101347ce96b5e39c83f7e6cfdc23b236de9d3a507166a39c2f1b18a4e63ff92a84c4e1b0891e2924fadc41fbb94d68feb991b7ca7bbd34c7637fbfae418ea94136e4ec37ca8a1f693b37c5d9a459e648bf82f8afe41f482748bbd3bb09e86d155abd29eae428f3bccb9050c7b80e5c7a388d7adaff723c76b39d7dc9ca9bf47007bb9faa8e903c07763a8b336b3285dcc47e5310c76226ca8f6d7e9eee4c59828dca18b72ac3a5a389fba199b81c4fdc453aae4ea9de8b65767de156885eded6a9c51a27ac644d86a9e851153a8f3eab474579b4c148e760502f34b898093181a99d51139fda597a0865859cda59a8941dc61bbbe1a23e55ffe9a9fa7f3b55cfd227ad882456ef0dddd0a62d7f25073a6c276d2b3512a885b64f8e4e429c2421a6861e0fcb33b35dd90f1202174136bde139da1393e690d91c3e7db8b9b2eb8d35600878079b4265c0bffda7bff876291913ddd07474f9465d2e6687d781a6feffff8614042abf789ab975b56d6a6aca6e4c154ab28a36ca7bc849fd34da3889daf8635c6b4ac9e2e87d2d2f23265a6a8a4955890b0e955eab10acdb834b316e76d5b9b4e96343b746c2a5ac094af44d7baa93ec9b1273074bed11f2c160c09a934e1591ee9f4ac2a900e51aad67e3138926640e052044e485c46a87b736ae741baf07444223a580e31853e71341de59d48b7df24c4e50dbedca15940dbaeea0425689570ed585ab9f79344e7e68ea4e94b982a0d37de8b0075dce4f6123c7220f06093953ba174765c18fa8694769d96b836d0509d7623884a67e383916a1d41410827c97e70f654a3db96175a9dc66657ad4bf445c1740df38abb2908909c4085f3135b2b59cdb7c4fd5134c7eb5d2454eecc0e1f0a1ec513786e27df5f070a6eabd7816ee0b200e5022be43747abe45e08cea3a938cceff36f39e09897169fbb002c0b7fd2f71e6fdc7d2cf044f7c7b7226d9dcd9bcfa2da79b5c81ca8fc7f6eb12b076ceffb4ffa84a4dcf191931d2b0e2e484ead61a93af14ea9bdfae6e89d606dc4904542b4082bc4d0fb776993859ebefc4caaa9b27877bf304a5fbfa378c7bf37e5ba0de1490dc4b5225c983fc1594c339a85364bb773149d1c91df1a057b7ca24ba3108ee5115e54f1654bd1c3da3dad1fe3b8a646610a50fefb22fc6ee0ac897903c79520fe8989edcce5b99adba45d1d6d06713ad1ac77b75c148f60dd538da8853941e4a29a3e51fc94d4f867ead2489089f4f9ef1f25634add89a32d54f7fc70459be2c4f14d4ad5dc6754f7af5ead5abe84a6d972b8c3e99555951f2aaf04434c7847c6969573849febe64f5e9c32d59e0cb16b7da407467c90a5eb6bab2c5766d6a3bf7b25d1902b2b122c88746221656e5a49cc34c4cfe1d0000ffff6a61b37f93160000")
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,27 @@
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 Executable file → Normal file
View File

@@ -1,166 +1,20 @@
// 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
#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 (
"errors"
"runtime"
"sync"
"unsafe"
// 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
)
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.
// WebView is our webview interface
type WebView interface {
// Run() starts the main UI loop until the user closes the webview window or
// Terminate() is called.
@@ -199,175 +53,21 @@ type WebView interface {
Exit()
}
// DialogType is an enumeration of all supported system dialog types
type DialogType int
type ExternalInvokeCallbackFunc func(w WebView, data string)
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)))
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
}

View File

@@ -0,0 +1,57 @@
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

@@ -0,0 +1,13 @@
#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

@@ -0,0 +1,386 @@
// 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

@@ -0,0 +1,522 @@
/*
* 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

@@ -0,0 +1,368 @@
// 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

@@ -39,23 +39,6 @@ extern "C"
#include <stdlib.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
#include <windows.h>
@@ -76,22 +59,6 @@ struct webview_priv
DWORD saved_ex_style;
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;
@@ -182,9 +149,6 @@ struct webview_priv
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)
{
@@ -264,301 +228,6 @@ struct webview_priv
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)
#pragma comment(lib, "user32.lib")
@@ -1431,6 +1100,13 @@ struct webview_priv
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.top = 0;
rect.right = w->width;
@@ -1923,433 +1599,6 @@ struct webview_priv
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_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
}
#endif

View File

@@ -1,32 +0,0 @@
package wails
import (
"github.com/wailsapp/wails/lib/interfaces"
"github.com/wailsapp/wails/lib/logger"
"github.com/wailsapp/wails/runtime"
)
// CustomLogger type alias
type CustomLogger = logger.CustomLogger
// Runtime is the Wails Runtime Interface, given to a user who has defined the WailsInit method
type Runtime struct {
Events *runtime.Events
Log *runtime.Log
Dialog *runtime.Dialog
Window *runtime.Window
Browser *runtime.Browser
FileSystem *runtime.FileSystem
}
// NewRuntime creates a new Runtime struct
func NewRuntime(eventManager interfaces.EventManager, renderer interfaces.Renderer) *Runtime {
return &Runtime{
Events: runtime.NewEvents(eventManager),
Log: runtime.NewLog(),
Dialog: runtime.NewDialog(renderer),
Window: runtime.NewWindow(renderer),
Browser: runtime.NewBrowser(),
FileSystem: runtime.NewFileSystem(),
}
}

File diff suppressed because one or more lines are too long

View File

@@ -15,6 +15,7 @@ import { NewBinding } from './bindings';
import { Callback } from './calls';
import { AddScript, InjectCSS } from './utils';
import { AddIPCListener } from './ipc';
import * as Store from './store';
// Initialise global if not already
window.wails = window.wails || {};
@@ -42,6 +43,7 @@ var runtime = {
Heartbeat,
Acknowledge,
},
Store,
_: internal,
};

82
runtime/js/core/store.js Normal file
View File

@@ -0,0 +1,82 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
/**
* Creates a new sync store with the given name and optional default value
*
* @export
* @param {string} name
* @param {*} optionalDefault
*/
export function New(name, optionalDefault) {
var data;
// Check we are initialised
if( !window.wails) {
throw Error('Wails is not initialised');
}
// Store for the callbacks
let callbacks = [];
// Subscribe to updates by providing a callback
this.subscribe = (callback) => {
callbacks.push(callback);
};
// sets the store data to the provided `newdata` value
this.set = (newdata) => {
data = newdata;
// Emit a notification to back end
window.wails.Events.Emit('wails:sync:store:updatedbyfrontend:'+name, JSON.stringify(data));
// Notify callbacks
callbacks.forEach( function(callback) {
callback(data);
});
};
// update mutates the value in the store by calling the
// provided method with the current value. The value returned
// by the updater function will be set as the new store value
this.update = (updater) => {
var newValue = updater(data);
this.set(newValue);
};
// Setup event callback
window.wails.Events.On('wails:sync:store:updatedbybackend:'+name, function(result) {
// Parse data
result = JSON.parse(result);
// Todo: Potential preprocessing?
// Save data
data = result;
// Notify callbacks
callbacks.forEach( function(callback) {
callback(data);
});
});
// Set to the optional default if set
if( optionalDefault ) {
this.set(optionalDefault);
}
return this;
}

File diff suppressed because it is too large Load Diff

View File

@@ -13,10 +13,12 @@ const Log = require('./log');
const Browser = require('./browser');
const Events = require('./events');
const Init = require('./init');
const Store = require('./store');
module.exports = {
Log: Log,
Browser: Browser,
Events: Events,
Init: Init
Init: Init,
Store: Store,
};

492
runtime/js/runtime/package-lock.json generated Normal file
View File

@@ -0,0 +1,492 @@
{
"name": "@wailsapp/runtime",
"version": "1.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
},
"camelcase": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
"integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
"dev": true
},
"cliui": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
"integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
"dev": true,
"requires": {
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1",
"wrap-ansi": "^2.0.0"
}
},
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true
},
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true
},
"dts-dom": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/dts-dom/-/dts-dom-3.6.0.tgz",
"integrity": "sha512-on5jxTgt+A6r0Zyyz6ZRHXaAO7J1VPnOd6+AmvI1vH440AlAZZNc5rUHzgPuTjGlrVr1rOWQYNl7ZJK6rDohbw==",
"dev": true
},
"dts-gen": {
"version": "0.5.8",
"resolved": "https://registry.npmjs.org/dts-gen/-/dts-gen-0.5.8.tgz",
"integrity": "sha512-kIAV6dlHaF7r5J+tIuOC1BJls2P72YM0cyWQUR88zcJEpX2ccRZe+HmXLfkkvfPwjvSO3FEqUiyC8On/grx5qw==",
"dev": true,
"requires": {
"dts-dom": "^3.6.0",
"parse-git-config": "^1.1.1",
"typescript": "^3.5.1",
"yargs": "^4.8.1"
}
},
"error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
"dev": true,
"requires": {
"is-arrayish": "^0.2.1"
}
},
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
},
"find-up": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
"integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
"dev": true,
"requires": {
"path-exists": "^2.0.0",
"pinkie-promise": "^2.0.0"
}
},
"fs-exists-sync": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz",
"integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=",
"dev": true
},
"get-caller-file": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
"integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
"dev": true
},
"git-config-path": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/git-config-path/-/git-config-path-1.0.1.tgz",
"integrity": "sha1-bTP37WPbDQ4RgTFQO6s6ykfVRmQ=",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
"fs-exists-sync": "^0.1.0",
"homedir-polyfill": "^1.0.0"
}
},
"graceful-fs": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
"dev": true
},
"homedir-polyfill": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
"integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==",
"dev": true,
"requires": {
"parse-passwd": "^1.0.0"
}
},
"hosted-git-info": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
"dev": true
},
"ini": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"dev": true
},
"invert-kv": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
"integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=",
"dev": true
},
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
"dev": true
},
"is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
"integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
"dev": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"requires": {
"number-is-nan": "^1.0.0"
}
},
"is-utf8": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
"integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
"dev": true
},
"lcid": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
"integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
"dev": true,
"requires": {
"invert-kv": "^1.0.0"
}
},
"load-json-file": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"parse-json": "^2.2.0",
"pify": "^2.0.0",
"pinkie-promise": "^2.0.0",
"strip-bom": "^2.0.0"
}
},
"lodash.assign": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
"integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=",
"dev": true
},
"normalize-package-data": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
"integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
"dev": true,
"requires": {
"hosted-git-info": "^2.1.4",
"resolve": "^1.10.0",
"semver": "2 || 3 || 4 || 5",
"validate-npm-package-license": "^3.0.1"
}
},
"number-is-nan": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"dev": true
},
"os-locale": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
"integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
"dev": true,
"requires": {
"lcid": "^1.0.0"
}
},
"parse-git-config": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/parse-git-config/-/parse-git-config-1.1.1.tgz",
"integrity": "sha1-06mYQxcTL1c5hxK7pDjhKVkN34w=",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
"fs-exists-sync": "^0.1.0",
"git-config-path": "^1.0.1",
"ini": "^1.3.4"
}
},
"parse-json": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
"integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
"dev": true,
"requires": {
"error-ex": "^1.2.0"
}
},
"parse-passwd": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
"integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
"dev": true
},
"path-exists": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
"integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
"dev": true,
"requires": {
"pinkie-promise": "^2.0.0"
}
},
"path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"path-type": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
"integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"pify": "^2.0.0",
"pinkie-promise": "^2.0.0"
}
},
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
},
"pinkie": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
"integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
"dev": true
},
"pinkie-promise": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
"integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
"dev": true,
"requires": {
"pinkie": "^2.0.0"
}
},
"read-pkg": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
"integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
"dev": true,
"requires": {
"load-json-file": "^1.0.0",
"normalize-package-data": "^2.3.2",
"path-type": "^1.0.0"
}
},
"read-pkg-up": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
"integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
"dev": true,
"requires": {
"find-up": "^1.0.0",
"read-pkg": "^1.0.0"
}
},
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"dev": true
},
"require-main-filename": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
"dev": true
},
"resolve": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
"integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
"dev": true,
"requires": {
"path-parse": "^1.0.6"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true
},
"spdx-correct": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
"integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==",
"dev": true,
"requires": {
"spdx-expression-parse": "^3.0.0",
"spdx-license-ids": "^3.0.0"
}
},
"spdx-exceptions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
"integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
"dev": true
},
"spdx-expression-parse": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
"integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
"dev": true,
"requires": {
"spdx-exceptions": "^2.1.0",
"spdx-license-ids": "^3.0.0"
}
},
"spdx-license-ids": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
"integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
"dev": true
},
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
"strip-ansi": "^3.0.0"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
"ansi-regex": "^2.0.0"
}
},
"strip-bom": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
"integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
"dev": true,
"requires": {
"is-utf8": "^0.2.0"
}
},
"typescript": {
"version": "3.9.7",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
"integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
"dev": true
},
"validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
"integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
"dev": true,
"requires": {
"spdx-correct": "^3.0.0",
"spdx-expression-parse": "^3.0.0"
}
},
"which-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
"integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=",
"dev": true
},
"window-size": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz",
"integrity": "sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU=",
"dev": true
},
"wrap-ansi": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
"dev": true,
"requires": {
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1"
}
},
"y18n": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
"integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
"dev": true
},
"yargs": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz",
"integrity": "sha1-wMQpJMpKqmsObaFznfshZDn53cA=",
"dev": true,
"requires": {
"cliui": "^3.2.0",
"decamelize": "^1.1.1",
"get-caller-file": "^1.0.1",
"lodash.assign": "^4.0.3",
"os-locale": "^1.4.0",
"read-pkg-up": "^1.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^1.0.1",
"set-blocking": "^2.0.0",
"string-width": "^1.0.1",
"which-module": "^1.0.0",
"window-size": "^0.2.0",
"y18n": "^3.2.1",
"yargs-parser": "^2.4.1"
}
},
"yargs-parser": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz",
"integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=",
"dev": true,
"requires": {
"camelcase": "^3.0.0",
"lodash.assign": "^4.0.6"
}
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@wailsapp/runtime",
"version": "1.0.11",
"version": "1.1.1",
"description": "Wails Javascript runtime library",
"main": "main.js",
"types": "runtime.d.ts",
@@ -25,4 +25,4 @@
"devDependencies": {
"dts-gen": "^0.5.8"
}
}
}

View File

@@ -21,6 +21,9 @@ declare const wailsapp__runtime: {
Info(message: string): void;
Warning(message: string): void;
};
Store: {
New(name: string, optionalDefault?: any): any;
};
};

View File

@@ -0,0 +1,27 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
/**
* Create a new Store with the given name and optional default value
*
* @export
* @param {string} name
* @param {*} optionalDefault
*/
function New(name, optionalDefault) {
return window.wails.Store.New(name, optionalDefault);
}
module.exports = {
New: New,
};

29
runtime/runtime.go Normal file
View File

@@ -0,0 +1,29 @@
package runtime
import "github.com/wailsapp/wails/lib/interfaces"
// Runtime is the Wails Runtime Interface, given to a user who has defined the WailsInit method
type Runtime struct {
Events *Events
Log *Log
Dialog *Dialog
Window *Window
Browser *Browser
FileSystem *FileSystem
Store *StoreProvider
}
// NewRuntime creates a new Runtime struct
func NewRuntime(eventManager interfaces.EventManager, renderer interfaces.Renderer) *Runtime {
result := &Runtime{
Events: NewEvents(eventManager),
Log: NewLog(),
Dialog: NewDialog(renderer),
Window: NewWindow(renderer),
Browser: NewBrowser(),
FileSystem: NewFileSystem(),
}
// We need a reference to itself
result.Store = NewStoreProvider(result)
return result
}

290
runtime/store.go Normal file
View File

@@ -0,0 +1,290 @@
// Package runtime contains all the methods and data structures related to the
// runtime library of Wails. This includes both Go and JS runtimes.
package runtime
import (
"bytes"
"encoding/json"
"fmt"
"log"
"reflect"
"sync"
)
// Options defines the optional data that may be used
// when creating a Store
type Options struct {
// The name of the store
Name string
// The runtime to attach the store to
Runtime *Runtime
// Indicates if notifying Go listeners should be notified of updates
// synchronously (on the current thread) or asynchronously using
// goroutines
NotifySynchronously bool
}
// StoreProvider is a struct that creates Stores
type StoreProvider struct {
runtime *Runtime
}
// NewStoreProvider creates new stores using the provided Runtime reference.
func NewStoreProvider(runtime *Runtime) *StoreProvider {
return &StoreProvider{
runtime: runtime,
}
}
// Store is where we keep named data
type Store struct {
name string
data reflect.Value
dataType reflect.Type
eventPrefix string
callbacks []reflect.Value
runtime *Runtime
notifySynchronously bool
// Lock
mux sync.Mutex
// Error handler
errorHandler func(error)
}
// New creates a new store
func (p *StoreProvider) New(name string, defaultValue interface{}) *Store {
dataType := reflect.TypeOf(defaultValue)
result := Store{
name: name,
runtime: p.runtime,
data: reflect.ValueOf(defaultValue),
dataType: dataType,
}
// Setup the sync listener
result.setupListener()
return &result
}
// OnError takes a function that will be called
// whenever an error occurs
func (s *Store) OnError(callback func(error)) {
s.errorHandler = callback
}
// Processes the updates sent by the front end
func (s *Store) processUpdatedData(data string) error {
// Decode incoming data
var rawdata json.RawMessage
d := json.NewDecoder(bytes.NewBufferString(data))
err := d.Decode(&rawdata)
if err != nil {
return err
}
// Create a new instance of our data and unmarshal
// the received value into it
newData := reflect.New(s.dataType).Interface()
err = json.Unmarshal(rawdata, &newData)
if err != nil {
return err
}
// Lock mutex for writing
s.mux.Lock()
// Handle nulls
if newData == nil {
s.data = reflect.Zero(s.dataType)
} else {
// Store the resultant value in the data store
s.data = reflect.ValueOf(newData).Elem()
}
// Unlock mutex
s.mux.Unlock()
return nil
}
// Setup listener for front end changes
func (s *Store) setupListener() {
// Listen for updates from the front end
s.runtime.Events.On("wails:sync:store:updatedbyfrontend:"+s.name, func(data ...interface{}) {
// Process the incoming data
err := s.processUpdatedData(data[0].(string))
if err != nil {
if s.errorHandler != nil {
s.errorHandler(err)
return
}
}
// Notify listeners
s.notify()
})
}
// notify the listeners of the current data state
func (s *Store) notify() {
// Execute callbacks
for _, callback := range s.callbacks {
// Build args
args := []reflect.Value{s.data}
if s.notifySynchronously {
callback.Call(args)
} else {
go callback.Call(args)
}
}
}
// Set will update the data held by the store
// and notify listeners of the change
func (s *Store) Set(data interface{}) error {
inType := reflect.TypeOf(data)
if inType != s.dataType {
return fmt.Errorf("invalid data given in Store.Set(). Expected %s, got %s", s.dataType.String(), inType.String())
}
// Save data
s.mux.Lock()
s.data = reflect.ValueOf(data)
s.mux.Unlock()
// Stringify data
newdata, err := json.Marshal(data)
if err != nil {
if s.errorHandler != nil {
return err
}
}
// Emit event to front end
s.runtime.Events.Emit("wails:sync:store:updatedbybackend:"+s.name, string(newdata))
// Notify subscribers
s.notify()
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
// providing a callback. Any updates to the store are sent
// to the callback
func (s *Store) Subscribe(callback interface{}) {
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
// of the correct signature. Absolutely cannot wait for
// generics to land rather than writing this nonsense.
func (s *Store) updaterCheck(updater interface{}) error {
// Get type
updaterType := reflect.TypeOf(updater)
// Check updater is a function
if updaterType.Kind() != reflect.Func {
return fmt.Errorf("invalid value given to store.Update(). Expected 'func(%s) %s'", s.dataType.String(), s.dataType.String())
}
// Check input param
if updaterType.NumIn() != 1 {
return fmt.Errorf("invalid number of parameters given in updater function. Expected 1")
}
// Check input data type
if updaterType.In(0) != s.dataType {
return fmt.Errorf("invalid type for input parameter given in updater function. Expected %s, got %s", s.dataType.String(), updaterType.In(0))
}
// Check output param
if updaterType.NumOut() != 1 {
return fmt.Errorf("invalid number of return parameters given in updater function. Expected 1")
}
// Check output data type
if updaterType.Out(0) != s.dataType {
return fmt.Errorf("invalid type for return parameter given in updater function. Expected %s, got %s", s.dataType.String(), updaterType.Out(0))
}
return nil
}
// Update takes a function that is passed the current state.
// The result of that function is then set as the new state
// of the store. This will notify listeners of the change
func (s *Store) Update(updater interface{}) {
err := s.updaterCheck(updater)
if err != nil {
log.Fatal(err)
}
// Build args
args := []reflect.Value{s.data}
// Make call
results := reflect.ValueOf(updater).Call(args)
// We will only have 1 result. Set the store to it
s.Set(results[0].Interface())
}