Compare commits

...

53 Commits

Author SHA1 Message Date
Lea Anthony
4a316a76fa Updated custom html 2019-01-29 08:30:38 +11:00
Lea Anthony
579747d0f7 update basic templates to use a frontend dir 2019-01-29 08:28:59 +11:00
Lea Anthony
6880c53082 Merge pull request #26 from wailsapp/Move-headless-capability-into-own-library
Move headless capability into own library
2019-01-23 08:36:02 +11:00
Lea Anthony
6e011e75c3 updated vue template 2019-01-23 08:35:12 +11:00
Lea Anthony
683ba7dc59 support bridge mode
streamline some messaging
2019-01-23 06:01:45 +11:00
Lea Anthony
717e598330 Linter fix 2019-01-16 08:19:30 +11:00
Lea Anthony
a6489a1044 Merge pull request #25 from wailsapp/create-wails-css
Update basic template to use default CSS
2019-01-15 18:53:12 +11:00
Lea Anthony
df911adcae Update basic template to use default CSS 2019-01-15 18:52:18 +11:00
Lea Anthony
d7c0b1ec58 Merge pull request #24 from wailsapp/create-wails-css
inject default css if none given
2019-01-15 18:51:08 +11:00
Lea Anthony
7135d4fa27 inject default css if none given 2019-01-15 18:50:26 +11:00
Lea Anthony
83e063bf2b Merge pull request #23 from wailsapp/Update-execution-order-in-Headless-mode
Update execution order in headless mode
2019-01-15 08:36:10 +11:00
Lea Anthony
6e0773b355 fix for rendering fragments via headless 2019-01-15 08:34:32 +11:00
Lea Anthony
60f34223b0 bugfix for force rebuild 2019-01-15 08:33:48 +11:00
Lea Anthony
539be2ce84 Updated comments 2019-01-14 22:46:45 +11:00
Lea Anthony
561198b81b Fix unicode escaping 2019-01-14 19:18:03 +11:00
Lea Anthony
c823215eb6 Merge pull request #21 from wailsapp/Fix-npm-project-name
Fix npm package name
2019-01-13 18:04:39 +11:00
Lea Anthony
93f890f6d9 Fix npm package name 2019-01-13 18:02:51 +11:00
Lea Anthony
8a3aec6866 Merge pull request #20 from wailsapp/Update-execution-order-in-Headless-mode
Numerous fixes for headless mode.
2019-01-13 17:58:34 +11:00
Lea Anthony
1ef8ed73ab Remove debug statements 2019-01-13 17:57:21 +11:00
Lea Anthony
9004c3955e Numerous fixes for headless mode.
Remove script dom elements for internal calls.
2019-01-13 17:54:38 +11:00
Lea Anthony
4c98ce7da1 Merge pull request #19 from wailsapp/Update-execution-order-in-Headless-mode
fix for ipc binding
2019-01-12 16:00:44 +11:00
Lea Anthony
0ae5381203 fix for ipc binding
made reconnect modal a bit better
2019-01-12 15:56:49 +11:00
Lea Anthony
3f2f1b45f6 Merge pull request #18 from wailsapp/Support-Packaging
Add Licenses
2019-01-12 09:37:27 +11:00
Lea Anthony
e6bec8f7cc Add Licenses 2019-01-12 09:26:51 +11:00
Lea Anthony
f1f15fc1c5 Merge pull request #17 from wailsapp/Support-Packaging
Initial port of packager
2019-01-11 21:17:53 +11:00
Lea Anthony
bcca09563c Initial port of packager 2019-01-11 21:16:52 +11:00
Lea Anthony
ee355659ce Merge pull request #16 from wailsapp/change-code-mount-point
now binds go code to window.backend
2019-01-11 20:03:20 +11:00
Lea Anthony
a660e4a9da now binds go code to window.backend 2019-01-11 20:02:43 +11:00
Lea Anthony
a44fd57e98 Merge pull request #15 from wailsapp/Port-Build
Port build
2019-01-11 07:01:18 +11:00
Lea Anthony
85de0bbf8a remove console.log 2019-01-11 07:00:40 +11:00
Lea Anthony
7274b6d19c Args messages to debug 2019-01-11 06:53:34 +11:00
Lea Anthony
c0371f141a Merge pull request #14 from wailsapp/Port-Build
Update vue template to use BoxString
2019-01-11 06:49:31 +11:00
Lea Anthony
5c96264234 Update vue template to use BoxString
Made arg marshalling messages Debug rather than Info
2019-01-11 06:39:42 +11:00
Lea Anthony
a3c41d1740 Merge pull request #13 from wailsapp/Port-Build
Fix frameworkspinner
2019-01-11 06:32:41 +11:00
Lea Anthony
7c15b780e2 Fix frameworkspinner
Fix BoxString for frameworks
2019-01-11 06:32:13 +11:00
Lea Anthony
fb081b4876 Merge pull request #12 from wailsapp/Port-Build
Port build
2019-01-11 06:16:30 +11:00
Lea Anthony
a28315f38b Fix Packr deprecation errors
Improved comments
2019-01-11 06:15:49 +11:00
Lea Anthony
49e4f00b62 Added release mode flag
fixed logging
logging info level by default
2019-01-10 22:48:54 +11:00
Lea Anthony
9167063976 Merge pull request #11 from wailsapp/Port-Build
Made init less verbose
2019-01-09 08:26:56 +11:00
Lea Anthony
aaa425724c Made init less verbose 2019-01-09 08:26:19 +11:00
Lea Anthony
ffdbb0af64 Merge pull request #10 from wailsapp/Port-Build
updated runtime assets
2019-01-09 08:10:14 +11:00
Lea Anthony
12dec650de updated runtime assets 2019-01-09 06:36:34 +11:00
Lea Anthony
d4b2563e9b Merge pull request #9 from wailsapp/Port-Build
mute logging of injected scripts
2019-01-09 06:35:52 +11:00
Lea Anthony
55817b65b5 mute logging of injected scripts 2019-01-09 06:35:22 +11:00
Lea Anthony
ab6e7531b4 Merge pull request #8 from wailsapp/Port-Build
Add assets
2019-01-08 21:33:04 +11:00
Lea Anthony
1e4cf9b0ad Add assets 2019-01-08 21:31:50 +11:00
Lea Anthony
13efa58c9a Merge pull request #7 from wailsapp/Port-Build
fixes building on linux
2019-01-08 21:04:35 +11:00
Lea Anthony
88b9b40bfe fixes building on linux
install packr on setup
2019-01-08 21:03:40 +11:00
Lea Anthony
0011e39c55 Merge pull request #6 from wailsapp/Port-Build
Initial Port
2019-01-08 20:15:49 +11:00
Lea Anthony
aa6b67734b Initial Port
Updated templates
2019-01-08 17:48:37 +11:00
Lea Anthony
733258cc83 Merge pull request #5 from wailsapp/Port-Build
Initial commit of wails build
2019-01-08 07:59:22 +11:00
Lea Anthony
4742fd7ed2 Initial commit of wails build 2019-01-08 07:58:46 +11:00
Lea Anthony
96996431b4 Merge pull request #4 from wailsapp/Port-Init
Port init
2019-01-07 22:59:27 +11:00
98 changed files with 7215 additions and 55 deletions

5
.gitignore vendored
View File

@@ -10,3 +10,8 @@
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
examples/**/example*
!examples/**/*.*
cmd/wails/wails
.DS_Store

23
a_wails-packr.go Normal file

File diff suppressed because one or more lines are too long

145
app.go Normal file
View File

@@ -0,0 +1,145 @@
package wails
import (
"github.com/wailsapp/wails/cmd"
"github.com/wailsapp/wails/cmd/frameworks"
)
// -------------------------------- Compile time Flags ------------------------------
// DebugMode indicates if we are in debug Mode
var DebugMode = "true"
// ----------------------------------------------------------------------------------
// App defines the main application struct
type App struct {
config *AppConfig // The Application configuration object
cli *cmd.Cli // In debug mode, we have a cli
renderer Renderer // The renderer is what we will render the app to
logLevel string // The log level of the app
headless bool // Indicates if the app should be started in headless mode
ipc *ipcManager // Handles the IPC calls
log *CustomLogger // Logger
bindingManager *bindingManager // Handles binding of Go code to renderer
eventManager *eventManager // Handles all the events
runtime *Runtime // The runtime object for registered structs
// This is a list of all the JS/CSS that needs injecting
// It will get injected in order
jsCache []string
cssCache []string
}
// CreateApp creates the application window with the given configuration
// If none given, the defaults are used
func CreateApp(optionalConfig ...*AppConfig) *App {
var userConfig *AppConfig
if len(optionalConfig) > 0 {
userConfig = optionalConfig[0]
}
result := &App{
logLevel: "info",
renderer: &webViewRenderer{},
ipc: newIPCManager(),
bindingManager: newBindingManager(),
eventManager: newEventManager(),
log: newCustomLogger("App"),
}
appconfig, err := newAppConfig(userConfig)
if err != nil {
result.log.Fatalf("Cannot use custom HTML: %s", err.Error())
}
result.config = appconfig
// Set up the CLI if not in release mode
if DebugMode == "true" {
result.cli = result.setupCli()
} else {
// Disable Inspector in release mode
result.config.DisableInspector = true
}
return result
}
// Run the app
func (a *App) Run() error {
if DebugMode == "true" {
return a.cli.Run()
}
a.logLevel = "error"
return a.start()
}
func (a *App) start() error {
// Set the log level
setLogLevel(a.logLevel)
// Log starup
a.log.Info("Starting")
// Check if we are to run in headless mode
if a.headless {
a.renderer = &Headless{}
}
// Initialise the renderer
err := a.renderer.Initialise(a.config, a.ipc, a.eventManager)
if err != nil {
return err
}
// Start event manager and give it our renderer
a.eventManager.start(a.renderer)
// Start the IPC Manager and give it the event manager and binding manager
a.ipc.start(a.eventManager, a.bindingManager)
// Create the runtime
a.runtime = newRuntime(a.eventManager, a.renderer)
// Start binding manager and give it our renderer
err = a.bindingManager.start(a.renderer, a.runtime)
if err != nil {
return err
}
// Inject framework, if specified
if frameworks.FrameworkToUse != nil {
a.renderer.InjectFramework(frameworks.FrameworkToUse.JS, frameworks.FrameworkToUse.CSS)
}
// Inject CSS
a.renderer.AddCSSList(a.cssCache)
// Inject JS
a.renderer.AddJSList(a.jsCache)
// Run the renderer
a.renderer.Run()
return nil
}
// Bind allows the user to bind the given object
// with the application
func (a *App) Bind(object interface{}) {
a.bindingManager.bind(object)
}
// AddJS adds a piece of Javascript to a cache that
// gets injected at runtime
func (a *App) AddJS(js string) {
a.jsCache = append(a.jsCache, js)
}
// AddCSS adds a CSS string to a cache that
// gets injected at runtime
func (a *App) AddCSS(js string) {
a.cssCache = append(a.cssCache, js)
}

31
app_cli.go Normal file
View File

@@ -0,0 +1,31 @@
package wails
import (
"fmt"
"github.com/wailsapp/wails/cmd"
)
// setupCli creates a new cli handler for the application
func (app *App) setupCli() *cmd.Cli {
// Create a new cli
result := cmd.NewCli(app.config.Title, "Debug build")
// Setup cli to handle loglevel and headless flags
result.
StringFlag("loglevel", "Sets the log level [debug|info|error|panic|fatal]. Default debug", &app.logLevel).
BoolFlag("headless", "Runs the app in headless mode", &app.headless).
Action(app.start)
// Banner
result.PreRun(func(cli *cmd.Cli) error {
log := cmd.NewLogger()
log.PrintBanner()
fmt.Println()
log.YellowUnderline(app.config.Title + " - Debug Build")
return nil
})
return result
}

99
app_config.go Normal file
View File

@@ -0,0 +1,99 @@
package wails
import (
"strings"
"github.com/dchest/htmlmin"
"github.com/gobuffalo/packr"
)
var assets = packr.NewBox("./assets/default")
// AppConfig is the configuration structure used when creating a Wails App object
type AppConfig struct {
Width, Height int
Title string
defaultHTML string
HTML string
JS string
CSS string
Colour string
Resizable bool
DisableInspector bool
isHTMLFragment bool
}
func (a *AppConfig) merge(in *AppConfig) error {
if in.CSS != "" {
a.CSS = in.CSS
}
if in.Title != "" {
a.Title = in.Title
}
if in.HTML != "" {
minified, err := htmlmin.Minify([]byte(in.HTML), &htmlmin.Options{
MinifyScripts: true,
})
if err != nil {
return err
}
inlineHTML := string(minified)
inlineHTML = strings.Replace(inlineHTML, "'", "\\'", -1)
inlineHTML = strings.Replace(inlineHTML, "\n", " ", -1)
a.HTML = strings.TrimSpace(inlineHTML)
// Deduce whether this is a full html page or a fragment
// The document is determined to be a fragment if an HMTL
// tag exists and is located before the first div tag
HTMLTagIndex := strings.Index(a.HTML, "<html")
DivTagIndex := strings.Index(a.HTML, "<div")
if HTMLTagIndex == -1 {
a.isHTMLFragment = true
} else {
if DivTagIndex < HTMLTagIndex {
a.isHTMLFragment = true
}
}
}
if in.Colour != "" {
a.Colour = in.Colour
}
if in.JS != "" {
a.JS = in.JS
}
if in.Width != 0 {
a.Width = in.Width
}
if in.Height != 0 {
a.Height = in.Height
}
a.Resizable = in.Resizable
a.DisableInspector = in.DisableInspector
return nil
}
// Creates the default configuration
func newAppConfig(userConfig *AppConfig) (*AppConfig, error) {
result := &AppConfig{
Width: 800,
Height: 600,
Resizable: true,
Title: "My Wails App",
Colour: "#FFF", // White by default
HTML: BoxString(&defaultAssets, "default.html"),
}
if userConfig != nil {
err := result.merge(userConfig)
if err != nil {
return nil, err
}
}
return result, nil
}

View File

@@ -0,0 +1,214 @@
/*
Wails Bridge (c) 2019-present Lea Anthony
This library creates a bridge between your application
and the frontend, allowing you to develop your app using
standard tooling (browser extensions, live reload, etc).
Usage:
```
import Bridge from "./wailsbridge";
Bridge.Start(startApp);
```
The given callback (startApp in the example) will be called
when the bridge has successfully initialised. It passes the
window.wails object back, in case it is not accessible directly.
*/
// Bridge object
window.wailsbridge = {
reconnectOverlay: null,
reconnectTimer: 300,
wsURL: "ws://localhost:34115/bridge",
connectionState: null,
config: {},
websocket: null,
callback: null,
overlayHTML:
'<div class="wails-reconnect-overlay"><div class="wails-reconnect-overlay-content"><div class="wails-reconnect-overlay-title">Wails Bridge</div><br><div class="wails-reconnect-overlay-loadingspinner"></div><br><div id="wails-reconnect-overlay-message">Waiting for backend</div></div></div>',
overlayCSS:
".wails-reconnect-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.6);font-family:sans-serif;display:none;z-index:999999}.wails-reconnect-overlay-content{padding:20px 30px;text-align:center;width:20em;position:relative;height:14em;border-radius:1em;margin:5% auto 0;background-color:#fff;box-shadow:1px 1px 20px 3px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC8AAAAuCAMAAACPpbA7AAAAqFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAAAAAAAAEBAQAAAAAAAAAAAAEBAQEBAQDAwMBAQEAAAABAQEAAAAAAAAAAAABAQEAAAAAAAACAgICAgIBAQEAAAAAAAAAAAAAAAAAAAAAAAABAQEAAAACAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBQWKCj6oAAAAN3RSTlMALiIqDhkGBAswJjP0GxP6NR4W9/ztjRDMhWU50G9g5eHXvbZ9XEI9xZTcqZl2aldKo55QwoCvZUgzhAAAAs9JREFUSMeNleeWqjAUhU0BCaH3Itiw9zKT93+zG02QK1hm/5HF+jzZJ6fQe6cyXE+jg9X7o9wxuylIIf4Tv2V3+bOrEXnf8dwQ/KQIGDN2/S+4OmVCVXL/ScBnfibxURqIByP/hONE8r8T+bDMlQ98KSl7Y8hzjpS8v1qtDh8u5f8KQpGpfnPPhqG8JeogN37Hq9eaN2xRhIwAaGnvws8F1ShxqK5ob2twYi1FAMD4rXsYtnC/JEiRbl4cUrCWhnMCLRFemXezXbb59QK4WASOsm6n2W1+4CBT2JmtzQ6fsrbGubR/NFbd2g5Y179+5w/GEHaKsHjYCet7CgrXU3txarNC7YxOVJtIj4/ERzMdZfzc31hp+8cD6eGILgarZY9uZ12hAs03vfBD9C171gS5Omz7OcvxALQIn4u8RRBBBcsi9WW2woO9ipLgfzpYlggg3ZRdROUC8KT7QLqq3W9KB5BbdFVg4929kdwp6+qaZnMCCNBdj+NyN1W885Ry/AL3D4AQbsVV4noCiM/C83kyYq80XlDAYQtralOiDzoRAHlotWl8q2tjvYlOgcg1A8jEApZa+C06TBdAz2Qv0wu11I/zZOyJQ6EwGez2P2b8PIQr1hwwnAZsAxwA4UAYOyXUxM/xp6tHAn4GUmPGM9R28oVxgC0e/zQJJI6DyhyZ1r7uzRQhpcW7x7vTaWSzKSG6aep77kroTEl3U81uSVaUTtgEINfC8epx+Q4F9SpplHG84Ek6m4RAq9/TLkOBrxyeuddZhHvGIp1XXfFy3Z3vtwNblKGiDn+J+92vwwABHghj7HnzlS1H5kB49AZvdGCFgiBPq69qfXPr3y++yilF0ON4R8eR7spAsLpZ95NqAW5tab1c4vkZm6aleajchMwYTdILQQTwE2OV411ZM9WztDjPql12caBi6gDpUKmDd4U1XNdQxZ4LIXQ5/Tr4P7I9tYcFrDK3AAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center}.wails-reconnect-overlay-title{font-size:2em}.wails-reconnect-overlay-message{font-size:1.3em}.wails-reconnect-overlay-loadingspinner{pointer-events:none;width:2.5em;height:2.5em;border:.4em solid transparent;border-color:#3E67EC #eee #eee;border-radius:50%;animation:loadingspin 1s linear infinite;margin:auto;padding:2.5em}@keyframes loadingspin{100%{transform:rotate(360deg)}}",
log: function (message) {
console.log(
"%c wails bridge %c " + message + " ",
"background: #aa0000; color: #fff; border-radius: 3px 0px 0px 3px; padding: 1px; font-size: 0.7rem",
"background: #009900; color: #fff; border-radius: 0px 3px 3px 0px; padding: 1px; font-size: 0.7rem"
);
}
};
// Adapted from webview - thanks zserge!
function injectCSS(css) {
var elem = document.createElement("style");
elem.setAttribute("type", "text/css");
if (elem.styleSheet) {
elem.styleSheet.cssText = css;
} else {
elem.appendChild(document.createTextNode(css));
}
var head = document.head || document.getElementsByTagName("head")[0];
head.appendChild(elem);
}
// Creates a node in the Dom
function createNode(parent, elementType, id, className, content) {
var d = document.createElement(elementType);
if (id) {
d.id = id;
}
if (className) {
d.className = className;
}
if (content) {
d.innerHTML = content;
}
parent.appendChild(d);
return d;
}
// Sets up the overlay
function setupOverlay() {
var body = document.body;
var wailsBridgeNode = createNode(body, "div", "wails-bridge");
wailsBridgeNode.innerHTML = window.wailsbridge.overlayHTML;
// Inject the overlay CSS
injectCSS(window.wailsbridge.overlayCSS);
}
// Start the Wails Bridge
function startBridge() {
// Setup the overlay
setupOverlay();
window.wailsbridge.websocket = null;
window.wailsbridge.connectTimer = null;
window.wailsbridge.reconnectOverlay = document.querySelector(
".wails-reconnect-overlay"
);
window.wailsbridge.connectionState = "disconnected";
// Shows the overlay
function showReconnectOverlay() {
window.wailsbridge.reconnectOverlay.style.display = "block";
}
// Hides the overlay
function hideReconnectOverlay() {
window.wailsbridge.reconnectOverlay.style.display = "none";
}
// Bridge external.invoke
window.external = {
invoke: function (msg) {
window.wailsbridge.websocket.send(msg);
}
};
// Adds a script to the Dom.
// Removes it if second parameter is true.
function addScript(script, remove) {
var s = document.createElement("script");
s.textContent = script;
document.head.appendChild(s);
// Remove internal messages from the DOM
if (remove) {
s.parentNode.removeChild(s);
}
}
// Handles incoming websocket connections
function handleConnect() {
window.wailsbridge.log("Connected to backend");
hideReconnectOverlay();
clearInterval(window.wailsbridge.connectTimer);
window.wailsbridge.websocket.onclose = handleDisconnect;
window.wailsbridge.websocket.onmessage = handleMessage;
window.wailsbridge.connectionState = "connected";
}
// Handles websocket disconnects
function handleDisconnect() {
window.wailsbridge.log("Disconnected from backend");
window.wailsbridge.websocket = null;
window.wailsbridge.connectionState = "disconnected";
showReconnectOverlay();
connect();
}
// Try to connect to the backend every 300ms (default value).
// Change this value in the main wailsbridge object.
function connect() {
window.wailsbridge.connectTimer = setInterval(function () {
if (window.wailsbridge.websocket == null) {
window.wailsbridge.websocket = new WebSocket(window.wailsbridge.wsURL);
window.wailsbridge.websocket.onopen = handleConnect;
window.wailsbridge.websocket.onerror = function (e) {
e.stopImmediatePropagation();
e.stopPropagation();
e.preventDefault();
window.wailsbridge.websocket = null;
return false;
};
}
}, window.wailsbridge.reconnectTimer);
}
function handleMessage(message) {
// As a bridge we ignore js and css injections
switch (message.data[0]) {
// Wails library - inject!
case "w":
addScript(message.data.slice(1));
// Now wails runtime is loaded, wails for the ready event
// and callback to the main app
window.wails.events.on("wails:loaded", function () {
window.wailsbridge.log("Wails Ready");
if (window.wailsbridge.callback) {
window.wailsbridge.log("Notifying application");
window.wailsbridge.callback(window.wails);
}
});
window.wailsbridge.log("Loaded Wails Runtime");
break;
// Notifications
case "n":
addScript(message.data.slice(1), true);
break;
// Binding
case "b":
var binding = message.data.slice(1)
//log("Binding: " + binding)
window.wails._.newBinding(binding);
break;
// Call back
case "c":
var callbackData = message.data.slice(1);
log("Callback = " + callbackData);
window.wails._.callback(callbackData);
break;
}
}
// Start by showing the overlay...
showReconnectOverlay();
// ...and attempt to connect
connect();
}
export default {
// The main function
// Passes the main Wails object to the callback if given.
Start: function (callback) {
// Save the callback
window.wailsbridge.callback = callback;
// Start Bridge
startBridge();
}
};

