mirror of
https://github.com/taigrr/wails.git
synced 2026-04-13 10:28:13 -07:00
Compare commits
1 Commits
v2-linux
...
443---ldfl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03200edd52 |
6
.github/workflows/latest-pre.yml
vendored
6
.github/workflows/latest-pre.yml
vendored
@@ -13,10 +13,10 @@ jobs:
|
|||||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Set up Go 1.13
|
- name: Set up Go 1.12
|
||||||
uses: actions/setup-go@v1
|
uses: actions/setup-go@v1
|
||||||
with:
|
with:
|
||||||
go-version: 1.13
|
go-version: 1.12
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
@@ -27,6 +27,6 @@ jobs:
|
|||||||
go get -v -d ./...
|
go get -v -d ./...
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build -v ./cmd/wails
|
run: go build -v ./cmd/wails
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: ./wails version
|
run: ./wails version
|
||||||
|
|||||||
6
.github/workflows/pr.yml
vendored
6
.github/workflows/pr.yml
vendored
@@ -13,10 +13,10 @@ jobs:
|
|||||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Set up Go 1.13
|
- name: Set up Go 1.12
|
||||||
uses: actions/setup-go@v1
|
uses: actions/setup-go@v1
|
||||||
with:
|
with:
|
||||||
go-version: 1.13
|
go-version: 1.12
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
@@ -27,6 +27,6 @@ jobs:
|
|||||||
go get -v -d ./...
|
go get -v -d ./...
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build -v ./cmd/wails
|
run: go build -v ./cmd/wails
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: ./wails version
|
run: ./wails version
|
||||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -4,7 +4,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
tags:
|
tags:
|
||||||
- '!**pre**'
|
- '!**pre**'
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
build:
|
build:
|
||||||
@@ -15,10 +15,10 @@ jobs:
|
|||||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Set up Go 1.13
|
- name: Set up Go 1.12
|
||||||
uses: actions/setup-go@v1
|
uses: actions/setup-go@v1
|
||||||
with:
|
with:
|
||||||
go-version: 1.13
|
go-version: 1.12
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
@@ -29,6 +29,6 @@ jobs:
|
|||||||
go get -v -d ./...
|
go get -v -d ./...
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build -v ./cmd/wails
|
run: go build -v ./cmd/wails
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: ./wails version
|
run: ./wails version
|
||||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -16,13 +16,4 @@ examples/**/example*
|
|||||||
cmd/wails/wails
|
cmd/wails/wails
|
||||||
.DS_Store
|
.DS_Store
|
||||||
tmp
|
tmp
|
||||||
node_modules/
|
node_modules/
|
||||||
package.json.md5
|
|
||||||
v2/test/**/frontend/dist
|
|
||||||
v2/test/**/build/
|
|
||||||
v2/test/frameless/icon.png
|
|
||||||
v2/test/hidden/icon.png
|
|
||||||
v2/internal/ffenestri/runtime.c
|
|
||||||
v2/internal/runtime/assets/desktop.js
|
|
||||||
v2/test/kitchensink/frontend/public/bundle.*
|
|
||||||
v2/pkg/parser/testproject/frontend/wails
|
|
||||||
@@ -15,6 +15,7 @@ Wails is what it is because of the time and effort given by these great people.
|
|||||||
* [admin_3.exe](https://github.com/bh90210)
|
* [admin_3.exe](https://github.com/bh90210)
|
||||||
* [iceleo-com](https://github.com/iceleo-com)
|
* [iceleo-com](https://github.com/iceleo-com)
|
||||||
* [fallendusk](https://github.com/fallendusk)
|
* [fallendusk](https://github.com/fallendusk)
|
||||||
|
* [Florian Didran](https://github.com/fdidron)
|
||||||
* [Nikolai Zimmermann](https://github.com/Chronophylos)
|
* [Nikolai Zimmermann](https://github.com/Chronophylos)
|
||||||
* [Toyam Cox](https://github.com/Vaelatern)
|
* [Toyam Cox](https://github.com/Vaelatern)
|
||||||
* [Robin Eklind](https://github.com/mewmew)
|
* [Robin Eklind](https://github.com/mewmew)
|
||||||
@@ -28,7 +29,3 @@ Wails is what it is because of the time and effort given by these great people.
|
|||||||
* [msms](https://github.com/sayuthisobri)
|
* [msms](https://github.com/sayuthisobri)
|
||||||
* [dedo1911](https://github.com/dedo1911)
|
* [dedo1911](https://github.com/dedo1911)
|
||||||
* [Florian Didron](https://github.com/fdidron)
|
* [Florian Didron](https://github.com/fdidron)
|
||||||
* [Christopher Murphy](https://github.com/Splode)
|
|
||||||
* [Zámbó, Levente](https://github.com/Lyimmi)
|
|
||||||
* [artem](https://github.com/Unix4ever)
|
|
||||||
* [Tim Kipp](https://github.com/timkippdev)
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ The official docs can be found at [https://wails.app](https://wails.app).
|
|||||||
|
|
||||||
Wails uses cgo to bind to the native rendering engines so a number of platform dependent libraries are needed as well as an installation of Go. The basic requirements are:
|
Wails uses cgo to bind to the native rendering engines so a number of platform dependent libraries are needed as well as an installation of Go. The basic requirements are:
|
||||||
|
|
||||||
- Go 1.13
|
- Go 1.12
|
||||||
- npm
|
- npm
|
||||||
|
|
||||||
### MacOS
|
### MacOS
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -293,11 +293,9 @@ func BuildApplication(binaryName string, forceRebuild bool, buildMode string, pa
|
|||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Removed by popular demand
|
if projectOptions.Platform == "windows" {
|
||||||
// TODO: Potentially add a flag to cleanup
|
helper.CleanWindows(projectOptions)
|
||||||
// if projectOptions.Platform == "windows" {
|
}
|
||||||
// helper.CleanWindows(projectOptions)
|
|
||||||
// }
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if projectOptions.CrossCompile {
|
if projectOptions.CrossCompile {
|
||||||
|
|||||||
@@ -212,8 +212,6 @@ func (b *PackageHelper) packageOSX(po *ProjectOptions) error {
|
|||||||
packageID := strings.Join([]string{"wails", name, version}, ".")
|
packageID := strings.Join([]string{"wails", name, version}, ".")
|
||||||
plistData := newPlistData(name, exe, packageID, version, author)
|
plistData := newPlistData(name, exe, packageID, version, author)
|
||||||
appname := po.Name + ".app"
|
appname := po.Name + ".app"
|
||||||
plistFilename := path.Join(build, appname, "Contents", "Info.plist")
|
|
||||||
customPlist := path.Join(b.fs.Cwd(), "info.plist")
|
|
||||||
|
|
||||||
// Check binary exists
|
// Check binary exists
|
||||||
source := path.Join(build, exe)
|
source := path.Join(build, exe)
|
||||||
@@ -232,48 +230,28 @@ func (b *PackageHelper) packageOSX(po *ProjectOptions) error {
|
|||||||
// Remove the existing package
|
// Remove the existing package
|
||||||
os.RemoveAll(appname)
|
os.RemoveAll(appname)
|
||||||
|
|
||||||
// Create directories
|
|
||||||
exeDir := path.Join(build, appname, "/Contents/MacOS")
|
exeDir := path.Join(build, appname, "/Contents/MacOS")
|
||||||
b.fs.MkDirs(exeDir, 0755)
|
b.fs.MkDirs(exeDir, 0755)
|
||||||
resourceDir := path.Join(build, appname, "/Contents/Resources")
|
resourceDir := path.Join(build, appname, "/Contents/Resources")
|
||||||
b.fs.MkDirs(resourceDir, 0755)
|
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))
|
||||||
|
|
||||||
// Do we have a custom plist in the project directory?
|
// Write the template to a buffer
|
||||||
if !fs.FileExists(customPlist) {
|
var tpl bytes.Buffer
|
||||||
|
err = tmpl.Execute(&tpl, plistData)
|
||||||
// No - create a new plist from our defaults
|
if err != nil {
|
||||||
tmpl := template.New("infoPlist")
|
return err
|
||||||
plistFile := filepath.Join(b.getPackageFileBaseDir(), "info.plist")
|
}
|
||||||
infoPlist, err := ioutil.ReadFile(plistFile)
|
filename := path.Join(build, appname, "Contents", "Info.plist")
|
||||||
if err != nil {
|
err = ioutil.WriteFile(filename, tpl.Bytes(), 0644)
|
||||||
return err
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save to the package
|
|
||||||
err = ioutil.WriteFile(plistFilename, tpl.Bytes(), 0644)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also write to project directory for customisation
|
|
||||||
err = ioutil.WriteFile(customPlist, tpl.Bytes(), 0644)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Yes - we have a plist. Copy it to the package verbatim
|
|
||||||
err = fs.CopyFile(customPlist, plistFilename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy executable
|
// Copy executable
|
||||||
@@ -315,7 +293,7 @@ func (b *PackageHelper) PackageWindows(po *ProjectOptions, cleanUp bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate icon from PNG
|
// Generate icon from PNG
|
||||||
err = generateWindowsIcon(icon, basename+".ico")
|
err = generateWindowsIcon(icon, po.BinaryName+".ico")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
homedir "github.com/mitchellh/go-homedir"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SystemHelper - Defines everything related to the system
|
// SystemHelper - Defines everything related to the system
|
||||||
@@ -37,11 +38,10 @@ func NewSystemHelper() *SystemHelper {
|
|||||||
// setSystemDirs calculates the system directories it is interested in
|
// setSystemDirs calculates the system directories it is interested in
|
||||||
func (s *SystemHelper) setSystemDirs() {
|
func (s *SystemHelper) setSystemDirs() {
|
||||||
var err error
|
var err error
|
||||||
s.homeDir, err = os.UserHomeDir()
|
s.homeDir, err = homedir.Dir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Cannot find home directory! Please file a bug report!")
|
log.Fatal("Cannot find home directory! Please file a bug report!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: A better config system
|
// TODO: A better config system
|
||||||
s.wailsSystemDir = filepath.Join(s.homeDir, ".wails")
|
s.wailsSystemDir = filepath.Join(s.homeDir, ".wails")
|
||||||
s.wailsSystemConfig = filepath.Join(s.wailsSystemDir, s.configFilename)
|
s.wailsSystemConfig = filepath.Join(s.wailsSystemDir, s.configFilename)
|
||||||
@@ -132,7 +132,7 @@ func (s *SystemHelper) setup() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const introText = `
|
const introText = `
|
||||||
Wails is a lightweight framework for creating web-like desktop apps in Go.
|
Wails is a lightweight framework for creating web-like desktop apps in Go.
|
||||||
I'll need to ask you a few questions so I can fill in your project templates and then I will try and see if you have the correct dependencies installed. If you don't have the right tools installed, I'll try and suggest how to install them.
|
I'll need to ask you a few questions so I can fill in your project templates and then I will try and see if you have the correct dependencies installed. If you don't have the right tools installed, I'll try and suggest how to install them.
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "^3.6.4",
|
"core-js": "^3.6.4",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.0",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.0",
|
||||||
"wails-react-scripts": "3.0.1-2",
|
"wails-react-scripts": "3.0.1-2",
|
||||||
"react-modal": "3.11.2",
|
"react-modal": "3.11.2",
|
||||||
"@wailsapp/runtime": "^1.0.10"
|
"@wailsapp/runtime": "^1.0.10"
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.8 KiB |
@@ -1,13 +1,10 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<head>
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta name="description" content="Web site created using create-react-app" />
|
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
|
||||||
<!--
|
<!--
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
@@ -23,9 +20,8 @@
|
|||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<title>React App</title>
|
<title>React App</title>
|
||||||
</head>
|
</head>
|
||||||
|
<body>
|
||||||
<body>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<!--
|
<!--
|
||||||
@@ -38,6 +34,5 @@
|
|||||||
To begin the development, run `npm start` or `yarn start`.
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
-->
|
-->
|
||||||
</body>
|
</body>
|
||||||
|
</html>
|
||||||
</html>
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 37 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 176 KiB |
@@ -6,16 +6,6 @@
|
|||||||
"src": "favicon.ico",
|
"src": "favicon.ico",
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
"type": "image/x-icon"
|
"type": "image/x-icon"
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "logo192.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "192x192"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "logo512.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "512x512"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
# https://www.robotstxt.org/robotstxt.html
|
|
||||||
User-agent: *
|
|
||||||
Disallow:
|
|
||||||
@@ -3,16 +3,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.App-logo {
|
.App-logo {
|
||||||
|
animation: App-logo-spin infinite 20s linear;
|
||||||
height: 40vmin;
|
height: 40vmin;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
|
||||||
.App-logo {
|
|
||||||
animation: App-logo-spin infinite 20s linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-header {
|
.App-header {
|
||||||
background-color: #282c34;
|
background-color: #282c34;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
|||||||
@@ -1,35 +1,48 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import Modal from 'react-modal';
|
import Modal from 'react-modal';
|
||||||
|
|
||||||
function HelloWorld() {
|
class HelloWorld extends React.Component {
|
||||||
const [showModal, setShowModal] = useState(false);
|
constructor(props, context) {
|
||||||
const [result, setResult] = useState(null);
|
super();
|
||||||
|
this.state = {
|
||||||
|
showModal: false
|
||||||
|
};
|
||||||
|
|
||||||
const handleOpenModal = () => {
|
this.handleOpenModal = this.handleOpenModal.bind(this);
|
||||||
setShowModal(true);
|
this.handleCloseModal = this.handleCloseModal.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
window.backend.basic().then((result) => setResult(result));
|
handleOpenModal () {
|
||||||
};
|
this.setState({ showModal: true });
|
||||||
|
|
||||||
const handleCloseModal = () => {
|
window.backend.basic().then(result =>
|
||||||
setShowModal(false);
|
this.setState({
|
||||||
};
|
result
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
handleCloseModal () {
|
||||||
<div className="App">
|
this.setState({ showModal: false });
|
||||||
<button onClick={() => handleOpenModal()} type="button">
|
}
|
||||||
Hello
|
|
||||||
</button>
|
render() {
|
||||||
<Modal
|
const { result } = this.state;
|
||||||
appElement={document.getElementById("app")}
|
return (
|
||||||
isOpen={showModal}
|
<div className="App">
|
||||||
contentLabel="Minimal Modal Example"
|
<button onClick={this.handleOpenModal} type="button">
|
||||||
>
|
Hello
|
||||||
<p>{result}</p>
|
</button>
|
||||||
<button onClick={() => handleCloseModal()}>Close Modal</button>
|
<Modal
|
||||||
</Modal>
|
isOpen={this.state.showModal}
|
||||||
</div>
|
contentLabel="Minimal Modal Example"
|
||||||
);
|
>
|
||||||
|
<p>{result}</p>
|
||||||
|
<button onClick={this.handleCloseModal}>Close Modal</button>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HelloWorld;
|
export default HelloWorld;
|
||||||
|
|||||||
@@ -3,20 +3,9 @@ import ReactDOM from 'react-dom';
|
|||||||
import 'core-js/stable';
|
import 'core-js/stable';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import * as serviceWorker from './serviceWorker';
|
|
||||||
|
|
||||||
import * as Wails from '@wailsapp/runtime';
|
import * as Wails from '@wailsapp/runtime';
|
||||||
|
|
||||||
Wails.Init(() => {
|
Wails.Init(() => {
|
||||||
ReactDOM.render(
|
ReactDOM.render(<App />, document.getElementById('app'));
|
||||||
<React.StrictMode>
|
|
||||||
<App />
|
|
||||||
</React.StrictMode>,
|
|
||||||
document.getElementById("app")
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// If you want your app to work offline and load faster, you can change
|
|
||||||
// unregister() to register() below. Note this comes with some pitfalls.
|
|
||||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
|
||||||
serviceWorker.unregister();
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "React JS",
|
"name": "React JS",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"shortdescription": "Create React App v4 template",
|
"shortdescription": "Create React App v3 template",
|
||||||
"description": "Create React App v4 standard tooling",
|
"description": "Create React App v3 standar tooling",
|
||||||
"install": "npm install",
|
"install": "npm install",
|
||||||
"build": "npm run build",
|
"build": "npm run build",
|
||||||
"author": "bh90210 <ktc@pm.me>",
|
"author": "bh90210 <ktc@pm.me>",
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import 'core-js/stable';
|
|
||||||
const runtime = require('@wailsapp/runtime');
|
const runtime = require('@wailsapp/runtime');
|
||||||
|
|
||||||
// Main entry point
|
// We need to wait for runtime.Init to complete before
|
||||||
function start() {
|
// running our JS
|
||||||
|
runtime.Init(() => {
|
||||||
|
|
||||||
// Ensure the default app div is 100% wide/high
|
// Ensure the default app div is 100% wide/high
|
||||||
var app = document.getElementById('app');
|
var app = document.getElementById('app');
|
||||||
@@ -19,12 +20,9 @@ function start() {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
// Connect button to Go method
|
// Connect button to Go method
|
||||||
document.getElementById('button').onclick = function() {
|
document.getElementById('button').onclick = () => {
|
||||||
window.backend.basic().then( function(result) {
|
window.backend.basic().then((result) => {
|
||||||
document.getElementById('result').innerText = result;
|
document.getElementById('result').innerText = result;
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
// We provide our entrypoint as a callback for runtime.Init
|
|
||||||
runtime.Init(start);
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
// Version - Wails version
|
// Version - Wails version
|
||||||
const Version = "v1.7.2-pre3"
|
const Version = "v1.6.0"
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/leaanthony/spinner"
|
"github.com/leaanthony/spinner"
|
||||||
|
"github.com/mitchellh/go-homedir"
|
||||||
"github.com/wailsapp/wails/cmd"
|
"github.com/wailsapp/wails/cmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ func updateToVersion(targetVersion *cmd.SemanticVersion, force bool) error {
|
|||||||
updateSpinner.Start("Installing Wails " + desiredVersion)
|
updateSpinner.Start("Installing Wails " + desiredVersion)
|
||||||
|
|
||||||
// Run command in non module directory
|
// Run command in non module directory
|
||||||
homeDir, err := os.UserHomeDir()
|
homeDir, err := homedir.Dir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Cannot find home directory! Please file a bug report!")
|
log.Fatal("Cannot find home directory! Please file a bug report!")
|
||||||
}
|
}
|
||||||
|
|||||||
8
go.mod
8
go.mod
@@ -14,18 +14,20 @@ require (
|
|||||||
github.com/leaanthony/spinner v0.5.3
|
github.com/leaanthony/spinner v0.5.3
|
||||||
github.com/mattn/go-colorable v0.1.1 // indirect
|
github.com/mattn/go-colorable v0.1.1 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.7 // indirect
|
github.com/mattn/go-isatty v0.0.7 // indirect
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||||
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
|
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
|
||||||
github.com/pkg/errors v0.8.1 // indirect
|
github.com/pkg/errors v0.8.1 // indirect
|
||||||
github.com/sirupsen/logrus v1.4.1
|
github.com/sirupsen/logrus v1.4.1
|
||||||
github.com/stretchr/testify v1.3.0 // indirect
|
github.com/stretchr/testify v1.3.0 // indirect
|
||||||
github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba
|
github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 // indirect
|
||||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8
|
golang.org/x/image v0.0.0-20200430140353-33d19683fad8
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect
|
golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5 // indirect
|
||||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
|
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862
|
||||||
golang.org/x/text v0.3.0
|
golang.org/x/text v0.3.0
|
||||||
gopkg.in/AlecAivazis/survey.v1 v1.8.4
|
gopkg.in/AlecAivazis/survey.v1 v1.8.4
|
||||||
gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22
|
gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.13
|
go 1.12
|
||||||
|
|||||||
16
go.sum
16
go.sum
@@ -46,6 +46,8 @@ github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc
|
|||||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
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/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=
|
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=
|
||||||
@@ -66,23 +68,21 @@ github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba h1:2DHfQOxcpWdGf5
|
|||||||
github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba/go.mod h1:iLnlXG2Pakcii2CU0cbY07DRCSvpWNa7nFxtevhOChk=
|
github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba/go.mod h1:iLnlXG2Pakcii2CU0cbY07DRCSvpWNa7nFxtevhOChk=
|
||||||
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
|
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
|
||||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
|
golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5 h1:6M3SDHlHHDCx2PcQw3S4KsR170vGqDhJDOmpVd4Hjak=
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/sys v0.0.0-20180606202747-9527bec2660b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180606202747-9527bec2660b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 h1:rM0ROo5vb9AdYJi1110yjWGMej9ITfKddS89P3Fkhug=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
|
|
||||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
gopkg.in/AlecAivazis/survey.v1 v1.8.4 h1:10xXXN3wgIhPheb5NI58zFgZv32Ana7P3Tl4shW+0Qc=
|
gopkg.in/AlecAivazis/survey.v1 v1.8.4 h1:10xXXN3wgIhPheb5NI58zFgZv32Ana7P3Tl4shW+0Qc=
|
||||||
|
|||||||
@@ -152,19 +152,12 @@ func (h *Bridge) NotifyEvent(event *messages.EventData) error {
|
|||||||
// Marshall the data
|
// Marshall the data
|
||||||
data, err = json.Marshal(event.Data)
|
data, err = json.Marshal(event.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.log.Errorf("Cannot marshal JSON data in event: %s ", err.Error())
|
h.log.Errorf("Cannot unmarshall JSON data in event: %s ", err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Double encode data to ensure everything is escaped correctly.
|
message := fmt.Sprintf("window.wails._.Notify('%s','%s')", event.Name, data)
|
||||||
data, err = json.Marshal(string(data))
|
|
||||||
if err != nil {
|
|
||||||
h.log.Errorf("Cannot marshal JSON data in event: %s ", err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
message := "window.wails._.Notify('" + event.Name + "'," + string(data) + ")"
|
|
||||||
dead := []*session{}
|
dead := []*session{}
|
||||||
for _, session := range h.sessions {
|
for _, session := range h.sessions {
|
||||||
err := session.evalJS(message, notifyMessage)
|
err := session.evalJS(message, notifyMessage)
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
package renderer
|
|
||||||
|
|
||||||
// Autogenerated by Mewn - Do not alter
|
|
||||||
|
|
||||||
import "github.com/leaanthony/mewn"
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
mewn.AddAsset(".", "../../runtime/assets/wails.js", "1f8b08000000000000ff94586d8f1bb711fe2b2ba255481f8f91dc14a977c318cee582a838df053ebbfda00a01b53b92e85b915b2e758a20f1bf17b3efba97a2050c7bc519728633cf3c33f468b533a9d7d650c38e8fca45208f216917234f1d3bea1585b95b30077ee74c84df02fe28acf365825bacc42579d4b1e3793c9af246181f43489a4d0637a52acfa96df772cbfb6fcfb815b91c4dfab5e0c5561aee452a817b91c9de550edcb1a317163fd9e974b7fc0aa91719acb481df9c2dc0f943a57604b3db8253cb1ce2d184afc1c72eb0c0bd70727875b233f5ee8c8ca43f146057d1fd61bbb4f9785cff2bbcbdf74e9bf567b51e8f5fb3f85c971f1f55be83987cb4d92e0712187f6d33f9fd77281bb576db6852bbebcfae5f25653a86f1981ae9a9618cff6d0c6d864ca257f43b94125b9922b2bd93198ff18fe82df59b30974e36cea50e94076a7679cef0382f1c75afb9ee38c960a576b9274f235edfc204c6df560e95555cfa201bb6b28e56308ab4890cf322a38e5bde5d17d8b103112c82586a93557e71cb588b2f873132f2399a9fdcf67da7d19f2a1adf43fc82b04330fa059c28c281714073f6494a1ac5264485b3dee225c54695777bd306abae02dc8067149210eea917a59cb040e76718f788cb12228c59ea49823900c61b4f66467bc2e9739f6781b12699c7903489abc34a7e86e56efde2ae5d609dd6ccacec8b4af940e99fca194ce54b7aab81deb573d6bda8950db47e515ee52f6a95ed756c7b1d5befb29cdc1560be7cba7971dfa13dbdd1fb456359bda0b86c0d68395ff4dca7da1c34a1c45cc68663fa962a7d98fd1c7b5ea8436e55164348ce9854afe85e9bcceec55ee9bc5c3a9dade1fdf325b1876569d307f0a20484358b1b25f8c383332a17da3cda07a086712d72306bbff971d2d50cc849023fb482042e2e989ec3821a16e8dfefef6e455d6e7a75a08eb1d0dd2dad11ab28c9ed9af0630e8f90c7866fa12cd51a620803e51dde28a524abb16306a2bc11e90a3043c9aa91ec5b940c855923841a1a4351d98856351e0c0b78cf3d2fcefad2639b9bb64c77793e927e3c6e3e4e27eae5847103fbe83767b7ba04da67de715b675527993d6a692ec825b9d85316f61b9d032de67a5193de8f13867aa92cc17fd65bb03b4f8708b2b4423725572acf236f2372612e48e4f516b2c8eebc883ec1bf7750fa68f6731c910bcd5860dcb3044dc8a3af8ffc55992c8738e50e903a62cb1d94367f84d885c4bb03e6096147f811c94f9bf5adda221633e555fc24cfc82b9a8554f97453c5d29ad2e620aa582330c21008eb33ea7aa4a446a720171543757a073caad15a53f293b3fb129ce8ea6f98c2e5ebba750d1a16f6b2c179ea0e85b7435ac6801b8999fba28dffcbdb0fcea9039d76547fb651acc17f5226b3db7f60a341f4cc278b9798fcdd64f2fdf4ddbbb77ffdeefbef26efde4ddf7c547e235cb597b25011c0b663f4c8d00a5fa3675d77444da44de99549b17f01637ee3ec3e42873f1f0ae800618cf511a62d52519aabb28c5419a9a83d90b040fd4697dc30ee11b197538ebfc555433067cdac9dbd842a8afc50b73fc3f8e5742411f6132925f59772ca42e09bb352b96b4b6533370b897f9d4ef345d31fd0e96d75d30425a2d8951bea06b9bcee3be9139ccd17a2cc75da7aa4dc7ab705e34b562fd329b2af3c9a015221248a127804e309f77565df9f39fb80e610f16d5dc33efaa50d03417d7261181f4d7a7cd79aa369e89dbeeab24eafda794695a55e9bd36918d49e45a709fcd0dd60c8a6d5ddbdec64735824ed3687038b67ff53bff7dc311cd67050f6384c876ec0604d042b30f4610c0dce110a6032790c7cd83fe4f0c7e9d44b7bfd2ac75fe5f116f63fd5b4113f9f8fe60b915a932a4f8d288b5c7b4a0461d56797c4f3939118a16d45d3ae15393949dc0fade0729ab80b39adadd40f04dc377aa096b141729b6a21ad139693489711968e8a1e55aeb3e8ab7a5465ea74e1239d81f17aa5c109c258e2e776814c3fb70b790c5519cded22d4ad1c44610bca1aabfabf5ad5ff9f55bd904ff80afbf0e0d1d4c2a6ab910a037d6e938e6f0df7c8b3dd8cd9f799b3f20769705a5cbf20edb921701f280bbc2590e7d94e8ccc20b5197cf934bbb2dbc21a3098780745ae52a0dffeabbcf876cd0961fdd27c72f94e5dae16c7b701457ffed3b80a823b1c1b4a28942b713e694a12dacb9399a943895a51a1ca12326c91ed0015475dfc413483072722fad8cc2011611d34595293ec8efabaa9d7196c38c4c93ac278aaceb895450bb7b6cf4bd20625faa6b3ea38f9a6cab983b52e3d64a3d188b496cedba61e5ac5ee9a8372ed4060c5591f673c831c3c44e8056f4e786f45dddc69b3c0625ca9ba3c0581f4c802bfb55eaf0ef1b39e83dcdcd395af58bca159c6ebc2ab961ae21a561eaea31fd56c8b95cb30757a983a18a42ea3e76943c7a2128cc7cc99ca3d115d238347c8ec91ac0839d8ae6b51cdc663dff287e35316aac6e343e01fb2ecbe2aa92737ac2f95d9b42a8fe6e9799d03fea2a4ae424c8cf0f0879786779a4b9b1d903dc164571b9d67880e188fafab21686630de57f7f72fb0deabb6fc2107c212c042fce0bdd3cb9d074a90d7092768ffdbb42c09e3202addfb0d807f3ffc21d2b2fc5cf919c3996f4f6ca2ceadcda07abcf7c20da8ec74ea7eaec137ce953f1d3eab6af0a30495080e3aeccc427d6bede319867af6dbd50dc2da803b8b80aebb3c4e82fc561e6fec3a76bc19d262cbabe496f1f1ce3cc9d25df368bd331f77b9d7450ef11dbfde6a1f5ff35f4139bf04759ed9fef184f34a728f3818f056554433e3c13daabc7aa3227b399cb5bbd5e1b08db34860950f1fd20763f739646b889fbcbb466866309365dd2ca6fa4dd1cee0b78936ade3035e309c7c831c8707518cd2eff1d7c18c32434386b27075f6c6e3b7ac6dc1d654257efe1f569e57cf8ee1167163d7a2e9456fdebc79135da9dd7ae3a32f6653314956134e8432c2f86b5b7bc2c4d9e835ad2f9f6e50035ed7b8d106a25b8b5afe75ad2b9befb6a6d173afeb5521401dcb02bfc687a0cecb185fcb901116162cf94f000000ffff04075a367c140000")
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -329,14 +329,7 @@ func (w *WebView) NotifyEvent(event *messages.EventData) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Double encode data to ensure everything is escaped correctly.
|
message := fmt.Sprintf("wails._.Notify('%s','%s')", event.Name, data)
|
||||||
data, err = json.Marshal(string(data))
|
|
||||||
if err != nil {
|
|
||||||
w.log.Errorf("Cannot marshal JSON data in event: %s ", err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
message := "window.wails._.Notify('" + event.Name + "'," + string(data) + ")"
|
|
||||||
return w.evalJS(message)
|
return w.evalJS(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -447,7 +447,6 @@ struct webview_priv
|
|||||||
}
|
}
|
||||||
gtk_file_filter_set_name(file_filter, filter);
|
gtk_file_filter_set_name(file_filter, filter);
|
||||||
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dlg), file_filter);
|
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dlg), file_filter);
|
||||||
g_strfreev(filters);
|
|
||||||
}
|
}
|
||||||
gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dlg), FALSE);
|
gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dlg), FALSE);
|
||||||
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dlg), FALSE);
|
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dlg), FALSE);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,9 +1,9 @@
|
|||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
import "os"
|
import homedir "github.com/mitchellh/go-homedir"
|
||||||
|
|
||||||
// FileSystem exposes file system utilities to the runtime
|
// FileSystem exposes file system utilities to the runtime
|
||||||
type FileSystem struct{}
|
type FileSystem struct {}
|
||||||
|
|
||||||
// NewFileSystem creates a new FileSystem struct
|
// NewFileSystem creates a new FileSystem struct
|
||||||
func NewFileSystem() *FileSystem {
|
func NewFileSystem() *FileSystem {
|
||||||
@@ -12,5 +12,5 @@ func NewFileSystem() *FileSystem {
|
|||||||
|
|
||||||
// HomeDir returns the user's home directory
|
// HomeDir returns the user's home directory
|
||||||
func (r *FileSystem) HomeDir() (string, error) {
|
func (r *FileSystem) HomeDir() (string, error) {
|
||||||
return os.UserHomeDir()
|
return homedir.Dir()
|
||||||
}
|
}
|
||||||
|
|||||||
7078
runtime/js/package-lock.json
generated
7078
runtime/js/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -29,15 +29,15 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/wailsapp/runtime#readme",
|
"homepage": "https://github.com/wailsapp/runtime#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "^7.11.5",
|
"@babel/cli": "^7.5.0",
|
||||||
"@babel/core": "^7.11.5",
|
"@babel/core": "^7.5.4",
|
||||||
"@babel/plugin-transform-object-assign": "^7.10.4",
|
"@babel/plugin-transform-object-assign": "^7.2.0",
|
||||||
"@babel/preset-env": "^7.11.5",
|
"@babel/preset-env": "^7.5.4",
|
||||||
"babel-loader": "^8.1.0",
|
"babel-loader": "^8.0.6",
|
||||||
"babel-preset-minify": "^0.5.1",
|
"babel-preset-minify": "^0.5.0",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.1.4",
|
||||||
"eslint": "^7.8.1",
|
"eslint": "^6.5.1",
|
||||||
"webpack": "^4.44.1",
|
"webpack": "^4.35.3",
|
||||||
"webpack-cli": "^3.3.12"
|
"webpack-cli": "^3.3.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
v2/.vscode/settings.json
vendored
13
v2/.vscode/settings.json
vendored
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"files.associations": {
|
|
||||||
"ios": "c",
|
|
||||||
"typeinfo": "c",
|
|
||||||
"sstream": "c",
|
|
||||||
"__functional_03": "c",
|
|
||||||
"functional": "c",
|
|
||||||
"__locale": "c",
|
|
||||||
"locale": "c",
|
|
||||||
"chrono": "c",
|
|
||||||
"system_error": "c"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
40
v2/NOTES.md
40
v2/NOTES.md
@@ -1,40 +0,0 @@
|
|||||||
|
|
||||||
# Packing linux
|
|
||||||
|
|
||||||
* create app, app.desktop, app.png (512x512)
|
|
||||||
* chmod +x app!
|
|
||||||
* ./linuxdeploy-x86_64.AppImage --appdir AppDir -i react.png -d react.desktop -e react --output appimage
|
|
||||||
|
|
||||||
|
|
||||||
# Wails Doctor
|
|
||||||
|
|
||||||
Tested on:
|
|
||||||
|
|
||||||
* Debian 8
|
|
||||||
* Ubuntu 20.04
|
|
||||||
* Ubuntu 19.10
|
|
||||||
* Solus 4.1
|
|
||||||
* Centos 8
|
|
||||||
* Gentoo
|
|
||||||
* OpenSUSE/leap
|
|
||||||
* Fedora 31
|
|
||||||
|
|
||||||
### Development
|
|
||||||
|
|
||||||
Add a new package manager processor here: `v2/internal/system/packagemanager/`. IsAvailable should work even if the package is installed.
|
|
||||||
Add your new package manager to the list of package managers in `v2/internal/system/packagemanager/packagemanager.go`:
|
|
||||||
|
|
||||||
```
|
|
||||||
var db = map[string]PackageManager{
|
|
||||||
"eopkg": NewEopkg(),
|
|
||||||
"apt": NewApt(),
|
|
||||||
"yum": NewYum(),
|
|
||||||
"pacman": NewPacman(),
|
|
||||||
"emerge": NewEmerge(),
|
|
||||||
"zypper": NewZypper(),
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Gentoo
|
|
||||||
|
|
||||||
* Setup docker image using: emerge-webrsync -x -v
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
# Wails v2 ALPHA
|
|
||||||
|
|
||||||
This branch contains WORK IN PROGRESS! There are no guarantees. Use at your peril!
|
|
||||||
|
|
||||||
This document will be updated as progress is made.
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
package build
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/leaanthony/clir"
|
|
||||||
"github.com/leaanthony/slicer"
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/commands/build"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AddBuildSubcommand adds the `build` command for the Wails application
|
|
||||||
func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
|
||||||
|
|
||||||
outputType := "desktop"
|
|
||||||
|
|
||||||
validTargetTypes := slicer.String([]string{"desktop", "hybrid", "server"})
|
|
||||||
|
|
||||||
command := app.NewSubCommand("build", "Builds the application")
|
|
||||||
|
|
||||||
// Setup target type flag
|
|
||||||
description := "Type of application to build. Valid types: " + validTargetTypes.Join(",")
|
|
||||||
command.StringFlag("t", description, &outputType)
|
|
||||||
|
|
||||||
// Setup production flag
|
|
||||||
production := false
|
|
||||||
command.BoolFlag("production", "Build in production mode", &production)
|
|
||||||
|
|
||||||
// Setup pack flag
|
|
||||||
pack := false
|
|
||||||
command.BoolFlag("pack", "Create a platform specific package", &pack)
|
|
||||||
|
|
||||||
compilerCommand := "go"
|
|
||||||
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
|
|
||||||
|
|
||||||
// Setup Platform flag
|
|
||||||
platform := runtime.GOOS
|
|
||||||
command.StringFlag("platform", "Platform to target", &platform)
|
|
||||||
|
|
||||||
// Quiet Build
|
|
||||||
quiet := false
|
|
||||||
command.BoolFlag("q", "Supress output to console", &quiet)
|
|
||||||
|
|
||||||
// ldflags to pass to `go`
|
|
||||||
ldflags := ""
|
|
||||||
command.StringFlag("ldflags", "optional ldflags", &ldflags)
|
|
||||||
|
|
||||||
// Log to file
|
|
||||||
logFile := ""
|
|
||||||
command.StringFlag("l", "Log to file", &logFile)
|
|
||||||
|
|
||||||
command.Action(func() error {
|
|
||||||
|
|
||||||
// Create logger
|
|
||||||
logger := clilogger.New(w)
|
|
||||||
logger.Mute(quiet)
|
|
||||||
|
|
||||||
// Validate output type
|
|
||||||
if !validTargetTypes.Contains(outputType) {
|
|
||||||
return fmt.Errorf("output type '%s' is not valid", outputType)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !quiet {
|
|
||||||
app.PrintBanner()
|
|
||||||
}
|
|
||||||
|
|
||||||
task := fmt.Sprintf("Building %s Application", strings.Title(outputType))
|
|
||||||
logger.Println(task)
|
|
||||||
logger.Println(strings.Repeat("-", len(task)))
|
|
||||||
|
|
||||||
// Setup mode
|
|
||||||
mode := build.Debug
|
|
||||||
if production {
|
|
||||||
mode = build.Production
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create BuildOptions
|
|
||||||
buildOptions := &build.Options{
|
|
||||||
Logger: logger,
|
|
||||||
OutputType: outputType,
|
|
||||||
Mode: mode,
|
|
||||||
Pack: pack,
|
|
||||||
Platform: platform,
|
|
||||||
LDFlags: ldflags,
|
|
||||||
Compiler: compilerCommand,
|
|
||||||
}
|
|
||||||
|
|
||||||
return doBuild(buildOptions)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// doBuild is our main build command
|
|
||||||
func doBuild(buildOptions *build.Options) error {
|
|
||||||
|
|
||||||
// Start Time
|
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
outputFilename, err := build.Build(buildOptions)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Output stats
|
|
||||||
elapsed := time.Since(start)
|
|
||||||
buildOptions.Logger.Println("")
|
|
||||||
buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.", outputFilename, elapsed.Round(time.Millisecond).String()))
|
|
||||||
buildOptions.Logger.Println("")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,261 +0,0 @@
|
|||||||
package dev
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
|
||||||
"github.com/leaanthony/clir"
|
|
||||||
"github.com/leaanthony/slicer"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/fs"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/process"
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/commands/build"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AddSubcommand adds the `dev` command for the Wails application
|
|
||||||
func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
|
||||||
|
|
||||||
command := app.NewSubCommand("dev", "Development mode")
|
|
||||||
|
|
||||||
outputType := "desktop"
|
|
||||||
|
|
||||||
validTargetTypes := slicer.String([]string{"desktop", "hybrid", "server"})
|
|
||||||
|
|
||||||
// Setup target type flag
|
|
||||||
description := "Type of application to develop. Valid types: " + validTargetTypes.Join(",")
|
|
||||||
command.StringFlag("t", description, &outputType)
|
|
||||||
|
|
||||||
// Passthrough ldflags
|
|
||||||
ldflags := ""
|
|
||||||
command.StringFlag("ldflags", "optional ldflags", &ldflags)
|
|
||||||
|
|
||||||
// compiler command
|
|
||||||
compilerCommand := "go"
|
|
||||||
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
|
|
||||||
|
|
||||||
// extensions to trigger rebuilds
|
|
||||||
extensions := "go"
|
|
||||||
command.StringFlag("m", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions)
|
|
||||||
|
|
||||||
command.Action(func() error {
|
|
||||||
|
|
||||||
// Validate inputs
|
|
||||||
if !validTargetTypes.Contains(outputType) {
|
|
||||||
return fmt.Errorf("output type '%s' is not valid", outputType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create logger
|
|
||||||
logger := clilogger.New(w)
|
|
||||||
app.PrintBanner()
|
|
||||||
|
|
||||||
// TODO: Check you are in a project directory
|
|
||||||
|
|
||||||
watcher, err := fsnotify.NewWatcher()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer watcher.Close()
|
|
||||||
|
|
||||||
var debugBinaryProcess *process.Process = nil
|
|
||||||
var buildFrontend bool = true
|
|
||||||
var extensionsThatTriggerARebuild = strings.Split(extensions, ",")
|
|
||||||
|
|
||||||
// Setup signal handler
|
|
||||||
quitChannel := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(quitChannel, os.Interrupt, os.Kill, syscall.SIGTERM)
|
|
||||||
|
|
||||||
debounceQuit := make(chan bool, 1)
|
|
||||||
|
|
||||||
// Do initial build
|
|
||||||
logger.Println("Building application for development...")
|
|
||||||
debugBinaryProcess = restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess)
|
|
||||||
|
|
||||||
go debounce(100*time.Millisecond, watcher.Events, debounceQuit, func(event fsnotify.Event) {
|
|
||||||
// logger.Println("event: %+v", event)
|
|
||||||
|
|
||||||
// Check for new directories
|
|
||||||
if event.Op&fsnotify.Create == fsnotify.Create {
|
|
||||||
// If this is a folder, add it to our watch list
|
|
||||||
if fs.DirExists(event.Name) {
|
|
||||||
if !strings.Contains(event.Name, "node_modules") {
|
|
||||||
watcher.Add(event.Name)
|
|
||||||
logger.Println("Watching directory: %s", event.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for file writes
|
|
||||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
|
||||||
|
|
||||||
// logger.Println("modified file: %s", event.Name)
|
|
||||||
var rebuild bool = false
|
|
||||||
|
|
||||||
// Iterate all file patterns
|
|
||||||
for _, pattern := range extensionsThatTriggerARebuild {
|
|
||||||
rebuild = strings.HasSuffix(event.Name, pattern)
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
if rebuild {
|
|
||||||
// Only build frontend when the file isn't a Go file
|
|
||||||
buildFrontend = !strings.HasSuffix(event.Name, "go")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !rebuild {
|
|
||||||
logger.Println("Filename change: %s did not match extension list %s", event.Name, extensions)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if buildFrontend {
|
|
||||||
logger.Println("Full rebuild triggered: %s updated", event.Name)
|
|
||||||
} else {
|
|
||||||
logger.Println("Partial build triggered: %s updated", event.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do a rebuild
|
|
||||||
|
|
||||||
// Try and build the app
|
|
||||||
newBinaryProcess := restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess)
|
|
||||||
|
|
||||||
// If we have a new process, save it
|
|
||||||
if newBinaryProcess != nil {
|
|
||||||
debugBinaryProcess = newBinaryProcess
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Get project dir
|
|
||||||
dir, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all subdirectories
|
|
||||||
dirs, err := fs.GetSubdirectories(dir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup a watcher for non-node_modules directories
|
|
||||||
dirs.Each(func(dir string) {
|
|
||||||
if strings.Contains(dir, "node_modules") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.Println("Watching directory: %s", dir)
|
|
||||||
err = watcher.Add(dir)
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Wait until we get a quit signal
|
|
||||||
quit := false
|
|
||||||
for quit == false {
|
|
||||||
select {
|
|
||||||
case <-quitChannel:
|
|
||||||
println()
|
|
||||||
// Notify debouncer to quit
|
|
||||||
debounceQuit <- true
|
|
||||||
quit = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kill the current program if running
|
|
||||||
if debugBinaryProcess != nil {
|
|
||||||
debugBinaryProcess.Kill()
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Println("Development mode exited")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Credit: https://drailing.net/2018/01/debounce-function-for-golang/
|
|
||||||
func debounce(interval time.Duration, input chan fsnotify.Event, quitChannel chan bool, cb func(arg fsnotify.Event)) {
|
|
||||||
var item fsnotify.Event
|
|
||||||
timer := time.NewTimer(interval)
|
|
||||||
exit:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case item = <-input:
|
|
||||||
timer.Reset(interval)
|
|
||||||
case <-timer.C:
|
|
||||||
if item.Name != "" {
|
|
||||||
cb(item)
|
|
||||||
}
|
|
||||||
case <-quitChannel:
|
|
||||||
break exit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, buildFrontend bool, debugBinaryProcess *process.Process) *process.Process {
|
|
||||||
|
|
||||||
appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand, buildFrontend)
|
|
||||||
println()
|
|
||||||
if err != nil {
|
|
||||||
logger.Println("[ERROR] Build Failed: %s", err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
logger.Println("Build new binary: %s", appBinary)
|
|
||||||
|
|
||||||
// Kill existing binary if need be
|
|
||||||
if debugBinaryProcess != nil {
|
|
||||||
killError := debugBinaryProcess.Kill()
|
|
||||||
|
|
||||||
if killError != nil {
|
|
||||||
logger.Fatal("Unable to kill debug binary (PID: %d)!", debugBinaryProcess.PID())
|
|
||||||
}
|
|
||||||
|
|
||||||
debugBinaryProcess = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Generate `backend.js`
|
|
||||||
|
|
||||||
// Start up new binary
|
|
||||||
newProcess := process.NewProcess(logger, appBinary)
|
|
||||||
err = newProcess.Start()
|
|
||||||
if err != nil {
|
|
||||||
// Remove binary
|
|
||||||
fs.DeleteFile(appBinary)
|
|
||||||
logger.Fatal("Unable to start application: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return newProcess
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, buildFrontend bool) (string, error) {
|
|
||||||
|
|
||||||
// Create random output file
|
|
||||||
outputFile := fmt.Sprintf("debug-%d", time.Now().Unix())
|
|
||||||
|
|
||||||
// Create BuildOptions
|
|
||||||
buildOptions := &build.Options{
|
|
||||||
Logger: logger,
|
|
||||||
OutputType: outputType,
|
|
||||||
Mode: build.Debug,
|
|
||||||
Pack: false,
|
|
||||||
Platform: runtime.GOOS,
|
|
||||||
LDFlags: ldflags,
|
|
||||||
Compiler: compilerCommand,
|
|
||||||
OutputFile: outputFile,
|
|
||||||
IgnoreFrontend: !buildFrontend,
|
|
||||||
}
|
|
||||||
|
|
||||||
return build.Build(buildOptions)
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
package doctor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/leaanthony/clir"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/system"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/system/packagemanager"
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AddSubcommand adds the `doctor` command for the Wails application
|
|
||||||
func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
|
||||||
|
|
||||||
command := app.NewSubCommand("doctor", "Diagnose your environment")
|
|
||||||
|
|
||||||
command.Action(func() error {
|
|
||||||
|
|
||||||
logger := clilogger.New(w)
|
|
||||||
|
|
||||||
app.PrintBanner()
|
|
||||||
logger.Print("Scanning system - please wait...")
|
|
||||||
|
|
||||||
// Get system info
|
|
||||||
info, err := system.GetInfo()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Println("Done.")
|
|
||||||
|
|
||||||
// Start a new tabwriter
|
|
||||||
w := new(tabwriter.Writer)
|
|
||||||
w.Init(os.Stdout, 8, 8, 0, '\t', 0)
|
|
||||||
|
|
||||||
// Write out the system information
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
fmt.Fprintf(w, "System\n")
|
|
||||||
fmt.Fprintf(w, "------\n")
|
|
||||||
fmt.Fprintf(w, "%s\t%s\n", "OS:", info.OS.Name)
|
|
||||||
fmt.Fprintf(w, "%s\t%s\n", "Version: ", info.OS.Version)
|
|
||||||
fmt.Fprintf(w, "%s\t%s\n", "ID:", info.OS.ID)
|
|
||||||
|
|
||||||
// Exit early if PM not found
|
|
||||||
if info.PM == nil {
|
|
||||||
fmt.Fprintf(w, "\n%s\t%s", "Package Manager:", "Not Found")
|
|
||||||
w.Flush()
|
|
||||||
println()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "%s\t%s\n", "Package Manager: ", info.PM.Name())
|
|
||||||
|
|
||||||
// Output Go Information
|
|
||||||
fmt.Fprintf(w, "%s\t%s\n", "Go Version:", runtime.Version())
|
|
||||||
fmt.Fprintf(w, "%s\t%s\n", "Platform:", runtime.GOOS)
|
|
||||||
fmt.Fprintf(w, "%s\t%s\n", "Architecture:", runtime.GOARCH)
|
|
||||||
|
|
||||||
// Output Dependencies Status
|
|
||||||
var dependenciesMissing = []string{}
|
|
||||||
var externalPackages = []*packagemanager.Dependancy{}
|
|
||||||
var dependenciesAvailableRequired = 0
|
|
||||||
var dependenciesAvailableOptional = 0
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
fmt.Fprintf(w, "Dependency\tPackage Name\tStatus\tVersion\n")
|
|
||||||
fmt.Fprintf(w, "----------\t------------\t------\t-------\n")
|
|
||||||
|
|
||||||
// Loop over dependencies
|
|
||||||
for _, dependency := range info.Dependencies {
|
|
||||||
|
|
||||||
name := dependency.Name
|
|
||||||
if dependency.Optional {
|
|
||||||
name += "*"
|
|
||||||
}
|
|
||||||
packageName := "Unknown"
|
|
||||||
status := "Not Found"
|
|
||||||
|
|
||||||
// If we found the package
|
|
||||||
if dependency.PackageName != "" {
|
|
||||||
|
|
||||||
packageName = dependency.PackageName
|
|
||||||
|
|
||||||
// If it's installed, update the status
|
|
||||||
if dependency.Installed {
|
|
||||||
status = "Installed"
|
|
||||||
} else {
|
|
||||||
// Generate meaningful status text
|
|
||||||
status = "Available"
|
|
||||||
|
|
||||||
if dependency.Optional {
|
|
||||||
dependenciesAvailableOptional++
|
|
||||||
} else {
|
|
||||||
dependenciesAvailableRequired++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !dependency.Optional {
|
|
||||||
dependenciesMissing = append(dependenciesMissing, dependency.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if dependency.External {
|
|
||||||
externalPackages = append(externalPackages, dependency)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", name, packageName, status, dependency.Version)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
fmt.Fprintf(w, "* - Optional Dependency\n")
|
|
||||||
w.Flush()
|
|
||||||
logger.Println("")
|
|
||||||
logger.Println("Diagnosis")
|
|
||||||
logger.Println("---------\n")
|
|
||||||
|
|
||||||
// Generate an appropriate diagnosis
|
|
||||||
|
|
||||||
if len(dependenciesMissing) == 0 && dependenciesAvailableRequired == 0 {
|
|
||||||
logger.Println("Your system is ready for Wails development!")
|
|
||||||
}
|
|
||||||
|
|
||||||
if dependenciesAvailableRequired != 0 {
|
|
||||||
log.Println("Install required packages using: " + info.Dependencies.InstallAllRequiredCommand())
|
|
||||||
}
|
|
||||||
|
|
||||||
if dependenciesAvailableOptional != 0 {
|
|
||||||
log.Println("Install optional packages using: " + info.Dependencies.InstallAllOptionalCommand())
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(externalPackages) > 0 {
|
|
||||||
for _, p := range externalPackages {
|
|
||||||
if p.Optional {
|
|
||||||
print("[Optional] ")
|
|
||||||
}
|
|
||||||
log.Println("Install " + p.Name + ": " + p.InstallCommand)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(dependenciesMissing) != 0 {
|
|
||||||
// TODO: Check if apps are available locally and if so, adjust the diagnosis
|
|
||||||
log.Println("Fatal:")
|
|
||||||
log.Println("Required dependencies missing: " + strings.Join(dependenciesMissing, " "))
|
|
||||||
log.Println("Please read this article on how to resolve this: https://wails.app/guides/resolving-missing-packages")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("")
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
package generate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/leaanthony/clir"
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/parser"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AddSubcommand adds the `dev` command for the Wails application
|
|
||||||
func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
|
||||||
|
|
||||||
command := app.NewSubCommand("generate", "Code Generation Tools")
|
|
||||||
|
|
||||||
// Backend API
|
|
||||||
backendAPI := command.NewSubCommand("module", "Generates a JS module for the frontend to interface with the backend")
|
|
||||||
|
|
||||||
// Quiet Init
|
|
||||||
quiet := false
|
|
||||||
backendAPI.BoolFlag("q", "Supress output to console", &quiet)
|
|
||||||
|
|
||||||
backendAPI.Action(func() error {
|
|
||||||
|
|
||||||
// Create logger
|
|
||||||
logger := clilogger.New(w)
|
|
||||||
logger.Mute(quiet)
|
|
||||||
|
|
||||||
app.PrintBanner()
|
|
||||||
|
|
||||||
logger.Print("Generating Javascript module for Go code...")
|
|
||||||
|
|
||||||
// Start Time
|
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
p, err := parser.GenerateWailsFrontendPackage()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Println("done.")
|
|
||||||
logger.Println("")
|
|
||||||
|
|
||||||
elapsed := time.Since(start)
|
|
||||||
packages := p.Packages
|
|
||||||
|
|
||||||
// Print report
|
|
||||||
for _, pkg := range p.Packages {
|
|
||||||
if pkg.ShouldGenerate() {
|
|
||||||
logPackage(pkg, logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Println("%d packages parsed in %s.", len(packages), elapsed)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func logPackage(pkg *parser.Package, logger *clilogger.CLILogger) {
|
|
||||||
|
|
||||||
logger.Println("Processed Go package '" + pkg.Gopackage.Name + "' as '" + pkg.Name + "'")
|
|
||||||
for _, strct := range pkg.Structs() {
|
|
||||||
logger.Println("")
|
|
||||||
logger.Println(" Processed struct '" + strct.Name + "'")
|
|
||||||
if strct.IsBound {
|
|
||||||
for _, method := range strct.Methods {
|
|
||||||
logger.Println(" Bound method '" + method.Name + "'")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strct.IsUsedAsData {
|
|
||||||
for _, field := range strct.Fields {
|
|
||||||
if !field.Ignored {
|
|
||||||
logger.Print(" Processed ")
|
|
||||||
if field.IsOptional {
|
|
||||||
logger.Print("optional ")
|
|
||||||
}
|
|
||||||
logger.Println("field '" + field.Name + "' as '" + field.JSName() + "'")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.Println("")
|
|
||||||
|
|
||||||
// logger.Println(" Original Go Package Path:", pkg.Gopackage.PkgPath)
|
|
||||||
// logger.Println(" Original Go Package Path:", pkg.Gopackage.PkgPath)
|
|
||||||
}
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
package initialise
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/leaanthony/clir"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/templates"
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AddSubcommand adds the `init` command for the Wails application
|
|
||||||
func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
|
||||||
|
|
||||||
// Load the template shortnames
|
|
||||||
validShortNames, err := templates.TemplateShortNames()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
command := app.NewSubCommand("init", "Initialise a new Wails project")
|
|
||||||
|
|
||||||
// Setup template name flag
|
|
||||||
templateName := "vanilla"
|
|
||||||
description := "Name of template to use. Valid tempates: " + validShortNames.Join(" ")
|
|
||||||
command.StringFlag("t", description, &templateName)
|
|
||||||
|
|
||||||
// Setup project name
|
|
||||||
projectName := ""
|
|
||||||
command.StringFlag("n", "Name of project", &projectName)
|
|
||||||
|
|
||||||
// Setup project directory
|
|
||||||
projectDirectory := ""
|
|
||||||
command.StringFlag("d", "Project directory", &projectDirectory)
|
|
||||||
|
|
||||||
// Quiet Init
|
|
||||||
quiet := false
|
|
||||||
command.BoolFlag("q", "Supress output to console", &quiet)
|
|
||||||
|
|
||||||
// VSCode project files
|
|
||||||
vscode := false
|
|
||||||
command.BoolFlag("vscode", "Generate VSCode project files", &vscode)
|
|
||||||
|
|
||||||
// List templates
|
|
||||||
list := false
|
|
||||||
command.BoolFlag("l", "List templates", &list)
|
|
||||||
|
|
||||||
command.Action(func() error {
|
|
||||||
|
|
||||||
// Create logger
|
|
||||||
logger := clilogger.New(w)
|
|
||||||
logger.Mute(quiet)
|
|
||||||
|
|
||||||
// Are we listing templates?
|
|
||||||
if list {
|
|
||||||
app.PrintBanner()
|
|
||||||
err := templates.OutputList(logger)
|
|
||||||
logger.Println("")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate output type
|
|
||||||
if !validShortNames.Contains(templateName) {
|
|
||||||
logger.Print(fmt.Sprintf("[ERROR] Template '%s' is not valid", templateName))
|
|
||||||
logger.Println("")
|
|
||||||
command.PrintHelp()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate name
|
|
||||||
if len(projectName) == 0 {
|
|
||||||
logger.Println("ERROR: Project name required")
|
|
||||||
logger.Println("")
|
|
||||||
command.PrintHelp()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !quiet {
|
|
||||||
app.PrintBanner()
|
|
||||||
}
|
|
||||||
|
|
||||||
task := fmt.Sprintf("Initialising Project %s", strings.Title(projectName))
|
|
||||||
logger.Println(task)
|
|
||||||
logger.Println(strings.Repeat("-", len(task)))
|
|
||||||
|
|
||||||
// Create Template Options
|
|
||||||
options := &templates.Options{
|
|
||||||
ProjectName: projectName,
|
|
||||||
TargetDir: projectDirectory,
|
|
||||||
TemplateName: templateName,
|
|
||||||
Logger: logger,
|
|
||||||
GenerateVSCode: vscode,
|
|
||||||
}
|
|
||||||
|
|
||||||
return initProject(options)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// initProject is our main init command
|
|
||||||
func initProject(options *templates.Options) error {
|
|
||||||
|
|
||||||
// Start Time
|
|
||||||
start := time.Now()
|
|
||||||
|
|
||||||
// Install the template
|
|
||||||
err := templates.Install(options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output stats
|
|
||||||
elapsed := time.Since(start)
|
|
||||||
options.Logger.Println("")
|
|
||||||
options.Logger.Println("Project Name: " + options.ProjectName)
|
|
||||||
options.Logger.Println("Project Directory: " + options.TargetDir)
|
|
||||||
options.Logger.Println("Project Template: " + options.TemplateName)
|
|
||||||
options.Logger.Println("")
|
|
||||||
if options.GenerateVSCode {
|
|
||||||
options.Logger.Println("VSCode config files generated.")
|
|
||||||
}
|
|
||||||
options.Logger.Println("")
|
|
||||||
options.Logger.Println(fmt.Sprintf("Initialised project '%s' in %s.", options.ProjectName, elapsed.Round(time.Millisecond).String()))
|
|
||||||
options.Logger.Println("")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/leaanthony/clir"
|
|
||||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build"
|
|
||||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/dev"
|
|
||||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/doctor"
|
|
||||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/generate"
|
|
||||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/initialise"
|
|
||||||
)
|
|
||||||
|
|
||||||
func fatal(message string) {
|
|
||||||
println(message)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
version := "v2.0.0-alpha"
|
|
||||||
|
|
||||||
app := clir.NewCli("Wails", "Go/HTML Application Framework", version)
|
|
||||||
|
|
||||||
build.AddBuildSubcommand(app, os.Stdout)
|
|
||||||
err = initialise.AddSubcommand(app, os.Stdout)
|
|
||||||
if err != nil {
|
|
||||||
fatal(err.Error())
|
|
||||||
}
|
|
||||||
err = doctor.AddSubcommand(app, os.Stdout)
|
|
||||||
if err != nil {
|
|
||||||
fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
err = dev.AddSubcommand(app, os.Stdout)
|
|
||||||
if err != nil {
|
|
||||||
fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
err = generate.AddSubcommand(app, os.Stdout)
|
|
||||||
if err != nil {
|
|
||||||
fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
err = app.Run()
|
|
||||||
if err != nil {
|
|
||||||
println("\n\nERROR: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
26
v2/go.mod
26
v2/go.mod
@@ -1,26 +0,0 @@
|
|||||||
module github.com/wailsapp/wails/v2
|
|
||||||
|
|
||||||
go 1.13
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/davecgh/go-spew v1.1.1
|
|
||||||
github.com/fatih/structtag v1.2.0
|
|
||||||
github.com/fsnotify/fsnotify v1.4.9
|
|
||||||
github.com/imdario/mergo v0.3.11
|
|
||||||
github.com/leaanthony/clir v1.0.4
|
|
||||||
github.com/leaanthony/gosod v0.0.4
|
|
||||||
github.com/leaanthony/slicer v1.5.0
|
|
||||||
github.com/matryer/is v1.4.0
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
|
||||||
github.com/olekukonko/tablewriter v0.0.4
|
|
||||||
github.com/pkg/errors v0.9.1
|
|
||||||
github.com/tdewolff/minify v2.3.6+incompatible
|
|
||||||
github.com/tdewolff/parse v2.3.4+incompatible // indirect
|
|
||||||
github.com/tdewolff/test v1.0.6 // indirect
|
|
||||||
github.com/xyproto/xpm v1.2.1
|
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202
|
|
||||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
|
|
||||||
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82
|
|
||||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
|
||||||
nhooyr.io/websocket v1.8.6
|
|
||||||
)
|
|
||||||
130
v2/go.sum
130
v2/go.sum
@@ -1,130 +0,0 @@
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
|
||||||
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
|
||||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
|
||||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
|
||||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
|
||||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
|
||||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
|
||||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
|
||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
|
||||||
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
|
||||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
|
||||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
|
||||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
|
||||||
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
|
||||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
|
||||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
|
||||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
|
||||||
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
|
|
||||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
|
||||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
||||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
|
||||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
|
||||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
|
||||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
|
|
||||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU=
|
|
||||||
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
|
|
||||||
github.com/leaanthony/gosod v0.0.4 h1:v4hepo4IyL8E8c9qzDsvYcA0KGh7Npf8As74K5ibQpI=
|
|
||||||
github.com/leaanthony/gosod v0.0.4/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
|
|
||||||
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
|
|
||||||
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
|
|
||||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
|
||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
|
||||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
|
||||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
|
||||||
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
|
|
||||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
|
||||||
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
|
||||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo=
|
|
||||||
github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs=
|
|
||||||
github.com/tdewolff/parse v2.3.4+incompatible h1:x05/cnGwIMf4ceLuDMBOdQ1qGniMoxpP46ghf0Qzh38=
|
|
||||||
github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ=
|
|
||||||
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
|
|
||||||
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
|
||||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
|
||||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
|
||||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
|
||||||
github.com/xyproto/xpm v1.2.1 h1:trdvGjjWBsOOKzBBUPT6JvaIQM3acJEEYfbxN7M96wg=
|
|
||||||
github.com/xyproto/xpm v1.2.1/go.mod h1:cMnesLsD0PBXLgjDfTDEaKr8XyTFsnP1QycSqRw7BiY=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
|
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
|
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
|
|
||||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82 h1:shxDsb9Dz27xzk3A0DxP0JuJnZMpKrdg8+E14eiUAX4=
|
|
||||||
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
|
|
||||||
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
// +build debug
|
|
||||||
|
|
||||||
package app
|
|
||||||
|
|
||||||
// Init initialises the application for a debug environment
|
|
||||||
func (a *App) Init() error {
|
|
||||||
a.debug = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
// +build !desktop,!hybrid,!server
|
|
||||||
|
|
||||||
package app
|
|
||||||
|
|
||||||
// This is the default application that will get run if the user compiles using `go build`.
|
|
||||||
// The reason we want to prevent that is that the `wails build` command does a lot of behind
|
|
||||||
// the scenes work such as asset compilation. If we allow `go build`, the state of these assets
|
|
||||||
// will be unknown and the application will not work as expected.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
|
||||||
)
|
|
||||||
|
|
||||||
// App defines a Wails application structure
|
|
||||||
type App struct {
|
|
||||||
Title string
|
|
||||||
Width int
|
|
||||||
Height int
|
|
||||||
Resizable bool
|
|
||||||
|
|
||||||
// Indicates if the app is running in debug mode
|
|
||||||
debug bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateApp returns a null application
|
|
||||||
func CreateApp(options *options.App) *App {
|
|
||||||
return &App{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the application
|
|
||||||
func (a *App) Run() error {
|
|
||||||
println(`FATAL: This application was built using "go build". This is unsupported. Please compile using "wails build".`)
|
|
||||||
os.Exit(1)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind the dummy interface
|
|
||||||
func (a *App) Bind(dummy interface{}) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
// +build desktop,!server
|
|
||||||
|
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/wailsapp/wails/v2/internal/binding"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/ffenestri"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/logger"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/signal"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/subsystem"
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
// App defines a Wails application structure
|
|
||||||
type App struct {
|
|
||||||
window *ffenestri.Application
|
|
||||||
servicebus *servicebus.ServiceBus
|
|
||||||
logger *logger.Logger
|
|
||||||
signal *signal.Manager
|
|
||||||
options *options.App
|
|
||||||
|
|
||||||
// Subsystems
|
|
||||||
log *subsystem.Log
|
|
||||||
runtime *subsystem.Runtime
|
|
||||||
event *subsystem.Event
|
|
||||||
binding *subsystem.Binding
|
|
||||||
call *subsystem.Call
|
|
||||||
dispatcher *messagedispatcher.Dispatcher
|
|
||||||
|
|
||||||
// Indicates if the app is in debug mode
|
|
||||||
debug bool
|
|
||||||
|
|
||||||
// This is our binding DB
|
|
||||||
bindings *binding.Bindings
|
|
||||||
|
|
||||||
// LogLevel Store
|
|
||||||
loglevelStore *runtime.Store
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create App
|
|
||||||
func CreateApp(options *options.App) *App {
|
|
||||||
|
|
||||||
// Merge default options
|
|
||||||
options.MergeDefaults()
|
|
||||||
|
|
||||||
// Set up logger
|
|
||||||
myLogger := logger.New(options.Logger)
|
|
||||||
myLogger.SetLogLevel(options.LogLevel)
|
|
||||||
|
|
||||||
window := ffenestri.NewApplicationWithConfig(options, myLogger)
|
|
||||||
|
|
||||||
result := &App{
|
|
||||||
window: window,
|
|
||||||
servicebus: servicebus.New(myLogger),
|
|
||||||
logger: myLogger,
|
|
||||||
bindings: binding.NewBindings(myLogger),
|
|
||||||
}
|
|
||||||
|
|
||||||
result.options = options
|
|
||||||
|
|
||||||
// Initialise the app
|
|
||||||
result.Init()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the application
|
|
||||||
func (a *App) Run() error {
|
|
||||||
|
|
||||||
// Setup signal handler
|
|
||||||
signal, err := signal.NewManager(a.servicebus, a.logger)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.signal = signal
|
|
||||||
a.signal.Start()
|
|
||||||
|
|
||||||
// Start the service bus
|
|
||||||
a.servicebus.Debug()
|
|
||||||
a.servicebus.Start()
|
|
||||||
|
|
||||||
// Start the runtime
|
|
||||||
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.runtime = runtime
|
|
||||||
a.runtime.Start()
|
|
||||||
|
|
||||||
// Application Stores
|
|
||||||
a.loglevelStore = a.runtime.GoRuntime().Store.New("loglevel", a.options.LogLevel)
|
|
||||||
|
|
||||||
// Start the binding subsystem
|
|
||||||
binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, a.runtime.GoRuntime())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.binding = binding
|
|
||||||
a.binding.Start()
|
|
||||||
|
|
||||||
// Start the logging subsystem
|
|
||||||
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.log = log
|
|
||||||
a.log.Start()
|
|
||||||
|
|
||||||
// create the dispatcher
|
|
||||||
dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.dispatcher = dispatcher
|
|
||||||
dispatcher.Start()
|
|
||||||
|
|
||||||
// Start the eventing subsystem
|
|
||||||
event, err := subsystem.NewEvent(a.servicebus, a.logger)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.event = event
|
|
||||||
a.event.Start()
|
|
||||||
|
|
||||||
// Start the call subsystem
|
|
||||||
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.call = call
|
|
||||||
a.call.Start()
|
|
||||||
|
|
||||||
// Dump bindings as a debug
|
|
||||||
bindingDump, err := a.bindings.ToJSON()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
result := a.window.Run(dispatcher, bindingDump)
|
|
||||||
a.logger.Trace("Ffenestri.Run() exited")
|
|
||||||
a.servicebus.Stop()
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind a struct to the application by passing in
|
|
||||||
// a pointer to it
|
|
||||||
func (a *App) Bind(structPtr interface{}) {
|
|
||||||
|
|
||||||
// Add the struct to the bindings
|
|
||||||
err := a.bindings.Add(structPtr)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Fatal("Error during binding: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
// +build !server,!desktop,hybrid
|
|
||||||
|
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/leaanthony/clir"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/binding"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/ffenestri"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/logger"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/subsystem"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/webserver"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config defines the Application's configuration
|
|
||||||
type Config struct {
|
|
||||||
Title string // Title is the value to be displayed in the title bar
|
|
||||||
Width int // Width is the desired window width
|
|
||||||
Height int // Height is the desired window height
|
|
||||||
DevTools bool // DevTools enables or disables the browser development tools
|
|
||||||
Resizable bool // Resizable when False prevents window resizing
|
|
||||||
ServerEnabled bool // ServerEnabled when True allows remote connections
|
|
||||||
}
|
|
||||||
|
|
||||||
// App defines a Wails application structure
|
|
||||||
type App struct {
|
|
||||||
config Config
|
|
||||||
window *ffenestri.Application
|
|
||||||
webserver *webserver.WebServer
|
|
||||||
binding *subsystem.Binding
|
|
||||||
call *subsystem.Call
|
|
||||||
event *subsystem.Event
|
|
||||||
log *subsystem.Log
|
|
||||||
runtime *subsystem.Runtime
|
|
||||||
|
|
||||||
bindings *binding.Bindings
|
|
||||||
logger *logger.Logger
|
|
||||||
dispatcher *messagedispatcher.Dispatcher
|
|
||||||
servicebus *servicebus.ServiceBus
|
|
||||||
|
|
||||||
debug bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create App
|
|
||||||
func CreateApp(options *Options) *App {
|
|
||||||
|
|
||||||
// Merge default options
|
|
||||||
options.mergeDefaults()
|
|
||||||
|
|
||||||
// Set up logger
|
|
||||||
myLogger := logger.New(os.Stdout)
|
|
||||||
myLogger.SetLogLevel(logger.INFO)
|
|
||||||
|
|
||||||
window := ffenestri.NewApplicationWithConfig(&ffenestri.Config{
|
|
||||||
Title: options.Title,
|
|
||||||
Width: options.Width,
|
|
||||||
Height: options.Height,
|
|
||||||
MinWidth: options.MinWidth,
|
|
||||||
MinHeight: options.MinHeight,
|
|
||||||
MaxWidth: options.MaxWidth,
|
|
||||||
MaxHeight: options.MaxHeight,
|
|
||||||
StartHidden: options.StartHidden,
|
|
||||||
|
|
||||||
// This should be controlled by the compile time flags...
|
|
||||||
DevTools: true,
|
|
||||||
|
|
||||||
Resizable: !options.DisableResize,
|
|
||||||
Fullscreen: options.Fullscreen,
|
|
||||||
}, myLogger)
|
|
||||||
|
|
||||||
app := &App{
|
|
||||||
window: window,
|
|
||||||
webserver: webserver.NewWebServer(myLogger),
|
|
||||||
servicebus: servicebus.New(myLogger),
|
|
||||||
logger: myLogger,
|
|
||||||
bindings: binding.NewBindings(myLogger),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialise the app
|
|
||||||
app.Init()
|
|
||||||
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the application
|
|
||||||
func (a *App) Run() error {
|
|
||||||
|
|
||||||
// Default app options
|
|
||||||
var port = 8080
|
|
||||||
var ip = "localhost"
|
|
||||||
var suppressLogging = false
|
|
||||||
|
|
||||||
// Create CLI
|
|
||||||
cli := clir.NewCli(filepath.Base(os.Args[0]), "Desktop/Server Build", "")
|
|
||||||
|
|
||||||
// Setup flags
|
|
||||||
cli.IntFlag("p", "Port to serve on", &port)
|
|
||||||
cli.StringFlag("i", "IP to serve on", &ip)
|
|
||||||
cli.BoolFlag("q", "Suppress logging", &suppressLogging)
|
|
||||||
|
|
||||||
// Setup main action
|
|
||||||
cli.Action(func() error {
|
|
||||||
|
|
||||||
// Set IP + Port
|
|
||||||
a.webserver.SetPort(port)
|
|
||||||
a.webserver.SetIP(ip)
|
|
||||||
a.webserver.SetBindings(a.bindings)
|
|
||||||
// Log information (if we aren't suppressing it)
|
|
||||||
if !suppressLogging {
|
|
||||||
cli.PrintBanner()
|
|
||||||
a.logger.Info("Running server at %s", a.webserver.URL())
|
|
||||||
}
|
|
||||||
|
|
||||||
a.servicebus.Start()
|
|
||||||
log, err := subsystem.NewLog(a.servicebus, a.logger)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.log = log
|
|
||||||
a.log.Start()
|
|
||||||
dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.dispatcher = dispatcher
|
|
||||||
a.dispatcher.Start()
|
|
||||||
|
|
||||||
// Start the runtime
|
|
||||||
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.runtime = runtime
|
|
||||||
a.runtime.Start()
|
|
||||||
|
|
||||||
// Start the binding subsystem
|
|
||||||
binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, runtime.GoRuntime())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.binding = binding
|
|
||||||
a.binding.Start()
|
|
||||||
|
|
||||||
// Start the eventing subsystem
|
|
||||||
event, err := subsystem.NewEvent(a.servicebus, a.logger)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.event = event
|
|
||||||
a.event.Start()
|
|
||||||
|
|
||||||
// Start the call subsystem
|
|
||||||
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.call = call
|
|
||||||
a.call.Start()
|
|
||||||
|
|
||||||
// Required so that the WailsInit functions are fired!
|
|
||||||
runtime.GoRuntime().Events.Emit("wails:loaded")
|
|
||||||
|
|
||||||
// Set IP + Port
|
|
||||||
a.webserver.SetPort(port)
|
|
||||||
a.webserver.SetIP(ip)
|
|
||||||
|
|
||||||
// Log information (if we aren't suppressing it)
|
|
||||||
if !suppressLogging {
|
|
||||||
cli.PrintBanner()
|
|
||||||
println("Running server at " + a.webserver.URL())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dump bindings as a debug
|
|
||||||
bindingDump, err := a.bindings.ToJSON()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
if err := a.webserver.Start(dispatcher, event); err != nil {
|
|
||||||
a.logger.Error("Webserver failed to start %s", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
result := a.window.Run(dispatcher, bindingDump)
|
|
||||||
a.servicebus.Stop()
|
|
||||||
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
|
|
||||||
return cli.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind a struct to the application by passing in
|
|
||||||
// a pointer to it
|
|
||||||
func (a *App) Bind(structPtr interface{}) {
|
|
||||||
|
|
||||||
// Add the struct to the bindings
|
|
||||||
err := a.bindings.Add(structPtr)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Fatal("Error during binding: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
// +build !debug
|
|
||||||
|
|
||||||
package app
|
|
||||||
|
|
||||||
// Init initialises the application for a production environment
|
|
||||||
func (a *App) Init() error {
|
|
||||||
println("Processing production cli options")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
// +build server,!desktop
|
|
||||||
|
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/leaanthony/clir"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/binding"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/logger"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/subsystem"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/webserver"
|
|
||||||
)
|
|
||||||
|
|
||||||
// App defines a Wails application structure
|
|
||||||
type App struct {
|
|
||||||
binding *subsystem.Binding
|
|
||||||
call *subsystem.Call
|
|
||||||
event *subsystem.Event
|
|
||||||
log *subsystem.Log
|
|
||||||
runtime *subsystem.Runtime
|
|
||||||
|
|
||||||
bindings *binding.Bindings
|
|
||||||
logger *logger.Logger
|
|
||||||
dispatcher *messagedispatcher.Dispatcher
|
|
||||||
servicebus *servicebus.ServiceBus
|
|
||||||
webserver *webserver.WebServer
|
|
||||||
|
|
||||||
debug bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create App
|
|
||||||
func CreateApp(options *Options) *App {
|
|
||||||
options.mergeDefaults()
|
|
||||||
// We ignore the inputs (for now)
|
|
||||||
|
|
||||||
// TODO: Allow logger output override on CLI
|
|
||||||
myLogger := logger.New(os.Stdout)
|
|
||||||
myLogger.SetLogLevel(logger.TRACE)
|
|
||||||
|
|
||||||
result := &App{
|
|
||||||
bindings: binding.NewBindings(myLogger),
|
|
||||||
logger: myLogger,
|
|
||||||
servicebus: servicebus.New(myLogger),
|
|
||||||
webserver: webserver.NewWebServer(myLogger),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialise app
|
|
||||||
result.Init()
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the application
|
|
||||||
func (a *App) Run() error {
|
|
||||||
|
|
||||||
// Default app options
|
|
||||||
var port = 8080
|
|
||||||
var ip = "localhost"
|
|
||||||
var supressLogging = false
|
|
||||||
var debugMode = false
|
|
||||||
|
|
||||||
// Create CLI
|
|
||||||
cli := clir.NewCli(filepath.Base(os.Args[0]), "Server Build", "")
|
|
||||||
|
|
||||||
// Setup flags
|
|
||||||
cli.IntFlag("p", "Port to serve on", &port)
|
|
||||||
cli.StringFlag("i", "IP to serve on", &ip)
|
|
||||||
cli.BoolFlag("d", "Debug mode", &debugMode)
|
|
||||||
cli.BoolFlag("q", "Supress logging", &supressLogging)
|
|
||||||
|
|
||||||
// Setup main action
|
|
||||||
cli.Action(func() error {
|
|
||||||
|
|
||||||
// Set IP + Port
|
|
||||||
a.webserver.SetPort(port)
|
|
||||||
a.webserver.SetIP(ip)
|
|
||||||
a.webserver.SetBindings(a.bindings)
|
|
||||||
// Log information (if we aren't supressing it)
|
|
||||||
if !supressLogging {
|
|
||||||
cli.PrintBanner()
|
|
||||||
a.logger.Info("Running server at %s", a.webserver.URL())
|
|
||||||
}
|
|
||||||
|
|
||||||
if debugMode {
|
|
||||||
a.servicebus.Debug()
|
|
||||||
}
|
|
||||||
a.servicebus.Start()
|
|
||||||
log, err := subsystem.NewLog(a.servicebus, a.logger)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.log = log
|
|
||||||
a.log.Start()
|
|
||||||
dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.dispatcher = dispatcher
|
|
||||||
a.dispatcher.Start()
|
|
||||||
|
|
||||||
// Start the runtime
|
|
||||||
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.runtime = runtime
|
|
||||||
a.runtime.Start()
|
|
||||||
|
|
||||||
// Start the binding subsystem
|
|
||||||
binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, runtime.GoRuntime())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.binding = binding
|
|
||||||
a.binding.Start()
|
|
||||||
|
|
||||||
// Start the eventing subsystem
|
|
||||||
event, err := subsystem.NewEvent(a.servicebus, a.logger)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.event = event
|
|
||||||
a.event.Start()
|
|
||||||
|
|
||||||
// Start the call subsystem
|
|
||||||
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.call = call
|
|
||||||
a.call.Start()
|
|
||||||
|
|
||||||
// Required so that the WailsInit functions are fired!
|
|
||||||
runtime.GoRuntime().Events.Emit("wails:loaded")
|
|
||||||
|
|
||||||
if err := a.webserver.Start(dispatcher, event); err != nil {
|
|
||||||
a.logger.Error("Webserver failed to start %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return cli.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind a struct to the application by passing in
|
|
||||||
// a pointer to it
|
|
||||||
func (a *App) Bind(structPtr interface{}) {
|
|
||||||
|
|
||||||
// Add the struct to the bindings
|
|
||||||
err := a.bindings.Add(structPtr)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Fatal("Error during binding: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
package assetdb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AssetDB is a database for assets encoded as byte slices
|
|
||||||
type AssetDB struct {
|
|
||||||
db map[string][]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAssetDB creates a new AssetDB and initialises a blank db
|
|
||||||
func NewAssetDB() *AssetDB {
|
|
||||||
return &AssetDB{
|
|
||||||
db: make(map[string][]byte),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddAsset saves the given byte slice under the given name
|
|
||||||
func (a *AssetDB) AddAsset(name string, data []byte) {
|
|
||||||
a.db[name] = data
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove removes the named asset
|
|
||||||
func (a *AssetDB) Remove(name string) {
|
|
||||||
delete(a.db, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Asset retrieves the byte slice for the given name
|
|
||||||
func (a *AssetDB) Read(name string) ([]byte, error) {
|
|
||||||
result := a.db[name]
|
|
||||||
if result == nil {
|
|
||||||
return nil, fmt.Errorf("asset '%s' not found", name)
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssetAsString returns the asset as a string.
|
|
||||||
// It also returns a boolean indicating whether the asset existed or not.
|
|
||||||
func (a *AssetDB) String(name string) (string, error) {
|
|
||||||
asset, err := a.Read(name)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return *(*string)(unsafe.Pointer(&asset)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AssetDB) Dump() {
|
|
||||||
fmt.Printf("Assets:\n")
|
|
||||||
for k, _ := range a.db {
|
|
||||||
fmt.Println(k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize converts the entire database to a file that when compiled will
|
|
||||||
// reconstruct the AssetDB during init()
|
|
||||||
// name: name of the asset.AssetDB instance
|
|
||||||
// pkg: package name placed at the top of the file
|
|
||||||
func (a *AssetDB) Serialize(name, pkg string) string {
|
|
||||||
var cdata strings.Builder
|
|
||||||
// Set buffer size to 4k
|
|
||||||
cdata.Grow(4096)
|
|
||||||
|
|
||||||
// Write header
|
|
||||||
header := `// DO NOT EDIT - Generated automatically
|
|
||||||
package %s
|
|
||||||
|
|
||||||
import "github.com/wailsapp/wails/v2/internal/assetdb"
|
|
||||||
|
|
||||||
var (
|
|
||||||
%s *assetdb.AssetDB = assetdb.NewAssetDB()
|
|
||||||
)
|
|
||||||
|
|
||||||
// AssetsDB is a clean interface to the assetdb.AssetDB struct
|
|
||||||
type AssetsDB interface {
|
|
||||||
Read(string) ([]byte, error)
|
|
||||||
String(string) (string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assets returns the asset database
|
|
||||||
func Assets() AssetsDB {
|
|
||||||
return %s
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
`
|
|
||||||
cdata.WriteString(fmt.Sprintf(header, pkg, name, name))
|
|
||||||
|
|
||||||
for aname, bytes := range a.db {
|
|
||||||
cdata.WriteString(fmt.Sprintf("\t%s.AddAsset(\"%s\", []byte{",
|
|
||||||
name,
|
|
||||||
aname))
|
|
||||||
|
|
||||||
l := len(bytes)
|
|
||||||
if l == 0 {
|
|
||||||
cdata.WriteString("0x00})\n")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert each byte to hex
|
|
||||||
for _, b := range bytes[:l-1] {
|
|
||||||
cdata.WriteString(fmt.Sprintf("0x%x, ", b))
|
|
||||||
}
|
|
||||||
cdata.WriteString(fmt.Sprintf("0x%x})\n", bytes[l-1]))
|
|
||||||
}
|
|
||||||
|
|
||||||
cdata.WriteString(`}`)
|
|
||||||
|
|
||||||
return cdata.String()
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package assetdb
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
import "github.com/matryer/is"
|
|
||||||
|
|
||||||
func TestExistsAsBytes(t *testing.T) {
|
|
||||||
|
|
||||||
is := is.New(t)
|
|
||||||
|
|
||||||
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
|
|
||||||
|
|
||||||
db := NewAssetDB()
|
|
||||||
db.AddAsset("hello", helloworld)
|
|
||||||
|
|
||||||
result, err := db.Read("hello")
|
|
||||||
|
|
||||||
is.True(err == nil)
|
|
||||||
is.Equal(result, helloworld)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotExistsAsBytes(t *testing.T) {
|
|
||||||
|
|
||||||
is := is.New(t)
|
|
||||||
|
|
||||||
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
|
|
||||||
|
|
||||||
db := NewAssetDB()
|
|
||||||
db.AddAsset("hello4", helloworld)
|
|
||||||
|
|
||||||
result, err := db.Read("hello")
|
|
||||||
|
|
||||||
is.True(err != nil)
|
|
||||||
is.True(result == nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExistsAsString(t *testing.T) {
|
|
||||||
|
|
||||||
is := is.New(t)
|
|
||||||
|
|
||||||
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
|
|
||||||
|
|
||||||
db := NewAssetDB()
|
|
||||||
db.AddAsset("hello", helloworld)
|
|
||||||
|
|
||||||
result, err := db.String("hello")
|
|
||||||
|
|
||||||
// Ensure it exists
|
|
||||||
is.True(err == nil)
|
|
||||||
|
|
||||||
// Ensure the string is the same as the byte slice
|
|
||||||
is.Equal(result, "Hello, World!")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotExistsAsString(t *testing.T) {
|
|
||||||
|
|
||||||
is := is.New(t)
|
|
||||||
|
|
||||||
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
|
|
||||||
|
|
||||||
db := NewAssetDB()
|
|
||||||
db.AddAsset("hello", helloworld)
|
|
||||||
|
|
||||||
result, err := db.String("help")
|
|
||||||
|
|
||||||
// Ensure it doesn't exist
|
|
||||||
is.True(err != nil)
|
|
||||||
|
|
||||||
// Ensure the string is blank
|
|
||||||
is.Equal(result, "")
|
|
||||||
}
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
// +build !desktop
|
|
||||||
package assetdb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errWhence = errors.New("Seek: invalid whence")
|
|
||||||
var errOffset = errors.New("Seek: invalid offset")
|
|
||||||
|
|
||||||
// Open implements the http.FileSystem interface for the AssetDB
|
|
||||||
func (a *AssetDB) Open(name string) (http.File, error) {
|
|
||||||
if name == "/" || name == "" {
|
|
||||||
return &Entry{name: "/", dir: true, db: a}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if data, ok := a.db[name]; ok {
|
|
||||||
return &Entry{name: name, data: data, size: len(data)}, nil
|
|
||||||
} else {
|
|
||||||
for n, _ := range a.db {
|
|
||||||
if strings.HasPrefix(n, name+"/") {
|
|
||||||
return &Entry{name: name, db: a, dir: true}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &Entry{}, os.ErrNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
// readdir returns the directory entries for the requested directory
|
|
||||||
func (a *AssetDB) readdir(name string) ([]os.FileInfo, error) {
|
|
||||||
dir := name
|
|
||||||
var ents []string
|
|
||||||
|
|
||||||
fim := make(map[string]os.FileInfo)
|
|
||||||
for fn, data := range a.db {
|
|
||||||
if strings.HasPrefix(fn, dir) {
|
|
||||||
pieces := strings.Split(fn[len(dir)+1:], "/")
|
|
||||||
if len(pieces) == 1 {
|
|
||||||
fim[pieces[0]] = FI{name: pieces[0], dir: false, size: len(data)}
|
|
||||||
ents = append(ents, pieces[0])
|
|
||||||
} else {
|
|
||||||
fim[pieces[0]] = FI{name: pieces[0], dir: true, size: -1}
|
|
||||||
ents = append(ents, pieces[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ents) == 0 {
|
|
||||||
return nil, os.ErrNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(ents)
|
|
||||||
var list []os.FileInfo
|
|
||||||
for _, dir := range ents {
|
|
||||||
list = append(list, fim[dir])
|
|
||||||
}
|
|
||||||
return list, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry implements the http.File interface to allow for
|
|
||||||
// use in the http.FileSystem implementation of AssetDB
|
|
||||||
type Entry struct {
|
|
||||||
name string
|
|
||||||
data []byte
|
|
||||||
dir bool
|
|
||||||
size int
|
|
||||||
db *AssetDB
|
|
||||||
off int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close is a noop
|
|
||||||
func (e Entry) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read fills the slice provided returning how many bytes were written
|
|
||||||
// and any errors encountered
|
|
||||||
func (e *Entry) Read(p []byte) (n int, err error) {
|
|
||||||
if e.off >= e.size {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
numout := len(p)
|
|
||||||
if numout > e.size {
|
|
||||||
numout = e.size
|
|
||||||
}
|
|
||||||
for i := 0; i < numout; i++ {
|
|
||||||
p[i] = e.data[e.off+i]
|
|
||||||
}
|
|
||||||
e.off += numout
|
|
||||||
n = int(numout)
|
|
||||||
err = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seek seeks to the specified offset from the location specified by whence
|
|
||||||
func (e *Entry) Seek(offset int64, whence int) (int64, error) {
|
|
||||||
switch whence {
|
|
||||||
default:
|
|
||||||
return 0, errWhence
|
|
||||||
case io.SeekStart:
|
|
||||||
offset += 0
|
|
||||||
case io.SeekCurrent:
|
|
||||||
offset += int64(e.off)
|
|
||||||
case io.SeekEnd:
|
|
||||||
offset += int64(e.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
if offset < 0 {
|
|
||||||
return 0, errOffset
|
|
||||||
}
|
|
||||||
e.off = int(offset)
|
|
||||||
return offset, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Readdir returns the directory entries inside this entry if it is a directory
|
|
||||||
func (e Entry) Readdir(count int) ([]os.FileInfo, error) {
|
|
||||||
ents := []os.FileInfo{}
|
|
||||||
if !e.dir {
|
|
||||||
return ents, errors.New("Not a directory")
|
|
||||||
}
|
|
||||||
return e.db.readdir(e.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stat returns information about this directory entry
|
|
||||||
func (e Entry) Stat() (os.FileInfo, error) {
|
|
||||||
return FI{e.name, e.size, e.dir}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FI is the AssetDB implementation of os.FileInfo.
|
|
||||||
type FI struct {
|
|
||||||
name string
|
|
||||||
size int
|
|
||||||
dir bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDir returns true if this is a directory
|
|
||||||
func (fi FI) IsDir() bool {
|
|
||||||
return fi.dir
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModTime always returns now
|
|
||||||
func (fi FI) ModTime() time.Time {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mode returns the file as readonly and directories
|
|
||||||
// as world writeable and executable
|
|
||||||
func (fi FI) Mode() os.FileMode {
|
|
||||||
if fi.IsDir() {
|
|
||||||
return 0755 | os.ModeDir
|
|
||||||
}
|
|
||||||
return 0444
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of this object without
|
|
||||||
// any leading slashes
|
|
||||||
func (fi FI) Name() string {
|
|
||||||
return path.Base(fi.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the size of this item
|
|
||||||
func (fi FI) Size() int64 {
|
|
||||||
return int64(fi.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sys returns nil
|
|
||||||
func (fi FI) Sys() interface{} {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
package assetdb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/matryer/is"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestOpenLeadingSlash(t *testing.T) {
|
|
||||||
is := is.New(t)
|
|
||||||
|
|
||||||
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
|
|
||||||
|
|
||||||
db := NewAssetDB()
|
|
||||||
db.AddAsset("/hello", helloworld)
|
|
||||||
|
|
||||||
file, err := db.Open("/hello")
|
|
||||||
// Ensure it does exist
|
|
||||||
is.True(err == nil)
|
|
||||||
|
|
||||||
buff := make([]byte, len(helloworld))
|
|
||||||
n, err := file.Read(buff)
|
|
||||||
fmt.Printf("Error %v\n", err)
|
|
||||||
is.True(err == nil)
|
|
||||||
is.Equal(n, len(helloworld))
|
|
||||||
result := string(buff)
|
|
||||||
|
|
||||||
// Ensure the string is blank
|
|
||||||
is.Equal(result, string(helloworld))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOpen(t *testing.T) {
|
|
||||||
is := is.New(t)
|
|
||||||
|
|
||||||
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
|
|
||||||
|
|
||||||
db := NewAssetDB()
|
|
||||||
db.AddAsset("/hello", helloworld)
|
|
||||||
|
|
||||||
file, err := db.Open("/hello")
|
|
||||||
|
|
||||||
// Ensure it does exist
|
|
||||||
is.True(err == nil)
|
|
||||||
|
|
||||||
buff := make([]byte, len(helloworld))
|
|
||||||
n, err := file.Read(buff)
|
|
||||||
is.True(err == nil)
|
|
||||||
is.Equal(n, len(helloworld))
|
|
||||||
result := string(buff)
|
|
||||||
|
|
||||||
// Ensure the string is blank
|
|
||||||
is.Equal(result, string(helloworld))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReaddir(t *testing.T) {
|
|
||||||
is := is.New(t)
|
|
||||||
|
|
||||||
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
|
|
||||||
|
|
||||||
db := NewAssetDB()
|
|
||||||
db.AddAsset("/hello", helloworld)
|
|
||||||
db.AddAsset("/directory/hello", helloworld)
|
|
||||||
db.AddAsset("/directory/subdirectory/hello", helloworld)
|
|
||||||
|
|
||||||
dir, err := db.Open("/doesntexist")
|
|
||||||
is.True(err == os.ErrNotExist)
|
|
||||||
ents, err := dir.Readdir(-1)
|
|
||||||
is.Equal([]os.FileInfo{}, ents)
|
|
||||||
|
|
||||||
dir, err = db.Open("/")
|
|
||||||
is.True(dir != nil)
|
|
||||||
is.True(err == nil)
|
|
||||||
ents, err = dir.Readdir(-1)
|
|
||||||
is.True(err == nil)
|
|
||||||
is.Equal(3, len(ents))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReaddirSubdirectory(t *testing.T) {
|
|
||||||
is := is.New(t)
|
|
||||||
|
|
||||||
var helloworld = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
|
|
||||||
|
|
||||||
db := NewAssetDB()
|
|
||||||
db.AddAsset("/hello", helloworld)
|
|
||||||
db.AddAsset("/directory/hello", helloworld)
|
|
||||||
db.AddAsset("/directory/subdirectory/hello", helloworld)
|
|
||||||
|
|
||||||
expected := []os.FileInfo{
|
|
||||||
FI{name: "hello", dir: false, size: len(helloworld)},
|
|
||||||
FI{name: "subdirectory", dir: true, size: -1},
|
|
||||||
}
|
|
||||||
|
|
||||||
dir, err := db.Open("/directory")
|
|
||||||
is.True(dir != nil)
|
|
||||||
is.True(err == nil)
|
|
||||||
ents, err := dir.Readdir(-1)
|
|
||||||
is.Equal(expected, ents)
|
|
||||||
|
|
||||||
// Check sub-subdirectory
|
|
||||||
dir, err = db.Open("/directory/subdirectory")
|
|
||||||
is.True(dir != nil)
|
|
||||||
is.True(err == nil)
|
|
||||||
ents, err = dir.Readdir(-1)
|
|
||||||
is.True(err == nil)
|
|
||||||
is.Equal([]os.FileInfo{FI{name: "hello", size: len(helloworld)}}, ents)
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package bind
|
|
||||||
|
|
||||||
func IsStructPointer(value interface{}) bool {
|
|
||||||
switch t := value.(type) {
|
|
||||||
default:
|
|
||||||
println(t)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package binding
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/internal/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Bindings struct {
|
|
||||||
db *DB
|
|
||||||
logger logger.CustomLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBindings returns a new Bindings object
|
|
||||||
func NewBindings(logger *logger.Logger) *Bindings {
|
|
||||||
return &Bindings{
|
|
||||||
db: newDB(),
|
|
||||||
logger: logger.CustomLogger("Bindings"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the given struct methods to the Bindings
|
|
||||||
func (b *Bindings) Add(structPtr interface{}) error {
|
|
||||||
|
|
||||||
methods, err := getMethods(structPtr)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannout bind value to app: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, method := range methods {
|
|
||||||
splitName := strings.Split(method.Name, ".")
|
|
||||||
packageName := splitName[0]
|
|
||||||
structName := splitName[1]
|
|
||||||
methodName := splitName[2]
|
|
||||||
|
|
||||||
// Is this WailsInit?
|
|
||||||
if method.IsWailsInit() {
|
|
||||||
err := b.db.AddWailsInit(method)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.logger.Trace("Registered WailsInit method: %s", method.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is this WailsShutdown?
|
|
||||||
if method.IsWailsShutdown() {
|
|
||||||
err := b.db.AddWailsShutdown(method)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b.logger.Trace("Registered WailsShutdown method: %s", method.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add it as a regular method
|
|
||||||
b.db.AddMethod(packageName, structName, methodName, method)
|
|
||||||
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bindings) DB() *DB {
|
|
||||||
return b.db
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bindings) ToJSON() (string, error) {
|
|
||||||
return b.db.ToJSON()
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
package binding
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BoundMethod defines all the data related to a Go method that is
|
|
||||||
// bound to the Wails application
|
|
||||||
type BoundMethod struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Inputs []*Parameter `json:"inputs,omitempty"`
|
|
||||||
Outputs []*Parameter `json:"outputs,omitempty"`
|
|
||||||
Comments string `json:"comments,omitempty"`
|
|
||||||
Method reflect.Value `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsWailsInit returns true if the method name is "WailsInit"
|
|
||||||
func (b *BoundMethod) IsWailsInit() bool {
|
|
||||||
return strings.HasSuffix(b.Name, "WailsInit")
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsWailsShutdown returns true if the method name is "WailsShutdown"
|
|
||||||
func (b *BoundMethod) IsWailsShutdown() bool {
|
|
||||||
return strings.HasSuffix(b.Name, "WailsShutdown")
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyWailsInit checks if the WailsInit signature is correct
|
|
||||||
func (b *BoundMethod) VerifyWailsInit() error {
|
|
||||||
// Must only have 1 input
|
|
||||||
if b.InputCount() != 1 {
|
|
||||||
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check input type
|
|
||||||
if !b.Inputs[0].IsType("*runtime.Runtime") {
|
|
||||||
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must only have 1 output
|
|
||||||
if b.OutputCount() != 1 {
|
|
||||||
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check output type
|
|
||||||
if !b.Outputs[0].IsError() {
|
|
||||||
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input must be of type Runtime
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyWailsShutdown checks if the WailsShutdown signature is correct
|
|
||||||
func (b *BoundMethod) VerifyWailsShutdown() error {
|
|
||||||
// Must have no inputs
|
|
||||||
if b.InputCount() != 0 {
|
|
||||||
return fmt.Errorf("invalid method signature for WailsShutdown: expected `WailsShutdown()`")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must have no outputs
|
|
||||||
if b.OutputCount() != 0 {
|
|
||||||
return fmt.Errorf("invalid method signature for WailsShutdown: expected `WailsShutdown()`")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input must be of type Runtime
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// InputCount returns the number of inputs this bound method has
|
|
||||||
func (b *BoundMethod) InputCount() int {
|
|
||||||
return len(b.Inputs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OutputCount returns the number of outputs this bound method has
|
|
||||||
func (b *BoundMethod) OutputCount() int {
|
|
||||||
return len(b.Outputs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseArgs method converts the input json into the types expected by the method
|
|
||||||
func (b *BoundMethod) ParseArgs(args []json.RawMessage) ([]interface{}, error) {
|
|
||||||
|
|
||||||
result := make([]interface{}, b.InputCount())
|
|
||||||
for index, arg := range args {
|
|
||||||
typ := b.Inputs[index].reflectType
|
|
||||||
inputValue := reflect.New(typ).Interface()
|
|
||||||
err := json.Unmarshal(arg, inputValue)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if inputValue == nil {
|
|
||||||
result[index] = reflect.Zero(typ).Interface()
|
|
||||||
} else {
|
|
||||||
result[index] = reflect.ValueOf(inputValue).Elem().Interface()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call will attempt to call this bound method with the given args
|
|
||||||
func (b *BoundMethod) Call(args []interface{}) (interface{}, error) {
|
|
||||||
// Check inputs
|
|
||||||
expectedInputLength := len(b.Inputs)
|
|
||||||
actualInputLength := len(args)
|
|
||||||
if expectedInputLength != actualInputLength {
|
|
||||||
return nil, fmt.Errorf("%s takes %d inputs. Received %d", b.Name, expectedInputLength, actualInputLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Convert inputs to reflect values **/
|
|
||||||
|
|
||||||
// Create slice for the input arguments to the method call
|
|
||||||
callArgs := make([]reflect.Value, expectedInputLength)
|
|
||||||
|
|
||||||
// Iterate over given arguments
|
|
||||||
for index, arg := range args {
|
|
||||||
// Save the converted argument
|
|
||||||
callArgs[index] = reflect.ValueOf(arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do the call
|
|
||||||
callResults := b.Method.Call(callArgs)
|
|
||||||
|
|
||||||
//** Check results **//
|
|
||||||
var returnValue interface{}
|
|
||||||
var err error
|
|
||||||
|
|
||||||
switch b.OutputCount() {
|
|
||||||
case 1:
|
|
||||||
// Loop over results and determine if the result
|
|
||||||
// is an error or not
|
|
||||||
for _, result := range callResults {
|
|
||||||
interfac := result.Interface()
|
|
||||||
temp, ok := interfac.(error)
|
|
||||||
if ok {
|
|
||||||
err = temp
|
|
||||||
} else {
|
|
||||||
returnValue = interfac
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 2:
|
|
||||||
returnValue = callResults[0].Interface()
|
|
||||||
if temp, ok := callResults[1].Interface().(error); ok {
|
|
||||||
err = temp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnValue, err
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
package binding
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"sync"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DB is our database of method bindings
|
|
||||||
type DB struct {
|
|
||||||
// map[packagename] -> map[structname] -> map[methodname]*method
|
|
||||||
store map[string]map[string]map[string]*BoundMethod
|
|
||||||
|
|
||||||
// This uses fully qualified method names as a shortcut for store traversal.
|
|
||||||
// It used for performance gains at runtime
|
|
||||||
methodMap map[string]*BoundMethod
|
|
||||||
|
|
||||||
// These are slices of methods registered using WailsInit and WailsShutdown
|
|
||||||
wailsInitMethods []*BoundMethod
|
|
||||||
wailsShutdownMethods []*BoundMethod
|
|
||||||
|
|
||||||
// Lock to ensure sync access to the data
|
|
||||||
lock sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDB() *DB {
|
|
||||||
return &DB{
|
|
||||||
store: make(map[string]map[string]map[string]*BoundMethod),
|
|
||||||
methodMap: make(map[string]*BoundMethod),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMethodFromStore returns the method for the given package/struct/method names
|
|
||||||
// nil is returned if any one of those does not exist
|
|
||||||
func (d *DB) GetMethodFromStore(packageName string, structName string, methodName string) *BoundMethod {
|
|
||||||
|
|
||||||
// Lock the db whilst processing and unlock on return
|
|
||||||
d.lock.RLock()
|
|
||||||
defer d.lock.RUnlock()
|
|
||||||
|
|
||||||
structMap, exists := d.store[packageName]
|
|
||||||
if !exists {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
methodMap, exists := structMap[structName]
|
|
||||||
if !exists {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return methodMap[methodName]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMethod returns the method for the given qualified method name
|
|
||||||
// qualifiedMethodName is "packagename.structname.methodname"
|
|
||||||
func (d *DB) GetMethod(qualifiedMethodName string) *BoundMethod {
|
|
||||||
|
|
||||||
// Lock the db whilst processing and unlock on return
|
|
||||||
d.lock.RLock()
|
|
||||||
defer d.lock.RUnlock()
|
|
||||||
|
|
||||||
return d.methodMap[qualifiedMethodName]
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddMethod adds the given method definition to the db using the given qualified path: packageName.structName.methodName
|
|
||||||
func (d *DB) AddMethod(packageName string, structName string, methodName string, methodDefinition *BoundMethod) {
|
|
||||||
|
|
||||||
// TODO: Validate inputs?
|
|
||||||
|
|
||||||
// Lock the db whilst processing and unlock on return
|
|
||||||
d.lock.Lock()
|
|
||||||
defer d.lock.Unlock()
|
|
||||||
|
|
||||||
// Get the map associated with the package name
|
|
||||||
structMap, exists := d.store[packageName]
|
|
||||||
if !exists {
|
|
||||||
// Create a new map for this packagename
|
|
||||||
d.store[packageName] = make(map[string]map[string]*BoundMethod)
|
|
||||||
structMap = d.store[packageName]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the map associated with the struct name
|
|
||||||
methodMap, exists := structMap[structName]
|
|
||||||
if !exists {
|
|
||||||
// Create a new map for this packagename
|
|
||||||
structMap[structName] = make(map[string]*BoundMethod)
|
|
||||||
methodMap = structMap[structName]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the method definition
|
|
||||||
methodMap[methodName] = methodDefinition
|
|
||||||
|
|
||||||
// Store in the methodMap
|
|
||||||
key := packageName + "." + structName + "." + methodName
|
|
||||||
d.methodMap[key] = methodDefinition
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddWailsInit checks the given method is a WailsInit method and if it
|
|
||||||
// is, adds it to the list of WailsInit methods
|
|
||||||
func (d *DB) AddWailsInit(method *BoundMethod) error {
|
|
||||||
err := method.VerifyWailsInit()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock the db whilst processing and unlock on return
|
|
||||||
d.lock.Lock()
|
|
||||||
defer d.lock.Unlock()
|
|
||||||
|
|
||||||
d.wailsInitMethods = append(d.wailsInitMethods, method)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddWailsShutdown checks the given method is a WailsInit method and if it
|
|
||||||
// is, adds it to the list of WailsShutdown methods
|
|
||||||
func (d *DB) AddWailsShutdown(method *BoundMethod) error {
|
|
||||||
err := method.VerifyWailsShutdown()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock the db whilst processing and unlock on return
|
|
||||||
d.lock.Lock()
|
|
||||||
defer d.lock.Unlock()
|
|
||||||
|
|
||||||
d.wailsShutdownMethods = append(d.wailsShutdownMethods, method)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToJSON converts the method map to JSON
|
|
||||||
func (d *DB) ToJSON() (string, error) {
|
|
||||||
|
|
||||||
// Lock the db whilst processing and unlock on return
|
|
||||||
d.lock.RLock()
|
|
||||||
defer d.lock.RUnlock()
|
|
||||||
|
|
||||||
bytes, err := json.Marshal(&d.store)
|
|
||||||
|
|
||||||
// Return zero copy string as this string will be read only
|
|
||||||
return *(*string)(unsafe.Pointer(&bytes)), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WailsInitMethods returns the list of registered WailsInit methods
|
|
||||||
func (d *DB) WailsInitMethods() []*BoundMethod {
|
|
||||||
return d.wailsInitMethods
|
|
||||||
}
|
|
||||||
|
|
||||||
// WailsShutdownMethods returns the list of registered WailsInit methods
|
|
||||||
func (d *DB) WailsShutdownMethods() []*BoundMethod {
|
|
||||||
return d.wailsShutdownMethods
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package binding
|
|
||||||
|
|
||||||
import "reflect"
|
|
||||||
|
|
||||||
// Parameter defines a Go method parameter
|
|
||||||
type Parameter struct {
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
TypeName string `json:"type"`
|
|
||||||
reflectType reflect.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
func newParameter(Name string, Type reflect.Type) *Parameter {
|
|
||||||
return &Parameter{
|
|
||||||
Name: Name,
|
|
||||||
TypeName: Type.String(),
|
|
||||||
reflectType: Type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsType returns true if the given
|
|
||||||
func (p *Parameter) IsType(typename string) bool {
|
|
||||||
return p.TypeName == typename
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsError returns true if the parameter type is an error
|
|
||||||
func (p *Parameter) IsError() bool {
|
|
||||||
return p.IsType("error")
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
package binding
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
// isStructPtr returns true if the value given is a
|
|
||||||
// pointer to a struct
|
|
||||||
func isStructPtr(value interface{}) bool {
|
|
||||||
return reflect.ValueOf(value).Kind() == reflect.Ptr &&
|
|
||||||
reflect.ValueOf(value).Elem().Kind() == reflect.Struct
|
|
||||||
}
|
|
||||||
|
|
||||||
// isStructPtr returns true if the value given is a struct
|
|
||||||
func isStruct(value interface{}) bool {
|
|
||||||
return reflect.ValueOf(value).Kind() == reflect.Struct
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMethods(value interface{}) ([]*BoundMethod, error) {
|
|
||||||
|
|
||||||
// Create result placeholder
|
|
||||||
var result []*BoundMethod
|
|
||||||
|
|
||||||
// Check type
|
|
||||||
if !isStructPtr(value) {
|
|
||||||
|
|
||||||
if isStruct(value) {
|
|
||||||
name := reflect.ValueOf(value).Type().Name()
|
|
||||||
return nil, fmt.Errorf("%s is a struct, not a pointer to a struct", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("not a pointer to a struct")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process Struct
|
|
||||||
structType := reflect.TypeOf(value)
|
|
||||||
structValue := reflect.ValueOf(value)
|
|
||||||
baseName := structType.String()[1:]
|
|
||||||
|
|
||||||
// Process Methods
|
|
||||||
for i := 0; i < structType.NumMethod(); i++ {
|
|
||||||
methodDef := structType.Method(i)
|
|
||||||
methodName := methodDef.Name
|
|
||||||
fullMethodName := baseName + "." + methodName
|
|
||||||
method := structValue.MethodByName(methodName)
|
|
||||||
|
|
||||||
// Create new method
|
|
||||||
boundMethod := &BoundMethod{
|
|
||||||
Name: fullMethodName,
|
|
||||||
Inputs: nil,
|
|
||||||
Outputs: nil,
|
|
||||||
Comments: "",
|
|
||||||
Method: method,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate inputs
|
|
||||||
methodType := method.Type()
|
|
||||||
inputParamCount := methodType.NumIn()
|
|
||||||
var inputs []*Parameter
|
|
||||||
for inputIndex := 0; inputIndex < inputParamCount; inputIndex++ {
|
|
||||||
input := methodType.In(inputIndex)
|
|
||||||
thisParam := newParameter("", input)
|
|
||||||
inputs = append(inputs, thisParam)
|
|
||||||
}
|
|
||||||
|
|
||||||
boundMethod.Inputs = inputs
|
|
||||||
|
|
||||||
// Iterate outputs
|
|
||||||
// TODO: Determine what to do about limiting return types
|
|
||||||
// especially around errors.
|
|
||||||
outputParamCount := methodType.NumOut()
|
|
||||||
var outputs []*Parameter
|
|
||||||
for outputIndex := 0; outputIndex < outputParamCount; outputIndex++ {
|
|
||||||
output := methodType.Out(outputIndex)
|
|
||||||
thisParam := newParameter("", output)
|
|
||||||
outputs = append(outputs, thisParam)
|
|
||||||
}
|
|
||||||
boundMethod.Outputs = outputs
|
|
||||||
|
|
||||||
// Save method in result
|
|
||||||
result = append(result, boundMethod)
|
|
||||||
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertArgToValue
|
|
||||||
func convertArgToValue(input json.RawMessage, target *Parameter) (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", r.(string)[23:])
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Do the conversion
|
|
||||||
|
|
||||||
// Handle nil values
|
|
||||||
if input == nil {
|
|
||||||
switch target.reflectType.Kind() {
|
|
||||||
case reflect.Chan,
|
|
||||||
reflect.Func,
|
|
||||||
reflect.Interface,
|
|
||||||
reflect.Map,
|
|
||||||
reflect.Ptr,
|
|
||||||
reflect.Slice:
|
|
||||||
result = reflect.ValueOf(input).Convert(target.reflectType)
|
|
||||||
default:
|
|
||||||
return reflect.Zero(target.reflectType), fmt.Errorf("Unable to use null value")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = reflect.ValueOf(input).Convert(target.reflectType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't like doing this but it's the only way to
|
|
||||||
// handle recover() correctly
|
|
||||||
return
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RandomID returns a random ID as a string
|
|
||||||
func RandomID() string {
|
|
||||||
b := make([]byte, 16)
|
|
||||||
_, err := rand.Read(b)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%x", b)
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,181 +0,0 @@
|
|||||||
package ffenestri
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/internal/logger"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
#cgo linux CFLAGS: -DFFENESTRI_LINUX=1
|
|
||||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
|
|
||||||
|
|
||||||
#cgo darwin CFLAGS: -DFFENESTRI_DARWIN=1
|
|
||||||
#cgo darwin LDFLAGS: -framework WebKit -lobjc
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include "ffenestri.h"
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
// DEBUG is the global Ffenestri debug flag.
|
|
||||||
// TODO: move to compile time.
|
|
||||||
var DEBUG bool = true
|
|
||||||
|
|
||||||
// Application is our main application object
|
|
||||||
type Application struct {
|
|
||||||
config *options.App
|
|
||||||
memory []unsafe.Pointer
|
|
||||||
|
|
||||||
// This is the main app pointer
|
|
||||||
app unsafe.Pointer
|
|
||||||
|
|
||||||
// Logger
|
|
||||||
logger logger.CustomLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Application) saveMemoryReference(mem unsafe.Pointer) {
|
|
||||||
a.memory = append(a.memory, mem)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Application) string2CString(str string) *C.char {
|
|
||||||
result := C.CString(str)
|
|
||||||
a.saveMemoryReference(unsafe.Pointer(result))
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
runtime.LockOSThread()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewApplicationWithConfig creates a new application based on the given config
|
|
||||||
func NewApplicationWithConfig(config *options.App, logger *logger.Logger) *Application {
|
|
||||||
return &Application{
|
|
||||||
config: config,
|
|
||||||
logger: logger.CustomLogger("Ffenestri"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewApplication creates a new Application with the default config
|
|
||||||
func NewApplication(logger *logger.Logger) *Application {
|
|
||||||
return &Application{
|
|
||||||
config: options.Default,
|
|
||||||
logger: logger.CustomLogger("Ffenestri"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Application) freeMemory() {
|
|
||||||
for _, mem := range a.memory {
|
|
||||||
// fmt.Printf("Freeing memory: %+v\n", mem)
|
|
||||||
C.free(mem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// bool2Cint converts a Go boolean to a C integer
|
|
||||||
func (a *Application) bool2Cint(value bool) C.int {
|
|
||||||
if value {
|
|
||||||
return C.int(1)
|
|
||||||
}
|
|
||||||
return C.int(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// dispatcher is the interface to send messages to
|
|
||||||
var dispatcher *messagedispatcher.DispatchClient
|
|
||||||
|
|
||||||
// Dispatcher is what we register out client with
|
|
||||||
type Dispatcher interface {
|
|
||||||
RegisterClient(client messagedispatcher.Client) *messagedispatcher.DispatchClient
|
|
||||||
}
|
|
||||||
|
|
||||||
// DispatchClient is the means for passing messages to the backend
|
|
||||||
type DispatchClient interface {
|
|
||||||
SendMessage(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
func intToColour(colour int) (C.int, C.int, C.int, C.int) {
|
|
||||||
var alpha = C.int(colour & 0xFF)
|
|
||||||
var blue = C.int((colour >> 8) & 0xFF)
|
|
||||||
var green = C.int((colour >> 16) & 0xFF)
|
|
||||||
var red = C.int((colour >> 24) & 0xFF)
|
|
||||||
return red, green, blue, alpha
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the application
|
|
||||||
func (a *Application) Run(incomingDispatcher Dispatcher, bindings string) error {
|
|
||||||
title := a.string2CString(a.config.Title)
|
|
||||||
width := C.int(a.config.Width)
|
|
||||||
height := C.int(a.config.Height)
|
|
||||||
resizable := a.bool2Cint(!a.config.DisableResize)
|
|
||||||
devtools := a.bool2Cint(a.config.DevTools)
|
|
||||||
fullscreen := a.bool2Cint(a.config.Fullscreen)
|
|
||||||
startHidden := a.bool2Cint(a.config.StartHidden)
|
|
||||||
logLevel := C.int(a.config.LogLevel)
|
|
||||||
app := C.NewApplication(title, width, height, resizable, devtools, fullscreen, startHidden, logLevel)
|
|
||||||
|
|
||||||
// Save app reference
|
|
||||||
a.app = unsafe.Pointer(app)
|
|
||||||
|
|
||||||
// Set Min Window Size
|
|
||||||
minWidth := C.int(a.config.MinWidth)
|
|
||||||
minHeight := C.int(a.config.MinHeight)
|
|
||||||
C.SetMinWindowSize(a.app, minWidth, minHeight)
|
|
||||||
|
|
||||||
// Set Max Window Size
|
|
||||||
maxWidth := C.int(a.config.MaxWidth)
|
|
||||||
maxHeight := C.int(a.config.MaxHeight)
|
|
||||||
C.SetMaxWindowSize(a.app, maxWidth, maxHeight)
|
|
||||||
|
|
||||||
// Set debug if needed
|
|
||||||
C.SetDebug(app, a.bool2Cint(DEBUG))
|
|
||||||
|
|
||||||
// TODO: Move frameless to Linux options
|
|
||||||
// if a.config.Frameless {
|
|
||||||
// C.DisableFrame(a.app)
|
|
||||||
// }
|
|
||||||
|
|
||||||
if a.config.RGBA != 0 {
|
|
||||||
r, g, b, alpha := intToColour(a.config.RGBA)
|
|
||||||
C.SetColour(a.app, r, g, b, alpha)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Escape bindings so C doesn't freak out
|
|
||||||
bindings = strings.ReplaceAll(bindings, `"`, `\"`)
|
|
||||||
|
|
||||||
// Set bindings
|
|
||||||
C.SetBindings(app, a.string2CString(bindings))
|
|
||||||
|
|
||||||
// save the dispatcher in a package variable so that the C callbacks
|
|
||||||
// can access it
|
|
||||||
dispatcher = incomingDispatcher.RegisterClient(newClient(a))
|
|
||||||
|
|
||||||
// Process platform settings
|
|
||||||
a.processPlatformSettings()
|
|
||||||
|
|
||||||
// Check we could initialise the application
|
|
||||||
if app != nil {
|
|
||||||
// Yes - Save memory reference and run app, cleaning up afterwards
|
|
||||||
a.saveMemoryReference(unsafe.Pointer(app))
|
|
||||||
C.Run(app, 0, nil)
|
|
||||||
} else {
|
|
||||||
// Oh no! We couldn't initialise the application
|
|
||||||
a.logger.Fatal("Cannot initialise Application.")
|
|
||||||
}
|
|
||||||
|
|
||||||
a.freeMemory()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// messageFromWindowCallback is called by any messages sent in
|
|
||||||
// webkit to window.external.invoke. It relays the message on to
|
|
||||||
// the dispatcher.
|
|
||||||
//export messageFromWindowCallback
|
|
||||||
func messageFromWindowCallback(data *C.char) {
|
|
||||||
dispatcher.DispatchMessage(C.GoString(data))
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#ifndef __FFENESTRI_H__
|
|
||||||
#define __FFENESTRI_H__
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
extern void *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel);
|
|
||||||
extern void SetMinWindowSize(void *app, int minWidth, int minHeight);
|
|
||||||
extern void SetMaxWindowSize(void *app, int maxWidth, int maxHeight);
|
|
||||||
extern void Run(void *app, int argc, char **argv);
|
|
||||||
extern void DestroyApplication(void *app);
|
|
||||||
extern void SetDebug(void *app, int flag);
|
|
||||||
extern void SetBindings(void *app, const char *bindings);
|
|
||||||
extern void ExecJS(void *app, const char *script);
|
|
||||||
extern void Hide(void *app);
|
|
||||||
extern void Show(void *app);
|
|
||||||
extern void Center(void *app);
|
|
||||||
extern void Maximise(void *app);
|
|
||||||
extern void Unmaximise(void *app);
|
|
||||||
extern void ToggleMaximise(void *app);
|
|
||||||
extern void Minimise(void *app);
|
|
||||||
extern void Unminimise(void *app);
|
|
||||||
extern void ToggleMinimise(void *app);
|
|
||||||
extern void SetColour(void *app, int red, int green, int blue, int alpha);
|
|
||||||
extern void SetSize(void *app, int width, int height);
|
|
||||||
extern void SetPosition(void *app, int x, int y);
|
|
||||||
extern void Quit(void *app);
|
|
||||||
extern void SetTitle(void *app, const char *title);
|
|
||||||
extern void Fullscreen(void *app);
|
|
||||||
extern void UnFullscreen(void *app);
|
|
||||||
extern void ToggleFullscreen(void *app);
|
|
||||||
extern void DisableFrame(void *app);
|
|
||||||
extern void OpenDialog(void *appPointer, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolveAliases, int treatPackagesAsDirectories);
|
|
||||||
extern void SaveDialog(void *appPointer, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories);
|
|
||||||
extern void DarkModeEnabled(void *appPointer, char *callbackID);
|
|
||||||
#endif
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
package ffenestri
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
#cgo linux CFLAGS: -DFFENESTRI_LINUX=1
|
|
||||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include "ffenestri.h"
|
|
||||||
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/internal/logger"
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client is our implentation of messageDispatcher.Client
|
|
||||||
type Client struct {
|
|
||||||
app *Application
|
|
||||||
logger logger.CustomLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
func newClient(app *Application) *Client {
|
|
||||||
return &Client{
|
|
||||||
app: app,
|
|
||||||
logger: app.logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Quit the application
|
|
||||||
func (c *Client) Quit() {
|
|
||||||
c.app.logger.Trace("Got shutdown message")
|
|
||||||
C.Quit(c.app.app)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NotifyEvent will pass on the event message to the frontend
|
|
||||||
func (c *Client) NotifyEvent(message string) {
|
|
||||||
eventMessage := `window.wails._.Notify(` + strconv.Quote(message) + `);`
|
|
||||||
c.app.logger.Trace("eventMessage = %+v", eventMessage)
|
|
||||||
C.ExecJS(c.app.app, c.app.string2CString(eventMessage))
|
|
||||||
}
|
|
||||||
|
|
||||||
// CallResult contains the result of the call from JS
|
|
||||||
func (c *Client) CallResult(message string) {
|
|
||||||
callbackMessage := `window.wails._.Callback(` + strconv.Quote(message) + `);`
|
|
||||||
c.app.logger.Trace("callbackMessage = %+v", callbackMessage)
|
|
||||||
C.ExecJS(c.app.app, c.app.string2CString(callbackMessage))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindowSetTitle sets the window title to the given string
|
|
||||||
func (c *Client) WindowSetTitle(title string) {
|
|
||||||
C.SetTitle(c.app.app, c.app.string2CString(title))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindowFullscreen will set the window to be fullscreen
|
|
||||||
func (c *Client) WindowFullscreen() {
|
|
||||||
C.Fullscreen(c.app.app)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindowUnFullscreen will unfullscreen the window
|
|
||||||
func (c *Client) WindowUnFullscreen() {
|
|
||||||
C.UnFullscreen(c.app.app)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindowShow will show the window
|
|
||||||
func (c *Client) WindowShow() {
|
|
||||||
C.Show(c.app.app)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindowHide will hide the window
|
|
||||||
func (c *Client) WindowHide() {
|
|
||||||
C.Hide(c.app.app)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindowCenter will hide the window
|
|
||||||
func (c *Client) WindowCenter() {
|
|
||||||
C.Center(c.app.app)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindowMaximise will maximise the window
|
|
||||||
func (c *Client) WindowMaximise() {
|
|
||||||
C.Maximise(c.app.app)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindowMinimise will minimise the window
|
|
||||||
func (c *Client) WindowMinimise() {
|
|
||||||
C.Minimise(c.app.app)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindowUnmaximise will unmaximise the window
|
|
||||||
func (c *Client) WindowUnmaximise() {
|
|
||||||
C.Unmaximise(c.app.app)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindowUnminimise will unminimise the window
|
|
||||||
func (c *Client) WindowUnminimise() {
|
|
||||||
C.Unminimise(c.app.app)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindowPosition will position the window to x,y on the
|
|
||||||
// monitor that the window is mostly on
|
|
||||||
func (c *Client) WindowPosition(x int, y int) {
|
|
||||||
C.SetPosition(c.app.app, C.int(x), C.int(y))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindowSize will resize the window to the given
|
|
||||||
// width and height
|
|
||||||
func (c *Client) WindowSize(width int, height int) {
|
|
||||||
C.SetSize(c.app.app, C.int(width), C.int(height))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindowSetColour sets the window colour
|
|
||||||
func (c *Client) WindowSetColour(colour int) {
|
|
||||||
r, g, b, a := intToColour(colour)
|
|
||||||
C.SetColour(c.app.app, r, g, b, a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenDialog will open a dialog with the given title and filter
|
|
||||||
func (c *Client) OpenDialog(dialogOptions *options.OpenDialog, callbackID string) {
|
|
||||||
C.OpenDialog(c.app.app,
|
|
||||||
c.app.string2CString(callbackID),
|
|
||||||
c.app.string2CString(dialogOptions.Title),
|
|
||||||
c.app.string2CString(dialogOptions.Filters),
|
|
||||||
c.app.string2CString(dialogOptions.DefaultFilename),
|
|
||||||
c.app.string2CString(dialogOptions.DefaultDirectory),
|
|
||||||
c.app.bool2Cint(dialogOptions.AllowFiles),
|
|
||||||
c.app.bool2Cint(dialogOptions.AllowDirectories),
|
|
||||||
c.app.bool2Cint(dialogOptions.AllowMultiple),
|
|
||||||
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
|
|
||||||
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
|
|
||||||
c.app.bool2Cint(dialogOptions.ResolveAliases),
|
|
||||||
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveDialog will open a dialog with the given title and filter
|
|
||||||
func (c *Client) SaveDialog(dialogOptions *options.SaveDialog, callbackID string) {
|
|
||||||
C.SaveDialog(c.app.app,
|
|
||||||
c.app.string2CString(callbackID),
|
|
||||||
c.app.string2CString(dialogOptions.Title),
|
|
||||||
c.app.string2CString(dialogOptions.Filters),
|
|
||||||
c.app.string2CString(dialogOptions.DefaultFilename),
|
|
||||||
c.app.string2CString(dialogOptions.DefaultDirectory),
|
|
||||||
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
|
|
||||||
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
|
|
||||||
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) DarkModeEnabled(callbackID string) {
|
|
||||||
C.DarkModeEnabled(c.app.app, c.app.string2CString(callbackID))
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,67 +0,0 @@
|
|||||||
package ffenestri
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo darwin CFLAGS: -DFFENESTRI_DARWIN=1
|
|
||||||
#cgo darwin LDFLAGS: -framework WebKit -lobjc
|
|
||||||
|
|
||||||
extern void TitlebarAppearsTransparent(void *);
|
|
||||||
extern void HideTitle(void *);
|
|
||||||
extern void HideTitleBar(void *);
|
|
||||||
extern void FullSizeContent(void *);
|
|
||||||
extern void UseToolbar(void *);
|
|
||||||
extern void HideToolbarSeparator(void *);
|
|
||||||
extern void DisableFrame(void *);
|
|
||||||
extern void SetAppearance(void *, const char *);
|
|
||||||
extern void WebviewIsTransparent(void *);
|
|
||||||
extern void SetWindowBackgroundIsTranslucent(void *);
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
func (a *Application) processPlatformSettings() {
|
|
||||||
|
|
||||||
mac := a.config.Mac
|
|
||||||
titlebar := mac.TitleBar
|
|
||||||
|
|
||||||
// HideTitle
|
|
||||||
if titlebar.HideTitle {
|
|
||||||
C.HideTitle(a.app)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HideTitleBar
|
|
||||||
if titlebar.HideTitleBar {
|
|
||||||
C.HideTitleBar(a.app)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full Size Content
|
|
||||||
if titlebar.FullSizeContent {
|
|
||||||
C.FullSizeContent(a.app)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toolbar
|
|
||||||
if titlebar.UseToolbar {
|
|
||||||
C.UseToolbar(a.app)
|
|
||||||
}
|
|
||||||
|
|
||||||
if titlebar.HideToolbarSeparator {
|
|
||||||
C.HideToolbarSeparator(a.app)
|
|
||||||
}
|
|
||||||
|
|
||||||
if titlebar.TitlebarAppearsTransparent {
|
|
||||||
C.TitlebarAppearsTransparent(a.app)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process window Appearance
|
|
||||||
if mac.Appearance != "" {
|
|
||||||
C.SetAppearance(a.app, a.string2CString(string(mac.Appearance)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the webview should be transparent
|
|
||||||
if mac.WebviewIsTransparent {
|
|
||||||
C.WebviewIsTransparent(a.app)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if window should be translucent
|
|
||||||
if mac.WindowBackgroundIsTranslucent {
|
|
||||||
C.SetWindowBackgroundIsTranslucent(a.app)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,984 +0,0 @@
|
|||||||
|
|
||||||
#ifndef __FFENESTRI_LINUX_H__
|
|
||||||
#define __FFENESTRI_LINUX_H__
|
|
||||||
|
|
||||||
#include "gtk/gtk.h"
|
|
||||||
#include "webkit2/webkit2.h"
|
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
|
|
||||||
// References to assets
|
|
||||||
extern const unsigned char *assets[];
|
|
||||||
extern const unsigned char runtime;
|
|
||||||
extern const char *icon[];
|
|
||||||
|
|
||||||
// Constants
|
|
||||||
#define PRIMARY_MOUSE_BUTTON 1
|
|
||||||
#define MIDDLE_MOUSE_BUTTON 2
|
|
||||||
#define SECONDARY_MOUSE_BUTTON 3
|
|
||||||
|
|
||||||
// MAIN DEBUG FLAG
|
|
||||||
int debug;
|
|
||||||
|
|
||||||
// Credit: https://stackoverflow.com/a/8465083
|
|
||||||
char *concat(const char *s1, const char *s2)
|
|
||||||
{
|
|
||||||
const size_t len1 = strlen(s1);
|
|
||||||
const size_t len2 = strlen(s2);
|
|
||||||
char *result = malloc(len1 + len2 + 1);
|
|
||||||
memcpy(result, s1, len1);
|
|
||||||
memcpy(result + len1, s2, len2 + 1);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug works like sprintf but mutes if the global debug flag is true
|
|
||||||
// Credit: https://stackoverflow.com/a/20639708
|
|
||||||
void Debug(char *message, ...)
|
|
||||||
{
|
|
||||||
if (debug)
|
|
||||||
{
|
|
||||||
char *temp = concat("TRACE | Ffenestri (C) | ", message);
|
|
||||||
message = concat(temp, "\n");
|
|
||||||
free(temp);
|
|
||||||
va_list args;
|
|
||||||
va_start(args, message);
|
|
||||||
vprintf(message, args);
|
|
||||||
free(message);
|
|
||||||
va_end(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void messageFromWindowCallback(const char *);
|
|
||||||
typedef void (*ffenestriCallback)(const char *);
|
|
||||||
|
|
||||||
struct Application
|
|
||||||
{
|
|
||||||
|
|
||||||
// Gtk Data
|
|
||||||
GtkApplication *application;
|
|
||||||
GtkWindow *mainWindow;
|
|
||||||
GtkWidget *webView;
|
|
||||||
int signalInvoke;
|
|
||||||
int signalWindowDrag;
|
|
||||||
int signalButtonPressed;
|
|
||||||
int signalButtonReleased;
|
|
||||||
int signalLoadChanged;
|
|
||||||
|
|
||||||
// Saves the events for the drag mouse button
|
|
||||||
GdkEventButton *dragButtonEvent;
|
|
||||||
|
|
||||||
// The number of the default drag button
|
|
||||||
int dragButton;
|
|
||||||
|
|
||||||
// Window Data
|
|
||||||
const char *title;
|
|
||||||
char *id;
|
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
int resizable;
|
|
||||||
int devtools;
|
|
||||||
int startHidden;
|
|
||||||
int fullscreen;
|
|
||||||
int minWidth;
|
|
||||||
int minHeight;
|
|
||||||
int maxWidth;
|
|
||||||
int maxHeight;
|
|
||||||
int frame;
|
|
||||||
|
|
||||||
// User Data
|
|
||||||
char *HTML;
|
|
||||||
|
|
||||||
// Callback
|
|
||||||
ffenestriCallback sendMessageToBackend;
|
|
||||||
|
|
||||||
// Bindings
|
|
||||||
const char *bindings;
|
|
||||||
|
|
||||||
// Lock - used for sync operations (Should we be using g_mutex?)
|
|
||||||
int lock;
|
|
||||||
};
|
|
||||||
|
|
||||||
void *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden)
|
|
||||||
{
|
|
||||||
// Setup main application struct
|
|
||||||
struct Application *result = malloc(sizeof(struct Application));
|
|
||||||
result->title = title;
|
|
||||||
result->width = width;
|
|
||||||
result->height = height;
|
|
||||||
result->resizable = resizable;
|
|
||||||
result->devtools = devtools;
|
|
||||||
result->fullscreen = fullscreen;
|
|
||||||
result->minWidth = 0;
|
|
||||||
result->minHeight = 0;
|
|
||||||
result->maxWidth = 0;
|
|
||||||
result->maxHeight = 0;
|
|
||||||
result->frame = 1;
|
|
||||||
result->startHidden = startHidden;
|
|
||||||
|
|
||||||
// Default drag button is PRIMARY
|
|
||||||
result->dragButton = PRIMARY_MOUSE_BUTTON;
|
|
||||||
|
|
||||||
result->sendMessageToBackend = (ffenestriCallback)messageFromWindowCallback;
|
|
||||||
|
|
||||||
// Create a unique ID based on the current unix timestamp
|
|
||||||
char temp[11];
|
|
||||||
sprintf(&temp[0], "%d", (int)time(NULL));
|
|
||||||
result->id = concat("wails.app", &temp[0]);
|
|
||||||
|
|
||||||
// Create the main GTK application
|
|
||||||
GApplicationFlags flags = G_APPLICATION_FLAGS_NONE;
|
|
||||||
result->application = gtk_application_new(result->id, flags);
|
|
||||||
|
|
||||||
// Return the application struct
|
|
||||||
return (void *)result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DestroyApplication(struct Application *app)
|
|
||||||
{
|
|
||||||
Debug("Destroying Application");
|
|
||||||
|
|
||||||
g_application_quit(G_APPLICATION(app->application));
|
|
||||||
|
|
||||||
// Release the GTK ID string
|
|
||||||
if (app->id != NULL)
|
|
||||||
{
|
|
||||||
free(app->id);
|
|
||||||
app->id = NULL;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug("Almost a double free for app->id");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Free the bindings
|
|
||||||
if (app->bindings != NULL)
|
|
||||||
{
|
|
||||||
free((void *)app->bindings);
|
|
||||||
app->bindings = NULL;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug("Almost a double free for app->bindings");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disconnect signal handlers
|
|
||||||
WebKitUserContentManager *manager = webkit_web_view_get_user_content_manager((WebKitWebView *)app->webView);
|
|
||||||
g_signal_handler_disconnect(manager, app->signalInvoke);
|
|
||||||
if( app->frame == 0) {
|
|
||||||
g_signal_handler_disconnect(manager, app->signalWindowDrag);
|
|
||||||
g_signal_handler_disconnect(app->webView, app->signalButtonPressed);
|
|
||||||
g_signal_handler_disconnect(app->webView, app->signalButtonReleased);
|
|
||||||
}
|
|
||||||
g_signal_handler_disconnect(app->webView, app->signalLoadChanged);
|
|
||||||
|
|
||||||
// Release the main GTK Application
|
|
||||||
if (app->application != NULL)
|
|
||||||
{
|
|
||||||
g_object_unref(app->application);
|
|
||||||
app->application = NULL;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug("Almost a double free for app->application");
|
|
||||||
}
|
|
||||||
Debug("Finished Destroying Application");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Quit will stop the gtk application and free up all the memory
|
|
||||||
// used by the application
|
|
||||||
void Quit(struct Application *app)
|
|
||||||
{
|
|
||||||
Debug("Quit Called");
|
|
||||||
gtk_window_close((GtkWindow *)app->mainWindow);
|
|
||||||
g_application_quit((GApplication *)app->application);
|
|
||||||
DestroyApplication(app);
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTitle sets the main window title to the given string
|
|
||||||
void SetTitle(struct Application *app, const char *title)
|
|
||||||
{
|
|
||||||
gtk_window_set_title(app->mainWindow, title);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fullscreen sets the main window to be fullscreen
|
|
||||||
void Fullscreen(struct Application *app)
|
|
||||||
{
|
|
||||||
gtk_window_fullscreen(app->mainWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnFullscreen resets the main window after a fullscreen
|
|
||||||
void UnFullscreen(struct Application *app)
|
|
||||||
{
|
|
||||||
gtk_window_unfullscreen(app->mainWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setMinMaxSize(struct Application *app)
|
|
||||||
{
|
|
||||||
GdkGeometry size;
|
|
||||||
size.min_width = size.min_height = size.max_width = size.max_height = 0;
|
|
||||||
int flags = 0;
|
|
||||||
if (app->maxHeight > 0 && app->maxWidth > 0)
|
|
||||||
{
|
|
||||||
size.max_height = app->maxHeight;
|
|
||||||
size.max_width = app->maxWidth;
|
|
||||||
flags |= GDK_HINT_MAX_SIZE;
|
|
||||||
}
|
|
||||||
if (app->minHeight > 0 && app->minWidth > 0)
|
|
||||||
{
|
|
||||||
size.min_height = app->minHeight;
|
|
||||||
size.min_width = app->minWidth;
|
|
||||||
flags |= GDK_HINT_MIN_SIZE;
|
|
||||||
}
|
|
||||||
gtk_window_set_geometry_hints(app->mainWindow, NULL, &size, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
char *fileDialogInternal(struct Application *app, GtkFileChooserAction chooserAction, char **args) {
|
|
||||||
GtkFileChooserNative *native;
|
|
||||||
GtkFileChooserAction action = chooserAction;
|
|
||||||
gint res;
|
|
||||||
char *filename;
|
|
||||||
|
|
||||||
char *title = args[0];
|
|
||||||
char *filter = args[1];
|
|
||||||
|
|
||||||
native = gtk_file_chooser_native_new(title,
|
|
||||||
app->mainWindow,
|
|
||||||
action,
|
|
||||||
"_Open",
|
|
||||||
"_Cancel");
|
|
||||||
|
|
||||||
GtkFileChooser *chooser = GTK_FILE_CHOOSER(native);
|
|
||||||
|
|
||||||
// If we have filters, process them
|
|
||||||
if (filter[0] != '\0') {
|
|
||||||
GtkFileFilter *file_filter = gtk_file_filter_new();
|
|
||||||
gchar **filters = g_strsplit(filter, ",", -1);
|
|
||||||
gint i;
|
|
||||||
for(i = 0; filters && filters[i]; i++) {
|
|
||||||
gtk_file_filter_add_pattern(file_filter, filters[i]);
|
|
||||||
// Debug("Adding filter pattern: %s\n", filters[i]);
|
|
||||||
}
|
|
||||||
gtk_file_filter_set_name(file_filter, filter);
|
|
||||||
gtk_file_chooser_add_filter(chooser, file_filter);
|
|
||||||
g_strfreev(filters);
|
|
||||||
}
|
|
||||||
|
|
||||||
res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
|
|
||||||
if (res == GTK_RESPONSE_ACCEPT)
|
|
||||||
{
|
|
||||||
filename = gtk_file_chooser_get_filename(chooser);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_object_unref(native);
|
|
||||||
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
// openFileDialogInternal opens a dialog to select a file
|
|
||||||
// NOTE: The result is a string that will need to be freed!
|
|
||||||
char *openFileDialogInternal(struct Application *app, char **args)
|
|
||||||
{
|
|
||||||
return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_OPEN, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
// saveFileDialogInternal opens a dialog to select a file
|
|
||||||
// NOTE: The result is a string that will need to be freed!
|
|
||||||
char *saveFileDialogInternal(struct Application *app, char **args)
|
|
||||||
{
|
|
||||||
return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_SAVE, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// openDirectoryDialogInternal opens a dialog to select a directory
|
|
||||||
// NOTE: The result is a string that will need to be freed!
|
|
||||||
char *openDirectoryDialogInternal(struct Application *app, char **args)
|
|
||||||
{
|
|
||||||
return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetMinWindowSize(struct Application *app, int minWidth, int minHeight)
|
|
||||||
{
|
|
||||||
app->minWidth = minWidth;
|
|
||||||
app->minHeight = minHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight)
|
|
||||||
{
|
|
||||||
app->maxWidth = maxWidth;
|
|
||||||
app->maxHeight = maxHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetColour sets the colour of the webview to the given colour string
|
|
||||||
int SetColour(struct Application *app, const char *colourString)
|
|
||||||
{
|
|
||||||
GdkRGBA rgba;
|
|
||||||
gboolean result = gdk_rgba_parse(&rgba, colourString);
|
|
||||||
if (result == FALSE)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// Debug("Setting webview colour to: %s", colourString);
|
|
||||||
webkit_web_view_get_background_color((WebKitWebView *)(app->webView), &rgba);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisableFrame disables the window frame
|
|
||||||
void DisableFrame(struct Application *app)
|
|
||||||
{
|
|
||||||
app->frame = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void syncCallback(GObject *source_object,
|
|
||||||
GAsyncResult *res,
|
|
||||||
void *data)
|
|
||||||
{
|
|
||||||
struct Application *app = (struct Application *)data;
|
|
||||||
app->lock = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void syncEval(struct Application *app, const gchar *script)
|
|
||||||
{
|
|
||||||
|
|
||||||
WebKitWebView *webView = (WebKitWebView *)(app->webView);
|
|
||||||
|
|
||||||
// wait for lock to free
|
|
||||||
while (app->lock == 1)
|
|
||||||
{
|
|
||||||
g_main_context_iteration(0, true);
|
|
||||||
}
|
|
||||||
// Set lock
|
|
||||||
app->lock = 1;
|
|
||||||
|
|
||||||
webkit_web_view_run_javascript(
|
|
||||||
webView,
|
|
||||||
script,
|
|
||||||
NULL, syncCallback, (void*)app);
|
|
||||||
|
|
||||||
while (app->lock == 1)
|
|
||||||
{
|
|
||||||
g_main_context_iteration(0, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void asyncEval(WebKitWebView *webView, const gchar *script)
|
|
||||||
{
|
|
||||||
webkit_web_view_run_javascript(
|
|
||||||
webView,
|
|
||||||
script,
|
|
||||||
NULL, NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef void (*dispatchMethod)(struct Application *app, void *);
|
|
||||||
|
|
||||||
struct dispatchData
|
|
||||||
{
|
|
||||||
struct Application *app;
|
|
||||||
dispatchMethod method;
|
|
||||||
void *args;
|
|
||||||
};
|
|
||||||
|
|
||||||
gboolean executeMethod(gpointer data)
|
|
||||||
{
|
|
||||||
struct dispatchData *d = (struct dispatchData *)data;
|
|
||||||
(d->method)(d->app, d->args);
|
|
||||||
g_free(d);
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExecJS(struct Application *app, char *js)
|
|
||||||
{
|
|
||||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
|
||||||
data->method = (dispatchMethod)syncEval;
|
|
||||||
data->args = js;
|
|
||||||
data->app = app;
|
|
||||||
|
|
||||||
gdk_threads_add_idle(executeMethod, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef char *(*dialogMethod)(struct Application *app, void *);
|
|
||||||
|
|
||||||
struct dialogCall
|
|
||||||
{
|
|
||||||
struct Application *app;
|
|
||||||
dialogMethod method;
|
|
||||||
void *args;
|
|
||||||
void *filter;
|
|
||||||
char *result;
|
|
||||||
int done;
|
|
||||||
};
|
|
||||||
|
|
||||||
gboolean executeMethodWithReturn(gpointer data)
|
|
||||||
{
|
|
||||||
struct dialogCall *d = (struct dialogCall *)data;
|
|
||||||
|
|
||||||
d->result = (d->method)(d->app, d->args);
|
|
||||||
d->done = 1;
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *OpenFileDialog(struct Application *app, char *title, char *filter)
|
|
||||||
{
|
|
||||||
struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
|
|
||||||
data->result = NULL;
|
|
||||||
data->done = 0;
|
|
||||||
data->method = (dialogMethod)openFileDialogInternal;
|
|
||||||
const char* dialogArgs[]={ title, filter };
|
|
||||||
data->args = dialogArgs;
|
|
||||||
data->app = app;
|
|
||||||
|
|
||||||
gdk_threads_add_idle(executeMethodWithReturn, data);
|
|
||||||
|
|
||||||
while (data->done == 0)
|
|
||||||
{
|
|
||||||
usleep(100000);
|
|
||||||
}
|
|
||||||
g_free(data);
|
|
||||||
return data->result;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *SaveFileDialog(struct Application *app, char *title, char *filter)
|
|
||||||
{
|
|
||||||
struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
|
|
||||||
data->result = NULL;
|
|
||||||
data->done = 0;
|
|
||||||
data->method = (dialogMethod)saveFileDialogInternal;
|
|
||||||
const char* dialogArgs[]={ title, filter };
|
|
||||||
data->args = dialogArgs;
|
|
||||||
data->app = app;
|
|
||||||
|
|
||||||
gdk_threads_add_idle(executeMethodWithReturn, data);
|
|
||||||
|
|
||||||
while (data->done == 0)
|
|
||||||
{
|
|
||||||
usleep(100000);
|
|
||||||
}
|
|
||||||
Debug("Dialog done");
|
|
||||||
Debug("Result = %s\n", data->result);
|
|
||||||
|
|
||||||
g_free(data);
|
|
||||||
// Fingers crossed this wasn't freed by g_free above
|
|
||||||
return data->result;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *OpenDirectoryDialog(struct Application *app, char *title, char *filter)
|
|
||||||
{
|
|
||||||
struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
|
|
||||||
data->result = NULL;
|
|
||||||
data->done = 0;
|
|
||||||
data->method = (dialogMethod)openDirectoryDialogInternal;
|
|
||||||
const char* dialogArgs[]={ title, filter };
|
|
||||||
data->args = dialogArgs;
|
|
||||||
data->app = app;
|
|
||||||
|
|
||||||
gdk_threads_add_idle(executeMethodWithReturn, data);
|
|
||||||
|
|
||||||
while (data->done == 0)
|
|
||||||
{
|
|
||||||
usleep(100000);
|
|
||||||
}
|
|
||||||
Debug("Directory Dialog done");
|
|
||||||
Debug("Result = %s\n", data->result);
|
|
||||||
g_free(data);
|
|
||||||
// Fingers crossed this wasn't freed by g_free above
|
|
||||||
return data->result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sets the icon to the XPM stored in icon
|
|
||||||
void setIcon(struct Application *app)
|
|
||||||
{
|
|
||||||
GdkPixbuf *appIcon = gdk_pixbuf_new_from_xpm_data((const char **)icon);
|
|
||||||
gtk_window_set_icon(app->mainWindow, appIcon);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void load_finished_cb(WebKitWebView *webView,
|
|
||||||
WebKitLoadEvent load_event,
|
|
||||||
struct Application *app)
|
|
||||||
{
|
|
||||||
switch (load_event)
|
|
||||||
{
|
|
||||||
// case WEBKIT_LOAD_STARTED:
|
|
||||||
// /* New load, we have now a provisional URI */
|
|
||||||
// // printf("Start downloading %s\n", webkit_web_view_get_uri(web_view));
|
|
||||||
// /* Here we could start a spinner or update the
|
|
||||||
// * location bar with the provisional URI */
|
|
||||||
// break;
|
|
||||||
// case WEBKIT_LOAD_REDIRECTED:
|
|
||||||
// // printf("Redirected to: %s\n", webkit_web_view_get_uri(web_view));
|
|
||||||
// break;
|
|
||||||
// case WEBKIT_LOAD_COMMITTED:
|
|
||||||
// /* The load is being performed. Current URI is
|
|
||||||
// * the final one and it won't change unless a new
|
|
||||||
// * load is requested or a navigation within the
|
|
||||||
// * same page is performed */
|
|
||||||
// // printf("Loading: %s\n", webkit_web_view_get_uri(web_view));
|
|
||||||
// break;
|
|
||||||
case WEBKIT_LOAD_FINISHED:
|
|
||||||
/* Load finished, we can now stop the spinner */
|
|
||||||
// printf("Finished loading: %s\n", webkit_web_view_get_uri(web_view));
|
|
||||||
|
|
||||||
// Bindings
|
|
||||||
Debug("Binding Methods");
|
|
||||||
syncEval(app, app->bindings);
|
|
||||||
|
|
||||||
// Runtime
|
|
||||||
Debug("Setting up Wails runtime");
|
|
||||||
syncEval(app, &runtime);
|
|
||||||
|
|
||||||
// Loop over assets
|
|
||||||
int index = 1;
|
|
||||||
while (1)
|
|
||||||
{
|
|
||||||
// Get next asset pointer
|
|
||||||
const char *asset = assets[index];
|
|
||||||
|
|
||||||
// If we have no more assets, break
|
|
||||||
if (asset == 0x00)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sync eval the asset
|
|
||||||
syncEval(app, asset);
|
|
||||||
index++;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set the icon
|
|
||||||
setIcon(app);
|
|
||||||
|
|
||||||
// Setup fullscreen
|
|
||||||
if (app->fullscreen)
|
|
||||||
{
|
|
||||||
Debug("Going fullscreen");
|
|
||||||
Fullscreen(app);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup resize
|
|
||||||
gtk_window_resize(GTK_WINDOW(app->mainWindow), app->width, app->height);
|
|
||||||
|
|
||||||
if (app->resizable)
|
|
||||||
{
|
|
||||||
gtk_window_set_default_size(GTK_WINDOW(app->mainWindow), app->width, app->height);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gtk_widget_set_size_request(GTK_WIDGET(app->mainWindow), app->width, app->height);
|
|
||||||
gtk_window_resize(GTK_WINDOW(app->mainWindow), app->width, app->height);
|
|
||||||
// Fix the min/max to the window size for good measure
|
|
||||||
app->minHeight = app->maxHeight = app->height;
|
|
||||||
app->minWidth = app->maxWidth = app->width;
|
|
||||||
}
|
|
||||||
gtk_window_set_resizable(GTK_WINDOW(app->mainWindow), app->resizable ? TRUE : FALSE);
|
|
||||||
setMinMaxSize(app);
|
|
||||||
|
|
||||||
// Centre by default
|
|
||||||
gtk_window_set_position(app->mainWindow, GTK_WIN_POS_CENTER);
|
|
||||||
|
|
||||||
// Show window and focus
|
|
||||||
if( app->startHidden == 0) {
|
|
||||||
gtk_widget_show_all(GTK_WIDGET(app->mainWindow));
|
|
||||||
gtk_widget_grab_focus(app->webView);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean disable_context_menu_cb(
|
|
||||||
WebKitWebView *web_view,
|
|
||||||
WebKitContextMenu *context_menu,
|
|
||||||
GdkEvent *event,
|
|
||||||
WebKitHitTestResult *hit_test_result,
|
|
||||||
gpointer user_data)
|
|
||||||
{
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void printEvent(const char *message, GdkEventButton *event)
|
|
||||||
{
|
|
||||||
Debug("%s: [button:%d] [x:%f] [y:%f] [time:%d]",
|
|
||||||
message,
|
|
||||||
event->button,
|
|
||||||
event->x_root,
|
|
||||||
event->y_root,
|
|
||||||
event->time);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void dragWindow(WebKitUserContentManager *contentManager,
|
|
||||||
WebKitJavascriptResult *result,
|
|
||||||
struct Application *app)
|
|
||||||
{
|
|
||||||
// If we get this message erroneously, ignore
|
|
||||||
if (app->dragButtonEvent == NULL)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore non-toplevel widgets
|
|
||||||
GtkWidget *window = gtk_widget_get_toplevel(GTK_WIDGET(app->webView));
|
|
||||||
if (!GTK_IS_WINDOW(window))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initiate the drag
|
|
||||||
printEvent("Starting drag with event", app->dragButtonEvent);
|
|
||||||
|
|
||||||
gtk_window_begin_move_drag(app->mainWindow,
|
|
||||||
app->dragButton,
|
|
||||||
app->dragButtonEvent->x_root,
|
|
||||||
app->dragButtonEvent->y_root,
|
|
||||||
app->dragButtonEvent->time);
|
|
||||||
// Clear the event
|
|
||||||
app->dragButtonEvent = NULL;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gboolean buttonPress(GtkWidget *widget, GdkEventButton *event, struct Application *app)
|
|
||||||
{
|
|
||||||
if (event->type == GDK_BUTTON_PRESS && event->button == app->dragButton)
|
|
||||||
{
|
|
||||||
app->dragButtonEvent = event;
|
|
||||||
}
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
gboolean buttonRelease(GtkWidget *widget, GdkEventButton *event, struct Application *app)
|
|
||||||
{
|
|
||||||
if (event->type == GDK_BUTTON_RELEASE && event->button == app->dragButton)
|
|
||||||
{
|
|
||||||
app->dragButtonEvent = NULL;
|
|
||||||
}
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sendMessageToBackend(WebKitUserContentManager *contentManager,
|
|
||||||
WebKitJavascriptResult *result,
|
|
||||||
struct Application *app)
|
|
||||||
{
|
|
||||||
#if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22
|
|
||||||
JSCValue *value = webkit_javascript_result_get_js_value(result);
|
|
||||||
char *message = jsc_value_to_string(value);
|
|
||||||
#else
|
|
||||||
JSGlobalContextRef context = webkit_javascript_result_get_global_context(result);
|
|
||||||
JSValueRef value = webkit_javascript_result_get_value(result);
|
|
||||||
JSStringRef js = JSValueToStringCopy(context, value, NULL);
|
|
||||||
size_t messageSize = JSStringGetMaximumUTF8CStringSize(js);
|
|
||||||
char *message = g_new(char, messageSize);
|
|
||||||
JSStringGetUTF8CString(js, message, messageSize);
|
|
||||||
JSStringRelease(js);
|
|
||||||
#endif
|
|
||||||
app->sendMessageToBackend(message);
|
|
||||||
g_free(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetDebug(struct Application *app, int flag)
|
|
||||||
{
|
|
||||||
debug = flag;
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCurrentMonitorGeometry gets the geometry of the monitor
|
|
||||||
// that the window is mostly on.
|
|
||||||
GdkRectangle getCurrentMonitorGeometry(GtkWindow *window) {
|
|
||||||
// Get the monitor that the window is currently on
|
|
||||||
GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(window));
|
|
||||||
GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
|
|
||||||
GdkMonitor *monitor = gdk_display_get_monitor_at_window (display, gdk_window);
|
|
||||||
|
|
||||||
// Get the geometry of the monitor
|
|
||||||
GdkRectangle result;
|
|
||||||
gdk_monitor_get_geometry (monitor,&result);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*******************
|
|
||||||
* Window Position *
|
|
||||||
*******************/
|
|
||||||
|
|
||||||
// Position holds an x/y corrdinate
|
|
||||||
struct Position {
|
|
||||||
int x;
|
|
||||||
int y;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Internal call for setting the position of the window.
|
|
||||||
void setPositionInternal(struct Application *app, struct Position *pos) {
|
|
||||||
|
|
||||||
// Get the monitor geometry
|
|
||||||
GdkRectangle m = getCurrentMonitorGeometry(app->mainWindow);
|
|
||||||
|
|
||||||
// Move the window relative to the monitor
|
|
||||||
gtk_window_move(app->mainWindow, m.x + pos->x, m.y + pos->y);
|
|
||||||
|
|
||||||
// Free memory
|
|
||||||
free(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPosition sets the position of the window to the given x/y
|
|
||||||
// coordinates. The x/y values are relative to the monitor
|
|
||||||
// the window is mostly on.
|
|
||||||
void SetPosition(struct Application *app, int x, int y) {
|
|
||||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
|
||||||
data->method = (dispatchMethod)setPositionInternal;
|
|
||||||
struct Position *pos = malloc(sizeof(struct Position));
|
|
||||||
pos->x = x;
|
|
||||||
pos->y = y;
|
|
||||||
data->args = pos;
|
|
||||||
data->app = app;
|
|
||||||
|
|
||||||
gdk_threads_add_idle(executeMethod, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/***************
|
|
||||||
* Window Size *
|
|
||||||
***************/
|
|
||||||
|
|
||||||
// Size holds a width/height
|
|
||||||
struct Size {
|
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Internal call for setting the size of the window.
|
|
||||||
void setSizeInternal(struct Application *app, struct Size *size) {
|
|
||||||
gtk_window_resize(app->mainWindow, size->width, size->height);
|
|
||||||
free(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSize sets the size of the window to the given width/height
|
|
||||||
void SetSize(struct Application *app, int width, int height) {
|
|
||||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
|
||||||
data->method = (dispatchMethod)setSizeInternal;
|
|
||||||
struct Size *size = malloc(sizeof(struct Size));
|
|
||||||
size->width = width;
|
|
||||||
size->height = height;
|
|
||||||
data->args = size;
|
|
||||||
data->app = app;
|
|
||||||
|
|
||||||
gdk_threads_add_idle(executeMethod, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// centerInternal will center the main window on the monitor it is mostly in
|
|
||||||
void centerInternal(struct Application *app)
|
|
||||||
{
|
|
||||||
// Get the geometry of the monitor
|
|
||||||
GdkRectangle m = getCurrentMonitorGeometry(app->mainWindow);
|
|
||||||
|
|
||||||
// Get the window width/height
|
|
||||||
int windowWidth, windowHeight;
|
|
||||||
gtk_window_get_size(app->mainWindow, &windowWidth, &windowHeight);
|
|
||||||
|
|
||||||
// Place the window at the center of the monitor
|
|
||||||
gtk_window_move(app->mainWindow, ((m.width - windowWidth) / 2) + m.x, ((m.height - windowHeight) / 2) + m.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Center the window
|
|
||||||
void Center(struct Application *app) {
|
|
||||||
|
|
||||||
// Setup a call to centerInternal on the main thread
|
|
||||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
|
||||||
data->method = (dispatchMethod)centerInternal;
|
|
||||||
data->app = app;
|
|
||||||
|
|
||||||
// Add call to main thread
|
|
||||||
gdk_threads_add_idle(executeMethod, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// hideInternal hides the main window
|
|
||||||
void hideInternal(struct Application *app) {
|
|
||||||
gtk_widget_hide (GTK_WIDGET(app->mainWindow));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide places the hideInternal method onto the main thread for execution
|
|
||||||
void Hide(struct Application *app) {
|
|
||||||
|
|
||||||
// Setup a call to hideInternal on the main thread
|
|
||||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
|
||||||
data->method = (dispatchMethod)hideInternal;
|
|
||||||
data->app = app;
|
|
||||||
|
|
||||||
// Add call to main thread
|
|
||||||
gdk_threads_add_idle(executeMethod, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// showInternal shows the main window
|
|
||||||
void showInternal(struct Application *app) {
|
|
||||||
gtk_widget_show_all(GTK_WIDGET(app->mainWindow));
|
|
||||||
gtk_widget_grab_focus(app->webView);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show places the showInternal method onto the main thread for execution
|
|
||||||
void Show(struct Application *app) {
|
|
||||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
|
||||||
data->method = (dispatchMethod)showInternal;
|
|
||||||
data->app = app;
|
|
||||||
|
|
||||||
gdk_threads_add_idle(executeMethod, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// maximiseInternal maximises the main window
|
|
||||||
void maximiseInternal(struct Application *app) {
|
|
||||||
gtk_window_maximize(GTK_WIDGET(app->mainWindow));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maximise places the maximiseInternal method onto the main thread for execution
|
|
||||||
void Maximise(struct Application *app) {
|
|
||||||
|
|
||||||
// Setup a call to maximiseInternal on the main thread
|
|
||||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
|
||||||
data->method = (dispatchMethod)maximiseInternal;
|
|
||||||
data->app = app;
|
|
||||||
|
|
||||||
// Add call to main thread
|
|
||||||
gdk_threads_add_idle(executeMethod, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// unmaximiseInternal unmaximises the main window
|
|
||||||
void unmaximiseInternal(struct Application *app) {
|
|
||||||
gtk_window_unmaximize(GTK_WIDGET(app->mainWindow));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmaximise places the unmaximiseInternal method onto the main thread for execution
|
|
||||||
void Unmaximise(struct Application *app) {
|
|
||||||
|
|
||||||
// Setup a call to unmaximiseInternal on the main thread
|
|
||||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
|
||||||
data->method = (dispatchMethod)unmaximiseInternal;
|
|
||||||
data->app = app;
|
|
||||||
|
|
||||||
// Add call to main thread
|
|
||||||
gdk_threads_add_idle(executeMethod, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// minimiseInternal minimises the main window
|
|
||||||
void minimiseInternal(struct Application *app) {
|
|
||||||
gtk_window_iconify(app->mainWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Minimise places the minimiseInternal method onto the main thread for execution
|
|
||||||
void Minimise(struct Application *app) {
|
|
||||||
|
|
||||||
// Setup a call to minimiseInternal on the main thread
|
|
||||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
|
||||||
data->method = (dispatchMethod)minimiseInternal;
|
|
||||||
data->app = app;
|
|
||||||
|
|
||||||
// Add call to main thread
|
|
||||||
gdk_threads_add_idle(executeMethod, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// unminimiseInternal unminimises the main window
|
|
||||||
void unminimiseInternal(struct Application *app) {
|
|
||||||
gtk_window_present(app->mainWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unminimise places the unminimiseInternal method onto the main thread for execution
|
|
||||||
void Unminimise(struct Application *app) {
|
|
||||||
|
|
||||||
// Setup a call to unminimiseInternal on the main thread
|
|
||||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
|
||||||
data->method = (dispatchMethod)unminimiseInternal;
|
|
||||||
data->app = app;
|
|
||||||
|
|
||||||
// Add call to main thread
|
|
||||||
gdk_threads_add_idle(executeMethod, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void SetBindings(struct Application *app, const char *bindings)
|
|
||||||
{
|
|
||||||
const char *temp = concat("window.wailsbindings = \"", bindings);
|
|
||||||
const char *jscall = concat(temp, "\";");
|
|
||||||
free((void *)temp);
|
|
||||||
app->bindings = jscall;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is called when the close button on the window is pressed
|
|
||||||
gboolean close_button_pressed(GtkWidget *widget,
|
|
||||||
GdkEvent *event,
|
|
||||||
struct Application *app)
|
|
||||||
{
|
|
||||||
app->sendMessageToBackend("WC"); // Window Close
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void setupWindow(struct Application *app)
|
|
||||||
{
|
|
||||||
|
|
||||||
// Create the window
|
|
||||||
GtkWidget *mainWindow = gtk_application_window_new(app->application);
|
|
||||||
// Save reference
|
|
||||||
app->mainWindow = GTK_WINDOW(mainWindow);
|
|
||||||
|
|
||||||
// Setup frame
|
|
||||||
gtk_window_set_decorated((GtkWindow *)mainWindow, app->frame);
|
|
||||||
|
|
||||||
// Setup title
|
|
||||||
gtk_window_set_title(GTK_WINDOW(mainWindow), app->title);
|
|
||||||
|
|
||||||
// Setup script handler
|
|
||||||
WebKitUserContentManager *contentManager = webkit_user_content_manager_new();
|
|
||||||
|
|
||||||
// Setup the invoke handler
|
|
||||||
webkit_user_content_manager_register_script_message_handler(contentManager, "external");
|
|
||||||
app->signalInvoke = g_signal_connect(contentManager, "script-message-received::external", G_CALLBACK(sendMessageToBackend), app);
|
|
||||||
|
|
||||||
// Setup the window drag handler if this is a frameless app
|
|
||||||
if ( app->frame == 0 ) {
|
|
||||||
webkit_user_content_manager_register_script_message_handler(contentManager, "windowDrag");
|
|
||||||
app->signalWindowDrag = g_signal_connect(contentManager, "script-message-received::windowDrag", G_CALLBACK(dragWindow), app);
|
|
||||||
// Setup the mouse handlers
|
|
||||||
app->signalButtonPressed = g_signal_connect(app->webView, "button-press-event", G_CALLBACK(buttonPress), app);
|
|
||||||
app->signalButtonReleased = g_signal_connect(app->webView, "button-release-event", G_CALLBACK(buttonRelease), app);
|
|
||||||
}
|
|
||||||
GtkWidget *webView = webkit_web_view_new_with_user_content_manager(contentManager);
|
|
||||||
|
|
||||||
// Save reference
|
|
||||||
app->webView = webView;
|
|
||||||
|
|
||||||
// Add the webview to the window
|
|
||||||
gtk_container_add(GTK_CONTAINER(mainWindow), webView);
|
|
||||||
|
|
||||||
|
|
||||||
// Load default HTML
|
|
||||||
app->signalLoadChanged = g_signal_connect(G_OBJECT(webView), "load-changed", G_CALLBACK(load_finished_cb), app);
|
|
||||||
|
|
||||||
// Load the user's HTML
|
|
||||||
// assets[0] is the HTML because the asset array is bundled like that by convention
|
|
||||||
webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webView), assets[0]);
|
|
||||||
|
|
||||||
// Check if we want to enable the dev tools
|
|
||||||
if (app->devtools)
|
|
||||||
{
|
|
||||||
WebKitSettings *settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webView));
|
|
||||||
// webkit_settings_set_enable_write_console_messages_to_stdout(settings, true);
|
|
||||||
webkit_settings_set_enable_developer_extras(settings, true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
g_signal_connect(G_OBJECT(webView), "context-menu", G_CALLBACK(disable_context_menu_cb), app);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen for close button signal
|
|
||||||
g_signal_connect(GTK_WIDGET(mainWindow), "delete-event", G_CALLBACK(close_button_pressed), app);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void activate(GtkApplication* _, struct Application *app)
|
|
||||||
{
|
|
||||||
setupWindow(app);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Run(struct Application *app, int argc, char **argv)
|
|
||||||
{
|
|
||||||
g_signal_connect(app->application, "activate", G_CALLBACK(activate), app);
|
|
||||||
g_application_run(G_APPLICATION(app->application), argc, argv);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,119 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com)
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Source: http://git.ozlabs.org/?p=ccan;a=tree;f=ccan/json;hb=HEAD
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef CCAN_JSON_H
|
|
||||||
#define CCAN_JSON_H
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
JSON_NULL,
|
|
||||||
JSON_BOOL,
|
|
||||||
JSON_STRING,
|
|
||||||
JSON_NUMBER,
|
|
||||||
JSON_ARRAY,
|
|
||||||
JSON_OBJECT,
|
|
||||||
} JsonTag;
|
|
||||||
|
|
||||||
typedef struct JsonNode JsonNode;
|
|
||||||
|
|
||||||
struct JsonNode
|
|
||||||
{
|
|
||||||
/* only if parent is an object or array (NULL otherwise) */
|
|
||||||
JsonNode *parent;
|
|
||||||
JsonNode *prev, *next;
|
|
||||||
|
|
||||||
/* only if parent is an object (NULL otherwise) */
|
|
||||||
char *key; /* Must be valid UTF-8. */
|
|
||||||
|
|
||||||
JsonTag tag;
|
|
||||||
union {
|
|
||||||
/* JSON_BOOL */
|
|
||||||
bool bool_;
|
|
||||||
|
|
||||||
/* JSON_STRING */
|
|
||||||
char *string_; /* Must be valid UTF-8. */
|
|
||||||
|
|
||||||
/* JSON_NUMBER */
|
|
||||||
double number_;
|
|
||||||
|
|
||||||
/* JSON_ARRAY */
|
|
||||||
/* JSON_OBJECT */
|
|
||||||
struct {
|
|
||||||
JsonNode *head, *tail;
|
|
||||||
} children;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/*** Encoding, decoding, and validation ***/
|
|
||||||
|
|
||||||
JsonNode *json_decode (const char *json);
|
|
||||||
char *json_encode (const JsonNode *node);
|
|
||||||
char *json_encode_string (const char *str);
|
|
||||||
char *json_stringify (const JsonNode *node, const char *space);
|
|
||||||
void json_delete (JsonNode *node);
|
|
||||||
|
|
||||||
bool json_validate (const char *json);
|
|
||||||
|
|
||||||
/*** Lookup and traversal ***/
|
|
||||||
|
|
||||||
JsonNode *json_find_element (JsonNode *array, int index);
|
|
||||||
JsonNode *json_find_member (JsonNode *object, const char *key);
|
|
||||||
|
|
||||||
JsonNode *json_first_child (const JsonNode *node);
|
|
||||||
|
|
||||||
#define json_foreach(i, object_or_array) \
|
|
||||||
for ((i) = json_first_child(object_or_array); \
|
|
||||||
(i) != NULL; \
|
|
||||||
(i) = (i)->next)
|
|
||||||
|
|
||||||
/*** Construction and manipulation ***/
|
|
||||||
|
|
||||||
JsonNode *json_mknull(void);
|
|
||||||
JsonNode *json_mkbool(bool b);
|
|
||||||
JsonNode *json_mkstring(const char *s);
|
|
||||||
JsonNode *json_mknumber(double n);
|
|
||||||
JsonNode *json_mkarray(void);
|
|
||||||
JsonNode *json_mkobject(void);
|
|
||||||
|
|
||||||
void json_append_element(JsonNode *array, JsonNode *element);
|
|
||||||
void json_prepend_element(JsonNode *array, JsonNode *element);
|
|
||||||
void json_append_member(JsonNode *object, const char *key, JsonNode *value);
|
|
||||||
void json_prepend_member(JsonNode *object, const char *key, JsonNode *value);
|
|
||||||
|
|
||||||
void json_remove_from_parent(JsonNode *node);
|
|
||||||
|
|
||||||
/*** Debugging ***/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Look for structure and encoding problems in a JsonNode or its descendents.
|
|
||||||
*
|
|
||||||
* If a problem is detected, return false, writing a description of the problem
|
|
||||||
* to errmsg (unless errmsg is NULL).
|
|
||||||
*/
|
|
||||||
bool json_check(const JsonNode *node, char errmsg[256]);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
package fs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/leaanthony/slicer"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LocalDirectory gets the caller's file directory
|
|
||||||
// Equivalent to node's __DIRNAME
|
|
||||||
func LocalDirectory() string {
|
|
||||||
_, thisFile, _, _ := runtime.Caller(1)
|
|
||||||
return filepath.Dir(thisFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RelativeToCwd returns an absolute path based on the cwd
|
|
||||||
// and the given relative path
|
|
||||||
func RelativeToCwd(relativePath string) (string, error) {
|
|
||||||
cwd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return filepath.Join(cwd, relativePath), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mkdir will create the given directory
|
|
||||||
func Mkdir(dirname string) error {
|
|
||||||
return os.Mkdir(dirname, 0755)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteFile will delete the given file
|
|
||||||
func DeleteFile(filename string) error {
|
|
||||||
return os.Remove(filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyFile from source to target
|
|
||||||
func CopyFile(source string, target string) error {
|
|
||||||
s, err := os.Open(source)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
d, err := os.Create(target)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := io.Copy(d, s); err != nil {
|
|
||||||
d.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return d.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DirExists - Returns true if the given path resolves to a directory on the filesystem
|
|
||||||
func DirExists(path string) bool {
|
|
||||||
fi, err := os.Lstat(path)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return fi.Mode().IsDir()
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileExists returns a boolean value indicating whether
|
|
||||||
// the given file exists
|
|
||||||
func FileExists(path string) bool {
|
|
||||||
fi, err := os.Lstat(path)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return fi.Mode().IsRegular()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RelativePath returns a qualified path created by joining the
|
|
||||||
// directory of the calling file and the given relative path.
|
|
||||||
//
|
|
||||||
// Example: RelativePath("..") in *this* file would give you '/path/to/wails2/v2/internal`
|
|
||||||
func RelativePath(relativepath string, optionalpaths ...string) string {
|
|
||||||
_, thisFile, _, _ := runtime.Caller(1)
|
|
||||||
localDir := filepath.Dir(thisFile)
|
|
||||||
|
|
||||||
// If we have optional paths, join them to the relativepath
|
|
||||||
if len(optionalpaths) > 0 {
|
|
||||||
paths := []string{relativepath}
|
|
||||||
paths = append(paths, optionalpaths...)
|
|
||||||
relativepath = filepath.Join(paths...)
|
|
||||||
}
|
|
||||||
result, err := filepath.Abs(filepath.Join(localDir, relativepath))
|
|
||||||
if err != nil {
|
|
||||||
// I'm allowing this for 1 reason only: It's fatal if the path
|
|
||||||
// supplied is wrong as it's only used internally in Wails. If we get
|
|
||||||
// that path wrong, we should know about it immediately. The other reason is
|
|
||||||
// that it cuts down a ton of unnecassary error handling.
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustLoadString attempts to load a string and will abort with a fatal message if
|
|
||||||
// something goes wrong
|
|
||||||
func MustLoadString(filename string) string {
|
|
||||||
data, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("FATAL: Unable to load file '%s': %s\n", filename, err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return *(*string)(unsafe.Pointer(&data))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MD5File returns the md5sum of the given file
|
|
||||||
func MD5File(filename string) (string, error) {
|
|
||||||
f, err := os.Open(filename)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
h := md5.New()
|
|
||||||
if _, err := io.Copy(h, f); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustMD5File will call MD5File and abort the program on error
|
|
||||||
func MustMD5File(filename string) string {
|
|
||||||
result, err := MD5File(filename)
|
|
||||||
if err != nil {
|
|
||||||
println("FATAL: Unable to MD5Sum file:", err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustWriteString will attempt to write the given data to the given filename
|
|
||||||
// It will abort the program in the event of a failure
|
|
||||||
func MustWriteString(filename string, data string) {
|
|
||||||
err := ioutil.WriteFile(filename, []byte(data), 0755)
|
|
||||||
if err != nil {
|
|
||||||
fatal("Unable to write file", filename, ":", err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fatal will print the optional messages and die
|
|
||||||
func fatal(message ...string) {
|
|
||||||
if len(message) > 0 {
|
|
||||||
print("FATAL:")
|
|
||||||
for text := range message {
|
|
||||||
print(text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSubdirectories returns a list of subdirectories for the given root directory
|
|
||||||
func GetSubdirectories(rootDir string) (*slicer.StringSlicer, error) {
|
|
||||||
var result slicer.StringSlicer
|
|
||||||
|
|
||||||
// Iterate root dir
|
|
||||||
err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// If we have a directory, save it
|
|
||||||
if info.IsDir() {
|
|
||||||
result.Add(path)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return &result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func DirIsEmpty(dir string) (bool, error) {
|
|
||||||
|
|
||||||
if !DirExists(dir) {
|
|
||||||
return false, fmt.Errorf("DirIsEmpty called with a non-existant directory: %s", dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CREDIT: https://stackoverflow.com/a/30708914/8325411
|
|
||||||
f, err := os.Open(dir)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
_, err = f.Readdirnames(1) // Or f.Readdir(1)
|
|
||||||
if err == io.EOF {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, err // Either not empty or error, suits both cases
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package fs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/matryer/is"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRelativePath(t *testing.T) {
|
|
||||||
|
|
||||||
is := is.New(t)
|
|
||||||
|
|
||||||
cwd, err := os.Getwd()
|
|
||||||
is.Equal(err, nil)
|
|
||||||
|
|
||||||
// Check current directory
|
|
||||||
actual := RelativePath(".")
|
|
||||||
is.Equal(actual, cwd)
|
|
||||||
|
|
||||||
// Check 2 parameters
|
|
||||||
actual = RelativePath("..", "fs")
|
|
||||||
is.Equal(actual, cwd)
|
|
||||||
|
|
||||||
// Check 3 parameters including filename
|
|
||||||
actual = RelativePath("..", "fs", "fs.go")
|
|
||||||
expected := filepath.Join(cwd, "fs.go")
|
|
||||||
is.Equal(actual, expected)
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
package html
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/url"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/tdewolff/minify"
|
|
||||||
"github.com/tdewolff/minify/js"
|
|
||||||
)
|
|
||||||
|
|
||||||
type assetTypes struct {
|
|
||||||
JS string
|
|
||||||
CSS string
|
|
||||||
FAVICON string
|
|
||||||
HTML string
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssetTypes is an enum for the asset type keys
|
|
||||||
var AssetTypes *assetTypes = &assetTypes{
|
|
||||||
JS: "javascript",
|
|
||||||
CSS: "css",
|
|
||||||
FAVICON: "favicon",
|
|
||||||
HTML: "html",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Asset describes an asset type and its path
|
|
||||||
type Asset struct {
|
|
||||||
Type string
|
|
||||||
Path string
|
|
||||||
Data string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the asset from disk
|
|
||||||
func (a *Asset) Load(basedirectory string) error {
|
|
||||||
assetpath := filepath.Join(basedirectory, a.Path)
|
|
||||||
data, err := ioutil.ReadFile(assetpath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.Data = string(data)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsString returns the data as a READ ONLY string
|
|
||||||
func (a *Asset) AsString() string {
|
|
||||||
return a.Data
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsCHexData processes the asset data so it may be used by C
|
|
||||||
func (a *Asset) AsCHexData() string {
|
|
||||||
|
|
||||||
// This will be our final string to hexify
|
|
||||||
dataString := a.Data
|
|
||||||
|
|
||||||
switch a.Type {
|
|
||||||
case AssetTypes.HTML:
|
|
||||||
|
|
||||||
// Escape HTML
|
|
||||||
var re = regexp.MustCompile(`\s{2,}`)
|
|
||||||
result := re.ReplaceAllString(a.Data, ``)
|
|
||||||
result = strings.ReplaceAll(result, "\n", "")
|
|
||||||
result = strings.ReplaceAll(result, "\r\n", "")
|
|
||||||
result = strings.ReplaceAll(result, "\n", "")
|
|
||||||
|
|
||||||
// Inject wailsloader code
|
|
||||||
result = strings.Replace(result, `</body>`, `<script id='wailsloader'>window.wailsloader = { html: true, runtime: false, userjs: false, usercss: false };var self=document.querySelector('#wailsloader');self.parentNode.removeChild(self);</script></body>`, 1)
|
|
||||||
|
|
||||||
url := url.URL{Path: result}
|
|
||||||
urlString := strings.ReplaceAll(url.String(), "/", "%2f")
|
|
||||||
|
|
||||||
// Save Data uRI string
|
|
||||||
dataString = "data:text/html;charset=utf-8," + urlString
|
|
||||||
|
|
||||||
case AssetTypes.CSS:
|
|
||||||
|
|
||||||
// Escape CSS data
|
|
||||||
var re = regexp.MustCompile(`\s{2,}`)
|
|
||||||
result := re.ReplaceAllString(a.Data, ``)
|
|
||||||
result = strings.ReplaceAll(result, "\n", "")
|
|
||||||
result = strings.ReplaceAll(result, "\r\n", "")
|
|
||||||
result = strings.ReplaceAll(result, "\n", "")
|
|
||||||
result = strings.ReplaceAll(result, "\t", "")
|
|
||||||
result = strings.ReplaceAll(result, `\`, `\\`)
|
|
||||||
result = strings.ReplaceAll(result, `"`, `\"`)
|
|
||||||
result = strings.ReplaceAll(result, `'`, `\'`)
|
|
||||||
result = strings.ReplaceAll(result, ` {`, `{`)
|
|
||||||
result = strings.ReplaceAll(result, `: `, `:`)
|
|
||||||
dataString = fmt.Sprintf("window.wails._.InjectCSS(\"%s\");", result)
|
|
||||||
|
|
||||||
case AssetTypes.JS:
|
|
||||||
m := minify.New()
|
|
||||||
m.AddFunc("application/javascript", js.Minify)
|
|
||||||
var err error
|
|
||||||
dataString, err = m.String("application/javascript", a.Data+";")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
a.Data = dataString
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get byte data of the string
|
|
||||||
bytes := *(*[]byte)(unsafe.Pointer(&dataString))
|
|
||||||
|
|
||||||
// Create a strings builder
|
|
||||||
var cdata strings.Builder
|
|
||||||
|
|
||||||
// Set buffer size to 4k
|
|
||||||
cdata.Grow(4096)
|
|
||||||
|
|
||||||
// Convert each byte to hex
|
|
||||||
for _, b := range bytes {
|
|
||||||
cdata.WriteString(fmt.Sprintf("0x%x, ", b))
|
|
||||||
}
|
|
||||||
|
|
||||||
return cdata.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dump will output the asset to the terminal
|
|
||||||
func (a *Asset) Dump() {
|
|
||||||
fmt.Printf("{ Type: %s, Path: %s, Data: %+v }\n", a.Type, a.Path, a.Data[:10])
|
|
||||||
}
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
package html
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/leaanthony/slicer"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/assetdb"
|
|
||||||
"golang.org/x/net/html"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AssetBundle is a collection of Assets
|
|
||||||
type AssetBundle struct {
|
|
||||||
assets []*Asset
|
|
||||||
basedirectory string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAssetBundle creates a new AssetBundle struct containing
|
|
||||||
// the given html and all the assets referenced by it
|
|
||||||
func NewAssetBundle(pathToHTML string) (*AssetBundle, error) {
|
|
||||||
|
|
||||||
// Create result
|
|
||||||
result := &AssetBundle{
|
|
||||||
basedirectory: filepath.Dir(pathToHTML),
|
|
||||||
}
|
|
||||||
|
|
||||||
err := result.loadAssets(pathToHTML)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadAssets processes the given html file and loads in
|
|
||||||
// all referenced assets
|
|
||||||
func (a *AssetBundle) loadAssets(pathToHTML string) error {
|
|
||||||
|
|
||||||
// Save HTML
|
|
||||||
htmlAsset := &Asset{
|
|
||||||
Type: AssetTypes.HTML,
|
|
||||||
Path: filepath.Base(pathToHTML),
|
|
||||||
}
|
|
||||||
err := htmlAsset.Load(a.basedirectory)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.assets = append(a.assets, htmlAsset)
|
|
||||||
|
|
||||||
return a.processHTML(htmlAsset.AsString())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Credit to: https://drstearns.github.io/tutorials/tokenizing/
|
|
||||||
func (a *AssetBundle) processHTML(htmldata string) error {
|
|
||||||
|
|
||||||
// Tokenize the html
|
|
||||||
buf := bytes.NewBufferString(htmldata)
|
|
||||||
tokenizer := html.NewTokenizer(buf)
|
|
||||||
|
|
||||||
for {
|
|
||||||
//get the next token type
|
|
||||||
tokenType := tokenizer.Next()
|
|
||||||
|
|
||||||
//if it's an error token, we either reached
|
|
||||||
//the end of the file, or the HTML was malformed
|
|
||||||
if tokenType == html.ErrorToken {
|
|
||||||
err := tokenizer.Err()
|
|
||||||
if err == io.EOF {
|
|
||||||
//end of the file, break out of the loop
|
|
||||||
break
|
|
||||||
}
|
|
||||||
//otherwise, there was an error tokenizing,
|
|
||||||
//which likely means the HTML was malformed.
|
|
||||||
//since this is a simple command-line utility,
|
|
||||||
//we can just use log.Fatalf() to report the error
|
|
||||||
//and exit the process with a non-zero status code
|
|
||||||
return tokenizer.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
//process the token according to the token type...
|
|
||||||
if tokenType == html.StartTagToken {
|
|
||||||
//get the token
|
|
||||||
token := tokenizer.Token()
|
|
||||||
|
|
||||||
//if the name of the element is "title"
|
|
||||||
if "link" == token.Data {
|
|
||||||
//the next token should be the page title
|
|
||||||
tokenType = tokenizer.Next()
|
|
||||||
//just make sure it's actually a text token
|
|
||||||
asset := &Asset{}
|
|
||||||
for _, attr := range token.Attr {
|
|
||||||
// Favicon
|
|
||||||
if attr.Key == "rel" && attr.Val == "icon" {
|
|
||||||
asset.Type = AssetTypes.FAVICON
|
|
||||||
}
|
|
||||||
if attr.Key == "href" {
|
|
||||||
asset.Path = attr.Val
|
|
||||||
}
|
|
||||||
// stylesheet
|
|
||||||
if attr.Key == "rel" && attr.Val == "stylesheet" {
|
|
||||||
asset.Type = AssetTypes.CSS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err := asset.Load(a.basedirectory)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.assets = append(a.assets, asset)
|
|
||||||
}
|
|
||||||
if "script" == token.Data {
|
|
||||||
|
|
||||||
tokenType = tokenizer.Next()
|
|
||||||
//just make sure it's actually a text token
|
|
||||||
asset := &Asset{Type: AssetTypes.JS}
|
|
||||||
for _, attr := range token.Attr {
|
|
||||||
if attr.Key == "src" {
|
|
||||||
asset.Path = attr.Val
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err := asset.Load(a.basedirectory)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.assets = append(a.assets, asset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteToCFile dumps all the assets to C files in the given directory
|
|
||||||
func (a *AssetBundle) WriteToCFile(targetDir string) (string, error) {
|
|
||||||
|
|
||||||
// Write out the assets.c file
|
|
||||||
var cdata strings.Builder
|
|
||||||
|
|
||||||
// Write header
|
|
||||||
header := `// assets.c
|
|
||||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL.
|
|
||||||
// This file was auto-generated. DO NOT MODIFY.
|
|
||||||
|
|
||||||
`
|
|
||||||
cdata.WriteString(header)
|
|
||||||
|
|
||||||
// Loop over the Assets
|
|
||||||
var err error
|
|
||||||
assetVariables := slicer.String()
|
|
||||||
var variableName string
|
|
||||||
for index, asset := range a.assets {
|
|
||||||
// For desktop we ignore the favicon
|
|
||||||
if asset.Type == AssetTypes.FAVICON {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
variableName = fmt.Sprintf("%s%d", asset.Type, index)
|
|
||||||
assetCdata := fmt.Sprintf("const unsigned char %s[]={ %s0x00 };\n", variableName, asset.AsCHexData())
|
|
||||||
cdata.WriteString(assetCdata)
|
|
||||||
assetVariables.Add(variableName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if assetVariables.Length() > 0 {
|
|
||||||
cdata.WriteString(fmt.Sprintf("\nconst unsigned char *assets[] = { %s, 0x00 };", assetVariables.Join(", ")))
|
|
||||||
} else {
|
|
||||||
cdata.WriteString("\nconst unsigned char *assets[] = { 0x00 };")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save file
|
|
||||||
assetsFile := filepath.Join(targetDir, "assets.c")
|
|
||||||
err = ioutil.WriteFile(assetsFile, []byte(cdata.String()), 0600)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return assetsFile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertToAssetDB returns an assetdb.AssetDB initialized with
|
|
||||||
// the items in the AssetBundle
|
|
||||||
func (a *AssetBundle) ConvertToAssetDB() (*assetdb.AssetDB, error) {
|
|
||||||
assetdb := assetdb.NewAssetDB()
|
|
||||||
|
|
||||||
// Loop over the Assets
|
|
||||||
for _, asset := range a.assets {
|
|
||||||
assetdb.AddAsset(asset.Path, []byte(asset.Data))
|
|
||||||
}
|
|
||||||
|
|
||||||
return assetdb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dump will output the assets to the terminal
|
|
||||||
func (a *AssetBundle) Dump() {
|
|
||||||
println("Assets:")
|
|
||||||
for _, asset := range a.assets {
|
|
||||||
asset.Dump()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
package logger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CustomLogger defines what a user can do with a logger
|
|
||||||
type CustomLogger interface {
|
|
||||||
// Writeln writes directly to the output with no log level plus line ending
|
|
||||||
Writeln(message string) error
|
|
||||||
|
|
||||||
// Write writes directly to the output with no log level
|
|
||||||
Write(message string) error
|
|
||||||
|
|
||||||
// Trace level logging. Works like Sprintf.
|
|
||||||
Trace(format string, args ...interface{}) error
|
|
||||||
|
|
||||||
// Debug level logging. Works like Sprintf.
|
|
||||||
Debug(format string, args ...interface{}) error
|
|
||||||
|
|
||||||
// Info level logging. Works like Sprintf.
|
|
||||||
Info(format string, args ...interface{}) error
|
|
||||||
|
|
||||||
// Warning level logging. Works like Sprintf.
|
|
||||||
Warning(format string, args ...interface{}) error
|
|
||||||
|
|
||||||
// Error level logging. Works like Sprintf.
|
|
||||||
Error(format string, args ...interface{}) error
|
|
||||||
|
|
||||||
// Fatal level logging. Works like Sprintf.
|
|
||||||
Fatal(format string, args ...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// customLogger is a utlility to log messages to a number of destinations
|
|
||||||
type customLogger struct {
|
|
||||||
logger *Logger
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new customLogger. You may pass in a number of `io.Writer`s that
|
|
||||||
// are the targets for the logs
|
|
||||||
func newcustomLogger(logger *Logger, name string) *customLogger {
|
|
||||||
result := &customLogger{
|
|
||||||
name: name,
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writeln writes directly to the output with no log level
|
|
||||||
// Appends a carriage return to the message
|
|
||||||
func (l *customLogger) Writeln(message string) error {
|
|
||||||
return l.logger.Writeln(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes directly to the output with no log level
|
|
||||||
func (l *customLogger) Write(message string) error {
|
|
||||||
return l.logger.Write(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trace level logging. Works like Sprintf.
|
|
||||||
func (l *customLogger) Trace(format string, args ...interface{}) error {
|
|
||||||
format = fmt.Sprintf("%s | %s", l.name, format)
|
|
||||||
return l.logger.Trace(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug level logging. Works like Sprintf.
|
|
||||||
func (l *customLogger) Debug(format string, args ...interface{}) error {
|
|
||||||
format = fmt.Sprintf("%s | %s", l.name, format)
|
|
||||||
return l.logger.Debug(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info level logging. Works like Sprintf.
|
|
||||||
func (l *customLogger) Info(format string, args ...interface{}) error {
|
|
||||||
format = fmt.Sprintf("%s | %s", l.name, format)
|
|
||||||
return l.logger.Info(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warning level logging. Works like Sprintf.
|
|
||||||
func (l *customLogger) Warning(format string, args ...interface{}) error {
|
|
||||||
format = fmt.Sprintf("%s | %s", l.name, format)
|
|
||||||
return l.logger.Warning(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error level logging. Works like Sprintf.
|
|
||||||
func (l *customLogger) Error(format string, args ...interface{}) error {
|
|
||||||
format = fmt.Sprintf("%s | %s", l.name, format)
|
|
||||||
return l.logger.Error(format, args...)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatal level logging. Works like Sprintf.
|
|
||||||
func (l *customLogger) Fatal(format string, args ...interface{}) {
|
|
||||||
format = fmt.Sprintf("%s | %s", l.name, format)
|
|
||||||
l.logger.Fatal(format, args...)
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
package logger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LogLevel is an alias for the public LogLevel
|
|
||||||
type LogLevel = logger.LogLevel
|
|
||||||
|
|
||||||
// Logger is a utlility to log messages to a number of destinations
|
|
||||||
type Logger struct {
|
|
||||||
output logger.Logger
|
|
||||||
logLevel LogLevel
|
|
||||||
showLevelInLog bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new Logger. You may pass in a number of `io.Writer`s that
|
|
||||||
// are the targets for the logs
|
|
||||||
func New(output logger.Logger) *Logger {
|
|
||||||
result := &Logger{
|
|
||||||
logLevel: logger.INFO,
|
|
||||||
showLevelInLog: true,
|
|
||||||
output: output,
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// CustomLogger creates a new custom logger that prints out a name/id
|
|
||||||
// before the messages
|
|
||||||
func (l *Logger) CustomLogger(name string) CustomLogger {
|
|
||||||
return newcustomLogger(l, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HideLogLevel removes the loglevel text from the start of each logged line
|
|
||||||
func (l *Logger) HideLogLevel() {
|
|
||||||
l.showLevelInLog = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLogLevel sets the minimum level of logs that will be output
|
|
||||||
func (l *Logger) SetLogLevel(level LogLevel) {
|
|
||||||
l.logLevel = level
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writeln writes directly to the output with no log level
|
|
||||||
// Appends a carriage return to the message
|
|
||||||
func (l *Logger) Writeln(message string) error {
|
|
||||||
return l.output.Print(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes directly to the output with no log level
|
|
||||||
func (l *Logger) Write(message string) error {
|
|
||||||
return l.output.Print(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print writes directly to the output with no log level
|
|
||||||
// Appends a carriage return to the message
|
|
||||||
func (l *Logger) Print(message string) error {
|
|
||||||
return l.Write(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trace level logging. Works like Sprintf.
|
|
||||||
func (l *Logger) Trace(format string, args ...interface{}) error {
|
|
||||||
if l.logLevel <= logger.TRACE {
|
|
||||||
return l.output.Trace(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug level logging. Works like Sprintf.
|
|
||||||
func (l *Logger) Debug(format string, args ...interface{}) error {
|
|
||||||
if l.logLevel <= logger.DEBUG {
|
|
||||||
return l.output.Debug(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info level logging. Works like Sprintf.
|
|
||||||
func (l *Logger) Info(format string, args ...interface{}) error {
|
|
||||||
if l.logLevel <= logger.INFO {
|
|
||||||
return l.output.Info(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warning level logging. Works like Sprintf.
|
|
||||||
func (l *Logger) Warning(format string, args ...interface{}) error {
|
|
||||||
if l.logLevel <= logger.WARNING {
|
|
||||||
return l.output.Warning(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error level logging. Works like Sprintf.
|
|
||||||
func (l *Logger) Error(format string, args ...interface{}) error {
|
|
||||||
if l.logLevel <= logger.ERROR {
|
|
||||||
return l.output.Error(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fatal level logging. Works like Sprintf.
|
|
||||||
func (l *Logger) Fatal(format string, args ...interface{}) {
|
|
||||||
err := l.output.Fatal(fmt.Sprintf(format, args...))
|
|
||||||
// Not much we can do but print it out before exiting
|
|
||||||
if err != nil {
|
|
||||||
println(err.Error())
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package messagedispatcher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/internal/logger"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client defines what a frontend client can do
|
|
||||||
type Client interface {
|
|
||||||
Quit()
|
|
||||||
NotifyEvent(message string)
|
|
||||||
CallResult(message string)
|
|
||||||
OpenDialog(dialogOptions *options.OpenDialog, callbackID string)
|
|
||||||
SaveDialog(dialogOptions *options.SaveDialog, callbackID string)
|
|
||||||
WindowSetTitle(title string)
|
|
||||||
WindowShow()
|
|
||||||
WindowHide()
|
|
||||||
WindowCenter()
|
|
||||||
WindowMaximise()
|
|
||||||
WindowUnmaximise()
|
|
||||||
WindowMinimise()
|
|
||||||
WindowUnminimise()
|
|
||||||
WindowPosition(x int, y int)
|
|
||||||
WindowSize(width int, height int)
|
|
||||||
WindowFullscreen()
|
|
||||||
WindowUnFullscreen()
|
|
||||||
WindowSetColour(colour int)
|
|
||||||
DarkModeEnabled(callbackID string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DispatchClient is what the frontends use to interface with the
|
|
||||||
// dispatcher
|
|
||||||
type DispatchClient struct {
|
|
||||||
id string
|
|
||||||
logger logger.CustomLogger
|
|
||||||
|
|
||||||
bus *servicebus.ServiceBus
|
|
||||||
|
|
||||||
// Client
|
|
||||||
frontend Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDispatchClient(id string, frontend Client, logger logger.CustomLogger, bus *servicebus.ServiceBus) *DispatchClient {
|
|
||||||
|
|
||||||
return &DispatchClient{
|
|
||||||
id: id,
|
|
||||||
frontend: frontend,
|
|
||||||
logger: logger,
|
|
||||||
bus: bus,
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// DispatchMessage is called by the front ends. It is passed
|
|
||||||
// an IPC message, translates it to a more concrete message
|
|
||||||
// type then publishes it on the service bus.
|
|
||||||
func (d *DispatchClient) DispatchMessage(incomingMessage string) {
|
|
||||||
|
|
||||||
// Parse the message
|
|
||||||
d.logger.Trace(fmt.Sprintf("Received message: %+v", incomingMessage))
|
|
||||||
parsedMessage, err := message.Parse(incomingMessage)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Error(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save this client id
|
|
||||||
parsedMessage.ClientID = d.id
|
|
||||||
|
|
||||||
d.logger.Trace("I got a parsedMessage: %+v", parsedMessage)
|
|
||||||
|
|
||||||
// Check error
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Error(err.Error())
|
|
||||||
// Hrm... what do we do with this?
|
|
||||||
d.bus.PublishForTarget("generic:message", incomingMessage, d.id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Publish the parsed message
|
|
||||||
d.bus.PublishForTarget(parsedMessage.Topic, parsedMessage.Data, d.id)
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package message
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CallMessage struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Args []json.RawMessage `json:"args"`
|
|
||||||
CallbackID string `json:"callbackID,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// callMessageParser does what it says on the tin!
|
|
||||||
func callMessageParser(message string) (*parsedMessage, error) {
|
|
||||||
|
|
||||||
// Sanity check: Call messages must be at least 3 bytes `C{}``
|
|
||||||
if len(message) < 3 {
|
|
||||||
return nil, fmt.Errorf("call message was an invalid length")
|
|
||||||
}
|
|
||||||
|
|
||||||
callMessage := new(CallMessage)
|
|
||||||
|
|
||||||
m := message[1:]
|
|
||||||
|
|
||||||
err := json.Unmarshal([]byte(m), callMessage)
|
|
||||||
if err != nil {
|
|
||||||
println(err.Error())
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
topic := "call:invoke"
|
|
||||||
|
|
||||||
// Create a new parsed message struct
|
|
||||||
parsedMessage := &parsedMessage{Topic: topic, Data: callMessage}
|
|
||||||
|
|
||||||
return parsedMessage, nil
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package message
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// dialogMessageParser does what it says on the tin!
|
|
||||||
func dialogMessageParser(message string) (*parsedMessage, error) {
|
|
||||||
|
|
||||||
// Sanity check: Dialog messages must be at least 4 bytes
|
|
||||||
if len(message) < 4 {
|
|
||||||
return nil, fmt.Errorf("dialog message was an invalid length")
|
|
||||||
}
|
|
||||||
|
|
||||||
var topic = "bad topic from dialogMessageParser"
|
|
||||||
var responseMessage *parsedMessage
|
|
||||||
|
|
||||||
// Switch the event type (with or without data)
|
|
||||||
switch message[0] {
|
|
||||||
// Format of Dialog response messages: D<dialog type><callbackID>|<[]string as json encoded string>
|
|
||||||
case 'D':
|
|
||||||
dialogType := message[1]
|
|
||||||
message = message[2:]
|
|
||||||
idx := strings.IndexByte(message, '|')
|
|
||||||
if idx < 0 {
|
|
||||||
return nil, fmt.Errorf("Invalid dialog response message format")
|
|
||||||
}
|
|
||||||
callbackID := message[:idx+1]
|
|
||||||
payloadData := message[idx+1:]
|
|
||||||
|
|
||||||
switch dialogType {
|
|
||||||
case 'O':
|
|
||||||
var data []string
|
|
||||||
topic = "dialog:openselected:" + callbackID
|
|
||||||
err := json.Unmarshal([]byte(payloadData), &data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
responseMessage = &parsedMessage{Topic: topic, Data: data}
|
|
||||||
case 'S':
|
|
||||||
topic = "dialog:saveselected:" + callbackID
|
|
||||||
responseMessage = &parsedMessage{Topic: topic, Data: payloadData}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("Invalid message to dialogMessageParser()")
|
|
||||||
}
|
|
||||||
|
|
||||||
return responseMessage, nil
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package message
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type EventMessage struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Data []interface{} `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type OnEventMessage struct {
|
|
||||||
Name string
|
|
||||||
Callback func(optionalData ...interface{})
|
|
||||||
Counter int
|
|
||||||
}
|
|
||||||
|
|
||||||
// eventMessageParser does what it says on the tin!
|
|
||||||
func eventMessageParser(message string) (*parsedMessage, error) {
|
|
||||||
|
|
||||||
// Sanity check: Event messages must be at least 2 bytes
|
|
||||||
if len(message) < 3 {
|
|
||||||
return nil, fmt.Errorf("event message was an invalid length")
|
|
||||||
}
|
|
||||||
|
|
||||||
eventMessage := new(EventMessage)
|
|
||||||
direction := message[1]
|
|
||||||
|
|
||||||
// Switch the event type (with or without data)
|
|
||||||
switch message[0] {
|
|
||||||
case 'E':
|
|
||||||
m := message[2:]
|
|
||||||
err := json.Unmarshal([]byte(m), eventMessage)
|
|
||||||
if err != nil {
|
|
||||||
println(err.Error())
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
topic := "event:emit:from:" + string(direction)
|
|
||||||
|
|
||||||
// Create a new parsed message struct
|
|
||||||
parsedMessage := &parsedMessage{Topic: topic, Data: eventMessage}
|
|
||||||
|
|
||||||
return parsedMessage, nil
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package message
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
var logMessageMap = map[byte]string{
|
|
||||||
'P': "log:print",
|
|
||||||
'T': "log:trace",
|
|
||||||
'D': "log:debug",
|
|
||||||
'I': "log:info",
|
|
||||||
'W': "log:warning",
|
|
||||||
'E': "log:error",
|
|
||||||
'F': "log:fatal",
|
|
||||||
'S': "log:setlevel",
|
|
||||||
}
|
|
||||||
|
|
||||||
// logMessageParser does what it says on the tin!
|
|
||||||
func logMessageParser(message string) (*parsedMessage, error) {
|
|
||||||
|
|
||||||
// Sanity check: Log messages must be at least 2 bytes
|
|
||||||
if len(message) < 2 {
|
|
||||||
return nil, fmt.Errorf("log message was an invalid length")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch on the log type
|
|
||||||
messageTopic := logMessageMap[message[1]]
|
|
||||||
|
|
||||||
// If the type is invalid, raise error
|
|
||||||
if messageTopic == "" {
|
|
||||||
return nil, fmt.Errorf("log message type '%c' invalid", message[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new parsed message struct
|
|
||||||
parsedMessage := &parsedMessage{Topic: messageTopic, Data: message[2:]}
|
|
||||||
|
|
||||||
return parsedMessage, nil
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package message
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// Parse
|
|
||||||
type parsedMessage struct {
|
|
||||||
Topic string
|
|
||||||
ClientID string
|
|
||||||
Data interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map of different message parsers based on the header byte of the message
|
|
||||||
var messageParsers = map[byte]func(string) (*parsedMessage, error){
|
|
||||||
'L': logMessageParser,
|
|
||||||
'R': runtimeMessageParser,
|
|
||||||
'E': eventMessageParser,
|
|
||||||
'C': callMessageParser,
|
|
||||||
'W': windowMessageParser,
|
|
||||||
'D': dialogMessageParser,
|
|
||||||
'S': systemMessageParser,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse will attempt to parse the given message
|
|
||||||
func Parse(message string) (*parsedMessage, error) {
|
|
||||||
|
|
||||||
parseMethod := messageParsers[message[0]]
|
|
||||||
if parseMethod == nil {
|
|
||||||
return nil, fmt.Errorf("message type '%b' invalid", message[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseMethod(message)
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package message
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// runtimeMessageParser does what it says on the tin!
|
|
||||||
func runtimeMessageParser(message string) (*parsedMessage, error) {
|
|
||||||
|
|
||||||
// Sanity check: Log messages must be at least 2 bytes
|
|
||||||
if len(message) < 3 {
|
|
||||||
return nil, fmt.Errorf("runtime message was an invalid length")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch on the runtime module type
|
|
||||||
module := message[1]
|
|
||||||
switch module {
|
|
||||||
case 'B':
|
|
||||||
return processBrowserMessage(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("unknown message: %s", message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// processBrowserMessage expects messages of the following format:
|
|
||||||
// RB<METHOD><DATA>
|
|
||||||
// O = Open
|
|
||||||
func processBrowserMessage(message string) (*parsedMessage, error) {
|
|
||||||
method := message[2]
|
|
||||||
switch method {
|
|
||||||
case 'O':
|
|
||||||
// Open URL
|
|
||||||
target := message[3:]
|
|
||||||
return &parsedMessage{Topic: "runtime:browser:open", Data: target}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("unknown browser message: %s", message)
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
package message
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// systemMessageParser does what it says on the tin!
|
|
||||||
func systemMessageParser(message string) (*parsedMessage, error) {
|
|
||||||
|
|
||||||
// Sanity check: system messages must be at least 4 bytes
|
|
||||||
if len(message) < 4 {
|
|
||||||
return nil, fmt.Errorf("system message was an invalid length")
|
|
||||||
}
|
|
||||||
|
|
||||||
var responseMessage *parsedMessage
|
|
||||||
|
|
||||||
// Remove 'S'
|
|
||||||
message = message[1:]
|
|
||||||
|
|
||||||
// Switch the event type (with or without data)
|
|
||||||
switch message[0] {
|
|
||||||
// Format of system response messages: S<command><callbackID>|<payload>
|
|
||||||
// DarkModeEnabled
|
|
||||||
case 'D':
|
|
||||||
message = message[1:]
|
|
||||||
idx := strings.IndexByte(message, '|')
|
|
||||||
if idx < 0 {
|
|
||||||
return nil, fmt.Errorf("Invalid system response message format")
|
|
||||||
}
|
|
||||||
callbackID := message[:idx]
|
|
||||||
payloadData := message[idx+1:]
|
|
||||||
|
|
||||||
topic := "systemresponse:" + callbackID
|
|
||||||
responseMessage = &parsedMessage{Topic: topic, Data: payloadData == "T"}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("Invalid message to systemMessageParser()")
|
|
||||||
}
|
|
||||||
|
|
||||||
return responseMessage, nil
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
package message
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// windowMessageParser does what it says on the tin!
|
|
||||||
func windowMessageParser(message string) (*parsedMessage, error) {
|
|
||||||
|
|
||||||
// Sanity check: Window messages must be at least 2 bytes
|
|
||||||
if len(message) < 2 {
|
|
||||||
return nil, fmt.Errorf("window message was an invalid length")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract event type
|
|
||||||
windowEvent := message[1]
|
|
||||||
parsedMessage := &parsedMessage{}
|
|
||||||
|
|
||||||
// Switch the windowEvent type
|
|
||||||
switch windowEvent {
|
|
||||||
|
|
||||||
// Closed window
|
|
||||||
case 'C':
|
|
||||||
parsedMessage.Topic = "quit"
|
|
||||||
parsedMessage.Data = "Window Closed"
|
|
||||||
|
|
||||||
// Center window
|
|
||||||
case 'c':
|
|
||||||
parsedMessage.Topic = "window:center"
|
|
||||||
parsedMessage.Data = ""
|
|
||||||
|
|
||||||
// Hide window
|
|
||||||
case 'H':
|
|
||||||
parsedMessage.Topic = "window:hide"
|
|
||||||
parsedMessage.Data = ""
|
|
||||||
|
|
||||||
// Show window
|
|
||||||
case 'S':
|
|
||||||
parsedMessage.Topic = "window:show"
|
|
||||||
parsedMessage.Data = ""
|
|
||||||
|
|
||||||
// Position window
|
|
||||||
case 'p':
|
|
||||||
parsedMessage.Topic = "window:position:" + message[3:]
|
|
||||||
parsedMessage.Data = ""
|
|
||||||
|
|
||||||
// Set window size
|
|
||||||
case 's':
|
|
||||||
parsedMessage.Topic = "window:size:" + message[3:]
|
|
||||||
parsedMessage.Data = ""
|
|
||||||
|
|
||||||
// Maximise window
|
|
||||||
case 'M':
|
|
||||||
parsedMessage.Topic = "window:maximise"
|
|
||||||
parsedMessage.Data = ""
|
|
||||||
|
|
||||||
// Unmaximise window
|
|
||||||
case 'U':
|
|
||||||
parsedMessage.Topic = "window:unmaximise"
|
|
||||||
parsedMessage.Data = ""
|
|
||||||
|
|
||||||
// Minimise window
|
|
||||||
case 'm':
|
|
||||||
parsedMessage.Topic = "window:minimise"
|
|
||||||
parsedMessage.Data = ""
|
|
||||||
|
|
||||||
// Unminimise window
|
|
||||||
case 'u':
|
|
||||||
parsedMessage.Topic = "window:unminimise"
|
|
||||||
parsedMessage.Data = ""
|
|
||||||
|
|
||||||
// Unknown event type
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown message: %s", message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsedMessage, nil
|
|
||||||
}
|
|
||||||
@@ -1,399 +0,0 @@
|
|||||||
package messagedispatcher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/internal/crypto"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/logger"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Dispatcher translates messages received from the frontend
|
|
||||||
// and publishes them onto the service bus
|
|
||||||
type Dispatcher struct {
|
|
||||||
quitChannel <-chan *servicebus.Message
|
|
||||||
resultChannel <-chan *servicebus.Message
|
|
||||||
eventChannel <-chan *servicebus.Message
|
|
||||||
windowChannel <-chan *servicebus.Message
|
|
||||||
dialogChannel <-chan *servicebus.Message
|
|
||||||
systemChannel <-chan *servicebus.Message
|
|
||||||
running bool
|
|
||||||
|
|
||||||
servicebus *servicebus.ServiceBus
|
|
||||||
logger logger.CustomLogger
|
|
||||||
|
|
||||||
// Clients
|
|
||||||
clients map[string]*DispatchClient
|
|
||||||
lock sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// New dispatcher. Needs a service bus to send to.
|
|
||||||
func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher, error) {
|
|
||||||
// Subscribe to call result messages
|
|
||||||
resultChannel, err := servicebus.Subscribe("call:result")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribe to event messages
|
|
||||||
eventChannel, err := servicebus.Subscribe("event:emit")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribe to quit messages
|
|
||||||
quitChannel, err := servicebus.Subscribe("quit")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribe to window messages
|
|
||||||
windowChannel, err := servicebus.Subscribe("window")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribe to dialog events
|
|
||||||
dialogChannel, err := servicebus.Subscribe("dialog:select")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
systemChannel, err := servicebus.Subscribe("system:")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &Dispatcher{
|
|
||||||
servicebus: servicebus,
|
|
||||||
eventChannel: eventChannel,
|
|
||||||
logger: logger.CustomLogger("Message Dispatcher"),
|
|
||||||
clients: make(map[string]*DispatchClient),
|
|
||||||
resultChannel: resultChannel,
|
|
||||||
quitChannel: quitChannel,
|
|
||||||
windowChannel: windowChannel,
|
|
||||||
dialogChannel: dialogChannel,
|
|
||||||
systemChannel: systemChannel,
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the subsystem
|
|
||||||
func (d *Dispatcher) Start() error {
|
|
||||||
|
|
||||||
d.logger.Trace("Starting")
|
|
||||||
|
|
||||||
d.running = true
|
|
||||||
|
|
||||||
// Spin off a go routine
|
|
||||||
go func() {
|
|
||||||
for d.running {
|
|
||||||
select {
|
|
||||||
case <-d.quitChannel:
|
|
||||||
d.processQuit()
|
|
||||||
d.running = false
|
|
||||||
case resultMessage := <-d.resultChannel:
|
|
||||||
d.processCallResult(resultMessage)
|
|
||||||
case eventMessage := <-d.eventChannel:
|
|
||||||
d.processEvent(eventMessage)
|
|
||||||
case windowMessage := <-d.windowChannel:
|
|
||||||
d.processWindowMessage(windowMessage)
|
|
||||||
case dialogMessage := <-d.dialogChannel:
|
|
||||||
d.processDialogMessage(dialogMessage)
|
|
||||||
case systemMessage := <-d.systemChannel:
|
|
||||||
d.processSystemMessage(systemMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call shutdown
|
|
||||||
d.shutdown()
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dispatcher) processQuit() {
|
|
||||||
d.lock.RLock()
|
|
||||||
defer d.lock.RUnlock()
|
|
||||||
for _, client := range d.clients {
|
|
||||||
client.frontend.Quit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dispatcher) shutdown() {
|
|
||||||
d.logger.Trace("Shutdown")
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterClient will register the given callback with the dispatcher
|
|
||||||
// and return a DispatchClient that the caller can use to send messages
|
|
||||||
func (d *Dispatcher) RegisterClient(client Client) *DispatchClient {
|
|
||||||
d.lock.Lock()
|
|
||||||
defer d.lock.Unlock()
|
|
||||||
|
|
||||||
// Create ID
|
|
||||||
id := d.getUniqueID()
|
|
||||||
d.clients[id] = newDispatchClient(id, client, d.logger, d.servicebus)
|
|
||||||
|
|
||||||
return d.clients[id]
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveClient will remove the registered client
|
|
||||||
func (d *Dispatcher) RemoveClient(dc *DispatchClient) {
|
|
||||||
d.lock.Lock()
|
|
||||||
defer d.lock.Unlock()
|
|
||||||
delete(d.clients, dc.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dispatcher) getUniqueID() string {
|
|
||||||
var uid string
|
|
||||||
for {
|
|
||||||
uid = crypto.RandomID()
|
|
||||||
|
|
||||||
if d.clients[uid] == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return uid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dispatcher) processCallResult(result *servicebus.Message) {
|
|
||||||
target := result.Target()
|
|
||||||
|
|
||||||
if target == "" {
|
|
||||||
// This is an error. Calls are 1:1!
|
|
||||||
d.logger.Fatal("No target for call result: %+v", result)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.lock.RLock()
|
|
||||||
client := d.clients[target]
|
|
||||||
d.lock.RUnlock()
|
|
||||||
if client == nil {
|
|
||||||
// This is fatal - unknown target!
|
|
||||||
d.logger.Fatal("Unknown target for call result: %+v", result)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.logger.Trace("Sending message to client %s: R%s", target, result.Data().(string))
|
|
||||||
client.frontend.CallResult(result.Data().(string))
|
|
||||||
}
|
|
||||||
|
|
||||||
// processSystem
|
|
||||||
func (d *Dispatcher) processSystemMessage(result *servicebus.Message) {
|
|
||||||
|
|
||||||
d.logger.Trace("Got system in message dispatcher: %+v", result)
|
|
||||||
|
|
||||||
splitTopic := strings.Split(result.Topic(), ":")
|
|
||||||
command := splitTopic[1]
|
|
||||||
callbackID := splitTopic[2]
|
|
||||||
switch command {
|
|
||||||
case "isdarkmode":
|
|
||||||
d.lock.RLock()
|
|
||||||
for _, client := range d.clients {
|
|
||||||
client.frontend.DarkModeEnabled(callbackID)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
d.lock.RUnlock()
|
|
||||||
|
|
||||||
default:
|
|
||||||
d.logger.Error("Unknown system command: %s", command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// processEvent will
|
|
||||||
func (d *Dispatcher) processEvent(result *servicebus.Message) {
|
|
||||||
|
|
||||||
d.logger.Trace("Got event in message dispatcher: %+v", result)
|
|
||||||
|
|
||||||
splitTopic := strings.Split(result.Topic(), ":")
|
|
||||||
eventType := splitTopic[1]
|
|
||||||
switch eventType {
|
|
||||||
case "emit":
|
|
||||||
eventFrom := splitTopic[3]
|
|
||||||
if eventFrom == "g" {
|
|
||||||
// This was sent from Go - notify frontend
|
|
||||||
eventData := result.Data().(*message.EventMessage)
|
|
||||||
// Unpack event
|
|
||||||
payload, err := json.Marshal(eventData)
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Error("Unable to marshal eventData: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
d.lock.RLock()
|
|
||||||
for _, client := range d.clients {
|
|
||||||
client.frontend.NotifyEvent(string(payload))
|
|
||||||
}
|
|
||||||
d.lock.RUnlock()
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
d.logger.Error("Unknown event type: %s", eventType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// processWindowMessage processes messages intended for the window
|
|
||||||
func (d *Dispatcher) processWindowMessage(result *servicebus.Message) {
|
|
||||||
d.lock.RLock()
|
|
||||||
defer d.lock.RUnlock()
|
|
||||||
splitTopic := strings.Split(result.Topic(), ":")
|
|
||||||
command := splitTopic[1]
|
|
||||||
switch command {
|
|
||||||
case "settitle":
|
|
||||||
title, ok := result.Data().(string)
|
|
||||||
if !ok {
|
|
||||||
d.logger.Error("Invalid title for 'window:settitle' : %#v", result.Data())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Notify clients
|
|
||||||
for _, client := range d.clients {
|
|
||||||
client.frontend.WindowSetTitle(title)
|
|
||||||
}
|
|
||||||
case "fullscreen":
|
|
||||||
// Notify clients
|
|
||||||
for _, client := range d.clients {
|
|
||||||
client.frontend.WindowFullscreen()
|
|
||||||
}
|
|
||||||
case "unfullscreen":
|
|
||||||
// Notify clients
|
|
||||||
for _, client := range d.clients {
|
|
||||||
client.frontend.WindowUnFullscreen()
|
|
||||||
}
|
|
||||||
case "setcolour":
|
|
||||||
colour, ok := result.Data().(int)
|
|
||||||
if !ok {
|
|
||||||
d.logger.Error("Invalid colour for 'window:setcolour' : %#v", result.Data())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Notify clients
|
|
||||||
for _, client := range d.clients {
|
|
||||||
client.frontend.WindowSetColour(colour)
|
|
||||||
}
|
|
||||||
case "show":
|
|
||||||
// Notify clients
|
|
||||||
for _, client := range d.clients {
|
|
||||||
client.frontend.WindowShow()
|
|
||||||
}
|
|
||||||
case "hide":
|
|
||||||
// Notify clients
|
|
||||||
for _, client := range d.clients {
|
|
||||||
client.frontend.WindowHide()
|
|
||||||
}
|
|
||||||
case "center":
|
|
||||||
// Notify clients
|
|
||||||
for _, client := range d.clients {
|
|
||||||
client.frontend.WindowCenter()
|
|
||||||
}
|
|
||||||
case "maximise":
|
|
||||||
// Notify clients
|
|
||||||
for _, client := range d.clients {
|
|
||||||
client.frontend.WindowMaximise()
|
|
||||||
}
|
|
||||||
case "unmaximise":
|
|
||||||
// Notify clients
|
|
||||||
for _, client := range d.clients {
|
|
||||||
client.frontend.WindowUnmaximise()
|
|
||||||
}
|
|
||||||
case "minimise":
|
|
||||||
// Notify clients
|
|
||||||
for _, client := range d.clients {
|
|
||||||
client.frontend.WindowMinimise()
|
|
||||||
}
|
|
||||||
case "unminimise":
|
|
||||||
// Notify clients
|
|
||||||
for _, client := range d.clients {
|
|
||||||
client.frontend.WindowUnminimise()
|
|
||||||
}
|
|
||||||
case "position":
|
|
||||||
// We need 2 arguments
|
|
||||||
if len(splitTopic) != 4 {
|
|
||||||
d.logger.Error("Invalid number of parameters for 'window:position' : %#v", result.Data())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
x, err1 := strconv.Atoi(splitTopic[2])
|
|
||||||
y, err2 := strconv.Atoi(splitTopic[3])
|
|
||||||
if err1 != nil || err2 != nil {
|
|
||||||
d.logger.Error("Invalid integer parameters for 'window:position' : %#v", result.Data())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Notify clients
|
|
||||||
for _, client := range d.clients {
|
|
||||||
client.frontend.WindowPosition(x, y)
|
|
||||||
}
|
|
||||||
case "size":
|
|
||||||
// We need 2 arguments
|
|
||||||
if len(splitTopic) != 4 {
|
|
||||||
d.logger.Error("Invalid number of parameters for 'window:size' : %#v", result.Data())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w, err1 := strconv.Atoi(splitTopic[2])
|
|
||||||
h, err2 := strconv.Atoi(splitTopic[3])
|
|
||||||
if err1 != nil || err2 != nil {
|
|
||||||
d.logger.Error("Invalid integer parameters for 'window:size' : %#v", result.Data())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Notifh clients
|
|
||||||
for _, client := range d.clients {
|
|
||||||
client.frontend.WindowSize(w, h)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
d.logger.Error("Unknown window command: %s", command)
|
|
||||||
}
|
|
||||||
d.logger.Trace("Got window in message dispatcher: %+v", result)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// processDialogMessage processes dialog messages
|
|
||||||
func (d *Dispatcher) processDialogMessage(result *servicebus.Message) {
|
|
||||||
splitTopic := strings.Split(result.Topic(), ":")
|
|
||||||
if len(splitTopic) < 4 {
|
|
||||||
d.logger.Error("Invalid dialog message : %#v", result.Data())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
command := splitTopic[1]
|
|
||||||
switch command {
|
|
||||||
case "select":
|
|
||||||
dialogType := splitTopic[2]
|
|
||||||
switch dialogType {
|
|
||||||
case "open":
|
|
||||||
dialogOptions, ok := result.Data().(*options.OpenDialog)
|
|
||||||
if !ok {
|
|
||||||
d.logger.Error("Invalid data for 'dialog:select:open' : %#v", result.Data())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// This is hardcoded in the sender too
|
|
||||||
callbackID := splitTopic[3]
|
|
||||||
|
|
||||||
// TODO: Work out what we mean in a multi window environment...
|
|
||||||
// For now we will just pick the first one
|
|
||||||
for _, client := range d.clients {
|
|
||||||
client.frontend.OpenDialog(dialogOptions, callbackID)
|
|
||||||
}
|
|
||||||
case "save":
|
|
||||||
dialogOptions, ok := result.Data().(*options.SaveDialog)
|
|
||||||
if !ok {
|
|
||||||
d.logger.Error("Invalid data for 'dialog:select:save' : %#v", result.Data())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// This is hardcoded in the sender too
|
|
||||||
callbackID := splitTopic[3]
|
|
||||||
|
|
||||||
// TODO: Work out what we mean in a multi window environment...
|
|
||||||
// For now we will just pick the first one
|
|
||||||
for _, client := range d.clients {
|
|
||||||
client.frontend.SaveDialog(dialogOptions, callbackID)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
d.logger.Error("Unknown dialog type: %s", dialogType)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
d.logger.Error("Unknown dialog command: %s", command)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# Parse
|
|
||||||
|
|
||||||
Parse will attempt to parse your Wails project to perform a number of tasks:
|
|
||||||
* Verify that you have bound struct pointers
|
|
||||||
* Generate JS helper files/docs
|
|
||||||
|
|
||||||
It currently checks bindings correctly if your code binds using one of the following methods:
|
|
||||||
* Literal Binding: `app.Bind(&MyStruct{})`
|
|
||||||
* Variable Binding: `app.Bind(m)` - m can be `m := &MyStruct{}` or `m := newMyStruct()`
|
|
||||||
* Function Binding: `app.Bind(newMyStruct())`
|
|
||||||
@@ -1,440 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/leaanthony/slicer"
|
|
||||||
"golang.org/x/tools/go/packages"
|
|
||||||
)
|
|
||||||
|
|
||||||
var internalMethods = slicer.String([]string{"WailsInit", "Wails Shutdown"})
|
|
||||||
|
|
||||||
var structCache = make(map[string]*ParsedStruct)
|
|
||||||
var boundStructs = make(map[string]*ParsedStruct)
|
|
||||||
var boundMethods = []string{}
|
|
||||||
var boundStructPointerLiterals = []string{}
|
|
||||||
var boundStructLiterals = slicer.StringSlicer{}
|
|
||||||
var boundVariables = slicer.StringSlicer{}
|
|
||||||
var app = ""
|
|
||||||
var structPointerFunctionDecls = make(map[string]string)
|
|
||||||
var structFunctionDecls = make(map[string]string)
|
|
||||||
var variableStructDecls = make(map[string]string)
|
|
||||||
var variableFunctionDecls = make(map[string]string)
|
|
||||||
|
|
||||||
type Parameter struct {
|
|
||||||
Name string
|
|
||||||
Type string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ParsedMethod struct {
|
|
||||||
Struct string
|
|
||||||
Name string
|
|
||||||
Comments []string
|
|
||||||
Inputs []*Parameter
|
|
||||||
Returns []*Parameter
|
|
||||||
}
|
|
||||||
|
|
||||||
type ParsedStruct struct {
|
|
||||||
Name string
|
|
||||||
Methods []*ParsedMethod
|
|
||||||
}
|
|
||||||
|
|
||||||
type BoundStructs []*ParsedStruct
|
|
||||||
|
|
||||||
func ParseProject(projectPath string) (BoundStructs, error) {
|
|
||||||
|
|
||||||
cfg := &packages.Config{Mode: packages.NeedFiles | packages.NeedSyntax | packages.NeedTypesInfo}
|
|
||||||
pkgs, err := packages.Load(cfg, projectPath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "load: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if packages.PrintErrors(pkgs) > 0 {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate the packages
|
|
||||||
for _, pkg := range pkgs {
|
|
||||||
|
|
||||||
// Iterate the files
|
|
||||||
for _, file := range pkg.Syntax {
|
|
||||||
|
|
||||||
var wailsPkgVar = ""
|
|
||||||
|
|
||||||
ast.Inspect(file, func(n ast.Node) bool {
|
|
||||||
switch x := n.(type) {
|
|
||||||
// Parse import declarations
|
|
||||||
case *ast.ImportSpec:
|
|
||||||
// Determine what wails has been imported as
|
|
||||||
if x.Path.Value == `"github.com/wailsapp/wails/v2"` {
|
|
||||||
wailsPkgVar = x.Name.Name
|
|
||||||
}
|
|
||||||
// Parse calls. We are looking for app.Bind() calls
|
|
||||||
case *ast.CallExpr:
|
|
||||||
f, ok := x.Fun.(*ast.SelectorExpr)
|
|
||||||
if ok {
|
|
||||||
n, ok := f.X.(*ast.Ident)
|
|
||||||
if ok {
|
|
||||||
//Check this is the Bind() call associated with the app variable
|
|
||||||
if n.Name == app && f.Sel.Name == "Bind" {
|
|
||||||
if len(x.Args) == 1 {
|
|
||||||
ce, ok := x.Args[0].(*ast.CallExpr)
|
|
||||||
if ok {
|
|
||||||
n, ok := ce.Fun.(*ast.Ident)
|
|
||||||
if ok {
|
|
||||||
// We found a bind method using a function call
|
|
||||||
// EG: app.Bind( newMyStruct() )
|
|
||||||
boundMethods = append(boundMethods, n.Name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We also want to check for Bind( &MyStruct{} )
|
|
||||||
ue, ok := x.Args[0].(*ast.UnaryExpr)
|
|
||||||
if ok {
|
|
||||||
if ue.Op.String() == "&" {
|
|
||||||
cl, ok := ue.X.(*ast.CompositeLit)
|
|
||||||
if ok {
|
|
||||||
t, ok := cl.Type.(*ast.Ident)
|
|
||||||
if ok {
|
|
||||||
// We have found Bind( &MyStruct{} )
|
|
||||||
boundStructPointerLiterals = append(boundStructPointerLiterals, t.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Let's check when the user binds a struct,
|
|
||||||
// rather than a struct pointer: Bind( MyStruct{} )
|
|
||||||
// We do this to provide better hints to the user
|
|
||||||
cl, ok := x.Args[0].(*ast.CompositeLit)
|
|
||||||
if ok {
|
|
||||||
t, ok := cl.Type.(*ast.Ident)
|
|
||||||
if ok {
|
|
||||||
boundStructLiterals.Add(t.Name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Also check for when we bind a variable
|
|
||||||
// myVariable := &MyStruct{}
|
|
||||||
// app.Bind( myVariable )
|
|
||||||
i, ok := x.Args[0].(*ast.Ident)
|
|
||||||
if ok {
|
|
||||||
boundVariables.Add(i.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We scan assignments for a number of reasons:
|
|
||||||
// * Determine the variable containing the main application
|
|
||||||
// * Determine the type of variables that get used in Bind()
|
|
||||||
// * Determine the type of variables that get created with var := &MyStruct{}
|
|
||||||
case *ast.AssignStmt:
|
|
||||||
for _, rhs := range x.Rhs {
|
|
||||||
ce, ok := rhs.(*ast.CallExpr)
|
|
||||||
if ok {
|
|
||||||
se, ok := ce.Fun.(*ast.SelectorExpr)
|
|
||||||
if ok {
|
|
||||||
i, ok := se.X.(*ast.Ident)
|
|
||||||
if ok {
|
|
||||||
// Have we found the wails package name?
|
|
||||||
if i.Name == wailsPkgVar {
|
|
||||||
// Check we are calling a function to create the app
|
|
||||||
if se.Sel.Name == "CreateApp" || se.Sel.Name == "CreateAppWithOptions" {
|
|
||||||
if len(x.Lhs) == 1 {
|
|
||||||
i, ok := x.Lhs[0].(*ast.Ident)
|
|
||||||
if ok {
|
|
||||||
// Found the app variable name
|
|
||||||
app = i.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Check for function assignment
|
|
||||||
// a := newMyStruct()
|
|
||||||
fe, ok := ce.Fun.(*ast.Ident)
|
|
||||||
if ok {
|
|
||||||
if len(x.Lhs) == 1 {
|
|
||||||
i, ok := x.Lhs[0].(*ast.Ident)
|
|
||||||
if ok {
|
|
||||||
// Store the variable -> Function mapping
|
|
||||||
// so we can later resolve the type
|
|
||||||
variableFunctionDecls[i.Name] = fe.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Check for literal assignment of struct
|
|
||||||
// EG: myvar := MyStruct{}
|
|
||||||
ue, ok := rhs.(*ast.UnaryExpr)
|
|
||||||
if ok {
|
|
||||||
cl, ok := ue.X.(*ast.CompositeLit)
|
|
||||||
if ok {
|
|
||||||
t, ok := cl.Type.(*ast.Ident)
|
|
||||||
if ok {
|
|
||||||
if len(x.Lhs) == 1 {
|
|
||||||
i, ok := x.Lhs[0].(*ast.Ident)
|
|
||||||
if ok {
|
|
||||||
variableStructDecls[i.Name] = t.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// We scan for functions to build up a list of function names
|
|
||||||
// for a number of reasons:
|
|
||||||
// * Determine which functions are struct methods that are bound
|
|
||||||
// * Determine
|
|
||||||
case *ast.FuncDecl:
|
|
||||||
if x.Recv != nil {
|
|
||||||
// This is a struct method
|
|
||||||
for _, field := range x.Recv.List {
|
|
||||||
se, ok := field.Type.(*ast.StarExpr)
|
|
||||||
if ok {
|
|
||||||
// This is a struct pointer method
|
|
||||||
i, ok := se.X.(*ast.Ident)
|
|
||||||
if ok {
|
|
||||||
// We want to ignore Internal functions
|
|
||||||
if internalMethods.Contains(x.Name.Name) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// If we haven't already found this struct,
|
|
||||||
// Create a placeholder in the cache
|
|
||||||
parsedStruct := structCache[i.Name]
|
|
||||||
if parsedStruct == nil {
|
|
||||||
structCache[i.Name] = &ParsedStruct{
|
|
||||||
Name: i.Name,
|
|
||||||
}
|
|
||||||
parsedStruct = structCache[i.Name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this method is Public
|
|
||||||
if string(x.Name.Name[0]) == strings.ToUpper((string(x.Name.Name[0]))) {
|
|
||||||
structMethod := &ParsedMethod{
|
|
||||||
Struct: i.Name,
|
|
||||||
Name: x.Name.Name,
|
|
||||||
}
|
|
||||||
// Check if the method has comments.
|
|
||||||
// If so, save it with the parsed method
|
|
||||||
if x.Doc != nil {
|
|
||||||
for _, comment := range x.Doc.List {
|
|
||||||
stringComment := comment.Text
|
|
||||||
if strings.HasPrefix(stringComment, "//") {
|
|
||||||
stringComment = stringComment[2:]
|
|
||||||
}
|
|
||||||
structMethod.Comments = append(structMethod.Comments, strings.TrimSpace(stringComment))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the input parameters
|
|
||||||
for _, inputField := range x.Type.Params.List {
|
|
||||||
t, ok := inputField.Type.(*ast.Ident)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, name := range inputField.Names {
|
|
||||||
structMethod.Inputs = append(structMethod.Inputs, &Parameter{Name: name.Name, Type: t.Name})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the output parameters
|
|
||||||
for _, outputField := range x.Type.Results.List {
|
|
||||||
t, ok := outputField.Type.(*ast.Ident)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(outputField.Names) == 0 {
|
|
||||||
structMethod.Returns = append(structMethod.Returns, &Parameter{Type: t.Name})
|
|
||||||
} else {
|
|
||||||
for _, name := range outputField.Names {
|
|
||||||
structMethod.Returns = append(structMethod.Returns, &Parameter{Name: name.Name, Type: t.Name})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append this method to the parsed struct
|
|
||||||
parsedStruct.Methods = append(parsedStruct.Methods, structMethod)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// This is a function declaration
|
|
||||||
// We care about its name and return type
|
|
||||||
// This will allow us to resolve types later
|
|
||||||
functionName := x.Name.Name
|
|
||||||
|
|
||||||
// Look for one that returns a single value
|
|
||||||
if x.Type != nil && x.Type.Results != nil && x.Type.Results.List != nil {
|
|
||||||
if len(x.Type.Results.List) == 1 {
|
|
||||||
// Check for *struct
|
|
||||||
t, ok := x.Type.Results.List[0].Type.(*ast.StarExpr)
|
|
||||||
if ok {
|
|
||||||
s, ok := t.X.(*ast.Ident)
|
|
||||||
if ok {
|
|
||||||
// println("*** Function", functionName, "found which returns: *"+s.Name)
|
|
||||||
structPointerFunctionDecls[functionName] = s.Name
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Check for functions that return a struct
|
|
||||||
// This is to help us provide hints if the user binds a struct
|
|
||||||
t, ok := x.Type.Results.List[0].Type.(*ast.Ident)
|
|
||||||
if ok {
|
|
||||||
// println("*** Function", functionName, "found which returns: "+t.Name)
|
|
||||||
structFunctionDecls[functionName] = t.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
// spew.Dump(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/***** Update bound structs ******/
|
|
||||||
|
|
||||||
// Resolve bound Methods
|
|
||||||
for _, method := range boundMethods {
|
|
||||||
s, ok := structPointerFunctionDecls[method]
|
|
||||||
if !ok {
|
|
||||||
s, ok = structFunctionDecls[method]
|
|
||||||
if !ok {
|
|
||||||
println("Fatal: Bind statement using", method, "but cannot find", method, "declaration")
|
|
||||||
} else {
|
|
||||||
println("Fatal: Cannot bind struct using method `" + method + "` because it returns a struct (" + s + "). Return a pointer to " + s + " instead.")
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
structDefinition := structCache[s]
|
|
||||||
if structDefinition == nil {
|
|
||||||
println("Fatal: Bind statement using `"+method+"` but cannot find struct", s, "definition")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
boundStructs[s] = structDefinition
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve bound vars
|
|
||||||
for _, structLiteral := range boundStructPointerLiterals {
|
|
||||||
s, ok := structCache[structLiteral]
|
|
||||||
if !ok {
|
|
||||||
println("Fatal: Bind statement using", structLiteral, "but cannot find", structLiteral, "declaration")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
boundStructs[structLiteral] = s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve bound variables
|
|
||||||
boundVariables.Each(func(variable string) {
|
|
||||||
v, ok := variableStructDecls[variable]
|
|
||||||
if !ok {
|
|
||||||
method, ok := variableFunctionDecls[variable]
|
|
||||||
if !ok {
|
|
||||||
println("Fatal: Bind statement using variable `" + variable + "` which does not resolve to a struct pointer")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve function name
|
|
||||||
v, ok = structPointerFunctionDecls[method]
|
|
||||||
if !ok {
|
|
||||||
v, ok = structFunctionDecls[method]
|
|
||||||
if !ok {
|
|
||||||
println("Fatal: Bind statement using", method, "but cannot find", method, "declaration")
|
|
||||||
} else {
|
|
||||||
println("Fatal: Cannot bind variable `" + variable + "` because it resolves to a struct (" + v + "). Return a pointer to " + v + " instead.")
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
s, ok := structCache[v]
|
|
||||||
if !ok {
|
|
||||||
println("Fatal: Bind statement using variable `" + variable + "` which resolves to a `" + v + "` but cannot find its declaration")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
boundStructs[v] = s
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
// Check for struct literals
|
|
||||||
boundStructLiterals.Each(func(structName string) {
|
|
||||||
println("Fatal: Cannot bind struct using struct literal `" + structName + "{}`. Create a pointer to " + structName + " instead.")
|
|
||||||
os.Exit(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Check for bound variables
|
|
||||||
// boundVariables.Each(func(varName string) {
|
|
||||||
// println("Fatal: Cannot bind struct using struct literal `" + structName + "{}`. Create a pointer to " + structName + " instead.")
|
|
||||||
// })
|
|
||||||
|
|
||||||
// spew.Dump(boundStructs)
|
|
||||||
// os.Exit(0)
|
|
||||||
|
|
||||||
// }
|
|
||||||
// Inspect the AST and print all identifiers and literals.
|
|
||||||
|
|
||||||
println("export {")
|
|
||||||
|
|
||||||
noOfStructs := len(boundStructs)
|
|
||||||
structCount := 0
|
|
||||||
for _, s := range boundStructs {
|
|
||||||
structCount++
|
|
||||||
println()
|
|
||||||
println(" " + s.Name + ": {")
|
|
||||||
println()
|
|
||||||
noOfMethods := len(s.Methods)
|
|
||||||
for methodCount, m := range s.Methods {
|
|
||||||
println(" /****************")
|
|
||||||
for _, comment := range m.Comments {
|
|
||||||
println(" *", comment)
|
|
||||||
}
|
|
||||||
if len(m.Comments) > 0 {
|
|
||||||
println(" *")
|
|
||||||
}
|
|
||||||
inputNames := ""
|
|
||||||
for _, input := range m.Inputs {
|
|
||||||
println(" * @param {"+input.Type+"}", input.Name)
|
|
||||||
inputNames += input.Name + ", "
|
|
||||||
}
|
|
||||||
print(" * @return Promise<")
|
|
||||||
for _, output := range m.Returns {
|
|
||||||
print(output.Type + "|")
|
|
||||||
}
|
|
||||||
println("Error>")
|
|
||||||
println(" *")
|
|
||||||
println(" ***/")
|
|
||||||
if len(inputNames) > 2 {
|
|
||||||
inputNames = inputNames[:len(inputNames)-2]
|
|
||||||
}
|
|
||||||
println(" ", m.Name+": function("+inputNames+") {")
|
|
||||||
println(" return window.backend." + s.Name + "." + m.Name + "(" + inputNames + ");")
|
|
||||||
print(" }")
|
|
||||||
if methodCount < noOfMethods-1 {
|
|
||||||
print(",")
|
|
||||||
}
|
|
||||||
println()
|
|
||||||
println()
|
|
||||||
}
|
|
||||||
print(" }")
|
|
||||||
if structCount < noOfStructs-1 {
|
|
||||||
print(",")
|
|
||||||
}
|
|
||||||
println()
|
|
||||||
}
|
|
||||||
println()
|
|
||||||
println("}")
|
|
||||||
println()
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package process
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Process defines a process that can be executed
|
|
||||||
type Process struct {
|
|
||||||
logger *clilogger.CLILogger
|
|
||||||
cmd *exec.Cmd
|
|
||||||
exitChannel chan bool
|
|
||||||
Running bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewProcess creates a new process struct
|
|
||||||
func NewProcess(logger *clilogger.CLILogger, cmd string, args ...string) *Process {
|
|
||||||
return &Process{
|
|
||||||
logger: logger,
|
|
||||||
cmd: exec.Command(cmd, args...),
|
|
||||||
exitChannel: make(chan bool, 1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the process
|
|
||||||
func (p *Process) Start() error {
|
|
||||||
|
|
||||||
err := p.cmd.Start()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
p.Running = true
|
|
||||||
|
|
||||||
go func(cmd *exec.Cmd, running *bool, logger *clilogger.CLILogger, exitChannel chan bool) {
|
|
||||||
logger.Println("Starting process (PID: %d)", cmd.Process.Pid)
|
|
||||||
cmd.Wait()
|
|
||||||
logger.Println("Exiting process (PID: %d)", cmd.Process.Pid)
|
|
||||||
*running = false
|
|
||||||
exitChannel <- true
|
|
||||||
}(p.cmd, &p.Running, p.logger, p.exitChannel)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kill the process
|
|
||||||
func (p *Process) Kill() error {
|
|
||||||
if !p.Running {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err := p.cmd.Process.Kill()
|
|
||||||
|
|
||||||
// Wait for command to exit properly
|
|
||||||
<-p.exitChannel
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// PID returns the process PID
|
|
||||||
func (p *Process) PID() int {
|
|
||||||
return p.cmd.Process.Pid
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
package project
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Project holds the data related to a Wails project
|
|
||||||
type Project struct {
|
|
||||||
|
|
||||||
/*** Application Data ***/
|
|
||||||
Name string `json:"name"`
|
|
||||||
|
|
||||||
// Application HTML, JS and CSS filenames
|
|
||||||
HTML string `json:"html"`
|
|
||||||
JS string `json:"js"`
|
|
||||||
CSS string `json:"css"`
|
|
||||||
BuildCommand string `json:"frontend:build"`
|
|
||||||
InstallCommand string `json:"frontend:install"`
|
|
||||||
/*** Internal Data ***/
|
|
||||||
|
|
||||||
// The path to the project directory
|
|
||||||
Path string
|
|
||||||
|
|
||||||
// The output filename
|
|
||||||
OutputFilename string `json:"outputfilename"`
|
|
||||||
|
|
||||||
// The type of application. EG: Desktop, Server, etc
|
|
||||||
OutputType string
|
|
||||||
|
|
||||||
// The platform to target
|
|
||||||
Platform string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the project from the current working directory
|
|
||||||
func Load(projectPath string) (*Project, error) {
|
|
||||||
|
|
||||||
// Attempt to load project.json
|
|
||||||
projectFile := filepath.Join(projectPath, "wails.json")
|
|
||||||
rawBytes, err := ioutil.ReadFile(projectFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal JSON
|
|
||||||
var result Project
|
|
||||||
err = json.Unmarshal(rawBytes, &result)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fix up our project paths
|
|
||||||
result.Path = filepath.ToSlash(projectPath) + "/"
|
|
||||||
result.HTML = filepath.Join(projectPath, result.HTML)
|
|
||||||
result.JS = filepath.Join(projectPath, result.JS)
|
|
||||||
result.CSS = filepath.Join(projectPath, result.CSS)
|
|
||||||
|
|
||||||
// Create default name if not given
|
|
||||||
if result.Name == "" {
|
|
||||||
result.Name = "wailsapp"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fix up OutputFilename
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "windows":
|
|
||||||
if !strings.HasSuffix(result.OutputFilename, ".exe") {
|
|
||||||
result.OutputFilename += ".exe"
|
|
||||||
}
|
|
||||||
case "darwin", "linux":
|
|
||||||
if strings.HasSuffix(result.OutputFilename, ".exe") {
|
|
||||||
result.OutputFilename = strings.TrimSuffix(result.OutputFilename, ".exe")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return our project data
|
|
||||||
return &result, nil
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,36 +0,0 @@
|
|||||||
package runtime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Browser defines all browser related operations
|
|
||||||
type Browser interface {
|
|
||||||
Open(target string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type browser struct{}
|
|
||||||
|
|
||||||
// Open a url / file using the system default application
|
|
||||||
// Credit: https://gist.github.com/hyg/9c4afcd91fe24316cbf0
|
|
||||||
func (b *browser) Open(target string) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "linux":
|
|
||||||
err = exec.Command("xdg-open", target).Start()
|
|
||||||
case "windows":
|
|
||||||
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", target).Start()
|
|
||||||
case "darwin":
|
|
||||||
err = exec.Command("open", target).Start()
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("unsupported platform")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBrowser() *browser {
|
|
||||||
return &browser{}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package runtime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/internal/logger"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBrowserOpen(t *testing.T) {
|
|
||||||
mylogger := logger.New(os.Stdout)
|
|
||||||
myServiceBus := servicebus.New(mylogger)
|
|
||||||
myRuntime := New(myServiceBus)
|
|
||||||
myRuntime.Browser.Open("http://www.google.com")
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
package runtime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/internal/crypto"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/options"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Dialog defines all Dialog related operations
|
|
||||||
type Dialog interface {
|
|
||||||
Open(dialogOptions *options.OpenDialog) []string
|
|
||||||
Save(dialogOptions *options.SaveDialog) string
|
|
||||||
}
|
|
||||||
|
|
||||||
// dialog exposes the Dialog interface
|
|
||||||
type dialog struct {
|
|
||||||
bus *servicebus.ServiceBus
|
|
||||||
}
|
|
||||||
|
|
||||||
// newDialogs creates a new Dialogs struct
|
|
||||||
func newDialog(bus *servicebus.ServiceBus) Dialog {
|
|
||||||
return &dialog{
|
|
||||||
bus: bus,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// processTitleAndFilter return the title and filter from the given params.
|
|
||||||
// title is the first string, filter is the second
|
|
||||||
func (r *dialog) processTitleAndFilter(params ...string) (string, string) {
|
|
||||||
|
|
||||||
var title, filter string
|
|
||||||
|
|
||||||
if len(params) > 0 {
|
|
||||||
title = params[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(params) > 1 {
|
|
||||||
filter = params[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return title, filter
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open prompts the user to select a file
|
|
||||||
func (r *dialog) Open(dialogOptions *options.OpenDialog) []string {
|
|
||||||
|
|
||||||
// Create unique dialog callback
|
|
||||||
uniqueCallback := crypto.RandomID()
|
|
||||||
|
|
||||||
// Subscribe to the respose channel
|
|
||||||
responseTopic := "dialog:openselected:" + uniqueCallback
|
|
||||||
dialogResponseChannel, err := r.bus.Subscribe(responseTopic)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
message := "dialog:select:open:" + uniqueCallback
|
|
||||||
r.bus.Publish(message, dialogOptions)
|
|
||||||
|
|
||||||
// Wait for result
|
|
||||||
var result *servicebus.Message = <-dialogResponseChannel
|
|
||||||
|
|
||||||
// Delete subscription to response topic
|
|
||||||
r.bus.UnSubscribe(responseTopic)
|
|
||||||
|
|
||||||
return result.Data().([]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save prompts the user to select a file
|
|
||||||
func (r *dialog) Save(dialogOptions *options.SaveDialog) string {
|
|
||||||
|
|
||||||
// Create unique dialog callback
|
|
||||||
uniqueCallback := crypto.RandomID()
|
|
||||||
|
|
||||||
// Subscribe to the respose channel
|
|
||||||
responseTopic := "dialog:saveselected:" + uniqueCallback
|
|
||||||
dialogResponseChannel, err := r.bus.Subscribe(responseTopic)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
message := "dialog:select:save:" + uniqueCallback
|
|
||||||
r.bus.Publish(message, dialogOptions)
|
|
||||||
|
|
||||||
// Wait for result
|
|
||||||
var result *servicebus.Message = <-dialogResponseChannel
|
|
||||||
|
|
||||||
// Delete subscription to response topic
|
|
||||||
r.bus.UnSubscribe(responseTopic)
|
|
||||||
|
|
||||||
return result.Data().(string)
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package runtime
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
|
||||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Events defines all events related operations
|
|
||||||
type Events interface {
|
|
||||||
On(eventName string, callback func(optionalData ...interface{}))
|
|
||||||
Once(eventName string, callback func(optionalData ...interface{}))
|
|
||||||
OnMultiple(eventName string, callback func(optionalData ...interface{}), maxCallbacks int)
|
|
||||||
Emit(eventName string, optionalData ...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// event exposes the events interface
|
|
||||||
type event struct {
|
|
||||||
bus *servicebus.ServiceBus
|
|
||||||
}
|
|
||||||
|
|
||||||
// newEvents creates a new Events struct
|
|
||||||
func newEvents(bus *servicebus.ServiceBus) Events {
|
|
||||||
return &event{
|
|
||||||
bus: bus,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// On registers a listener for a particular event
|
|
||||||
func (r *event) On(eventName string, callback func(optionalData ...interface{})) {
|
|
||||||
eventMessage := &message.OnEventMessage{
|
|
||||||
Name: eventName,
|
|
||||||
Callback: callback,
|
|
||||||
Counter: -1,
|
|
||||||
}
|
|
||||||
r.bus.Publish("event:on", eventMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once registers a listener for a particular event. After the first callback, the
|
|
||||||
// listener is deleted.
|
|
||||||
func (r *event) Once(eventName string, callback func(optionalData ...interface{})) {
|
|
||||||
eventMessage := &message.OnEventMessage{
|
|
||||||
Name: eventName,
|
|
||||||
Callback: callback,
|
|
||||||
Counter: 1,
|
|
||||||
}
|
|
||||||
r.bus.Publish("event:on", eventMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnMultiple registers a listener for a particular event, for a given maximum amount of callbacks.
|
|
||||||
// Once the callback has been run `maxCallbacks` times, the listener is deleted.
|
|
||||||
func (r *event) OnMultiple(eventName string, callback func(optionalData ...interface{}), maxCallbacks int) {
|
|
||||||
eventMessage := &message.OnEventMessage{
|
|
||||||
Name: eventName,
|
|
||||||
Callback: callback,
|
|
||||||
Counter: maxCallbacks,
|
|
||||||
}
|
|
||||||
r.bus.Publish("event:on", eventMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emit pass through
|
|
||||||
func (r *event) Emit(eventName string, optionalData ...interface{}) {
|
|
||||||
eventMessage := &message.EventMessage{
|
|
||||||
Name: eventName,
|
|
||||||
Data: optionalData,
|
|
||||||
}
|
|
||||||
|
|
||||||
r.bus.Publish("event:emit:from:g", eventMessage)
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es6": true,
|
|
||||||
"amd": true,
|
|
||||||
"node": true,
|
|
||||||
},
|
|
||||||
"extends": "eslint:recommended",
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 2016,
|
|
||||||
"sourceType": "module",
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"linebreak-style": 0,
|
|
||||||
"quotes": [
|
|
||||||
"error",
|
|
||||||
"single"
|
|
||||||
],
|
|
||||||
"semi": [
|
|
||||||
"error",
|
|
||||||
"always"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
module.exports = function (api) {
|
|
||||||
api.cache(true);
|
|
||||||
|
|
||||||
const presets = [
|
|
||||||
[
|
|
||||||
"@babel/preset-env",
|
|
||||||
{
|
|
||||||
"useBuiltIns": "entry",
|
|
||||||
"corejs": {
|
|
||||||
"version": 3,
|
|
||||||
"proposals": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
return {
|
|
||||||
presets,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
/*
|
|
||||||
_ __ _ __
|
|
||||||
| | / /___ _(_) /____
|
|
||||||
| | /| / / __ `/ / / ___/
|
|
||||||
| |/ |/ / /_/ / / (__ )
|
|
||||||
|__/|__/\__,_/_/_/____/
|
|
||||||
The lightweight framework for web-like apps
|
|
||||||
(c) Lea Anthony 2019-present
|
|
||||||
*/
|
|
||||||
/* jshint esversion: 6 */
|
|
||||||
|
|
||||||
import { Call } from './calls';
|
|
||||||
|
|
||||||
window.backend = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
|
|
||||||
Map of this format:
|
|
||||||
|
|
||||||
{
|
|
||||||
packageName: {
|
|
||||||
structName: {
|
|
||||||
methodName: {
|
|
||||||
name: "",
|
|
||||||
inputs: [
|
|
||||||
{
|
|
||||||
type: <type>
|
|
||||||
}
|
|
||||||
],
|
|
||||||
outputs: [
|
|
||||||
{
|
|
||||||
type: <type>
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function SetBindings(bindingsMap) {
|
|
||||||
try {
|
|
||||||
bindingsMap = JSON.parse(bindingsMap);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialise the backend map
|
|
||||||
window.backend = window.backend || {};
|
|
||||||
|
|
||||||
// Iterate package names
|
|
||||||
Object.keys(bindingsMap).forEach((packageName) => {
|
|
||||||
|
|
||||||
// Create inner map if it doesn't exist
|
|
||||||
window.backend[packageName] = window.backend[packageName] || {};
|
|
||||||
|
|
||||||
// Iterate struct names
|
|
||||||
Object.keys(bindingsMap[packageName]).forEach((structName) => {
|
|
||||||
|
|
||||||
// Create inner map if it doesn't exist
|
|
||||||
window.backend[packageName][structName] = window.backend[packageName][structName] || {};
|
|
||||||
|
|
||||||
Object.keys(bindingsMap[packageName][structName]).forEach((methodName) => {
|
|
||||||
|
|
||||||
window.backend[packageName][structName][methodName] = function () {
|
|
||||||
|
|
||||||
// No timeout by default
|
|
||||||
var timeout = 0;
|
|
||||||
|
|
||||||
// Actual function
|
|
||||||
function dynamic() {
|
|
||||||
var args = [].slice.call(arguments);
|
|
||||||
return Call([packageName, structName, methodName].join('.'), 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;
|
|
||||||
}();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// /**
|
|
||||||
// * Determines if the given identifier is valid Javascript
|
|
||||||
// *
|
|
||||||
// * @param {boolean} name
|
|
||||||
// * @returns
|
|
||||||
// */
|
|
||||||
// function isValidIdentifier(name) {
|
|
||||||
// // Don't xss yourself :-)
|
|
||||||
// try {
|
|
||||||
// new Function('var ' + name);
|
|
||||||
// return true;
|
|
||||||
// } catch (e) {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user