5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-03 01:13:03 +08:00

[windows-x] Support embed.fs assets, log runtime

This commit is contained in:
Lea Anthony 2021-08-01 20:13:14 +10:00
parent 1a69d93d32
commit 244b3dc2b4
21 changed files with 585 additions and 22 deletions

View File

@ -13,6 +13,7 @@ require (
github.com/gorilla/websocket v1.4.1
github.com/imdario/mergo v0.3.12
github.com/jackmordaunt/icns v1.0.0
github.com/jchv/go-webview2 v0.0.0-20210720204005-cbb937ae0f7f
github.com/klauspost/compress v1.11.3 // indirect
github.com/leaanthony/clir v1.0.4
github.com/leaanthony/debme v1.2.1
@ -45,5 +46,6 @@ require (
nhooyr.io/websocket v1.8.6
)
replace github.com/tadvi/winc v0.0.0-20190405175627-5454f291903d => C:\Users\leaan\GolandProjects\winc
replace github.com/tadvi/winc v0.0.0-20190405175627-5454f291903d => C:\Users\leaan\GolandProjects\winc
replace github.com/jchv/go-webview2 v0.0.0-20210720204005-cbb937ae0f7f => C:\Users\leaan\GolandProjects\go-webview2

View File

@ -72,6 +72,8 @@ github.com/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmA
github.com/jackmordaunt/icns v1.0.0/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jchv/go-winloader v0.0.0-20200815041850-dec1ee9a7fd5 h1:pdFFlHXY9tZXmJz+tRSm1DzYEH4ebha7cffmm607bMU=
github.com/jchv/go-winloader v0.0.0-20200815041850-dec1ee9a7fd5/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
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=
@ -111,8 +113,6 @@ 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/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e h1:9MlwzLdW7QSDrhDjFlsEYmxpFyIoXmYRon3dt0io31k=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
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=
@ -145,8 +145,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tadvi/winc v0.0.0-20190405175627-5454f291903d h1:IMe61i0F1F/AugXLCNYqi8NSqRm6ybWloiU/lQRnk+I=
github.com/tadvi/winc v0.0.0-20190405175627-5454f291903d/go.mod h1:tNBZi4sduF/C3bQE2wGTIccmErQ4A9M9QkPsICVg+oE=
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=
@ -205,8 +203,9 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -4,6 +4,7 @@ import (
"context"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/frontend/dispatcher"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/internal/signal"
@ -22,9 +23,6 @@ type App struct {
// Indicates if the app is in debug mode
debug bool
// This is our binding DB
bindings *binding.Bindings
// Startup/Shutdown
startupCallback func(ctx context.Context)
shutdownCallback func()
@ -131,12 +129,13 @@ func CreateApp(appoptions *options.App) (*App, error) {
bindingExemptions := []interface{}{appoptions.Startup, appoptions.Shutdown}
appBindings := binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions)
appFrontend := NewFrontend(appoptions, myLogger, appBindings)
messageDispatcher := dispatcher.NewDispatcher(myLogger, appBindings)
appFrontend := NewFrontend(appoptions, myLogger, appBindings, messageDispatcher)
result := &App{
frontend: appFrontend,
logger: myLogger,
bindings: appBindings,
menuManager: menuManager,
startupCallback: appoptions.Startup,
shutdownCallback: appoptions.Shutdown,

View File

@ -8,6 +8,6 @@ import (
"github.com/wailsapp/wails/v2/pkg/options"
)
func NewFrontend(appoptions *options.App, myLogger *logger.Logger, bindings *binding.Bindings) frontend.Frontend {
return windows.NewFrontend(appoptions, myLogger, bindings)
func NewFrontend(appoptions *options.App, myLogger *logger.Logger, bindings *binding.Bindings, dispatcher frontend.Dispatcher) frontend.Frontend {
return windows.NewFrontend(appoptions, myLogger, bindings, dispatcher)
}

View File

@ -0,0 +1,74 @@
package assetserver
import (
"bytes"
"embed"
"fmt"
"github.com/leaanthony/debme"
"github.com/leaanthony/slicer"
"io/fs"
"path/filepath"
"strings"
)
type AssetServer struct {
assets debme.Debme
indexFile []byte
}
func (a *AssetServer) IndexHTML() string {
return string(a.indexFile)
}
func NewAssetServer(assets embed.FS) (*AssetServer, error) {
result := &AssetServer{}
err := result.init(assets)
return result, err
}
func injectScript(input string, script string) ([]byte, error) {
splits := strings.Split(input, "<head>")
if len(splits) != 2 {
return nil, fmt.Errorf("unable to locate a </body> tag in your html")
}
var result bytes.Buffer
result.WriteString(splits[0])
result.WriteString("<head>")
result.WriteString(script)
result.WriteString(splits[1])
return result.Bytes(), nil
}
func processAssets(assets embed.FS) (debme.Debme, error) {
result, err := debme.FS(assets, ".")
if err != nil {
return result, err
}
// Find index.html
stat, err := fs.Stat(assets, "index.html")
if stat != nil {
return debme.FS(assets, ".")
}
var indexFiles slicer.StringSlicer
err = fs.WalkDir(result, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if strings.HasSuffix(path, "index.html") {
indexFiles.Add(path)
}
return nil
})
if err != nil {
return debme.Debme{}, err
}
if indexFiles.Length() > 1 {
return debme.Debme{}, fmt.Errorf("multiple 'index.html' files found in assets")
}
path, _ := filepath.Split(indexFiles.AsSlice()[0])
return debme.FS(assets, path)
}

View File

@ -0,0 +1,43 @@
// +build desktop
package assetserver
import (
"embed"
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
"net/http"
)
func (a *AssetServer) init(assets embed.FS) error {
var err error
a.assets, err = processAssets(assets)
if err != nil {
return err
}
indexHTML, err := a.assets.ReadFile("index.html")
if err != nil {
return err
}
a.indexFile, err = injectScript(string(indexHTML), "<script>"+runtime.RuntimeJS+"</script>")
if err != nil {
return err
}
return nil
}
func (a *AssetServer) Load(filename string) ([]byte, string, error) {
var content []byte
var err error
switch filename {
case "/":
content = a.indexFile
default:
content, err = a.assets.ReadFile(filename)
}
if err != nil {
return nil, "", err
}
mimeType := http.DetectContentType(content)
return content, mimeType, nil
}

View File

@ -0,0 +1,48 @@
// +build desktop
package assetserver
import (
"embed"
"github.com/matryer/is"
"github.com/wailsapp/wails/v2/internal/frontend/assetserver/testdata"
"strconv"
"testing"
)
var runtimeInjection = `<script defer src="/wails/runtime.js"></script>`
var expected = `<html><head><link rel="stylesheet" href="/main.css"></head><body data-wails-drag><div id="logo"></div>` + runtimeInjection + `</body></html>`
//go:embed testdata/subdir
var subdir embed.FS
//go:embed testdata
var multiple embed.FS
func TestAssetServer_Init(t *testing.T) {
is2 := is.New(t)
tests := []struct {
assets embed.FS
want string
wantErr bool
}{
{testdata.TopLevelFS, expected, false},
{subdir, expected, false},
{multiple, expected, true},
}
for idx, tt := range tests {
t.Run(strconv.Itoa(idx), func(t *testing.T) {
server, err := NewAssetServer(tt.assets)
if tt.wantErr {
is2.True(err != nil)
} else {
is2.NoErr(err)
is2.Equal(string(server.indexFile), tt.want)
}
})
}
}

View File

@ -0,0 +1,8 @@
<html>
<head>
<link href="/main.css" rel="stylesheet">
</head>
<body data-wails-drag>
<div id="logo"></div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,20 @@
import {ready} from '@wails/runtime';
ready(() => {
// Get input + focus
let nameElement = document.getElementById("name");
nameElement.focus();
// Setup the greet function
window.greet = function () {
// Get name
let name = nameElement.value;
// Call App.Greet(name)
window.backend.main.App.Greet(name).then((result) => {
// Update result with data back from App.Greet()
document.getElementById("result").innerText = result;
});
};
});

View File

@ -0,0 +1,8 @@
<html>
<head>
<link href="/main.css" rel="stylesheet">
</head>
<body data-wails-drag>
<div id="logo"></div>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,20 @@
import {ready} from '@wails/runtime';
ready(() => {
// Get input + focus
let nameElement = document.getElementById("name");
nameElement.focus();
// Setup the greet function
window.greet = function () {
// Get name
let name = nameElement.value;
// Call App.Greet(name)
window.backend.main.App.Greet(name).then((result) => {
// Update result with data back from App.Greet()
document.getElementById("result").innerText = result;
});
};
});

View File

@ -0,0 +1,6 @@
package testdata
import "embed"
//go:embed index.html main.css main.js
var TopLevelFS embed.FS

View File

@ -0,0 +1,5 @@
package frontend
type Dispatcher interface {
ProcessMessage(message string) error
}

View File

@ -0,0 +1,45 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
// IPC Listeners
const listeners = [];
/**
* Adds a listener to IPC messages
* @param {function} callback
*/
export function AddIPCListener(callback) {
listeners.push(callback);
}
/**
* SendMessage sends the given message to the backend
*
* @param {string} message
*/
export function SendMessage(message) {
// Call Platform specific invoke method
if (PLATFORM === "windows") {
window.chrome.webview.postMessage(message);
} else if (PLATFORM === "darwin") {
window.blah();
} else {
console.error("Unsupported Platform");
}
// Also send to listeners
if (listeners.length > 0) {
for (let i = 0; i < listeners.length; i++) {
listeners[i](message);
}
}
}

View File

@ -0,0 +1,115 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
import {SendMessage} from './ipc';
/**
* Sends a log message to the backend with the given level + message
*
* @param {string} level
* @param {string} message
*/
function sendLogMessage(level, message) {
// Log Message format:
// l[type][message]
SendMessage('L' + level + message);
}
/**
* Log the given trace message with the backend
*
* @export
* @param {string} message
*/
export function Trace(message) {
sendLogMessage('T', message);
}
/**
* Log the given message with the backend
*
* @export
* @param {string} message
*/
export function Print(message) {
sendLogMessage('P', message);
}
/**
* Log the given debug message with the backend
*
* @export
* @param {string} message
*/
export function Debug(message) {
sendLogMessage('D', message);
}
/**
* Log the given info message with the backend
*
* @export
* @param {string} message
*/
export function Info(message) {
sendLogMessage('I', message);
}
/**
* Log the given warning message with the backend
*
* @export
* @param {string} message
*/
export function Warning(message) {
sendLogMessage('W', message);
}
/**
* Log the given error message with the backend
*
* @export
* @param {string} message
*/
export function Error(message) {
sendLogMessage('E', message);
}
/**
* Log the given fatal message with the backend
*
* @export
* @param {string} message
*/
export function Fatal(message) {
sendLogMessage('F', message);
}
/**
* Sets the Log level to the given log level
*
* @export
* @param {number} loglevel
*/
export function SetLogLevel(loglevel) {
sendLogMessage('S', loglevel);
}
// Log levels
export const Level = {
TRACE: 1,
DEBUG: 2,
INFO: 3,
WARNING: 4,
ERROR: 5,
};

View File

@ -0,0 +1,6 @@
package runtime
import _ "embed"
//go:embed runtime_windows.js
var RuntimeJS string

View File

@ -2,21 +2,32 @@ package windows
import (
"context"
"github.com/jchv/go-webview2/pkg/edge"
"github.com/tadvi/winc"
"github.com/tadvi/winc/w32"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/frontend/assetserver"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options"
"log"
"runtime"
"strings"
)
type Frontend struct {
frontendOptions *options.App
logger *logger.Logger
chromium *edge.Chromium
// Assets
assets *assetserver.AssetServer
// main window handle
mainWindow *Window
minWidth, minHeight, maxWidth, maxHeight int
bindings *binding.Bindings
dispatcher frontend.Dispatcher
}
func (f *Frontend) Run() error {
@ -30,6 +41,12 @@ func (f *Frontend) Run() error {
mainWindow.Show()
}
f.setupChromium()
mainWindow.OnSize().Bind(func(arg *winc.Event) {
f.chromium.Resize()
})
mainWindow.OnClose().Bind(func(arg *winc.Event) {
if f.frontendOptions.HideWindowOnClose {
f.WindowHide()
@ -131,11 +148,77 @@ func (f *Frontend) Quit() {
winc.Exit()
}
func NewFrontend(appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings) *Frontend {
func (f *Frontend) setupChromium() {
chromium := edge.NewChromium()
chromium.MessageCallback = f.processMessage
chromium.WebResourceRequestedCallback = f.processRequest
chromium.Embed(f.mainWindow.Handle())
chromium.Resize()
chromium.AddWebResourceRequestedFilter("*", edge.COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL)
chromium.Navigate("file://index.html")
f.chromium = chromium
}
return &Frontend{
func (f *Frontend) processRequest(sender *edge.ICoreWebView2, args *edge.ICoreWebView2WebResourceRequestedEventArgs) uintptr {
// Get the request
requestObject, _ := args.GetRequest()
uri, _ := requestObject.GetURI()
// Translate URI
uri = strings.TrimPrefix(uri, "file://index.html")
if !strings.HasPrefix(uri, "/") {
return 0
}
// Load file from asset store
content, mimeType, err := f.assets.Load(uri)
if err != nil {
return 0
}
// Create stream for response
stream, err := w32.SHCreateMemStream(content)
if err != nil {
log.Fatal(err)
}
env := f.chromium.Environment()
var response *edge.ICoreWebView2WebResourceResponse
err = env.CreateWebResourceResponse(stream, 200, "OK", "Content-Type: "+mimeType, &response)
if err != nil {
return 0
}
// Send response back
err = args.PutResponse(response)
if err != nil {
return 0
}
return 0
}
func (f *Frontend) processMessage(message string) {
err := f.dispatcher.ProcessMessage(message)
if err != nil {
// TODO: Work out what this means
return
}
}
func NewFrontend(appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher) *Frontend {
result := &Frontend{
frontendOptions: appoptions,
logger: myLogger,
bindings: appBindings,
dispatcher: dispatcher,
}
if appoptions.Windows.Assets != nil {
assets, err := assetserver.NewAssetServer(*appoptions.Windows.Assets)
if err != nil {
log.Fatal(err)
}
result.assets = assets
}
return result
}

View File

@ -19,31 +19,31 @@ func (l *DefaultLogger) Print(message string) {
// Trace level logging. Works like Sprintf.
func (l *DefaultLogger) Trace(message string) {
println("TRACE | " + message)
println("TRA | " + message)
}
// Debug level logging. Works like Sprintf.
func (l *DefaultLogger) Debug(message string) {
println("DEBUG | " + message)
println("DEB | " + message)
}
// Info level logging. Works like Sprintf.
func (l *DefaultLogger) Info(message string) {
println("INFO | " + message)
println("INF | " + message)
}
// Warning level logging. Works like Sprintf.
func (l *DefaultLogger) Warning(message string) {
println("WARN | " + message)
println("WAR | " + message)
}
// Error level logging. Works like Sprintf.
func (l *DefaultLogger) Error(message string) {
println("ERROR | " + message)
println("ERR | " + message)
}
// Fatal level logging. Works like Sprintf.
func (l *DefaultLogger) Fatal(message string) {
println("FATAL | " + message)
println("FAT | " + message)
os.Exit(1)
}

View File

@ -1,6 +1,9 @@
package windows
import "github.com/wailsapp/wails/v2/pkg/menu"
import (
"embed"
"github.com/wailsapp/wails/v2/pkg/menu"
)
// Options are options specific to Windows
type Options struct {
@ -8,4 +11,5 @@ type Options struct {
WindowBackgroundIsTranslucent bool
DisableWindowIcon bool
Menu *menu.Menu
Assets *embed.FS
}