View File

@@ -0,0 +1,15 @@
/*
Wails Bridge (c) 2019-present Lea Anthony
This prod version is to get around having to rewrite your code
for production. When doing a release build, this file will be used
instead of the full version.
*/
export default {
// The main function
// Passes the main Wails object to the callback if given.
Start: function (callback) {
return callback();
}
};

View File

@@ -0,0 +1 @@
<div id="app"></div>

2
assets/default/jquery.3.3.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

39
assets/default/wails.css Normal file

File diff suppressed because one or more lines are too long

337
assets/default/wails.js Normal file
View File

@@ -0,0 +1,337 @@
// Wails runtime JS
(function () {
window.wails = window.wails || {};
window.backend = {};
/****************** Utility Functions ************************/
// -------------- Random --------------
// AwesomeRandom
function cryptoRandom() {
var array = new Uint32Array(1);
return window.crypto.getRandomValues(array)[0];
}
// LOLRandom
function basicRandom() {
return Math.random() * 9007199254740991;
}
// Pick one based on browser capability
var randomFunc;
if (window.crypto) {
randomFunc = cryptoRandom;
} else {
randomFunc = basicRandom;
}
// -------------- Identifiers ---------------
function isValidIdentifier(name) {
// Don't xss yourself :-)
try {
new Function("var " + name);
return true
} catch (e) {
return false
}
}
// -------------- JS ----------------
function addScript(js, callbackID) {
var script = document.createElement("script");
script.text = js;
document.body.appendChild(script);
window.wails.events.emit(callbackID);
}
// -------------- CSS ---------------
// Adapted from webview - thanks zserge!
function injectCSS(css) {
var elem = document.createElement('style');
elem.setAttribute('type', 'text/css');
if (elem.styleSheet) {
elem.styleSheet.cssText = css;
} else {
elem.appendChild(document.createTextNode(css));
}
var head = document.head || document.getElementsByTagName('head')[0];
head.appendChild(elem)
}
/************************* Bindings *************************/
var bindingsBasePath = window.backend;
// Creates the path given in the bindings path
function addBindingPath(pathSections) {
// Start at the base path
var currentPath = bindingsBasePath
// for each section of the given path
for (var section of pathSections) {
// Is section a valid javascript identifier?
if (!isValidIdentifier(section)) {
var errMessage = section + " is not a valid javascript identifier."
var err = new Error(errMessage)
return [null, err]
}
// Add if doesn't exist
if (!currentPath[section]) {
currentPath[section] = {}
}
// update current path to new path
currentPath = currentPath[section]
}
return [currentPath, null]
}
function newBinding(bindingName) {
// Get all the sections of the binding
var bindingSections = bindingName.split('.').splice(1);
// Get the actual function/method call name
var callName = bindingSections.pop();
let pathToBinding;
let err;
// Add path to binding
[pathToBinding, err] = addBindingPath(bindingSections)
if (err != null) {
// We need to return an error
return err
}
// Add binding call
pathToBinding[callName] = function () {
// No timeout by default
var timeout = 0;
// Actual function
function dynamic() {
var args = [].slice.call(arguments)
return call(bindingName, args, timeout);
}
// Allow setting timeout to function
dynamic.setTimeout = function (newTimeout) {
timeout = newTimeout;
}
// Allow getting timeout to function
dynamic.getTimeout = function () {
return timeout;
}
return dynamic;
}();
}
/************************************************************/
/*************************** Calls **************************/
var callbacks = {};
// Call sends a message to the backend to call the binding with the
// given data. A promise is returned and will be completed when the
// backend responds. This will be resolved when the call was successful
// or rejected if an error is passed back.
// There is a timeout mechanism. If the call doesn't respond in the given
// time (in milliseconds) then the promise is rejected.
function call(bindingName, data, timeout) {
// Timeout infinite by default
if (timeout == null || timeout == undefined) {
timeout = 0;
}
// Create a promise
return new Promise(function (resolve, reject) {
// Create a unique callbackID
var callbackID;
do {
callbackID = bindingName + "-" + randomFunc();
} while (callbacks[callbackID])
// Set timeout
if (timeout > 0) {
var timeoutHandle = setTimeout(function () {
reject(Error("Call to " + bindingName + " timed out. Request ID: " + callbackID))
}, timeout);
}
// Store callback
callbacks[callbackID] = {
timeoutHandle: timeoutHandle,
reject: reject,
resolve: resolve
}
try {
var payloaddata = JSON.stringify(data)
// Create the message
message = {
type: "call",
callbackid: callbackID,
payload: {
bindingName: bindingName,
data: payloaddata,
}
}
// Make the call
var payload = JSON.stringify(message)
external.invoke(payload);
} catch (e) {
console.error(e)
}
})
}
// Called by the backend to return data to a previously called
// binding invocation
function callback(incomingMessage) {
// Parse the message
var message
try {
message = JSON.parse(incomingMessage)
} catch (e) {
wails.log.debug("Invalid JSON passed to callback: " + e.message)
wails.log.debug("Message: " + incomingMessage)
return
}
callbackID = message.callbackid
callbackData = callbacks[callbackID]
if (!callbackData) {
console.error("Callback '" + callbackID + "' not registed!!!")
return
}
clearTimeout(callbackData.timeoutHandle)
delete callbacks[callbackID]
if (message.error) {
return callbackData.reject(message.error)
}
return callbackData.resolve(message.data)
}
/************************************************************/
/************************** Events **************************/
var eventListeners = {};
// Registers event listeners
function on(eventName, callback) {
eventListeners[eventName] = eventListeners[eventName] || [];
eventListeners[eventName].push(callback);
}
// notify informs frontend listeners that an event was emitted with the given data
function notify(eventName, data) {
if (eventListeners[eventName]) {
eventListeners[eventName].forEach(element => {
var parsedData = []
// Parse data if we have it
if (data) {
try {
parsedData = JSON.parse(data);
} catch (e) {
wails.log.error("Invalid JSON data sent to notify. Event name = " + eventName)
}
}
element.apply(null, parsedData);
});
}
}
// emit an event with the given name and data
function emit(eventName) {
// Calculate the data
var data = JSON.stringify([].slice.apply(arguments).slice(1));
// Notify backend
message = {
type: "event",
payload: {
name: eventName,
data: data,
}
}
external.invoke(JSON.stringify(message));
}
// Events calls
window.wails.events = { emit: emit, on: on };
/************************************************************/
/************************* Logging **************************/
// Sends a log message to the backend with the given
// level + message
function sendLogMessage(level, message) {
// Log Message
message = {
type: "log",
payload: {
level: level,
message: message,
}
}
external.invoke(JSON.stringify(message));
}
function logDebug(message) {
sendLogMessage("debug", message);
}
function logInfo(message) {
sendLogMessage("info", message);
}
function logWarning(message) {
sendLogMessage("warning", message);
}
function logError(message) {
sendLogMessage("error", message);
}
function logFatal(message) {
sendLogMessage("fatal", message);
}
window.wails.log = {
debug: logDebug,
info: logInfo,
warning: logWarning,
error: logError,
fatal: logFatal,
};
/************************** Exports *************************/
window.wails._ = {
newBinding: newBinding,
callback: callback,
notify: notify,
sendLogMessage: sendLogMessage,
callbacks: callbacks,
injectCSS: injectCSS,
addScript: addScript,
}
/************************************************************/
// Notify backend that the runtime has finished loading
window.wails.events.emit("wails:loaded");
})()

229
assets/headless/index.html Normal file
View File

@@ -0,0 +1,229 @@
<html>
<head>
<title>Wails Headless</title>
<style>
.wails-reconnect-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
font-family: sans-serif;
display: none;
z-index: 999999;
}
.wails-reconnect-overlay-content {
padding: 20px 30px;
text-align: center;
width: 20em;
position: relative;
height: 14em;
border-radius: 1em;
margin: 5% auto 0;
background-color: white;
box-shadow: 1px 1px 20px 3px;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC8AAAAuCAMAAACPpbA7AAAAqFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAAAAAAAAEBAQAAAAAAAAAAAAEBAQEBAQDAwMBAQEAAAABAQEAAAAAAAAAAAABAQEAAAAAAAACAgICAgIBAQEAAAAAAAAAAAAAAAAAAAAAAAABAQEAAAACAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBQWKCj6oAAAAN3RSTlMALiIqDhkGBAswJjP0GxP6NR4W9/ztjRDMhWU50G9g5eHXvbZ9XEI9xZTcqZl2aldKo55QwoCvZUgzhAAAAs9JREFUSMeNleeWqjAUhU0BCaH3Itiw9zKT93+zG02QK1hm/5HF+jzZJ6fQe6cyXE+jg9X7o9wxuylIIf4Tv2V3+bOrEXnf8dwQ/KQIGDN2/S+4OmVCVXL/ScBnfibxURqIByP/hONE8r8T+bDMlQ98KSl7Y8hzjpS8v1qtDh8u5f8KQpGpfnPPhqG8JeogN37Hq9eaN2xRhIwAaGnvws8F1ShxqK5ob2twYi1FAMD4rXsYtnC/JEiRbl4cUrCWhnMCLRFemXezXbb59QK4WASOsm6n2W1+4CBT2JmtzQ6fsrbGubR/NFbd2g5Y179+5w/GEHaKsHjYCet7CgrXU3txarNC7YxOVJtIj4/ERzMdZfzc31hp+8cD6eGILgarZY9uZ12hAs03vfBD9C171gS5Omz7OcvxALQIn4u8RRBBBcsi9WW2woO9ipLgfzpYlggg3ZRdROUC8KT7QLqq3W9KB5BbdFVg4929kdwp6+qaZnMCCNBdj+NyN1W885Ry/AL3D4AQbsVV4noCiM/C83kyYq80XlDAYQtralOiDzoRAHlotWl8q2tjvYlOgcg1A8jEApZa+C06TBdAz2Qv0wu11I/zZOyJQ6EwGez2P2b8PIQr1hwwnAZsAxwA4UAYOyXUxM/xp6tHAn4GUmPGM9R28oVxgC0e/zQJJI6DyhyZ1r7uzRQhpcW7x7vTaWSzKSG6aep77kroTEl3U81uSVaUTtgEINfC8epx+Q4F9SpplHG84Ek6m4RAq9/TLkOBrxyeuddZhHvGIp1XXfFy3Z3vtwNblKGiDn+J+92vwwABHghj7HnzlS1H5kB49AZvdGCFgiBPq69qfXPr3y++yilF0ON4R8eR7spAsLpZ95NqAW5tab1c4vkZm6aleajchMwYTdILQQTwE2OV411ZM9WztDjPql12caBi6gDpUKmDd4U1XNdQxZ4LIXQ5/Tr4P7I9tYcFrDK3AAAAAElFTkSuQmCC");
background-repeat: no-repeat;
background-position: center;
}
.wails-reconnect-overlay-title {
font-size: 2em;
}
.wails-reconnect-overlay-message {
font-size: 1.3em;
}
/* https://codepen.io/EastingAndNorthing/pen/aNWrZz - Cheers Mark! */
.wails-reconnect-overlay-loadingspinner {
pointer-events: none;
width: 2.5em;
height: 2.5em;
border: 0.4em solid transparent;
border-color: #eee;
border-top-color: #3E67EC;
border-radius: 50%;
animation: loadingspin 1s linear infinite;
margin: auto;
padding: 2.5em;
}
@keyframes loadingspin {
100% {
transform: rotate(360deg)
}
}
</style>
</head>
<body>
<div class="wails-reconnect-overlay">
<div class="wails-reconnect-overlay-content">
<div class="wails-reconnect-overlay-title">Disconnected</div><br>
<div class="wails-reconnect-overlay-loadingspinner"></div><br>
<div class="wails-reconnect-overlay-message">Waiting for backend</div>
</div>
</div>
<div id="app"></div>
<script id="wails-headless-runtime">
(function () {
var websocket = null;
var connectTimer = null;
var reconnectOverlay = document.querySelector(".wails-reconnect-overlay");
var connectionState = "disconnected";
function showReconnectOverlay() {
reconnectOverlay.style.display = 'block';
}
function hideReconnectOverlay() {
reconnectOverlay.style.display = 'none';
}
window.external = {
invoke: function (msg) {
websocket.send(msg);
}
};
// Adds a script to the Dom.
// Removes it if second parameter is true.
function addScript(script, remove) {
var s = document.createElement("script");
s.textContent = script;
document.head.appendChild(s);
// Remove internal messages from the DOM
if (remove) {
s.parentNode.removeChild(s);
}
}
// Adapted from webview - thanks zserge!
function injectCSS(css) {
var elem = document.createElement("style");
elem.setAttribute("type", "text/css");
if (elem.styleSheet) {
elem.styleSheet.cssText = css;
} else {
elem.appendChild(document.createTextNode(css));
}
var head = document.head || document.getElementsByTagName("head")[0];
head.appendChild(elem);
}
function handleConnect() {
log("Connected to backend");
hideReconnectOverlay();
clearInterval(connectTimer);
websocket.onclose = handleDisconnect;
websocket.onmessage = handleMessage;
connectionState = "connected";
// websocket.onerror = function () { }
}
function handleDisconnect() {
log("Disconnected from backend");
websocket = null;
connectionState = "disconnected";
showReconnectOverlay();
connect();
}
function connect() {
connectTimer = setInterval(function () {
if (websocket == null) {
websocket = new WebSocket("ws://localhost:34115/ws")
websocket.onopen = handleConnect;
websocket.onerror = function (e) {
e.stopImmediatePropagation();
e.stopPropagation();
e.preventDefault();
websocket = null;
return false
}
}
}, 300);
}
function log(message) {
console.log(
"%c wails headless %c " + message + " ",
"background: #aa0000; color: #fff; border-radius: 3px 0px 0px 3px; padding: 1px; font-size: 0.7rem",
"background: #009900; color: #fff; border-radius: 0px 3px 3px 0px; padding: 1px; font-size: 0.7rem"
);
}
function handleMessage(message) {
// As a bridge we ignore js and css injections
debugger;
switch (message.data[0]) {
// Wails library - inject!
case "w":
addScript(message.data.slice(1));
// Now wails runtime is loaded, wails for the ready event
// and callback to the main app
window.wails.events.on("wails:loaded", function () {
log("Wails Ready");
});
log("Loaded Wails Runtime");
break;
// Notification
case "n":
log("Notification: " + message.data.slice(1))
addScript(message.data.slice(1), true);
break;
// Binding
case "b":
var binding = message.data.slice(1)
//log("Binding: " + binding)
window.wails._.newBinding(binding);
break;
// Call back
case "c":
var callbackData = message.data.slice(1);
log("Callback = " + callbackData);
window.wails._.callback(callbackData);
break;
// CSS
case "s":
addScript(message.data.slice(1), true);
break;
// JS
case "j":
addScript(message.data.slice(1));
break;
// HTML
case "h":
addScript(message.data.slice(1), true);
break;
default:
log("Ignored message: " + message.data.slice(0, 100))
}
}
connect();
}());
</script>
</body>
</html>

160
binding_function.go Normal file
View File

@@ -0,0 +1,160 @@
package wails
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"runtime"
)
type boundFunction struct {
fullName string
function reflect.Value
functionType reflect.Type
inputs []reflect.Type
returnTypes []reflect.Type
log *CustomLogger
hasErrorReturnType bool
}
// Creates a new bound function based on the given method + type
func newBoundFunction(object interface{}) (*boundFunction, error) {
objectValue := reflect.ValueOf(object)
objectType := reflect.TypeOf(object)
name := runtime.FuncForPC(objectValue.Pointer()).Name()
result := &boundFunction{
fullName: name,
function: objectValue,
functionType: objectType,
log: newCustomLogger(name),
}
err := result.processParameters()
return result, err
}
func (b *boundFunction) processParameters() error {
// Param processing
functionType := b.functionType
// Input parameters
inputParamCount := functionType.NumIn()
if inputParamCount > 0 {
b.inputs = make([]reflect.Type, inputParamCount)
// We start at 1 as the first param is the struct
for index := 0; index < inputParamCount; index++ {
param := functionType.In(index)
name := param.Name()
kind := param.Kind()
b.inputs[index] = param
typ := param
index := index
b.log.DebugFields("Input param", Fields{
"index": index,
"name": name,
"kind": kind,
"typ": typ,
})
}
}
// Process return/output declarations
returnParamsCount := functionType.NumOut()
// Guard against bad number of return types
switch returnParamsCount {
case 0:
case 1:
// Check if it's an error type
param := functionType.Out(0)
paramName := param.Name()
if paramName == "error" {
b.hasErrorReturnType = true
}
// Save return type
b.returnTypes = append(b.returnTypes, param)
case 2:
// Check the second return type is an error
secondParam := functionType.Out(1)
secondParamName := secondParam.Name()
if secondParamName != "error" {
return fmt.Errorf("last return type of method '%s' must be an error (got %s)", b.fullName, secondParamName)
}
// Check the second return type is an error
firstParam := functionType.Out(0)
firstParamName := firstParam.Name()
if firstParamName == "error" {
return fmt.Errorf("first return type of method '%s' must not be an error", b.fullName)
}
b.hasErrorReturnType = true
// Save return types
b.returnTypes = append(b.returnTypes, firstParam)
b.returnTypes = append(b.returnTypes, secondParam)
default:
return fmt.Errorf("cannot register method '%s' with %d return parameters. Please use up to 2", b.fullName, returnParamsCount)
}
return nil
}
// call the method with the given data
func (b *boundFunction) call(data string) ([]reflect.Value, error) {
// The data will be an array of values so we will decode the
// input data into
var jsArgs []interface{}
d := json.NewDecoder(bytes.NewBufferString(data))
// d.UseNumber()
err := d.Decode(&jsArgs)
if err != nil {
return nil, fmt.Errorf("Invalid data passed to method call: %s", err.Error())
}
// Check correct number of inputs
if len(jsArgs) != len(b.inputs) {
return nil, fmt.Errorf("Invalid number of parameters given to %s. Expected %d but got %d", b.fullName, len(b.inputs), len(jsArgs))
}
// Set up call
args := make([]reflect.Value, len(b.inputs))
for index := 0; index < len(b.inputs); index++ {
// Set the input values
value, err := b.setInputValue(index, b.inputs[index], jsArgs[index])
if err != nil {
return nil, err
}
args[index] = value
}
b.log.Debugf("Unmarshalled Args: %+v\n", jsArgs)
b.log.Debugf("Converted Args: %+v\n", args)
results := b.function.Call(args)
b.log.Debugf("results = %+v", results)
return results, nil
}
// Attempts to set the method input <typ> for parameter <index> with the given value <val>
func (b *boundFunction) setInputValue(index int, typ reflect.Type, val interface{}) (result reflect.Value, err error) {
// Catch type conversion panics thrown by convert
defer func() {
if r := recover(); r != nil {
// Modify error
err = fmt.Errorf("%s for parameter %d of function %s", r.(string)[23:], index+1, b.fullName)
}
}()
// Do the conversion
result = reflect.ValueOf(val).Convert(typ)
return result, err
}

267
binding_manager.go Normal file
View File

@@ -0,0 +1,267 @@
package wails
import (
"fmt"
"reflect"
"unicode"
)
/**
binding:
Name() // Full name (package+name)
Call(params)
**/
type bindingManager struct {
methods map[string]*boundMethod
functions map[string]*boundFunction
initMethods []*boundMethod
log *CustomLogger
renderer Renderer
runtime *Runtime // The runtime object to pass to bound structs
objectsToBind []interface{}
bindPackageNames bool // Package name should be considered when binding
}
func newBindingManager() *bindingManager {
result := &bindingManager{
methods: make(map[string]*boundMethod),
functions: make(map[string]*boundFunction),
log: newCustomLogger("Bind"),
}
return result
}
// Sets flag to indicate package names should be considered when binding
func (b *bindingManager) BindPackageNames() {
b.bindPackageNames = true
}
func (b *bindingManager) start(renderer Renderer, runtime *Runtime) error {
b.log.Info("Starting")
b.renderer = renderer
b.runtime = runtime
err := b.initialise()
if err != nil {
b.log.Errorf("Binding error: %s", err.Error())
return err
}
err = b.callWailsInitMethods()
return err
}
func (b *bindingManager) initialise() error {
var err error
// var binding *boundMethod
b.log.Info("Binding Go Functions/Methods")
// Create bindings for objects
for _, object := range b.objectsToBind {
// Safeguard against nils
if object == nil {
return fmt.Errorf("attempted to bind nil object")
}
// Determine kind of object
objectType := reflect.TypeOf(object)
objectKind := objectType.Kind()
switch objectKind {
case reflect.Ptr:
err = b.bindMethod(object)
case reflect.Func:
// spew.Dump(result.objectType.String())
err = b.bindFunction(object)
default:
err = fmt.Errorf("cannot bind object of type '%s'", objectKind.String())
}
// Return error if set
if err != nil {
return err
}
}
return nil
}
// bind the given struct method
func (b *bindingManager) bindMethod(object interface{}) error {
objectType := reflect.TypeOf(object)
baseName := objectType.String()
// Strip pointer if there
if baseName[0] == '*' {
baseName = baseName[1:]
}
b.log.Debugf("Processing struct: %s", baseName)
// Iterate over method definitions
for i := 0; i < objectType.NumMethod(); i++ {
// Get method definition
methodDef := objectType.Method(i)
methodName := methodDef.Name
fullMethodName := baseName + "." + methodName
method := reflect.ValueOf(object).MethodByName(methodName)
// Skip unexported methods
if !unicode.IsUpper([]rune(methodName)[0]) {
continue
}
// Create a new boundMethod
newMethod, err := newBoundMethod(methodName, fullMethodName, method, objectType)
if err != nil {
return err
}
// Check if it's a wails init function
if newMethod.isWailsInit {
b.log.Debugf("Detected WailsInit function: %s", fullMethodName)
b.initMethods = append(b.initMethods, newMethod)
} else {
// Save boundMethod
b.log.Infof("Bound Method: %s()", fullMethodName)
b.methods[fullMethodName] = newMethod
// Inform renderer of new binding
b.renderer.NewBinding(fullMethodName)
}
}
return nil
}
// bind the given function object
func (b *bindingManager) bindFunction(object interface{}) error {
newFunction, err := newBoundFunction(object)
if err != nil {
return err
}
// Save method
b.log.Infof("Bound Function: %s()", newFunction.fullName)
b.functions[newFunction.fullName] = newFunction
// Register with Renderer
b.renderer.NewBinding(newFunction.fullName)
return nil
}
// Save the given object to be bound at start time
func (b *bindingManager) bind(object interface{}) {
// Store binding
b.objectsToBind = append(b.objectsToBind, object)
}
// process an incoming call request
func (b *bindingManager) processCall(callData *callData) (interface{}, error) {
b.log.Debugf("Wanting to call %s", callData.BindingName)
// Determine if this is function call or method call by the number of
// dots in the binding name
dotCount := 0
for _, character := range callData.BindingName {
if character == '.' {
dotCount++
}
}
// Return values
var result []reflect.Value
var err error
// We need to catch reflect related panics and return
// a decent error message
// TODO: DEBUG THIS!
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("%s", r.(string))
}
}()
switch dotCount {
case 1:
function := b.functions[callData.BindingName]
if function == nil {
return nil, fmt.Errorf("Invalid function name '%s'", callData.BindingName)
}
result, err = function.call(callData.Data)
if err != nil {
return nil, err
}
// Do we have an error return type?
if function.hasErrorReturnType {
// We do - last result is an error type
// Check if the last result was nil
b.log.Debugf("# of return types: %d", len(function.returnTypes))
b.log.Debugf("# of results: %d", len(result))
errorResult := result[len(function.returnTypes)-1]
if !errorResult.IsNil() {
// It wasn't - we have an error
return nil, errorResult.Interface().(error)
}
}
return result[0].Interface(), nil
case 2:
// do we have this method?
method := b.methods[callData.BindingName]
if method == nil {
return nil, fmt.Errorf("Invalid method name '%s'", callData.BindingName)
}
result, err = method.call(callData.Data)
if err != nil {
return nil, err
}
// Do we have an error return type?
if method.hasErrorReturnType {
// We do - last result is an error type
// Check if the last result was nil
b.log.Debugf("# of return types: %d", len(method.returnTypes))
b.log.Debugf("# of results: %d", len(result))
errorResult := result[len(method.returnTypes)-1]
if !errorResult.IsNil() {
// It wasn't - we have an error
return nil, errorResult.Interface().(error)
}
}
if result != nil {
return result[0].Interface(), nil
}
return nil, nil
default:
return nil, fmt.Errorf("Invalid binding name '%s'", callData.BindingName)
}
}
// callWailsInitMethods calls all of the WailsInit methods that were
// registered with the runtime object
func (b *bindingManager) callWailsInitMethods() error {
// Create reflect value for runtime object
runtimeValue := reflect.ValueOf(b.runtime)
params := []reflect.Value{runtimeValue}
// Iterate initMethods
for _, initMethod := range b.initMethods {
// Call
result := initMethod.method.Call(params)
// Check errors
err := result[0].Interface()
if err != nil {
return err.(error)
}
}
return nil
}

211
binding_method.go Normal file
View File

@@ -0,0 +1,211 @@
package wails
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
)
type boundMethod struct {
Name string
fullName string
method reflect.Value
inputs []reflect.Type
returnTypes []reflect.Type
log *CustomLogger
hasErrorReturnType bool // Indicates if there is an error return type
isWailsInit bool
}
// Creates a new bound method based on the given method + type
func newBoundMethod(name string, fullName string, method reflect.Value, objectType reflect.Type) (*boundMethod, error) {
result := &boundMethod{
Name: name,
method: method,
fullName: fullName,
}
// Setup logger
result.log = newCustomLogger(result.fullName)
// Check if Parameters are valid
err := result.processParameters()
// Are we a WailsInit method?
if result.Name == "WailsInit" {
err = result.processWailsInit()
}
return result, err
}
func (b *boundMethod) processParameters() error {
// Param processing
methodType := b.method.Type()
// Input parameters
inputParamCount := methodType.NumIn()
if inputParamCount > 0 {
b.inputs = make([]reflect.Type, inputParamCount)
// We start at 1 as the first param is the struct
for index := 0; index < inputParamCount; index++ {
param := methodType.In(index)
name := param.Name()
kind := param.Kind()
b.inputs[index] = param
typ := param
index := index
b.log.DebugFields("Input param", Fields{
"index": index,
"name": name,
"kind": kind,
"typ": typ,
})
}
}
// Process return/output declarations
returnParamsCount := methodType.NumOut()
// Guard against bad number of return types
switch returnParamsCount {
case 0:
case 1:
// Check if it's an error type
param := methodType.Out(0)
paramName := param.Name()
if paramName == "error" {
b.hasErrorReturnType = true
}
// Save return type
b.returnTypes = append(b.returnTypes, param)
case 2:
// Check the second return type is an error
secondParam := methodType.Out(1)
secondParamName := secondParam.Name()
if secondParamName != "error" {
return fmt.Errorf("last return type of method '%s' must be an error (got %s)", b.Name, secondParamName)
}
// Check the second return type is an error
firstParam := methodType.Out(0)
firstParamName := firstParam.Name()
if firstParamName == "error" {
return fmt.Errorf("first return type of method '%s' must not be an error", b.Name)
}
b.hasErrorReturnType = true
// Save return types
b.returnTypes = append(b.returnTypes, firstParam)
b.returnTypes = append(b.returnTypes, secondParam)
default:
return fmt.Errorf("cannot register method '%s' with %d return parameters. Please use up to 2", b.Name, returnParamsCount)
}
return nil
}
// call the method with the given data
func (b *boundMethod) call(data string) ([]reflect.Value, error) {
// The data will be an array of values so we will decode the
// input data into
var jsArgs []interface{}
d := json.NewDecoder(bytes.NewBufferString(data))
// d.UseNumber()
err := d.Decode(&jsArgs)
if err != nil {
return nil, fmt.Errorf("Invalid data passed to method call: %s", err.Error())
}
// Check correct number of inputs
if len(jsArgs) != len(b.inputs) {
return nil, fmt.Errorf("Invalid number of parameters given to %s. Expected %d but got %d", b.fullName, len(b.inputs), len(jsArgs))
}
// Set up call
args := make([]reflect.Value, len(b.inputs))
for index := 0; index < len(b.inputs); index++ {
// Set the input values
value, err := b.setInputValue(index, b.inputs[index], jsArgs[index])
if err != nil {
return nil, err
}
args[index] = value
}
b.log.Debugf("Unmarshalled Args: %+v\n", jsArgs)
b.log.Debugf("Converted Args: %+v\n", args)
results := b.method.Call(args)
b.log.Debugf("results = %+v", results)
return results, nil
}
// Attempts to set the method input <typ> for parameter <index> with the given value <val>
func (b *boundMethod) setInputValue(index int, typ reflect.Type, val interface{}) (result reflect.Value, err error) {
// Catch type conversion panics thrown by convert
defer func() {
if r := recover(); r != nil {
// Modify error
fmt.Printf("Recovery message: %+v\n", r)
err = fmt.Errorf("%s for parameter %d of method %s", r.(string)[23:], index+1, b.fullName)
}
}()
// Do the conversion
// Handle nil values
if val == nil {
switch typ.Kind() {
case reflect.Chan,
reflect.Func,
reflect.Interface,
reflect.Map,
reflect.Ptr,
reflect.Slice:
logger.Debug("Converting nil to type")
result = reflect.ValueOf(val).Convert(typ)
default:
logger.Debug("Cannot convert nil to type, returning error")
return reflect.Zero(typ), fmt.Errorf("Unable to use null value for parameter %d of method %s", index+1, b.fullName)
}
} else {
result = reflect.ValueOf(val).Convert(typ)
}
return result, err
}
func (b *boundMethod) processWailsInit() error {
// We must have only 1 input, it must be *wails.Runtime
if len(b.inputs) != 1 {
return fmt.Errorf("Invalid WailsInit() definition. Expected 1 input, but got %d", len(b.inputs))
}
// It must be *wails.Runtime
inputName := b.inputs[0].String()
b.log.Debugf("WailsInit input type: %s", inputName)
if inputName != "*wails.Runtime" {
return fmt.Errorf("Invalid WailsInit() definition. Expected input to be wails.Runtime, but got %s", inputName)
}
// We must have only 1 output, it must be error
if len(b.returnTypes) != 1 {
return fmt.Errorf("Invalid WailsInit() definition. Expected 1 return type, but got %d", len(b.returnTypes))
}
// It must be *wails.Runtime
outputName := b.returnTypes[0].String()
b.log.Debugf("WailsInit output type: %s", outputName)
if outputName != "error" {
return fmt.Errorf("Invalid WailsInit() definition. Expected input to be error, but got %s", outputName)
}
// We are indeed a wails Init method
b.isWailsInit = true
return nil
}

View File

@@ -105,6 +105,7 @@ func NewCommand(name string, description string, app *Cli, parentCommandPath str
Shortdescription: description,
SubCommandsMap: make(map[string]*Command),
App: app,
log: NewLogger(),
}
// Set up command path
@@ -181,6 +182,7 @@ func (c *Command) Run(args []string) error {
// Nothing left we can do
c.PrintHelp()
return nil
}

View File

@@ -10,7 +10,7 @@ func init() {
assets := packr.NewBox("./bootstrap4default/assets")
FrameworkToUse = &Framework{
Name: "Bootstrap 4",
JS: assets.String("bootstrap.bundle.min.js"),
CSS: assets.String("bootstrap.min.css"),
JS: BoxString(&assets, "bootstrap.bundle.min.js"),
CSS: BoxString(&assets, "bootstrap.min.css"),
}
}

View File

@@ -11,7 +11,7 @@ func init() {
assets := packr.NewBox("./assets")
frameworks.FrameworkToUse = &frameworks.Framework{
Name: "Bootstrap 4",
JS: assets.String("bootstrap.bundle.min.js"),
CSS: assets.String("bootstrap.min.css"),
JS: BoxString(&assets, "bootstrap.bundle.min.js"),
CSS: BoxString(&assets, "bootstrap.min.css"),
}
}

View File

@@ -10,7 +10,7 @@ func init() {
assets := packr.NewBox("./bootstrap4lux/assets")
FrameworkToUse = &Framework{
Name: "Bootstrap 4 (Lux)",
JS: assets.String("bootstrap.bundle.min.js"),
CSS: assets.String("bootstrap.min.css"),
JS: BoxString(&assets, "bootstrap.bundle.min.js"),
CSS: BoxString(&assets, "bootstrap.min.css"),
}
}

View File

@@ -11,7 +11,7 @@ func init() {
assets := packr.NewBox("./assets")
frameworks.FrameworkToUse = &frameworks.Framework{
Name: "Bootstrap 4 (Lux)",
JS: assets.String("bootstrap.bundle.min.js"),
CSS: assets.String("bootstrap.min.css"),
JS: BoxString(&assets, "bootstrap.bundle.min.js"),
CSS: BoxString(&assets, "bootstrap.min.css"),
}
}

View File

@@ -1,5 +1,11 @@
package frameworks
import (
"log"
"github.com/gobuffalo/packr"
)
// Framework has details about a specific framework
type Framework struct {
Name string
@@ -11,3 +17,12 @@ type Framework struct {
// FrameworkToUse is the framework we will use when building
// Set by `wails init`, used by `wails build`
var FrameworkToUse *Framework
// BoxString extracts a string from a packr box
func BoxString(box *packr.Box, filename string) string {
result, err := box.FindString(filename)
if err != nil {
log.Fatal(err)
}
return result
}

View File

@@ -9,52 +9,88 @@ import (
// Logger struct
type Logger struct {
errorOnly bool
}
// NewLogger creates a new logger!
func NewLogger() *Logger {
return &Logger{}
return &Logger{errorOnly: false}
}
func (l *Logger) SetErrorOnly(errorOnly bool) {
l.errorOnly = errorOnly
}
// Yellow - Outputs yellow text
func (l *Logger) Yellow(format string, a ...interface{}) {
if l.errorOnly {
return
}
color.New(color.FgHiYellow).PrintfFunc()(format+"\n", a...)
}
// Yellowf - Outputs yellow text without the newline
func (l *Logger) Yellowf(format string, a ...interface{}) {
if l.errorOnly {
return
}
color.New(color.FgHiYellow).PrintfFunc()(format, a...)
}
// Green - Outputs Green text
func (l *Logger) Green(format string, a ...interface{}) {
if l.errorOnly {
return
}
color.New(color.FgHiGreen).PrintfFunc()(format+"\n", a...)
}
// White - Outputs White text
func (l *Logger) White(format string, a ...interface{}) {
if l.errorOnly {
return
}
color.New(color.FgHiWhite).PrintfFunc()(format+"\n", a...)
}
// WhiteUnderline - Outputs White text with underline
func (l *Logger) WhiteUnderline(format string, a ...interface{}) {
if l.errorOnly {
return
}
l.White(format, a...)
l.White(l.underline(format))
}
// YellowUnderline - Outputs Yellow text with underline
func (l *Logger) YellowUnderline(format string, a ...interface{}) {
if l.errorOnly {
return
}
l.Yellow(format, a...)
l.Yellow(l.underline(format))
}
// underline returns a string of a line, the length of the message given to it
func (l *Logger) underline(message string) string {
if l.errorOnly {
return ""
}
return strings.Repeat("-", len(message))
}
// Red - Outputs Red text
func (l *Logger) Red(format string, a ...interface{}) {
if l.errorOnly {
return
}
color.New(color.FgHiRed).PrintfFunc()(format+"\n", a...)
}

197
cmd/package.go Normal file
View File

@@ -0,0 +1,197 @@
package cmd
import (
"bytes"
"fmt"
"image"
"io/ioutil"
"os"
"path"
"path/filepath"
"runtime"
"strings"
"text/template"
"time"
"github.com/jackmordaunt/icns"
)
// PackageHelper helps with the 'wails package' command
type PackageHelper struct {
fs *FSHelper
log *Logger
system *SystemHelper
}
// NewPackageHelper creates a new PackageHelper!
func NewPackageHelper() *PackageHelper {
return &PackageHelper{
fs: NewFSHelper(),
log: NewLogger(),
system: NewSystemHelper(),
}
}
type plistData struct {
Title string
Exe string
PackageID string
Version string
Author string
Date string
}
func newPlistData(title, exe, packageID, version, author string) *plistData {
now := time.Now().Format(time.RFC822)
return &plistData{
Title: title,
Exe: exe,
Version: version,
PackageID: packageID,
Author: author,
Date: now,
}
}
func defaultString(val string, defaultVal string) string {
if val != "" {
return val
}
return defaultVal
}
func (b *PackageHelper) getPackageFileBaseDir() string {
// Calculate template base dir
_, filename, _, _ := runtime.Caller(1)
return filepath.Join(path.Dir(filename), "packages", runtime.GOOS)
}
// Package the application into a platform specific package
func (b *PackageHelper) Package(po *ProjectOptions) error {
// Check we have the exe
if !b.fs.FileExists(po.BinaryName) {
return fmt.Errorf("cannot bundle non-existant binary file '%s'. Please build with 'wails build' first", po.BinaryName)
}
switch runtime.GOOS {
case "darwin":
return b.packageOSX(po)
case "windows":
return fmt.Errorf("windows is not supported at this time. Please see https://github.com/wailsapp/wails/issues/3")
case "linux":
return fmt.Errorf("linux is not supported at this time. Please see https://github.com/wailsapp/wails/issues/2")
default:
return fmt.Errorf("platform '%s' not supported for bundling yet", runtime.GOOS)
}
}
// Package the application for OSX
func (b *PackageHelper) packageOSX(po *ProjectOptions) error {
system := NewSystemHelper()
config, err := system.LoadConfig()
if err != nil {
return err
}
name := defaultString(po.Name, "WailsTest")
exe := defaultString(po.BinaryName, name)
version := defaultString(po.Version, "0.1.0")
author := defaultString(config.Name, "Anonymous")
packageID := strings.Join([]string{"wails", name, version}, ".")
plistData := newPlistData(name, exe, packageID, version, author)
appname := po.Name + ".app"
// Check binary exists
source := path.Join(b.fs.Cwd(), exe)
if !b.fs.FileExists(source) {
// We need to build!
return fmt.Errorf("Target '%s' not available. Has it been compiled yet?", exe)
}
// Remove the existing package
os.RemoveAll(appname)
exeDir := path.Join(b.fs.Cwd(), appname, "/Contents/MacOS")
b.fs.MkDirs(exeDir, 0755)
resourceDir := path.Join(b.fs.Cwd(), appname, "/Contents/Resources")
b.fs.MkDirs(resourceDir, 0755)
tmpl := template.New("infoPlist")
plistFile := filepath.Join(b.getPackageFileBaseDir(), "info.plist")
infoPlist, err := ioutil.ReadFile(plistFile)
if err != nil {
return err
}
tmpl.Parse(string(infoPlist))
// Write the template to a buffer
var tpl bytes.Buffer
err = tmpl.Execute(&tpl, plistData)
if err != nil {
return err
}
filename := path.Join(b.fs.Cwd(), appname, "Contents", "Info.plist")
err = ioutil.WriteFile(filename, tpl.Bytes(), 0644)
if err != nil {
return err
}
// Copy executable
target := path.Join(exeDir, exe)
err = b.fs.CopyFile(source, target)
if err != nil {
return err
}
err = os.Chmod(target, 0755)
if err != nil {
return err
}
err = b.packageIcon(resourceDir)
return err
}
func (b *PackageHelper) packageIcon(resourceDir string) error {
// TODO: Read this from project.json
const appIconFilename = "appicon.png"
srcIcon := path.Join(b.fs.Cwd(), appIconFilename)
// Check if appicon.png exists
if !b.fs.FileExists(srcIcon) {
// Install default icon
iconfile := filepath.Join(b.getPackageFileBaseDir(), "icon.png")
iconData, err := ioutil.ReadFile(iconfile)
if err != nil {
return err
}
err = ioutil.WriteFile(srcIcon, iconData, 0644)
if err != nil {
return err
}
}
tgtBundle := path.Join(resourceDir, "iconfile.icns")
imageFile, err := os.Open(srcIcon)
if err != nil {
return err
}
defer imageFile.Close()
srcImg, _, err := image.Decode(imageFile)
if err != nil {
return err
}
dest, err := os.Create(tgtBundle)
if err != nil {
return err
}
defer dest.Close()
if err := icns.Encode(dest, srcImg); err != nil {
return err
}
return nil
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

View File

@@ -0,0 +1,11 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>CFBundleName</key><string>{{.Title}}</string>
<key>CFBundleExecutable</key><string>{{.Exe}}</string>
<key>CFBundleIdentifier</key><string>{{.PackageID}}</string>
<key>CFBundleVersion</key><string>{{.Version}}</string>
<key>CFBundleGetInfoString</key><string>Built by {{.Author}} at {{.Date}} using Wails (https://wails.app)</string>
<key>CFBundleShortVersionString</key><string>{{.Version}}</string>
<key>CFBundleIconFile</key><string>iconfile</string>
<key>NSHighResolutionCapable</key><string>true</string>
</dict></plist>

View File

@@ -2,17 +2,23 @@ package cmd
import (
"bytes"
"fmt"
"os/exec"
"path/filepath"
"strings"
"syscall"
)
// ProgramHelper - Utility functions around installed applications
type ProgramHelper struct{}
type ProgramHelper struct {
shell *ShellHelper
}
// NewProgramHelper - Creates a new ProgramHelper
func NewProgramHelper() *ProgramHelper {
return &ProgramHelper{}
return &ProgramHelper{
shell: NewShellHelper(),
}
}
// IsInstalled tries to determine if the given binary name is installed
@@ -83,3 +89,36 @@ func (p *Program) Run(vars ...string) (stdout, stderr string, exitCode int, err
}
return
}
// InstallGoPackage installs the given Go package
func (p *ProgramHelper) InstallGoPackage(packageName string) error {
args := strings.Split("get -u "+packageName, " ")
_, stderr, err := p.shell.Run("go", args...)
if err != nil {
fmt.Println(stderr)
}
return err
}
// RunCommand runs the given command
func (p *ProgramHelper) RunCommand(command string) error {
args := strings.Split(command, " ")
return p.RunCommandArray(args)
}
// RunCommandArray runs the command specified in the array
func (p *ProgramHelper) RunCommandArray(args []string) error {
program := args[0]
// TODO: Run FindProgram here and get the full path to the exe
program, err := exec.LookPath(program)
if err != nil {
fmt.Printf("ERROR: Looks like '%s' isn't installed. Please install and try again.", program)
return err
}
args = args[1:]
_, stderr, err := p.shell.Run(program, args...)
if err != nil {
fmt.Println(stderr)
}
return err
}

View File

@@ -249,7 +249,7 @@ func (po *ProjectOptions) PromptForInputs() error {
}
// Setup NPM Project name
po.NPMProjectName = strings.Replace(po.Name, " ", "_", -1)
po.NPMProjectName = strings.ToLower(strings.Replace(po.Name, " ", "_", -1))
// If we selected custom, prompt for framework
if po.Template == "custom - Choose your own CSS Framework" {

27
cmd/shell.go Normal file
View File

@@ -0,0 +1,27 @@
package cmd
import (
"bytes"
"os/exec"
)
// ShellHelper helps with Shell commands
type ShellHelper struct {
}
// NewShellHelper creates a new ShellHelper!
func NewShellHelper() *ShellHelper {
return &ShellHelper{}
}
// Run the given command
func (sh *ShellHelper) Run(command string, vars ...string) (stdout, stderr string, err error) {
cmd := exec.Command(command, vars...)
var stdo, stde bytes.Buffer
cmd.Stdout = &stdo
cmd.Stderr = &stde
err = cmd.Run()
stdout = string(stdo.Bytes())
stderr = string(stde.Bytes())
return
}

View File

@@ -220,6 +220,15 @@ func (sc *SystemConfig) load(filename string) error {
return nil
}
// CheckDependenciesSilent checks for dependencies but
// only outputs if there's an error
func CheckDependenciesSilent(logger *Logger) (bool, error) {
logger.SetErrorOnly(true)
result, err := CheckDependencies(logger)
logger.SetErrorOnly(false)
return result, err
}
// CheckDependencies will look for Wails dependencies on the system
// Errors are reported in error and the bool return value is whether
// the dependencies are all installed.
@@ -281,6 +290,7 @@ func CheckDependencies(logger *Logger) (bool, error) {
}
}
}
logger.White("")
return !errors, err
}

View File

@@ -10,12 +10,12 @@ import (
"path/filepath"
"runtime"
"strings"
"github.com/alecthomas/template"
"text/template"
)
const templateSuffix = ".template"
// TemplateHelper helps with creating projects
type TemplateHelper struct {
system *SystemHelper
fs *FSHelper
@@ -25,12 +25,14 @@ type TemplateHelper struct {
metadataFilename string
}
// Template defines a single template
type Template struct {
Name string
Dir string
Metadata map[string]interface{}
}
// NewTemplateHelper creates a new template helper
func NewTemplateHelper() *TemplateHelper {
result := TemplateHelper{
system: NewSystemHelper(),
@@ -45,6 +47,7 @@ func NewTemplateHelper() *TemplateHelper {
return &result
}
// GetTemplateNames returns a map of all available templates
func (t *TemplateHelper) GetTemplateNames() (map[string]string, error) {
templateDirs, err := t.fs.GetSubdirs(t.templateDir)
if err != nil {
@@ -53,6 +56,8 @@ func (t *TemplateHelper) GetTemplateNames() (map[string]string, error) {
return templateDirs, nil
}
// GetTemplateDetails returns a map of Template structs containing details
// of the found templates
func (t *TemplateHelper) GetTemplateDetails() (map[string]*Template, error) {
templateDirs, err := t.fs.GetSubdirs(t.templateDir)
if err != nil {
@@ -81,6 +86,7 @@ func (t *TemplateHelper) GetTemplateDetails() (map[string]*Template, error) {
return result, nil
}
// LoadMetadata loads the template's 'metadata.json' file
func (t *TemplateHelper) LoadMetadata(dir string) (map[string]interface{}, error) {
templateFile := filepath.Join(dir, t.metadataFilename)
result := make(map[string]interface{})
@@ -95,6 +101,7 @@ func (t *TemplateHelper) LoadMetadata(dir string) (map[string]interface{}, error
return result, err
}
// TemplateExists returns true if the given template name exists
func (t *TemplateHelper) TemplateExists(templateName string) (bool, error) {
templates, err := t.GetTemplateNames()
if err != nil {
@@ -104,6 +111,8 @@ func (t *TemplateHelper) TemplateExists(templateName string) (bool, error) {
return exists, nil
}
// InstallTemplate installs the template given in the project options to the
// project path given
func (t *TemplateHelper) InstallTemplate(projectPath string, projectOptions *ProjectOptions) error {
// Get template files

View File

@@ -0,0 +1,8 @@
<div style="text-align:center">
<div
class="wails-logo"
style="width: 25rem;margin: auto;height: 25rem;"
></div>
<h1>Basic Template</h1>
Welcome to your basic Wails app!
</div>

View File

@@ -0,0 +1 @@
module {{.BinaryName}}

View File

@@ -1,19 +1,21 @@
package main
import (
"github.com/gobuffalo/packr"
wails "github.com/wailsapp/wails"
)
var html = `<h1> Basic Template </h1>`
func main() {
// Assets
assets := packr.NewBox("./frontend")
// Initialise the app
app := wails.CreateApp(&wails.AppConfig{
Width: 1024,
Height: 768,
Title: "My Project",
HTML: html,
Title: "{{.Name}}",
HTML: wails.BoxString(&assets, "main.html"),
})
app.Run()
}

View File

@@ -0,0 +1,8 @@
<div style="text-align:center">
<div
class="wails-logo"
style="width: 25rem;margin: auto;height: 25rem;"
></div>
<h1>Custom CSS Template</h1>
Welcome to your basic Wails app with custom CSS!
</div>

View File

@@ -0,0 +1 @@
module {{.BinaryName}}

View File

@@ -1,19 +0,0 @@
package main
import (
wails "github.com/wailsapp/wails"
)
var html = `<h1> Custom Project </h1>`
func main() {
// Initialise the app
app := wails.CreateApp(&wails.AppConfig{
Width: 800,
Height: 600,
Title: "My Project",
HTML: html,
})
app.Run()
}

View File

@@ -0,0 +1,21 @@
package main
import (
"github.com/gobuffalo/packr"
wails "github.com/wailsapp/wails"
)
func main() {
// Assets
assets := packr.NewBox("./frontend")
// Initialise the app
app := wails.CreateApp(&wails.AppConfig{
Width: 1024,
Height: 768,
Title: "{{.Name}}",
HTML: wails.BoxString(&assets, "main.html"),
})
app.Run()
}

View File

@@ -1,5 +1,6 @@
{
"name": "frontend",
"name": "{{.NPMProjectName}}",
"author": "{{.Author.Name}}<{{.Author.Email}}>",
"version": "0.1.0",
"private": true,
"scripts": {
@@ -14,4 +15,4 @@
"@vue/cli-service": "^3.1.4",
"vue-template-compiler": "^2.5.17"
}
}
}

View File

@@ -8,6 +8,7 @@
<script>
import "../assets/css/quote.css";
import { eventBus } from "../main";
export default {
data() {
@@ -18,14 +19,13 @@ export default {
methods: {
getNewQuote: function() {
var self = this;
wails.$.main.QuotesCollection.GetQuote().then(result => {
console.log(result);
backend.QuotesCollection.GetQuote().then(result => {
self.quote = result;
});
}
},
created() {
this.getNewQuote();
eventBus.$on("ready", this.getNewQuote);
}
};
</script>

View File

@@ -1,8 +1,14 @@
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
import Vue from "vue";
export const eventBus = new Vue();
import App from "./App.vue";
new Vue({
render: h => h(App),
}).$mount('#app')
render: h => h(App)
}).$mount("#app");
import Bridge from "./wailsbridge";
Bridge.Start(startApp);
function startApp() {
eventBus.$emit("ready");
}

View File

@@ -0,0 +1,213 @@
/*
Wails Bridge (c) 2019-present Lea Anthony
This library creates a bridge between your application
and the frontend, allowing you to develop your app using
standard tooling (browser extensions, live reload, etc).
Usage:
```
import Bridge from "./wailsbridge";
Bridge.Start(startApp);
```
The given callback (startApp in the example) will be called
when the bridge has successfully initialised.
*/
// Bridge object
window.wailsbridge = {
reconnectOverlay: null,
reconnectTimer: 300,
wsURL: "ws://localhost:34115/bridge",
connectionState: null,
config: {},
websocket: null,
callback: null,
overlayHTML:
'<div class="wails-reconnect-overlay"><div class="wails-reconnect-overlay-content"><div class="wails-reconnect-overlay-title">Wails Bridge</div><br><div class="wails-reconnect-overlay-loadingspinner"></div><br><div id="wails-reconnect-overlay-message">Waiting for backend</div></div></div>',
overlayCSS:
".wails-reconnect-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.6);font-family:sans-serif;display:none;z-index:999999}.wails-reconnect-overlay-content{padding:20px 30px;text-align:center;width:20em;position:relative;height:14em;border-radius:1em;margin:5% auto 0;background-color:#fff;box-shadow:1px 1px 20px 3px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC8AAAAuCAMAAACPpbA7AAAAqFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAAAAAAAAEBAQAAAAAAAAAAAAEBAQEBAQDAwMBAQEAAAABAQEAAAAAAAAAAAABAQEAAAAAAAACAgICAgIBAQEAAAAAAAAAAAAAAAAAAAAAAAABAQEAAAACAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBQWKCj6oAAAAN3RSTlMALiIqDhkGBAswJjP0GxP6NR4W9/ztjRDMhWU50G9g5eHXvbZ9XEI9xZTcqZl2aldKo55QwoCvZUgzhAAAAs9JREFUSMeNleeWqjAUhU0BCaH3Itiw9zKT93+zG02QK1hm/5HF+jzZJ6fQe6cyXE+jg9X7o9wxuylIIf4Tv2V3+bOrEXnf8dwQ/KQIGDN2/S+4OmVCVXL/ScBnfibxURqIByP/hONE8r8T+bDMlQ98KSl7Y8hzjpS8v1qtDh8u5f8KQpGpfnPPhqG8JeogN37Hq9eaN2xRhIwAaGnvws8F1ShxqK5ob2twYi1FAMD4rXsYtnC/JEiRbl4cUrCWhnMCLRFemXezXbb59QK4WASOsm6n2W1+4CBT2JmtzQ6fsrbGubR/NFbd2g5Y179+5w/GEHaKsHjYCet7CgrXU3txarNC7YxOVJtIj4/ERzMdZfzc31hp+8cD6eGILgarZY9uZ12hAs03vfBD9C171gS5Omz7OcvxALQIn4u8RRBBBcsi9WW2woO9ipLgfzpYlggg3ZRdROUC8KT7QLqq3W9KB5BbdFVg4929kdwp6+qaZnMCCNBdj+NyN1W885Ry/AL3D4AQbsVV4noCiM/C83kyYq80XlDAYQtralOiDzoRAHlotWl8q2tjvYlOgcg1A8jEApZa+C06TBdAz2Qv0wu11I/zZOyJQ6EwGez2P2b8PIQr1hwwnAZsAxwA4UAYOyXUxM/xp6tHAn4GUmPGM9R28oVxgC0e/zQJJI6DyhyZ1r7uzRQhpcW7x7vTaWSzKSG6aep77kroTEl3U81uSVaUTtgEINfC8epx+Q4F9SpplHG84Ek6m4RAq9/TLkOBrxyeuddZhHvGIp1XXfFy3Z3vtwNblKGiDn+J+92vwwABHghj7HnzlS1H5kB49AZvdGCFgiBPq69qfXPr3y++yilF0ON4R8eR7spAsLpZ95NqAW5tab1c4vkZm6aleajchMwYTdILQQTwE2OV411ZM9WztDjPql12caBi6gDpUKmDd4U1XNdQxZ4LIXQ5/Tr4P7I9tYcFrDK3AAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center}.wails-reconnect-overlay-title{font-size:2em}.wails-reconnect-overlay-message{font-size:1.3em}.wails-reconnect-overlay-loadingspinner{pointer-events:none;width:2.5em;height:2.5em;border:.4em solid transparent;border-color:#3E67EC #eee #eee;border-radius:50%;animation:loadingspin 1s linear infinite;margin:auto;padding:2.5em}@keyframes loadingspin{100%{transform:rotate(360deg)}}",
log: function(message) {
console.log(
"%c wails bridge %c " + message + " ",
"background: #aa0000; color: #fff; border-radius: 3px 0px 0px 3px; padding: 1px; font-size: 0.7rem",
"background: #009900; color: #fff; border-radius: 0px 3px 3px 0px; padding: 1px; font-size: 0.7rem"
);
}
};
// Adapted from webview - thanks zserge!
function injectCSS(css) {
var elem = document.createElement("style");
elem.setAttribute("type", "text/css");
if (elem.styleSheet) {
elem.styleSheet.cssText = css;
} else {
elem.appendChild(document.createTextNode(css));
}
var head = document.head || document.getElementsByTagName("head")[0];
head.appendChild(elem);
}
// Creates a node in the Dom
function createNode(parent, elementType, id, className, content) {
var d = document.createElement(elementType);
if (id) {
d.id = id;
}
if (className) {
d.className = className;
}
if (content) {
d.innerHTML = content;
}
parent.appendChild(d);
return d;
}
// Sets up the overlay
function setupOverlay() {
var body = document.body;
var wailsBridgeNode = createNode(body, "div", "wails-bridge");
wailsBridgeNode.innerHTML = window.wailsbridge.overlayHTML;
// Inject the overlay CSS
injectCSS(window.wailsbridge.overlayCSS);
}
// Start the Wails Bridge
function startBridge() {
// Setup the overlay
setupOverlay();
window.wailsbridge.websocket = null;
window.wailsbridge.connectTimer = null;
window.wailsbridge.reconnectOverlay = document.querySelector(
".wails-reconnect-overlay"
);
window.wailsbridge.connectionState = "disconnected";
// Shows the overlay
function showReconnectOverlay() {
window.wailsbridge.reconnectOverlay.style.display = "block";
}
// Hides the overlay
function hideReconnectOverlay() {
window.wailsbridge.reconnectOverlay.style.display = "none";
}
// Bridge external.invoke
window.external = {
invoke: function(msg) {
window.wailsbridge.websocket.send(msg);
}
};
// Adds a script to the Dom.
// Removes it if second parameter is true.
function addScript(script, remove) {
var s = document.createElement("script");
s.textContent = script;
document.head.appendChild(s);
// Remove internal messages from the DOM
if (remove) {
s.parentNode.removeChild(s);
}
}
// Handles incoming websocket connections
function handleConnect() {
window.wailsbridge.log("Connected to backend");
hideReconnectOverlay();
clearInterval(window.wailsbridge.connectTimer);
window.wailsbridge.websocket.onclose = handleDisconnect;
window.wailsbridge.websocket.onmessage = handleMessage;
window.wailsbridge.connectionState = "connected";
}
// Handles websocket disconnects
function handleDisconnect() {
window.wailsbridge.log("Disconnected from backend");
window.wailsbridge.websocket = null;
window.wailsbridge.connectionState = "disconnected";
showReconnectOverlay();
connect();
}
// Try to connect to the backend every 300ms (default value).
// Change this value in the main wailsbridge object.
function connect() {
window.wailsbridge.connectTimer = setInterval(function() {
if (window.wailsbridge.websocket == null) {
window.wailsbridge.websocket = new WebSocket(window.wailsbridge.wsURL);
window.wailsbridge.websocket.onopen = handleConnect;
window.wailsbridge.websocket.onerror = function(e) {
e.stopImmediatePropagation();
e.stopPropagation();
e.preventDefault();
window.wailsbridge.websocket = null;
return false;
};
}
}, window.wailsbridge.reconnectTimer);
}
function handleMessage(message) {
// As a bridge we ignore js and css injections
switch (message.data[0]) {
// Wails library - inject!
case "w":
addScript(message.data.slice(1));
// Now wails runtime is loaded, wails for the ready event
// and callback to the main app
window.wails.events.on("wails:loaded", function() {
window.wailsbridge.log("Wails Ready");
if (window.wailsbridge.callback) {
window.wailsbridge.log("Notifying application");
window.wailsbridge.callback();
}
});
window.wailsbridge.log("Loaded Wails Runtime");
break;
// Notifications
case "n":
addScript(message.data.slice(1), true);
break;
// Binding
case "b":
var binding = message.data.slice(1);
//window.wailsbridge.log("Binding: " + binding)
window.wails._.newBinding(binding);
break;
// Call back
case "c":
var callbackData = message.data.slice(1);
// window.wailsbridge.log("Callback = " + callbackData);
window.wails._.callback(callbackData);
break;
}
}
// Start by showing the overlay...
showReconnectOverlay();
// ...and attempt to connect
connect();
}
export default {
// The main function
// Passes the main Wails object to the callback if given.
Start: function(callback) {
// Save the callback
window.wailsbridge.callback = callback;
// Start Bridge
startBridge();
}
};

View File

@@ -0,0 +1,15 @@
/*
Wails Bridge (c) 2019-present Lea Anthony
This prod version is to get around having to rewrite your code
for production. When doing a release build, this file will be used
instead of the full version.
*/
export default {
// The main function
// Passes the main Wails object to the callback if given.
Start: function (callback) {
return callback();
}
};

View File

@@ -0,0 +1 @@
module {{.BinaryName}}

View File

@@ -12,9 +12,9 @@ func main() {
app := wails.CreateApp(&wails.AppConfig{
Width: 1024,
Height: 768,
Title: "Vue Simple",
JS: assets.String("app.js"),
CSS: assets.String("app.css"),
Title: "{{.Name}}",
JS: wails.BoxString(&assets, "app.js"),
CSS: wails.BoxString(&assets, "app.css"),
})
app.Bind(newQuotesCollection())
app.Run()

View File

@@ -4,6 +4,8 @@ import (
"fmt"
"runtime"
"github.com/leaanthony/spinner"
"github.com/wailsapp/wails/cmd"
)
@@ -86,6 +88,19 @@ Create your first project by running 'wails init'.`
}
}
// packr
if !programHelper.IsInstalled("packr") {
buildSpinner := spinner.New()
buildSpinner.SetSpinSpeed(50)
buildSpinner.Start("Installing packr...")
err := programHelper.InstallGoPackage("github.com/gobuffalo/packr/...")
if err != nil {
buildSpinner.Error()
return err
}
buildSpinner.Success()
}
logger.White("")
if !errors {

View File

@@ -34,13 +34,11 @@ Any flags that are required and not given will be prompted for.`
return err
}
success, err := cmd.CheckDependencies(logger)
success, err := cmd.CheckDependenciesSilent(logger)
if !success {
return err
}
logger.White("")
// Do we want to just force defaults?
if projectOptions.UseDefaults {
// Use defaults

251
cmd/wails/3_build.go Normal file
View File

@@ -0,0 +1,251 @@
package main
import (
"fmt"
"io/ioutil"
"os"
"github.com/leaanthony/slicer"
"github.com/leaanthony/spinner"
"github.com/wailsapp/wails/cmd"
)
func init() {
var packageApp = false
var forceRebuild = false
var releaseMode = false
buildSpinner := spinner.NewSpinner()
buildSpinner.SetSpinSpeed(50)
commandDescription := `This command will check to ensure all pre-requistes are installed prior to building. If not, it will attempt to install them. Building comprises of a number of steps: install frontend dependencies, build frontend, pack frontend, compile main application.`
initCmd := app.Command("build", "Builds your Wails project").
LongDescription(commandDescription).
BoolFlag("p", "Package application on successful build (Implies -r)", &packageApp).
BoolFlag("f", "Force rebuild of application components", &forceRebuild).
BoolFlag("r", "Build in Release mode", &releaseMode)
initCmd.Action(func() error {
log := cmd.NewLogger()
message := "Building Application"
if forceRebuild {
message += " (force rebuild)"
}
log.WhiteUnderline(message)
// Project options
projectOptions := &cmd.ProjectOptions{}
// Check we are in project directory
// Check project.json loads correctly
fs := cmd.NewFSHelper()
err := projectOptions.LoadConfig(fs.Cwd())
if err != nil {
return err
}
// Validate config
// Check if we have a frontend
if projectOptions.FrontEnd != nil {
if projectOptions.FrontEnd.Dir == "" {
return fmt.Errorf("Frontend directory not set in project.json")
}
if projectOptions.FrontEnd.Build == "" {
return fmt.Errorf("Frontend build command not set in project.json")
}
if projectOptions.FrontEnd.Install == "" {
return fmt.Errorf("Frontend install command not set in project.json")
}
}
// Check pre-requisites are installed
// Program checker
program := cmd.NewProgramHelper()
if projectOptions.FrontEnd != nil {
// npm
if !program.IsInstalled("npm") {
return fmt.Errorf("it appears npm is not installed. Please install and run again")
}
}
// packr
if !program.IsInstalled("packr") {
buildSpinner.Start("Installing packr...")
err := program.InstallGoPackage("github.com/gobuffalo/packr/...")
if err != nil {
buildSpinner.Error()
return err
}
buildSpinner.Success()
}
// Save project directory
projectDir := fs.Cwd()
// Install backend deps - needed?
if projectOptions.FrontEnd != nil {
// Install frontend deps
err = os.Chdir(projectOptions.FrontEnd.Dir)
if err != nil {
return err
}
// Check if frontend deps have been updated
feSpinner := spinner.New("Installing frontend dependencies (This may take a while)...")
feSpinner.SetSpinSpeed(50)
feSpinner.Start()
requiresNPMInstall := true
// Read in package.json MD5
packageJSONMD5, err := fs.FileMD5("package.json")
if err != nil {
return err
}
const md5sumFile = "package.json.md5"
// If we aren't forcing the install and the md5sum file exists
if !forceRebuild && fs.FileExists(md5sumFile) {
// Yes - read contents
savedMD5sum, err := fs.LoadAsString(md5sumFile)
// File exists
if err == nil {
// Compare md5
if savedMD5sum == packageJSONMD5 {
// Same - no need for reinstall
requiresNPMInstall = false
feSpinner.Success("Skipped frontend dependencies (-f to force rebuild)")
}
}
}
// Md5 sum package.json
// Different? Build
if requiresNPMInstall || forceRebuild {
// Install dependencies
err = program.RunCommand(projectOptions.FrontEnd.Install)
if err != nil {
feSpinner.Error()
return err
}
feSpinner.Success()
// Update md5sum file
ioutil.WriteFile(md5sumFile, []byte(packageJSONMD5), 0644)
}
// Build frontend
buildFESpinner := spinner.New("Building frontend...")
buildFESpinner.SetSpinSpeed(50)
buildFESpinner.Start()
err = program.RunCommand(projectOptions.FrontEnd.Build)
if err != nil {
buildFESpinner.Error()
return err
}
buildFESpinner.Success()
}
// Run packr in project directory
err = os.Chdir(projectDir)
if err != nil {
return err
}
// Support build tags
buildTags := []string{}
// Do we have any frameworks specified?
frameworkSpinner := spinner.New()
frameworkSpinner.SetSpinSpeed(50)
if projectOptions.Framework != nil {
frameworkSpinner.Start()
frameworkSpinner.Success("Compiling support for " + projectOptions.Framework.Name)
buildTags = append(buildTags, projectOptions.Framework.BuildTag)
}
// // Initialise Go Module - if go.mod doesn't exist
// if !fs.FileExists("go.mod") {
// buildSpinner.Start("Initialising Go module...")
// err = program.RunCommand("go mod init " + projectOptions.BinaryName)
// if err != nil {
// buildSpinner.Error()
// return err
// }
// buildSpinner.Success()
// }
depSpinner := spinner.New("Installing Dependencies...")
depSpinner.SetSpinSpeed(50)
depSpinner.Start()
installCommand := "go get"
err = program.RunCommand(installCommand)
if err != nil {
depSpinner.Error()
return err
}
depSpinner.Success()
compileMessage := "Packing + Compiling project"
if releaseMode || packageApp {
compileMessage += " (Release Mode)"
}
packSpinner := spinner.New(compileMessage + "...")
packSpinner.SetSpinSpeed(50)
packSpinner.Start()
buildCommand := slicer.String()
buildCommand.AddSlice([]string{"packr", "build"})
// Add build tags
if len(buildTags) > 0 {
buildCommand.Add("--tags")
buildCommand.AddSlice(buildTags)
}
if projectOptions.BinaryName != "" {
buildCommand.Add("-o")
buildCommand.Add(projectOptions.BinaryName)
}
// If we are forcing a rebuild
if forceRebuild {
buildCommand.Add("-a")
}
// Release mode
if releaseMode || packageApp {
buildCommand.AddSlice([]string{"-ldflags", "-X github.com/wailsapp/wails.DebugMode=false"})
}
err = program.RunCommandArray(buildCommand.AsSlice())
if err != nil {
packSpinner.Error()
return err
}
packSpinner.Success()
if packageApp == false {
logger.Yellow("Awesome! Project '%s' built!", projectOptions.Name)
return nil
}
// Package app
packageSpinner := spinner.New("Packaging Application")
packageSpinner.SetSpinSpeed(50)
packageSpinner.Start()
packager := cmd.NewPackageHelper()
err = packager.Package(projectOptions)
if err != nil {
packageSpinner.Error()
return err
}
packageSpinner.Success()
logger.Yellow("Awesome! Project '%s' built!", projectOptions.Name)
return nil
})
}

148
event_manager.go Normal file
View File

@@ -0,0 +1,148 @@
package wails
import (
"fmt"
"sync"
)
// eventManager handles and processes events
type eventManager struct {
incomingEvents chan *eventData
listeners map[string][]*eventListener
exit bool
log *CustomLogger
renderer Renderer // Messages will be dispatched to the frontend
}
// newEventManager creates a new event manager with a 100 event buffer
func newEventManager() *eventManager {
return &eventManager{
incomingEvents: make(chan *eventData, 100),
listeners: make(map[string][]*eventListener),
exit: false,
log: newCustomLogger("Events"),
}
}
// PushEvent places the given event on to the event queue
func (e *eventManager) PushEvent(eventData *eventData) {
e.incomingEvents <- eventData
}
// eventListener holds a callback function which is invoked when
// the event listened for is emitted. It has a counter which indicates
// how the total number of events it is interested in. A value of zero
// means it does not expire (default).
type eventListener struct {
callback func(...interface{}) // Function to call with emitted event data
counter int // Expire after counter callbacks. 0 = infinite
expired bool // Indicates if the listener has expired
}
// Creates a new event listener from the given callback function
func (e *eventManager) addEventListener(eventName string, callback func(...interface{}), counter int) error {
// Sanity check inputs
if callback == nil {
return fmt.Errorf("nil callback bassed to addEventListener")
}
// Check event has been registered before
if e.listeners[eventName] == nil {
e.listeners[eventName] = []*eventListener{}
}
// Create the callback
listener := &eventListener{
callback: callback,
counter: counter,
}
// Register listener
e.listeners[eventName] = append(e.listeners[eventName], listener)
// All good mate
return nil
}
func (e *eventManager) On(eventName string, callback func(...interface{})) {
// Add a persistent eventListener (counter = 0)
e.addEventListener(eventName, callback, 0)
}
// Emit broadcasts the given event to the subscribed listeners
func (e *eventManager) Emit(eventName string, optionalData ...interface{}) {
e.incomingEvents <- &eventData{Name: eventName, Data: optionalData}
}
// Starts the event manager's queue processing
func (e *eventManager) start(renderer Renderer) {
e.log.Info("Starting")
// Store renderer
e.renderer = renderer
// Set up waitgroup so we can wait for goroutine to start
var wg sync.WaitGroup
wg.Add(1)
// Run main loop in seperate goroutine
go func() {
wg.Done()
e.log.Info("Listening")
for e.exit == false {
// TODO: Listen for application exit
select {
case event := <-e.incomingEvents:
e.log.DebugFields("Got Event", Fields{
"data": event.Data,
"name": event.Name,
})
// Notify renderer
e.renderer.NotifyEvent(event)
// Notify Go listeners
var listenersToRemove []*eventListener
// Iterate listeners
for _, listener := range e.listeners[event.Name] {
// Call listener, perhaps with data
if event.Data == nil {
go listener.callback()
} else {
unpacked := event.Data.([]interface{})
go listener.callback(unpacked...)
}
// Update listen counter
if listener.counter > 0 {
listener.counter = listener.counter - 1
if listener.counter == 0 {
listener.expired = true
}
}
}
// Remove expired listners in place
if len(listenersToRemove) > 0 {
listeners := e.listeners[event.Name][:0]
for _, listener := range listeners {
if !listener.expired {
listeners = append(listeners, listener)
}
}
}
}
}
}()
// Wait for goroutine to start
wg.Wait()
}
func (e *eventManager) stop() {
e.exit = true
}

12
go.mod
View File

@@ -2,10 +2,17 @@ module github.com/wailsapp/wails
require (
github.com/AlecAivazis/survey v1.7.1
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect
github.com/dchest/cssmin v0.0.0-20151210170030-fb8d9b44afdc // indirect
github.com/dchest/htmlmin v0.0.0-20150526090704-e254725e81ac
github.com/dchest/jsmin v0.0.0-20160823214000-faeced883947 // indirect
github.com/fatih/color v1.7.0
github.com/go-playground/colors v1.2.0
github.com/gobuffalo/packr v1.21.9
github.com/gorilla/websocket v1.4.0
github.com/jackmordaunt/icns v1.0.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/leaanthony/slicer v0.0.0-20190110113548-aa9ea12f976a
github.com/leaanthony/spinner v0.4.0
github.com/leaanthony/synx v0.0.0-20180923230033-60efbd9984b0 // indirect
github.com/leaanthony/wincursor v0.0.0-20180705115120-056510f32d15 // indirect
@@ -13,5 +20,8 @@ require (
github.com/mattn/go-isatty v0.0.4 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mitchellh/go-homedir v1.0.0
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/sirupsen/logrus v1.2.0
golang.org/x/net v0.0.0-20190107155100-1a61f4433d85 // indirect
gopkg.in/AlecAivazis/survey.v1 v1.7.1 // indirect
)

23
go.sum
View File

@@ -14,6 +14,12 @@ github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8Nz
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/cssmin v0.0.0-20151210170030-fb8d9b44afdc h1:VBS1z48BFEe00G81z8MKOtwX7f/ISkuH38NscT8iVPw=
github.com/dchest/cssmin v0.0.0-20151210170030-fb8d9b44afdc/go.mod h1:ABJPuor7YlcsHmvJ1QxX38e2NcufLY3hm0yXv+cy9sI=
github.com/dchest/htmlmin v0.0.0-20150526090704-e254725e81ac h1:DpMwFluHWoZpV9ex5XjkWO4HyCz5HLVI8XbHw0FhHi4=
github.com/dchest/htmlmin v0.0.0-20150526090704-e254725e81ac/go.mod h1:XsAE+b4rOZc8gvgsgF+wU75mNBvBcyED1wdd9PBLlJ0=
github.com/dchest/jsmin v0.0.0-20160823214000-faeced883947 h1:Fm10/KNuoAyBm2P5P5H91Xy21hGcZnBdjR+cMdytv1M=
github.com/dchest/jsmin v0.0.0-20160823214000-faeced883947/go.mod h1:Dv9D0NUlAsaQcGQZa5kc5mqR9ua72SmA8VXi4cd+cBw=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -22,6 +28,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-playground/colors v1.2.0 h1:0EdjTXKrr2g1L/LQTYtIqabeHpZuGZz1U4osS1T8+5M=
github.com/go-playground/colors v1.2.0/go.mod h1:miw1R2JIE19cclPxsXqNdzLZsk4DP4iF+m88bRc7kfM=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/gobuffalo/buffalo v0.12.8-0.20181004233540-fac9bb505aa8/go.mod h1:sLyT7/dceRXJUxSsE813JTQtA3Eb1vjxWfo/N//vXIY=
github.com/gobuffalo/buffalo v0.13.0/go.mod h1:Mjn1Ba9wpIbpbrD+lIDMy99pQ0H0LiddMIIDGse7qT4=
@@ -185,11 +193,15 @@ github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.1.2/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
github.com/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmAMQ=
github.com/jackmordaunt/icns v1.0.0/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo=
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
@@ -199,11 +211,14 @@ github.com/karrick/godirwalk v1.7.7/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46s
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leaanthony/slicer v0.0.0-20190110113548-aa9ea12f976a h1:+nH6CKt4ZdMj+AabQrU0SLtZWYyQ1ovzLCA21se+raw=
github.com/leaanthony/slicer v0.0.0-20190110113548-aa9ea12f976a/go.mod h1:VMB/HGvr3uR3MRpFWHWAm0w+DHQLzPHYe2pKfpFlQIQ=
github.com/leaanthony/spinner v0.4.0 h1:y/7FqQqqObRKYI+33bg9DGhHIY7cQHicm+Vz0Uda0Ik=
github.com/leaanthony/spinner v0.4.0/go.mod h1:2Mmv+8Brcw3NwPT1DdOLmW6+zWpSamDDFFsUvVHo2cc=
github.com/leaanthony/spinner v0.5.0 h1:OJKn+0KP6ilHxwCEOv5Lo0wPM4PgWZWLJTeUprGJK0g=
@@ -246,6 +261,8 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/monoculum/formam v0.0.0-20180901015400-4e68be1d79ba/go.mod h1:RKgILGEJq24YyJ2ban8EO0RUVSJlF1pGsEvoLEACr/Q=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/nicksnyder/go-i18n v1.10.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -271,6 +288,7 @@ github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A=
github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
@@ -301,6 +319,7 @@ golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180816102801-aaf60122140d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -314,7 +333,10 @@ golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181207154023-610586996380 h1:zPQexyRtNYBc7bcHmehl1dH6TB3qn8zytv8cBGLDNY0=
golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190107155100-1a61f4433d85 h1:3DfFuyqY+mca6oIDfim5rft3+Kl/CHLe7RdPrUMzwv0=
golang.org/x/net v0.0.0-20190107155100-1a61f4433d85/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -333,6 +355,7 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181106135930-3a76605856fd/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e h1:njOxP/wVblhCLIUhjHXf6X+dzTt5OQ3vMQo9mkOIKIo=
golang.org/x/sys v0.0.0-20181206074257-70b957f3b65e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

38
ipc_call.go Normal file
View File

@@ -0,0 +1,38 @@
package wails
import (
"fmt"
)
type callData struct {
BindingName string `json:"bindingName"`
Data string `json:"data,omitempty"`
}
func init() {
messageProcessors["call"] = processCallData
}
func processCallData(message *ipcMessage) (*ipcMessage, error) {
var payload callData
// Decode binding call data
payloadMap := message.Payload.(map[string]interface{})
// Check for binding name
if payloadMap["bindingName"] == nil {
return nil, fmt.Errorf("bindingName not given in call")
}
payload.BindingName = payloadMap["bindingName"].(string)
// Check for data
if payloadMap["data"] != nil {
payload.Data = payloadMap["data"].(string)
}
// Reassign payload to decoded data
message.Payload = &payload
return message, nil
}

40
ipc_event.go Normal file
View File

@@ -0,0 +1,40 @@
package wails
import (
"encoding/json"
)
type eventData struct {
Name string `json:"name"`
Data interface{} `json:"data"`
}
// Register the message handler
func init() {
messageProcessors["event"] = processEventData
}
// This processes the given event message
func processEventData(message *ipcMessage) (*ipcMessage, error) {
// TODO: Is it worth double checking this is actually an event message,
// even though that's done by the caller?
var payload eventData
// Decode event data
payloadMap := message.Payload.(map[string]interface{})
payload.Name = payloadMap["name"].(string)
// decode the payload data
var data []interface{}
err := json.Unmarshal([]byte(payloadMap["data"].(string)), &data)
if err != nil {
return nil, err
}
payload.Data = data
// Reassign payload to decoded data
message.Payload = &payload
return message, nil
}

27
ipc_log.go Normal file
View File

@@ -0,0 +1,27 @@
package wails
type logData struct {
Level string `json:"level"`
Message string `json:"string"`
}
// Register the message handler
func init() {
messageProcessors["log"] = processLogData
}
// This processes the given log message
func processLogData(message *ipcMessage) (*ipcMessage, error) {
var payload logData
// Decode event data
payloadMap := message.Payload.(map[string]interface{})
payload.Level = payloadMap["level"].(string)
payload.Message = payloadMap["message"].(string)
// Reassign payload to decoded data
message.Payload = &payload
return message, nil
}

162
ipc_manager.go Normal file
View File

@@ -0,0 +1,162 @@
package wails
import (
"fmt"
)
type ipcManager struct {
renderer Renderer // The renderer
messageQueue chan *ipcMessage
// quitChannel chan struct{}
// signals chan os.Signal
log *CustomLogger
eventManager *eventManager
bindingManager *bindingManager
}
func newIPCManager() *ipcManager {
result := &ipcManager{
messageQueue: make(chan *ipcMessage, 100),
// quitChannel: make(chan struct{}),
// signals: make(chan os.Signal, 1),
log: newCustomLogger("IPC"),
}
return result
}
// Sets the renderer, returns the dispatch function
func (i *ipcManager) bindRenderer(renderer Renderer) {
i.renderer = renderer
}
func (i *ipcManager) start(eventManager *eventManager, bindingManager *bindingManager) {
// Store manager references
i.eventManager = eventManager
i.bindingManager = bindingManager
i.log.Info("Starting")
// signal.Notify(manager.signals, os.Interrupt)
go func() {
running := true
for running {
select {
case incomingMessage := <-i.messageQueue:
i.log.DebugFields("Processing message", Fields{
"1D": &incomingMessage,
})
switch incomingMessage.Type {
case "call":
callData := incomingMessage.Payload.(*callData)
i.log.DebugFields("Processing call", Fields{
"1D": &incomingMessage,
"bindingName": callData.BindingName,
"data": callData.Data,
})
go func() {
result, err := bindingManager.processCall(callData)
i.log.DebugFields("processed call", Fields{"result": result, "err": err})
if err != nil {
incomingMessage.ReturnError(err.Error())
} else {
incomingMessage.ReturnSuccess(result)
}
i.log.DebugFields("Finished processing call", Fields{
"1D": &incomingMessage,
})
}()
case "event":
// Extract event data
eventData := incomingMessage.Payload.(*eventData)
// Log
i.log.DebugFields("Processing event", Fields{
"name": eventData.Name,
"data": eventData.Data,
})
// Push the event to the event manager
i.eventManager.PushEvent(eventData)
// Log
i.log.DebugFields("Finished processing event", Fields{
"name": eventData.Name,
})
case "log":
logdata := incomingMessage.Payload.(*logData)
switch logdata.Level {
case "info":
logger.Info(logdata.Message)
case "debug":
logger.Debug(logdata.Message)
case "warning":
logger.Warning(logdata.Message)
case "error":
logger.Error(logdata.Message)
case "fatal":
logger.Fatal(logdata.Message)
default:
i.log.ErrorFields("Invalid log level sent", Fields{
"level": logdata.Level,
"message": logdata.Message,
})
}
default:
i.log.Debugf("bad message sent to MessageQueue! Unknown type: %s", incomingMessage.Type)
}
// Log
i.log.DebugFields("Finished processing message", Fields{
"1D": &incomingMessage,
})
// case <-manager.quitChannel:
// Debug("[MessageQueue] Quit caught")
// running = false
// case <-manager.signals:
// Debug("[MessageQueue] Signal caught")
// running = false
}
}
i.log.Debug("Stopping")
}()
}
// Dispatch receives JSON encoded messages from the renderer.
// It processes the message to ensure that it is valid and places
// the processed message on the message queue
func (i *ipcManager) Dispatch(message string) {
// Create a new IPC Message
incomingMessage, err := newIPCMessage(message, i.SendResponse)
if err != nil {
i.log.ErrorFields("Could not understand incoming message! ", map[string]interface{}{
"message": message,
"error": err,
})
return
}
// Put message on queue
i.log.DebugFields("Message received", map[string]interface{}{
"type": incomingMessage.Type,
"payload": incomingMessage.Payload,
})
// Put incoming message on the message queue
i.messageQueue <- incomingMessage
}
// SendResponse sends the given response back to the frontend
func (i *ipcManager) SendResponse(response *ipcResponse) error {
// Serialise the Message
data, err := response.Serialise()
if err != nil {
fmt.Printf(err.Error())
return err
}
// Call back to the front end
return i.renderer.Callback(data)
}

93
ipc_message.go Normal file
View File

@@ -0,0 +1,93 @@
package wails
import (
"encoding/json"
"fmt"
)
// Message handler
type messageProcessorFunc func(*ipcMessage) (*ipcMessage, error)
var messageProcessors = make(map[string]messageProcessorFunc)
// ipcMessage is the struct version of the Message sent from the frontend.
// The payload has the specialised message data
type ipcMessage struct {
Type string `json:"type"`
Payload interface{} `json:"payload"`
CallbackID string `json:"callbackid,omitempty"`
sendResponse func(*ipcResponse) error
}
func parseMessage(incomingMessage string) (*ipcMessage, error) {
// Parse message
var message ipcMessage
err := json.Unmarshal([]byte(incomingMessage), &message)
return &message, err
}
func newIPCMessage(incomingMessage string, responseFunction func(*ipcResponse) error) (*ipcMessage, error) {
// Parse the Message
message, err := parseMessage(incomingMessage)
if err != nil {
return nil, err
}
// Check message type is valid
messageProcessor := messageProcessors[message.Type]
if messageProcessor == nil {
return nil, fmt.Errorf("unknown message type: %s", message.Type)
}
// Process message payload
message, err = messageProcessor(message)
if err != nil {
return nil, err
}
// Set the response function
message.sendResponse = responseFunction
return message, nil
}
// hasCallbackID checks if the message can send an error back to the frontend
func (m *ipcMessage) hasCallbackID() error {
if m.CallbackID == "" {
return fmt.Errorf("attempted to return error to message with no Callback ID")
}
return nil
}
// ReturnError returns an error back to the frontend
func (m *ipcMessage) ReturnError(format string, args ...interface{}) error {
// Ignore ReturnError if no callback ID given
err := m.hasCallbackID()
if err != nil {
return err
}
// Create response
response := newErrorResponse(m.CallbackID, fmt.Sprintf(format, args...))
// Send response
return m.sendResponse(response)
}
// ReturnSuccess returns a success message back with the given data
func (m *ipcMessage) ReturnSuccess(data interface{}) error {
// Ignore ReturnSuccess if no callback ID given
err := m.hasCallbackID()
if err != nil {
return err
}
// Create the response
response := newSuccessResponse(m.CallbackID, data)
// Send response
return m.sendResponse(response)
}

43
ipc_response.go Normal file
View File

@@ -0,0 +1,43 @@
package wails
import (
"encoding/json"
"strings"
)
// ipcResponse contains the response data from an RPC call
type ipcResponse struct {
CallbackID string `json:"callbackid"`
ErrorMessage string `json:"error,omitempty"`
Data interface{} `json:"data,omitempty"`
}
// newErrorResponse returns the given error message to the frontend with the callbackid
func newErrorResponse(callbackID string, errorMessage string) *ipcResponse {
// Create response object
result := &ipcResponse{
CallbackID: callbackID,
ErrorMessage: errorMessage,
}
return result
}
// newSuccessResponse returns the given data to the frontend with the callbackid
func newSuccessResponse(callbackID string, data interface{}) *ipcResponse {
// Create response object
result := &ipcResponse{
CallbackID: callbackID,
Data: data,
}
return result
}
// Serialise formats the response to a string
func (i *ipcResponse) Serialise() (string, error) {
b, err := json.Marshal(i)
result := strings.Replace(string(b), "\\", "\\\\", -1)
result = strings.Replace(result, "'", "\\'", -1)
return result, err
}

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Alec Aivazis
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.

View File

@@ -0,0 +1,29 @@
Go Port:
Copyright (c) 2013 Dmitry Chestnykh <dmitry@codingrobots.com>
Original:
Copyright (c) 2008 Ryan Grove <ryan@wonko.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of this project nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,24 @@
Copyright (c) 2013 Dmitry Chestnykh. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013 Fatih Arslan
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.

View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Dean Karn
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.

View File

@@ -0,0 +1,8 @@
The MIT License (MIT)
Copyright (c) 2018 Mark Bates
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.

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018 Mark Bates
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.

View File

@@ -0,0 +1,8 @@
The MIT License (MIT)
Copyright (c) 2016 Mark Bates
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.

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018 Mark Bates
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.

View File

@@ -0,0 +1,22 @@
Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Jack Mordaunt
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.

View File

@@ -0,0 +1,19 @@
Copyright (C) 2014 Kevin Ballard
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.

View File

@@ -0,0 +1,9 @@
(The MIT License)
Copyright (c) 2017 marvin + konsorten GmbH (open-source@konsorten.de)
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.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Lea Anthony
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.

View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2018 Lea Anthony
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.

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018-Present Lea Anthony
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.

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018 Mark Bates
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.

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Yasuhiro Matsumoto
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.

View File

@@ -0,0 +1,9 @@
Copyright (c) Yasuhiro MATSUMOTO <mattn.jp@gmail.com>
MIT License (Expat)
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.

View File

@@ -0,0 +1,9 @@
The MIT License (MIT)
Copyright (c) 2013 Mario L. Gutierrez
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.

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Mitchell Hashimoto
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.

View File

@@ -0,0 +1,13 @@
Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

View File

@@ -0,0 +1,23 @@
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Simon Eskildsen
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.

View File

@@ -0,0 +1,27 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,27 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,27 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Alec Aivazis
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.

View File

@@ -0,0 +1,22 @@
Copyright (c) 2014 Takashi Kokubun
MIT License
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.

42
log.go Normal file
View File

@@ -0,0 +1,42 @@
package wails
import (
"os"
"strings"
log "github.com/sirupsen/logrus"
)
// Global logger reference
var logger = log.New()
// Fields is used by the customLogger object to output
// fields along with a message
type Fields map[string]interface{}
// Default options for the global logger
func init() {
logger.SetOutput(os.Stdout)
logger.SetLevel(log.DebugLevel)
}
// Sets the log level to the given level
func setLogLevel(level string) {
switch strings.ToLower(level) {
case "info":
logger.SetLevel(log.InfoLevel)
case "debug":
logger.SetLevel(log.DebugLevel)
case "warn":
logger.SetLevel(log.WarnLevel)
case "error":
logger.SetLevel(log.ErrorLevel)
case "fatal":
logger.SetLevel(log.FatalLevel)
case "panic":
logger.SetLevel(log.PanicLevel)
default:
logger.SetLevel(log.DebugLevel)
logger.Warnf("Log level '%s' not recognised. Setting to Debug.", level)
}
}

103
log_custom.go Normal file
View File

@@ -0,0 +1,103 @@
package wails
// CustomLogger is a wrapper object to logrus
type CustomLogger struct {
prefix string
errorOnly bool
}
func newCustomLogger(prefix string) *CustomLogger {
return &CustomLogger{
prefix: "[" + prefix + "] ",
}
}
// Info level message
func (c *CustomLogger) Info(message string) {
logger.Info(c.prefix + message)
}
// Infof - formatted message
func (c *CustomLogger) Infof(message string, args ...interface{}) {
logger.Infof(c.prefix+message, args...)
}
// InfoFields - message with fields
func (c *CustomLogger) InfoFields(message string, fields Fields) {
logger.WithFields(map[string]interface{}(fields)).Info(c.prefix + message)
}
// Debug level message
func (c *CustomLogger) Debug(message string) {
logger.Debug(c.prefix + message)
}
// Debugf - formatted message
func (c *CustomLogger) Debugf(message string, args ...interface{}) {
logger.Debugf(c.prefix+message, args...)
}
// DebugFields - message with fields
func (c *CustomLogger) DebugFields(message string, fields Fields) {
logger.WithFields(map[string]interface{}(fields)).Debug(c.prefix + message)
}
// Warn level message
func (c *CustomLogger) Warn(message string) {
logger.Warn(c.prefix + message)
}
// Warnf - formatted message
func (c *CustomLogger) Warnf(message string, args ...interface{}) {
logger.Warnf(c.prefix+message, args...)
}
// WarnFields - message with fields
func (c *CustomLogger) WarnFields(message string, fields Fields) {
logger.WithFields(map[string]interface{}(fields)).Warn(c.prefix + message)
}
// Error level message
func (c *CustomLogger) Error(message string) {
logger.Error(c.prefix + message)
}
// Errorf - formatted message
func (c *CustomLogger) Errorf(message string, args ...interface{}) {
logger.Errorf(c.prefix+message, args...)
}
// ErrorFields - message with fields
func (c *CustomLogger) ErrorFields(message string, fields Fields) {
logger.WithFields(map[string]interface{}(fields)).Error(c.prefix + message)
}
// Fatal level message
func (c *CustomLogger) Fatal(message string) {
logger.Fatal(c.prefix + message)
}
// Fatalf - formatted message
func (c *CustomLogger) Fatalf(message string, args ...interface{}) {
logger.Fatalf(c.prefix+message, args...)
}
// FatalFields - message with fields
func (c *CustomLogger) FatalFields(message string, fields Fields) {
logger.WithFields(map[string]interface{}(fields)).Fatal(c.prefix + message)
}
// Panic level message
func (c *CustomLogger) Panic(message string) {
logger.Panic(c.prefix + message)
}
// Panicf - formatted message
func (c *CustomLogger) Panicf(message string, args ...interface{}) {
logger.Panicf(c.prefix+message, args...)
}
// PanicFields - message with fields
func (c *CustomLogger) PanicFields(message string, fields Fields) {
logger.WithFields(map[string]interface{}(fields)).Panic(c.prefix + message)
}

31
renderer.go Normal file
View File

@@ -0,0 +1,31 @@
package wails
// Renderer is an interface describing a Wails target to render the app to
type Renderer interface {
Initialise(*AppConfig, *ipcManager, *eventManager) error
Run() error
// Binding
NewBinding(bindingName string) error
Callback(data string) error
// Events
NotifyEvent(eventData *eventData) error
// Injection
InjectFramework(js string, css string)
AddJSList(js []string)
AddCSSList(css []string)
// Dialog Runtime
SelectFile() string
SelectDirectory() string
SelectSaveFile() string
// Window Runtime
SetColour(string) error
Fullscreen()
UnFullscreen()
SetTitle(title string)
Close()
}

354
renderer_headless.go Normal file
View File

@@ -0,0 +1,354 @@
package wails
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/dchest/htmlmin"
"github.com/gobuffalo/packr"
"github.com/gorilla/websocket"
)
var headlessAssets = packr.NewBox("./assets/headless")
var defaultAssets = packr.NewBox("./assets/default")
type messageType int
const (
jsMessage messageType = iota
cssMessage
htmlMessage
notifyMessage
bindingMessage
callbackMessage
wailsRuntimeMessage
)
func (m messageType) toString() string {
return [...]string{"j", "s", "h", "n", "b", "c", "w"}[m]
}
// Headless is a backend that opens a local web server
// and renders the files over a websocket
type Headless struct {
// Common
log *CustomLogger
ipcManager *ipcManager
appConfig *AppConfig
eventManager *eventManager
bindingCache []string
frameworkJS string
frameworkCSS string
jsCache []string
cssCache []string
// Headless specific
initialisationJS []string
server *http.Server
theConnection *websocket.Conn
bridgeMode bool
connectionType string
}
// Initialise the Headless Renderer
func (h *Headless) Initialise(appConfig *AppConfig, ipcManager *ipcManager, eventManager *eventManager) error {
h.ipcManager = ipcManager
h.appConfig = appConfig
h.eventManager = eventManager
h.bridgeMode = false
h.connectionType = "Websocket"
ipcManager.bindRenderer(h)
h.log = newCustomLogger("Headless")
return nil
}
func (h *Headless) evalJS(js string, mtype messageType) error {
message := mtype.toString() + js
if h.theConnection == nil {
h.initialisationJS = append(h.initialisationJS, message)
} else {
// Prepend message type to message
h.sendMessage(h.theConnection, message)
}
return nil
}
func (h *Headless) injectCSS(css string) {
// Minify css to overcome issues in the browser with carriage returns
minified, err := htmlmin.Minify([]byte(css), &htmlmin.Options{
MinifyStyles: true,
})
if err != nil {
h.log.Fatal("Unable to minify CSS: " + css)
}
minifiedCSS := string(minified)
minifiedCSS = strings.Replace(minifiedCSS, "\\", "\\\\", -1)
minifiedCSS = strings.Replace(minifiedCSS, "'", "\\'", -1)
minifiedCSS = strings.Replace(minifiedCSS, "\n", " ", -1)
inject := fmt.Sprintf("wails._.injectCSS('%s')", minifiedCSS)
h.evalJS(inject, cssMessage)
}
func (h *Headless) rootHandler(w http.ResponseWriter, r *http.Request) {
indexHTML := BoxString(&headlessAssets, "index.html")
fmt.Fprintf(w, "%s", indexHTML)
}
func (h *Headless) wsBridgeHandler(w http.ResponseWriter, r *http.Request) {
h.bridgeMode = true
h.connectionType = "Bridge"
h.wsHandler(w, r)
}
func (h *Headless) wsHandler(w http.ResponseWriter, r *http.Request) {
conn, err := websocket.Upgrade(w, r, w.Header(), 1024, 1024)
if err != nil {
http.Error(w, "Could not open websocket connection", http.StatusBadRequest)
}
h.theConnection = conn
h.log.Infof("%s connection accepted [%p].", h.connectionType, h.theConnection)
conn.SetCloseHandler(func(int, string) error {
h.log.Infof("%s connection dropped [%p].", h.connectionType, h.theConnection)
h.theConnection = nil
return nil
})
go h.start(conn)
}
func (h *Headless) sendMessage(conn *websocket.Conn, msg string) {
if err := conn.WriteMessage(websocket.TextMessage, []byte(msg)); err != nil {
h.log.Error(err.Error())
}
}
func (h *Headless) start(conn *websocket.Conn) {
// set external.invoke
h.log.Infof("Connected to frontend.")
h.log.Infof("Mode = %s", h.connectionType)
wailsRuntime := BoxString(&defaultAssets, "wails.js")
h.evalJS(wailsRuntime, wailsRuntimeMessage)
if !h.bridgeMode {
// Inject jquery
jquery := BoxString(&defaultAssets, "jquery.3.3.1.min.js")
h.evalJS(jquery, jsMessage)
}
// Inject the initial JS
for _, js := range h.initialisationJS {
h.sendMessage(h.theConnection, js)
}
// Inject bindings
for _, binding := range h.bindingCache {
h.evalJS(binding, bindingMessage)
}
// In Bridge mode, we only send the wails runtime and bindings
// so ignore this whole section
if !h.bridgeMode {
// Inject Framework
if h.frameworkJS != "" {
h.evalJS(h.frameworkJS, jsMessage)
}
if h.frameworkCSS != "" {
h.injectCSS(h.frameworkCSS)
}
// Inject user CSS
if h.appConfig.CSS != "" {
outputCSS := fmt.Sprintf("%.45s", h.appConfig.CSS)
if len(outputCSS) > 45 {
outputCSS += "..."
}
h.log.DebugFields("Inject User CSS", Fields{"css": outputCSS})
h.injectCSS(h.appConfig.CSS)
} else {
// Use default wails css
h.log.Debug("Injecting Default Wails CSS")
defaultCSS := BoxString(&defaultAssets, "wails.css")
h.injectCSS(defaultCSS)
}
// Inject all the CSS files that have been added
for _, css := range h.cssCache {
h.injectCSS(css)
}
// Inject all the JS files that have been added
for _, js := range h.jsCache {
h.evalJS(js, jsMessage)
}
// Inject user JS
if h.appConfig.JS != "" {
outputJS := fmt.Sprintf("%.45s", h.appConfig.JS)
if len(outputJS) > 45 {
outputJS += "..."
}
h.log.DebugFields("Inject User JS", Fields{"js": outputJS})
h.evalJS(h.appConfig.JS, jsMessage)
}
var injectHTML string
if h.appConfig.isHTMLFragment {
injectHTML = fmt.Sprintf("$('#app').html('%s')", h.appConfig.HTML)
h.evalJS(injectHTML, htmlMessage)
}
}
// Emit that everything is loaded and ready
h.eventManager.Emit("wails:ready")
for {
messageType, buffer, err := conn.ReadMessage()
if messageType == -1 {
return
}
if err != nil {
h.log.Errorf("Error reading message: ", err)
continue
}
h.log.Debugf("Got message: %#v\n", string(buffer))
h.ipcManager.Dispatch(string(buffer))
}
}
// Run the app in headless mode!
func (h *Headless) Run() error {
h.server = &http.Server{Addr: ":34115"}
http.HandleFunc("/ws", h.wsHandler)
http.HandleFunc("/bridge", h.wsBridgeHandler)
http.HandleFunc("/", h.rootHandler)
h.log.Info("Headless mode started.")
h.log.Info("If using the Wails bridge, it will connect automatically.")
h.log.Info("You may also connect manually by browsing to http://localhost:34115")
err := h.server.ListenAndServe()
if err != nil {
h.log.Fatal(err.Error())
}
return err
}
// NewBinding creates a new binding with the frontend
func (h *Headless) NewBinding(methodName string) error {
h.bindingCache = append(h.bindingCache, methodName)
return nil
}
// InjectFramework sets up what JS/CSS should be injected
// at startup
func (h *Headless) InjectFramework(js, css string) {
h.frameworkJS = js
h.frameworkCSS = css
}
// SelectFile is unsupported for Headless but required
// for the Renderer interface
func (h *Headless) SelectFile() string {
h.log.Error("SelectFile() unsupported in headless mode")
return ""
}
// SelectDirectory is unsupported for Headless but required
// for the Renderer interface
func (h *Headless) SelectDirectory() string {
h.log.Error("SelectDirectory() unsupported in headless mode")
return ""
}
// SelectSaveFile is unsupported for Headless but required
// for the Renderer interface
func (h *Headless) SelectSaveFile() string {
h.log.Error("SelectSaveFile() unsupported in headless mode")
return ""
}
// AddJSList adds a slice of JS strings to the list of js
// files injected at startup
func (h *Headless) AddJSList(jsCache []string) {
h.jsCache = jsCache
}
// AddCSSList adds a slice of CSS strings to the list of css
// files injected at startup
func (h *Headless) AddCSSList(cssCache []string) {
h.cssCache = cssCache
}
// Callback sends a callback to the frontend
func (h *Headless) Callback(data string) error {
return h.evalJS(data, callbackMessage)
}
// NotifyEvent notifies the frontend of an event
func (h *Headless) NotifyEvent(event *eventData) error {
// Look out! Nils about!
var err error
if event == nil {
err = fmt.Errorf("Sent nil event to renderer.webViewRenderer")
logger.Error(err)
return err
}
// Default data is a blank array
data := []byte("[]")
// Process event data
if event.Data != nil {
// Marshall the data
data, err = json.Marshal(event.Data)
if err != nil {
h.log.Errorf("Cannot unmarshall JSON data in event: %s ", err.Error())
return err
}
}
message := fmt.Sprintf("window.wails._.notify('%s','%s')", event.Name, data)
return h.evalJS(message, notifyMessage)
}
// SetColour is unsupported for Headless but required
// for the Renderer interface
func (h *Headless) SetColour(colour string) error {
h.log.WarnFields("SetColour ignored for headless more", Fields{"col": colour})
return nil
}
// Fullscreen is unsupported for Headless but required
// for the Renderer interface
func (h *Headless) Fullscreen() {
h.log.Warn("Fullscreen() unsupported in headless mode")
}
// UnFullscreen is unsupported for Headless but required
// for the Renderer interface
func (h *Headless) UnFullscreen() {
h.log.Warn("UnFullscreen() unsupported in headless mode")
}
// SetTitle is currently unsupported for Headless but required
// for the Renderer interface
func (h *Headless) SetTitle(title string) {
h.log.WarnFields("SetTitle() unsupported in headless mode", Fields{"title": title})
}
// Close is unsupported for Headless but required
// for the Renderer interface
func (h *Headless) Close() {
h.log.Warn("Close() unsupported in headless mode")
}

389
renderer_webview.go Normal file
View File

@@ -0,0 +1,389 @@
package wails
import (
"encoding/json"
"fmt"
"math/rand"
"sync"
"time"
"github.com/go-playground/colors"
"github.com/gobuffalo/packr"
"github.com/wailsapp/wails/webview"
)
// Window defines the main application window
// Default values in []
type webViewRenderer struct {
window webview.WebView // The webview object
ipc *ipcManager
log *CustomLogger
config *AppConfig
eventManager *eventManager
bindingCache []string
frameworkJS string
frameworkCSS string
// This is a list of all the JS/CSS that needs injecting
// It will get injected in order
jsCache []string
cssCache []string
}
// Initialise sets up the WebView
func (w *webViewRenderer) Initialise(config *AppConfig, ipc *ipcManager, eventManager *eventManager) error {
// Store reference to eventManager
w.eventManager = eventManager
// Set up logger
w.log = newCustomLogger("WebView")
// Set up the dispatcher function
w.ipc = ipc
ipc.bindRenderer(w)
// Save the config
w.config = config
// Create the WebView instance
w.window = webview.NewWebview(webview.Settings{
Width: config.Width,
Height: config.Height,
Title: config.Title,
Resizable: config.Resizable,
URL: config.defaultHTML,
Debug: !config.DisableInspector,
ExternalInvokeCallback: func(_ webview.WebView, message string) {
w.ipc.Dispatch(message)
},
})
// SignalManager.OnExit(w.Exit)
// Set colour
err := w.SetColour(config.Colour)
if err != nil {
return err
}
w.log.Info("Initialised")
return nil
}
func (w *webViewRenderer) SetColour(colour string) error {
color, err := colors.Parse(colour)
if err != nil {
return err
}
rgba := color.ToRGBA()
alpha := uint8(255 * rgba.A)
w.window.Dispatch(func() {
w.window.SetColor(rgba.R, rgba.G, rgba.B, alpha)
})
return nil
}
// evalJS evaluates the given js in the WebView
// I should rename this to evilJS lol
func (w *webViewRenderer) evalJS(js string) error {
outputJS := fmt.Sprintf("%.45s", js)
if len(js) > 45 {
outputJS += "..."
}
w.log.DebugFields("Eval", Fields{"js": outputJS})
//
w.window.Dispatch(func() {
w.window.Eval(js)
})
return nil
}
// evalJSSync evaluates the given js in the WebView synchronously
// Do not call this from the main thread or you'll nuke your app because
// you won't get the callback.
func (w *webViewRenderer) evalJSSync(js string) error {
minified, err := escapeJS(js)
if err != nil {
return err
}
outputJS := fmt.Sprintf("%.45s", js)
if len(js) > 45 {
outputJS += "..."
}
w.log.DebugFields("EvalSync", Fields{"js": outputJS})
ID := fmt.Sprintf("syncjs:%d:%d", time.Now().Unix(), rand.Intn(9999))
var wg sync.WaitGroup
wg.Add(1)
go func() {
exit := false
// We are done when we recieve the Callback ID
w.log.Debug("SyncJS: sending with ID = " + ID)
w.eventManager.On(ID, func(...interface{}) {
w.log.Debug("SyncJS: Got callback ID = " + ID)
wg.Done()
exit = true
})
command := fmt.Sprintf("wails._.addScript('%s', '%s')", minified, ID)
w.window.Dispatch(func() {
w.window.Eval(command)
})
for exit == false {
time.Sleep(time.Millisecond * 1)
}
}()
wg.Wait()
return nil
}
// injectCSS adds the given CSS to the WebView
func (w *webViewRenderer) injectCSS(css string) {
w.window.Dispatch(func() {
w.window.InjectCSS(css)
})
}
// Quit the window
func (w *webViewRenderer) Exit() {
w.window.Exit()
}
// Run the window main loop
func (w *webViewRenderer) Run() error {
w.log.Info("Run()")
// Runtime assets
assets := packr.NewBox("./assets/default")
wailsRuntime := BoxString(&assets, "wails.js")
w.evalJS(wailsRuntime)
// Ping the wait channel when the wails runtime is loaded
w.eventManager.On("wails:loaded", func(...interface{}) {
// Run this in a different go routine to free up the main process
go func() {
// Will we mount a custom component
// Inject jquery
jquery := BoxString(&assets, "jquery.3.3.1.min.js")
w.evalJSSync(jquery)
// Inject Bindings
for _, binding := range w.bindingCache {
w.evalJSSync(binding)
}
// Inject Framework
if w.frameworkJS != "" {
w.evalJSSync(w.frameworkJS)
}
if w.frameworkCSS != "" {
w.injectCSS(w.frameworkCSS)
}
// Do we have custom html?
// If given an HMTL fragment, mount it on #app
// Otherwise, replace the html tag
var injectHTML string
if w.config.isHTMLFragment {
injectHTML = fmt.Sprintf("$('#app').html('%s')", w.config.HTML)
} else {
injectHTML = fmt.Sprintf("$('html').html('%s')", w.config.HTML)
}
w.evalJSSync(injectHTML)
// Inject user CSS
if w.config.CSS != "" {
outputCSS := fmt.Sprintf("%.45s", w.config.CSS)
if len(outputCSS) > 45 {
outputCSS += "..."
}
w.log.DebugFields("Inject User CSS", Fields{"css": outputCSS})
w.injectCSS(w.config.CSS)
} else {
// Use default wails css
w.log.Debug("Injecting Default Wails CSS")
defaultCSS := BoxString(&defaultAssets, "wails.css")
w.injectCSS(defaultCSS)
}
// Inject all the CSS files that have been added
for _, css := range w.cssCache {
w.injectCSS(css)
}
// Inject all the JS files that have been added
for _, js := range w.jsCache {
w.evalJSSync(js)
}
// Inject user JS
if w.config.JS != "" {
outputJS := fmt.Sprintf("%.45s", w.config.JS)
if len(outputJS) > 45 {
outputJS += "..."
}
w.log.DebugFields("Inject User JS", Fields{"js": outputJS})
w.evalJSSync(w.config.JS)
}
// Emit that everything is loaded and ready
w.eventManager.Emit("wails:ready")
}()
})
// Kick off main window loop
w.window.Run()
return nil
}
// Binds the given method name with the front end
func (w *webViewRenderer) NewBinding(methodName string) error {
objectCode := fmt.Sprintf("window.wails._.newBinding('%s');", methodName)
w.bindingCache = append(w.bindingCache, objectCode)
return nil
}
func (w *webViewRenderer) InjectFramework(js, css string) {
w.frameworkJS = js
w.frameworkCSS = css
}
func (w *webViewRenderer) SelectFile() string {
var result string
// We need to run this on the main thread, however Dispatch is
// non-blocking so we launch this in a goroutine and wait for
// dispatch to finish before returning the result
var wg sync.WaitGroup
wg.Add(1)
go func() {
w.window.Dispatch(func() {
result = w.window.Dialog(webview.DialogTypeOpen, 0, "Select File", "")
wg.Done()
})
}()
wg.Wait()
return result
}
func (w *webViewRenderer) SelectDirectory() string {
var result string
// We need to run this on the main thread, however Dispatch is
// non-blocking so we launch this in a goroutine and wait for
// dispatch to finish before returning the result
var wg sync.WaitGroup
wg.Add(1)
go func() {
w.window.Dispatch(func() {
result = w.window.Dialog(webview.DialogTypeOpen, webview.DialogFlagDirectory, "Select Directory", "")
wg.Done()
})
}()
wg.Wait()
return result
}
func (w *webViewRenderer) SelectSaveFile() string {
var result string
// We need to run this on the main thread, however Dispatch is
// non-blocking so we launch this in a goroutine and wait for
// dispatch to finish before returning the result
var wg sync.WaitGroup
wg.Add(1)
go func() {
w.window.Dispatch(func() {
result = w.window.Dialog(webview.DialogTypeSave, 0, "Save file", "")
wg.Done()
})
}()
wg.Wait()
return result
}
// AddJS adds a piece of Javascript to a cache that
// gets injected at runtime
func (w *webViewRenderer) AddJSList(jsCache []string) {
w.jsCache = jsCache
}
// AddCSSList sets the cssCache to the given list of strings
func (w *webViewRenderer) AddCSSList(cssCache []string) {
w.cssCache = cssCache
}
// Callback sends a callback to the frontend
func (w *webViewRenderer) Callback(data string) error {
callbackCMD := fmt.Sprintf("window.wails._.callback('%s');", data)
return w.evalJS(callbackCMD)
}
func (w *webViewRenderer) NotifyEvent(event *eventData) error {
// Look out! Nils about!
var err error
if event == nil {
err = fmt.Errorf("Sent nil event to renderer.webViewRenderer")
logger.Error(err)
return err
}
// Default data is a blank array
data := []byte("[]")
// Process event data
if event.Data != nil {
// Marshall the data
data, err = json.Marshal(event.Data)
if err != nil {
w.log.Errorf("Cannot unmarshall JSON data in event: %s ", err.Error())
return err
}
}
message := fmt.Sprintf("wails._.notify('%s','%s')", event.Name, data)
return w.evalJS(message)
}
// Window
func (w *webViewRenderer) Fullscreen() {
if w.config.Resizable == false {
w.log.Warn("Cannot call Fullscreen() - App.Resizable = false")
return
}
w.window.Dispatch(func() {
w.window.SetFullscreen(true)
})
}
func (w *webViewRenderer) UnFullscreen() {
if w.config.Resizable == false {
w.log.Warn("Cannot call UnFullscreen() - App.Resizable = false")
return
}
w.window.Dispatch(func() {
w.window.SetFullscreen(false)
})
}
func (w *webViewRenderer) SetTitle(title string) {
w.window.Dispatch(func() {
w.window.SetTitle(title)
})
}
func (w *webViewRenderer) Close() {
w.window.Dispatch(func() {
w.window.Terminate()
})
}

18
runtime.go Normal file
View File

@@ -0,0 +1,18 @@
package wails
// Runtime is the Wails Runtime Interface, given to a user who has defined the WailsInit method
type Runtime struct {
Events *RuntimeEvents
Log *RuntimeLog
Dialog *RuntimeDialog
Window *RuntimeWindow
}
func newRuntime(eventManager *eventManager, renderer Renderer) *Runtime {
return &Runtime{
Events: newRuntimeEvents(eventManager),
Log: newRuntimeLog(),
Dialog: newRuntimeDialog(renderer),
Window: newRuntimeWindow(renderer),
}
}

28
runtime_dialog.go Normal file
View File

@@ -0,0 +1,28 @@
package wails
// RuntimeDialog exposes an interface to native dialogs
type RuntimeDialog struct {
renderer Renderer
}
// newRuntimeDialog creates a new RuntimeDialog struct
func newRuntimeDialog(renderer Renderer) *RuntimeDialog {
return &RuntimeDialog{
renderer: renderer,
}
}
// SelectFile prompts the user to select a file
func (r *RuntimeDialog) SelectFile() string {
return r.renderer.SelectFile()
}
// SelectDirectory prompts the user to select a directory
func (r *RuntimeDialog) SelectDirectory() string {
return r.renderer.SelectDirectory()
}
// SelectSaveFile prompts the user to select a file for saving
func (r *RuntimeDialog) SelectSaveFile() string {
return r.renderer.SelectSaveFile()
}

22
runtime_events.go Normal file
View File

@@ -0,0 +1,22 @@
package wails
// RuntimeEvents exposes the events interface
type RuntimeEvents struct {
eventManager *eventManager
}
func newRuntimeEvents(eventManager *eventManager) *RuntimeEvents {
return &RuntimeEvents{
eventManager: eventManager,
}
}
// On pass through
func (r *RuntimeEvents) On(eventName string, callback func(optionalData ...interface{})) {
r.eventManager.On(eventName, callback)
}
// Emit pass through
func (r *RuntimeEvents) Emit(eventName string, optionalData ...interface{}) {
r.eventManager.Emit(eventName, optionalData)
}

14
runtime_log.go Normal file
View File

@@ -0,0 +1,14 @@
package wails
// RuntimeLog exposes the logging interface to the runtime
type RuntimeLog struct {
}
func newRuntimeLog() *RuntimeLog {
return &RuntimeLog{}
}
// New creates a new logger
func (r *RuntimeLog) New(prefix string) *CustomLogger {
return newCustomLogger(prefix)
}

37
runtime_window.go Normal file
View File

@@ -0,0 +1,37 @@
package wails
// RuntimeWindow exposes an interface for manipulating the window
type RuntimeWindow struct {
renderer Renderer
}
func newRuntimeWindow(renderer Renderer) *RuntimeWindow {
return &RuntimeWindow{
renderer: renderer,
}
}
// SetColour sets the the window colour
func (r *RuntimeWindow) SetColour(colour string) error {
return r.renderer.SetColour(colour)
}
// Fullscreen makes the window fullscreen
func (r *RuntimeWindow) Fullscreen() {
r.renderer.Fullscreen()
}
// UnFullscreen attempts to restore the window to the size/position before fullscreen
func (r *RuntimeWindow) UnFullscreen() {
r.renderer.UnFullscreen()
}
// SetTitle sets the the window title
func (r *RuntimeWindow) SetTitle(title string) {
r.renderer.SetTitle(title)
}
// Close shuts down the window and therefore the app
func (r *RuntimeWindow) Close() {
r.renderer.Close()
}

24
utils.go Normal file
View File

@@ -0,0 +1,24 @@
package wails
import (
"log"
"strings"
"github.com/gobuffalo/packr"
)
func escapeJS(js string) (string, error) {
result := strings.Replace(js, "\\", "\\\\", -1)
result = strings.Replace(result, "'", "\\'", -1)
result = strings.Replace(result, "\n", "\\n", -1)
return result, nil
}
// BoxString wraps packr.FindString
func BoxString(box *packr.Box, filename string) string {
result, err := box.FindString(filename)
if err != nil {
log.Fatal(err)
}
return result
}

372
webview/webview.go Normal file
View File

@@ -0,0 +1,372 @@
// Package wails implements Go bindings to https://github.com/zserge/webview C library.
// It is a modified version of webview.go from that repository
// Bindings closely repeat the C APIs and include both, a simplified
// single-function API to just open a full-screen webview window, and a more
// advanced and featureful set of APIs, including Go-to-JavaScript bindings.
//
// The library uses gtk-webkit, Cocoa/Webkit and MSHTML (IE8..11) as a browser
// engine and supports Linux, MacOS and Windows 7..10 respectively.
//
package webview
/*
#cgo linux openbsd freebsd CFLAGS: -DWEBVIEW_GTK=1
#cgo linux openbsd freebsd pkg-config: gtk+-3.0 webkit2gtk-4.0
#cgo windows CFLAGS: -DWEBVIEW_WINAPI=1
#cgo windows LDFLAGS: -lole32 -lcomctl32 -loleaut32 -luuid -lgdi32
#cgo darwin CFLAGS: -DWEBVIEW_COCOA=1 -x objective-c
#cgo darwin LDFLAGS: -framework Cocoa -framework WebKit
#include <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) {
webview_dialog(w, dlgtype, flags,
(const char*)title, (const char*) arg, res, ressz);
}
static inline int CgoWebViewEval(void *w, char *js) {
return webview_eval((struct webview *)w, js);
}
static inline void CgoWebViewInjectCSS(void *w, char *css) {
webview_inject_css((struct webview *)w, css);
}
extern void _webviewDispatchGoCallback(void *);
static inline void _webview_dispatch_cb(struct webview *w, void *arg) {
_webviewDispatchGoCallback(arg);
}
static inline void CgoWebViewDispatch(void *w, uintptr_t arg) {
webview_dispatch((struct webview *)w, _webview_dispatch_cb, (void *)arg);
}
*/
import "C"
import (
"errors"
"runtime"
"sync"
"unsafe"
)
func init() {
// Ensure that main.main is called from the main thread
runtime.LockOSThread()
}
// Open is a simplified API to open a single native window with a full-size webview in
// it. It can be helpful if you want to communicate with the core app using XHR
// or WebSockets (as opposed to using JavaScript bindings).
//
// Window appearance can be customized using title, width, height and resizable parameters.
// URL must be provided and can user either a http or https protocol, or be a
// local file:// URL. On some platforms "data:" URLs are also supported
// (Linux/MacOS).
func Open(title, url string, w, h int, resizable bool) error {
titleStr := C.CString(title)
defer C.free(unsafe.Pointer(titleStr))
urlStr := C.CString(url)
defer C.free(unsafe.Pointer(urlStr))
resize := C.int(0)
if resizable {
resize = C.int(1)
}
r := C.webview(titleStr, urlStr, C.int(w), C.int(h), resize)
if r != 0 {
return errors.New("failed to create webview")
}
return nil
}
// ExternalInvokeCallbackFunc is a function type that is called every time
// "window.external.invoke()" is called from JavaScript. Data is the only
// obligatory string parameter passed into the "invoke(data)" function from
// JavaScript. To pass more complex data serialized JSON or base64 encoded
// string can be used.
type ExternalInvokeCallbackFunc func(w WebView, data string)
// Settings is a set of parameters to customize the initial WebView appearance
// and behavior. It is passed into the webview.New() constructor.
type Settings struct {
// WebView main window title
Title string
// URL to open in a webview
URL string
// Window width in pixels
Width int
// Window height in pixels
Height int
// Allows/disallows window resizing
Resizable bool
// Enable debugging tools (Linux/BSD/MacOS, on Windows use Firebug)
Debug bool
// A callback that is executed when JavaScript calls "window.external.invoke()"
ExternalInvokeCallback ExternalInvokeCallbackFunc
}
// WebView is an interface that wraps the basic methods for controlling the UI
// loop, handling multithreading and providing JavaScript bindings.
type WebView interface {
// Run() starts the main UI loop until the user closes the webview window or
// Terminate() is called.
Run()
// Loop() runs a single iteration of the main UI.
Loop(blocking bool) bool
// SetTitle() changes window title. This method must be called from the main
// thread only. See Dispatch() for more details.
SetTitle(title string)
// SetFullscreen() controls window full-screen mode. This method must be
// called from the main thread only. See Dispatch() for more details.
SetFullscreen(fullscreen bool)
// SetColor() changes window background color. This method must be called from
// the main thread only. See Dispatch() for more details.
SetColor(r, g, b, a uint8)
// Eval() evaluates an arbitrary JS code inside the webview. This method must
// be called from the main thread only. See Dispatch() for more details.
Eval(js string) error
// InjectJS() injects an arbitrary block of CSS code using the JS API. This
// method must be called from the main thread only. See Dispatch() for more
// details.
InjectCSS(css string)
// Dialog() opens a system dialog of the given type and title. String
// argument can be provided for certain dialogs, such as alert boxes. For
// alert boxes argument is a message inside the dialog box.
Dialog(dlgType DialogType, flags int, title string, arg string) string
// Terminate() breaks the main UI loop. This method must be called from the main thread
// only. See Dispatch() for more details.
Terminate()
// Dispatch() schedules some arbitrary function to be executed on the main UI
// thread. This may be helpful if you want to run some JavaScript from
// background threads/goroutines, or to terminate the app.
Dispatch(func())
// Exit() closes the window and cleans up the resources. Use Terminate() to
// forcefully break out of the main UI loop.
Exit()
}
// DialogType is an enumeration of all supported system dialog types
type DialogType int
const (
// DialogTypeOpen is a system file open dialog
DialogTypeOpen DialogType = iota
// DialogTypeSave is a system file save dialog
DialogTypeSave
// DialogTypeAlert is a system alert dialog (message box)
DialogTypeAlert
)
const (
// DialogFlagFile is a normal file picker dialog
DialogFlagFile = C.WEBVIEW_DIALOG_FLAG_FILE
// DialogFlagDirectory is an open directory dialog
DialogFlagDirectory = C.WEBVIEW_DIALOG_FLAG_DIRECTORY
// DialogFlagInfo is an info alert dialog
DialogFlagInfo = C.WEBVIEW_DIALOG_FLAG_INFO
// DialogFlagWarning is a warning alert dialog
DialogFlagWarning = C.WEBVIEW_DIALOG_FLAG_WARNING
// DialogFlagError is an error dialog
DialogFlagError = C.WEBVIEW_DIALOG_FLAG_ERROR
)
var (
m sync.Mutex
index uintptr
fns = map[uintptr]func(){}
cbs = map[WebView]ExternalInvokeCallbackFunc{}
)
type webview struct {
w unsafe.Pointer
}
var _ WebView = &webview{}
func boolToInt(b bool) int {
if b {
return 1
}
return 0
}
// NewWebview creates and opens a new webview window using the given settings. The
// returned object implements the WebView interface. This function returns nil
// if a window can not be created.
func NewWebview(settings Settings) WebView {
if settings.Width == 0 {
settings.Width = 640
}
if settings.Height == 0 {
settings.Height = 480
}
if settings.Title == "" {
settings.Title = "WebView"
}
w := &webview{}
w.w = C.CgoWebViewCreate(C.int(settings.Width), C.int(settings.Height),
C.CString(settings.Title), C.CString(settings.URL),
C.int(boolToInt(settings.Resizable)), C.int(boolToInt(settings.Debug)))
m.Lock()
if settings.ExternalInvokeCallback != nil {
cbs[w] = settings.ExternalInvokeCallback
} else {
cbs[w] = func(w WebView, data string) {}
}
m.Unlock()
return w
}
func (w *webview) Loop(blocking bool) bool {
block := C.int(0)
if blocking {
block = 1
}
return C.CgoWebViewLoop(w.w, block) == 0
}
func (w *webview) Run() {
for w.Loop(true) {
}
}
func (w *webview) Exit() {
C.CgoWebViewExit(w.w)
}
func (w *webview) Dispatch(f func()) {
m.Lock()
for ; fns[index] != nil; index++ {
}
fns[index] = f
m.Unlock()
C.CgoWebViewDispatch(w.w, C.uintptr_t(index))
}
func (w *webview) SetTitle(title string) {
p := C.CString(title)
defer C.free(unsafe.Pointer(p))
C.CgoWebViewSetTitle(w.w, p)
}
func (w *webview) SetColor(r, g, b, a uint8) {
C.CgoWebViewSetColor(w.w, C.uint8_t(r), C.uint8_t(g), C.uint8_t(b), C.uint8_t(a))
}
func (w *webview) SetFullscreen(fullscreen bool) {
C.CgoWebViewSetFullscreen(w.w, C.int(boolToInt(fullscreen)))
}
func (w *webview) Dialog(dlgType DialogType, flags int, title string, arg string) string {
const maxPath = 4096
titlePtr := C.CString(title)
defer C.free(unsafe.Pointer(titlePtr))
argPtr := C.CString(arg)
defer C.free(unsafe.Pointer(argPtr))
resultPtr := (*C.char)(C.calloc((C.size_t)(unsafe.Sizeof((*C.char)(nil))), (C.size_t)(maxPath)))
defer C.free(unsafe.Pointer(resultPtr))
C.CgoDialog(w.w, C.int(dlgType), C.int(flags), titlePtr,
argPtr, resultPtr, C.size_t(maxPath))
return C.GoString(resultPtr)
}
func (w *webview) Eval(js string) error {
p := C.CString(js)
defer C.free(unsafe.Pointer(p))
switch C.CgoWebViewEval(w.w, p) {
case -1:
return errors.New("evaluation failed")
}
return nil
}
func (w *webview) InjectCSS(css string) {
p := C.CString(css)
defer C.free(unsafe.Pointer(p))
C.CgoWebViewInjectCSS(w.w, p)
}
func (w *webview) Terminate() {
C.CgoWebViewTerminate(w.w)
}
//export _webviewDispatchGoCallback
func _webviewDispatchGoCallback(index unsafe.Pointer) {
var f func()
m.Lock()
f = fns[uintptr(index)]
delete(fns, uintptr(index))
m.Unlock()
f()
}
//export _webviewExternalInvokeCallback
func _webviewExternalInvokeCallback(w unsafe.Pointer, data unsafe.Pointer) {
m.Lock()
var (
cb ExternalInvokeCallbackFunc
wv WebView
)
for wv, cb = range cbs {
if wv.(*webview).w == w {
break
}
}
m.Unlock()
cb(wv, C.GoString((*C.char)(data)))
}

1927
webview/webview.h Normal file

File diff suppressed because it is too large Load Diff