Compare commits

...

7 Commits

Author SHA1 Message Date
Lea Anthony
febd867fa7 Merge branch 'feature/v2-mac' into v2-alpha
# Conflicts:
#	v2/cmd/wails/internal/commands/build/build.go
#	v2/cmd/wails/internal/commands/dev/dev.go
#	v2/cmd/wails/internal/commands/doctor/doctor.go
#	v2/cmd/wails/internal/commands/initialise/initialise.go
#	v2/cmd/wails/main.go
#	v2/go.mod
#	v2/go.sum
#	v2/internal/app/default.go
#	v2/internal/app/desktop.go
#	v2/internal/app/hybrid.go
#	v2/internal/app/server.go
#	v2/internal/binding/binding.go
#	v2/internal/binding/boundMethod.go
#	v2/internal/binding/db.go
#	v2/internal/binding/reflect.go
#	v2/internal/ffenestri/ffenestri.go
#	v2/internal/ffenestri/ffenestri.h
#	v2/internal/ffenestri/ffenestri_client.go
#	v2/internal/ffenestri/ffenestri_linux.c
#	v2/internal/fs/fs.go
#	v2/internal/html/asset.go
#	v2/internal/html/assetbundle.go
#	v2/internal/logger/custom_logger.go
#	v2/internal/logger/default_logger.go
#	v2/internal/messagedispatcher/dispatchclient.go
#	v2/internal/messagedispatcher/message/call.go
#	v2/internal/messagedispatcher/message/event.go
#	v2/internal/messagedispatcher/message/log.go
#	v2/internal/messagedispatcher/message/messageparser.go
#	v2/internal/messagedispatcher/message/runtime.go
#	v2/internal/messagedispatcher/message/window.go
#	v2/internal/messagedispatcher/messagedispatcher.go
#	v2/internal/parse/parse.go
#	v2/internal/process/process.go
#	v2/internal/project/project.go
#	v2/internal/runtime/js/core/bindings.js
#	v2/internal/runtime/js/core/browser.js
#	v2/internal/runtime/js/core/calls.js
#	v2/internal/runtime/js/core/desktop.js
#	v2/internal/runtime/js/core/events.js
#	v2/internal/runtime/js/core/log.js
#	v2/internal/runtime/js/core/main.js
#	v2/internal/runtime/js/core/utils.js
#	v2/internal/runtime/js/desktop/linux.js
#	v2/internal/runtime/js/package-lock.json
#	v2/internal/runtime/js/package.json
#	v2/internal/runtime/js/package.json.md5
#	v2/internal/runtime/js/runtime/.npmignore
#	v2/internal/runtime/js/runtime/README.md
#	v2/internal/runtime/js/runtime/browser.js
#	v2/internal/runtime/js/runtime/events.js
#	v2/internal/runtime/js/runtime/init.js
#	v2/internal/runtime/js/runtime/log.js
#	v2/internal/runtime/js/runtime/main.js
#	v2/internal/runtime/js/runtime/package.json
#	v2/internal/runtime/js/runtime/runtime.d.ts
#	v2/internal/runtime/js/webpack.desktop.js
#	v2/internal/servicebus/servicebus.go
#	v2/internal/signal/signal.go
#	v2/internal/subsystem/binding.go
#	v2/internal/subsystem/call.go
#	v2/internal/subsystem/event.go
#	v2/internal/subsystem/log.go
#	v2/internal/subsystem/runtime.go
#	v2/internal/system/packagemanager/packagemanager.go
#	v2/internal/system/system_windows.go
#	v2/internal/templates/templates.go
#	v2/internal/templates/templates/svelte-mui/basic.tmpl.go
#	v2/internal/templates/templates/svelte-mui/frontend/package.json
#	v2/internal/templates/templates/svelte-mui/main.tmpl.go
#	v2/internal/templates/templates/vanilla/basic.tmpl.go
#	v2/internal/templates/templates/vanilla/frontend/index.html
#	v2/internal/templates/templates/vanilla/frontend/main.css
#	v2/internal/templates/templates/vanilla/main.tmpl.go
#	v2/internal/templates/templates/vanilla/wails.tmpl.json
#	v2/internal/templates/templates/vuetify2-basic/basic.tmpl.go
#	v2/internal/templates/templates/vuetify2-basic/main.tmpl.go
#	v2/internal/templates/templates/vuetify2-basic/wails.tmpl.json
#	v2/internal/webserver/websockets.go
#	v2/pkg/commands/build/base.go
#	v2/pkg/commands/build/build.go
#	v2/pkg/commands/build/builder.go
#	v2/pkg/commands/build/desktop.go
#	v2/pkg/commands/build/hybrid.go
#	v2/pkg/commands/build/packager.go
#	v2/pkg/commands/build/packager_linux.go
#	v2/pkg/commands/build/server.go
#	v2/test/disable-resize/basic.go
#	v2/test/disable-resize/main.go
#	v2/test/frameless/basic.go
#	v2/test/frameless/main.go
#	v2/test/hidden/basic.go
#	v2/test/hidden/main.go
#	v2/test/minmax/basic.go
#	v2/test/minmax/main.go
#	v2/test/runtime/calc.go
#	v2/test/runtime/frontend/package-lock.json
#	v2/test/runtime/frontend/src/assets/css/main.css
#	v2/test/runtime/frontend/src/index.html
#	v2/test/runtime/frontend/src/main.js
#	v2/test/runtime/main.go
#	v2/test/runtime/runtime.go
#	v2/wails.go
2021-03-20 15:06:17 +11:00
Travis McLane
9806b9c651 update scripts/build.sh to run test only on v1 2020-09-04 13:38:17 -05:00
Lea Anthony
2a20867d00 Add README 2020-09-02 10:36:03 -05:00
Lea Anthony
48ff661150 package doc 2020-09-02 10:36:03 -05:00
Lea Anthony
19d59bef51 Update module path 2020-09-02 10:36:03 -05:00
Travis McLane
bdcb2fe810 Merge commit '25a157e6614739fbd4df1da3d11d46afe72ae9a8' as 'v2' 2020-09-01 19:34:51 -05:00
Travis McLane
25a157e661 Squashed 'v2/' content from commit 7d8960e
git-subtree-dir: v2
git-subtree-split: 7d8960e87431924f5705df4c777758a0eb32e145
2020-09-01 19:34:51 -05:00
116 changed files with 3191 additions and 4256 deletions

View File

@@ -2,19 +2,19 @@ package build
import (
"fmt"
"io"
"os"
"runtime"
"strings"
"time"
"github.com/leaanthony/clir"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/internal/logger"
"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) {
func AddBuildSubcommand(app *clir.Cli) {
outputType := "desktop"
@@ -32,7 +32,7 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
// Setup pack flag
pack := false
command.BoolFlag("package", "Create a platform specific package", &pack)
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)
@@ -43,7 +43,7 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
// Quiet Build
quiet := false
command.BoolFlag("q", "Suppress output to console", &quiet)
command.BoolFlag("q", "Supress output to console", &quiet)
// ldflags to pass to `go`
ldflags := ""
@@ -53,38 +53,28 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
logFile := ""
command.StringFlag("l", "Log to file", &logFile)
// Retain assets
keepAssets := false
command.BoolFlag("k", "Keep generated assets", &keepAssets)
appleIdentity := ""
if runtime.GOOS == "darwin" {
command.StringFlag("sign", "Signs your app with the given identity.", &appleIdentity)
}
command.Action(func() error {
// Create logger
logger := clilogger.New(w)
logger.Mute(quiet)
logger := logger.New()
if !quiet {
logger.AddOutput(os.Stdout)
}
// Validate output type
if !validTargetTypes.Contains(outputType) {
return fmt.Errorf("output type '%s' is not valid", outputType)
logger.Fatal(fmt.Sprintf("Output type '%s' is not valid.", outputType))
os.Exit(1)
}
if !quiet {
app.PrintBanner()
}
// Ensure package is used with apple identity
if appleIdentity != "" && pack == false {
return fmt.Errorf("must use `-package` flag when using `-sign`")
}
task := fmt.Sprintf("Building %s Application", strings.Title(outputType))
logger.Println(task)
logger.Println(strings.Repeat("-", len(task)))
logger.Writeln(task)
logger.Writeln(strings.Repeat("-", len(task)))
// Setup mode
mode := build.Debug
@@ -94,15 +84,13 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: outputType,
Mode: mode,
Pack: pack,
Platform: platform,
LDFlags: ldflags,
Compiler: compilerCommand,
KeepAssets: keepAssets,
AppleIdentity: appleIdentity,
Logger: logger,
OutputType: outputType,
Mode: mode,
Pack: pack,
Platform: platform,
LDFlags: ldflags,
Compiler: compilerCommand,
}
return doBuild(buildOptions)
@@ -119,12 +107,11 @@ func doBuild(buildOptions *build.Options) error {
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("")
buildOptions.Logger.Writeln("")
buildOptions.Logger.Writeln(fmt.Sprintf("Built '%s' in %s.", outputFilename, elapsed.Round(time.Millisecond).String()))
buildOptions.Logger.Writeln("")
return nil
}

View File

@@ -2,45 +2,35 @@ package dev
import (
"fmt"
"io"
"os"
"os/signal"
"path/filepath"
"runtime"
"strings"
"syscall"
"time"
"github.com/wailsapp/wails/v2/internal/colour"
"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/logger"
"github.com/wailsapp/wails/v2/internal/process"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/build"
)
func LogGreen(message string, args ...interface{}) {
text := fmt.Sprintf(message, args...)
println(colour.Green(text))
}
func LogRed(message string, args ...interface{}) {
text := fmt.Sprintf(message, args...)
println(colour.Red(text))
}
func LogDarkYellow(message string, args ...interface{}) {
text := fmt.Sprintf(message, args...)
println(colour.DarkYellow(text))
}
// AddSubcommand adds the `dev` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
func AddSubcommand(app *clir.Cli) 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)
@@ -51,19 +41,18 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
// extensions to trigger rebuilds
extensions := "go"
command.StringFlag("e", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions)
// extensions to trigger rebuilds
showWarnings := false
command.BoolFlag("w", "Show warnings", &showWarnings)
loglevel := ""
command.StringFlag("loglevel", "Loglevel to use - Trace, Debug, Info, Warning, Error", &loglevel)
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)
logger := logger.New()
logger.AddOutput(os.Stdout)
app.PrintBanner()
// TODO: Check you are in a project directory
@@ -75,6 +64,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
defer watcher.Close()
var debugBinaryProcess *process.Process = nil
var buildFrontend bool = true
var extensionsThatTriggerARebuild = strings.Split(extensions, ",")
// Setup signal handler
@@ -84,27 +74,19 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
debounceQuit := make(chan bool, 1)
// Do initial build
logger.Println("Building application for development...")
newProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess, loglevel)
if newProcess != nil {
debugBinaryProcess = newProcess
}
if err != nil {
return err
}
logger.Info("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)
// logger.Info("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") {
err := watcher.Add(event.Name)
if err != nil {
logger.Fatal("%s", err.Error())
}
LogGreen("[New Directory] Watching new directory: %s", event.Name)
watcher.Add(event.Name)
logger.Info("Watching directory: %s", event.Name)
}
}
return
@@ -113,33 +95,38 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
// Check for file writes
if event.Op&fsnotify.Write == fsnotify.Write {
// logger.Info("modified file: %s", event.Name)
var rebuild bool = false
// Iterate all file patterns
for _, pattern := range extensionsThatTriggerARebuild {
if strings.HasSuffix(event.Name, pattern) {
rebuild = true
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 {
if showWarnings {
LogDarkYellow("[File change] %s did not match extension list (%s)", event.Name, extensions)
}
logger.Info("Filename change: %s did not match extension list %s", event.Name, extensions)
return
}
LogGreen("[Attempting rebuild] %s updated", event.Name)
if buildFrontend {
logger.Info("Full rebuild triggered: %s updated", event.Name)
} else {
logger.Info("Partial build triggered: %s updated", event.Name)
}
// Do a rebuild
// Try and build the app
newBinaryProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess, loglevel)
if err != nil {
fmt.Printf("Error during build: %s", err.Error())
return
}
newBinaryProcess := restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess)
// If we have a new process, save it
if newBinaryProcess != nil {
debugBinaryProcess = newBinaryProcess
@@ -149,28 +136,23 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
})
// Get project dir
projectDir, err := os.Getwd()
dir, err := os.Getwd()
if err != nil {
return err
}
// Get all subdirectories
dirs, err := fs.GetSubdirectories(projectDir)
dirs, err := fs.GetSubdirectories(dir)
if err != nil {
return err
}
LogGreen("Watching (sub)/directory: %s", projectDir)
// Setup a watcher for non-node_modules directories
dirs.Each(func(dir string) {
if strings.Contains(dir, "node_modules") {
return
}
// Ignore build directory
if strings.HasPrefix(dir, filepath.Join(projectDir, "build")) {
return
}
logger.Info("Watching directory: %s", dir)
err = watcher.Add(dir)
if err != nil {
logger.Fatal(err.Error())
@@ -182,7 +164,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
for quit == false {
select {
case <-quitChannel:
LogGreen("\nCaught quit")
println()
// Notify debouncer to quit
debounceQuit <- true
quit = true
@@ -191,13 +173,10 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
// Kill the current program if running
if debugBinaryProcess != nil {
err := debugBinaryProcess.Kill()
if err != nil {
return err
}
debugBinaryProcess.Kill()
}
LogGreen("Development mode exited")
logger.Info("Development mode exited")
return nil
})
@@ -224,15 +203,15 @@ exit:
}
}
func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, debugBinaryProcess *process.Process, loglevel string) (*process.Process, error) {
func restartApp(logger *logger.Logger, outputType string, ldflags string, compilerCommand string, buildFrontend bool, debugBinaryProcess *process.Process) *process.Process {
appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand)
appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand, buildFrontend)
println()
if err != nil {
LogRed("Build error - continuing to run current version")
LogDarkYellow(err.Error())
return nil, nil
logger.Error("Build Failed: %s", err.Error())
return nil
}
logger.Info("Build new binary: %s", appBinary)
// Kill existing binary if need be
if debugBinaryProcess != nil {
@@ -248,24 +227,21 @@ func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string,
// TODO: Generate `backend.js`
// Start up new binary
newProcess := process.NewProcess(logger, appBinary, "-loglevel", loglevel)
newProcess := process.NewProcess(logger, appBinary)
err = newProcess.Start()
if err != nil {
// Remove binary
deleteError := fs.DeleteFile(appBinary)
if deleteError != nil {
logger.Fatal("Unable to delete app binary: " + appBinary)
}
fs.DeleteFile(appBinary)
logger.Fatal("Unable to start application: %s", err.Error())
}
return newProcess, nil
return newProcess
}
func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string) (string, error) {
func buildApp(logger *logger.Logger, outputType string, ldflags string, compilerCommand string, buildFrontend bool) (string, error) {
// Create random output file
outputFile := fmt.Sprintf("dev-%d", time.Now().Unix())
outputFile := fmt.Sprintf("debug-%d", time.Now().Unix())
// Create BuildOptions
buildOptions := &build.Options{
@@ -277,7 +253,7 @@ func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, co
LDFlags: ldflags,
Compiler: compilerCommand,
OutputFile: outputFile,
IgnoreFrontend: true,
IgnoreFrontend: !buildFrontend,
}
return build.Build(buildOptions)

View File

@@ -2,37 +2,37 @@ package doctor
import (
"fmt"
"io"
"log"
"os"
"runtime"
"strings"
"text/tabwriter"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/logger"
"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 {
func AddSubcommand(app *clir.Cli) error {
command := app.NewSubCommand("doctor", "Diagnose your environment")
command.Action(func() error {
logger := clilogger.New(w)
// Create logger
logger := logger.New()
logger.AddOutput(os.Stdout)
app.PrintBanner()
logger.Print("Scanning system - please wait...")
print("Scanning system - please wait...")
// Get system info
info, err := system.GetInfo()
if err != nil {
return err
}
logger.Println("Done.")
println("Done.")
// Start a new tabwriter
w := new(tabwriter.Writer)
@@ -112,22 +112,22 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "* - Optional Dependency\n")
w.Flush()
logger.Println("")
logger.Println("Diagnosis")
logger.Println("---------\n")
println()
println("Diagnosis")
println("---------\n")
// Generate an appropriate diagnosis
if len(dependenciesMissing) == 0 && dependenciesAvailableRequired == 0 {
logger.Println("Your system is ready for Wails development!")
println("Your system is ready for Wails development!")
}
if dependenciesAvailableRequired != 0 {
log.Println("Install required packages using: " + info.Dependencies.InstallAllRequiredCommand())
println("Install required packages using: " + info.Dependencies.InstallAllRequiredCommand())
}
if dependenciesAvailableOptional != 0 {
log.Println("Install optional packages using: " + info.Dependencies.InstallAllOptionalCommand())
println("Install optional packages using: " + info.Dependencies.InstallAllOptionalCommand())
}
if len(externalPackages) > 0 {
@@ -135,18 +135,18 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
if p.Optional {
print("[Optional] ")
}
log.Println("Install " + p.Name + ": " + p.InstallCommand)
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")
println("Fatal:")
println("Required dependencies missing: " + strings.Join(dependenciesMissing, " "))
println("Please read this article on how to resolve this: https://wails.app/guides/resolving-missing-packages")
}
log.Println("")
println()
return nil
})

View File

@@ -2,19 +2,17 @@ package initialise
import (
"fmt"
"io"
"os"
"strings"
"time"
"github.com/leaanthony/clir"
"github.com/pkg/errors"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/templates"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/git"
)
// AddSubcommand adds the `init` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
func AddSubcommand(app *clir.Cli) error {
// Load the template shortnames
validShortNames, err := templates.TemplateShortNames()
@@ -34,24 +32,13 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
command.StringFlag("n", "Name of project", &projectName)
// Setup project directory
projectDirectory := ""
projectDirectory := "."
command.StringFlag("d", "Project directory", &projectDirectory)
// Quiet Init
quiet := false
command.BoolFlag("q", "Supress output to console", &quiet)
initGit := false
gitInstalled := git.IsInstalled()
if gitInstalled {
// Git Init
command.BoolFlag("g", "Initialise git repository", &initGit)
}
// VSCode project files
vscode := false
command.BoolFlag("vscode", "Generate VSCode project files", &vscode)
// List templates
list := false
command.BoolFlag("l", "List templates", &list)
@@ -59,29 +46,32 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
command.Action(func() error {
// Create logger
logger := clilogger.New(w)
logger.Mute(quiet)
logger := logger.New()
if !quiet {
logger.AddOutput(os.Stdout)
}
// Are we listing templates?
if list {
app.PrintBanner()
err := templates.OutputList(logger)
logger.Println("")
logger.Writeln("")
return err
}
// Validate output type
if !validShortNames.Contains(templateName) {
logger.Print(fmt.Sprintf("[ERROR] Template '%s' is not valid", templateName))
logger.Println("")
logger.Write(fmt.Sprintf("ERROR: Template '%s' is not valid", templateName))
logger.Writeln("")
command.PrintHelp()
return nil
}
// Validate name
if len(projectName) == 0 {
logger.Println("ERROR: Project name required")
logger.Println("")
logger.Writeln("ERROR: Project name required")
logger.Writeln("")
command.PrintHelp()
return nil
}
@@ -91,23 +81,15 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
}
task := fmt.Sprintf("Initialising Project %s", strings.Title(projectName))
logger.Println(task)
logger.Println(strings.Repeat("-", len(task)))
logger.Writeln(task)
logger.Writeln(strings.Repeat("-", len(task)))
// Create Template Options
options := &templates.Options{
ProjectName: projectName,
TargetDir: projectDirectory,
TemplateName: templateName,
Logger: logger,
GenerateVSCode: vscode,
InitGit: initGit,
}
// Try to discover author details from git config
err := findAuthorDetails(options)
if err != nil {
return err
ProjectName: projectName,
TargetDir: projectDirectory,
TemplateName: templateName,
Logger: logger,
}
return initProject(options)
@@ -128,55 +110,11 @@ func initProject(options *templates.Options) error {
return err
}
if options.InitGit {
err = initGit(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)
if options.GenerateVSCode {
options.Logger.Println("VSCode config files generated.")
}
if options.InitGit {
options.Logger.Println("Git repository initialised.")
}
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
}
func initGit(options *templates.Options) error {
err := git.InitRepo(options.TargetDir)
if err != nil {
return errors.Wrap(err, "Unable to initialise git repository:")
}
return nil
}
func findAuthorDetails(options *templates.Options) error {
if git.IsInstalled() {
name, err := git.Name()
if err != nil {
return err
}
options.AuthorName = strings.TrimSpace(name)
email, err := git.Email()
if err != nil {
return err
}
options.AuthorEmail = strings.TrimSpace(email)
}
options.Logger.Writeln("")
options.Logger.Writeln(fmt.Sprintf("Initialised project '%s' in %s.", options.ProjectName, elapsed.Round(time.Millisecond).String()))
options.Logger.Writeln("")
return nil
}

View File

@@ -1,19 +1,11 @@
package main
import (
"fmt"
"os"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/update"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/debug"
"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"
)
@@ -22,44 +14,19 @@ func fatal(message string) {
os.Exit(1)
}
func banner(_ *clir.Cli) string {
return fmt.Sprintf("%s %s", colour.Yellow("Wails CLI"), colour.DarkRed(version))
}
func main() {
var err error
version := "v2.0.0-alpha"
app := clir.NewCli("Wails", "Go/HTML Appkit", version)
app := clir.NewCli("Wails", "Go/HTML Application Framework", version)
app.SetBannerFunction(banner)
build.AddBuildSubcommand(app, os.Stdout)
err = initialise.AddSubcommand(app, os.Stdout)
build.AddBuildSubcommand(app)
err = initialise.AddSubcommand(app)
if err != nil {
fatal(err.Error())
}
err = debug.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 = update.AddSubcommand(app, os.Stdout, version)
err = doctor.AddSubcommand(app)
if err != nil {
fatal(err.Error())
}

View File

@@ -1,29 +1,14 @@
module github.com/wailsapp/wails/v2
go 1.16
go 1.13
require (
github.com/Masterminds/semver v1.5.0
github.com/davecgh/go-spew v1.1.1
github.com/fatih/structtag v1.2.0
github.com/fsnotify/fsnotify v1.4.9
github.com/gorilla/websocket v1.4.1
github.com/imdario/mergo v0.3.11
github.com/jackmordaunt/icns v1.0.0
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/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/leaanthony/clir v1.0.2
github.com/leaanthony/gosod v0.0.3
github.com/leaanthony/slicer v1.4.1
github.com/matryer/is v1.3.0
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/wzshiming/ctc v1.2.3
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
nhooyr.io/websocket v1.8.6
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
nhooyr.io/websocket v1.7.4
)

132
v2/go.sum
View File

@@ -1,125 +1,57 @@
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
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/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/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
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/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmAMQ=
github.com/jackmordaunt/icns v1.0.0/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo=
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/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/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/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.2 h1:Ham9Ep7NH95il0x/ci5s2ExJa/K4B2dE8L2uaSj2cxo=
github.com/leaanthony/clir v1.0.2/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
github.com/leaanthony/gosod v0.0.3 h1:VlPilye0zoH4I0WihShyoiTHyiAN1v6dcBW4mMvGJao=
github.com/leaanthony/gosod v0.0.3/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
github.com/leaanthony/slicer v1.4.1 h1:X/SmRIDhkUAolP79mSTO0jTcVX1k504PJBqvV6TwP0w=
github.com/leaanthony/slicer v1.4.1/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/matryer/is v1.3.0 h1:9qiso3jaJrOe6qBRJRBt2Ldht05qDiFP9le0JOIhRSI=
github.com/matryer/is v1.3.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
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/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/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/wzshiming/ctc v1.2.3 h1:q+hW3IQNsjIlOFBTGZZZeIXTElFM4grF4spW/errh/c=
github.com/wzshiming/ctc v1.2.3/go.mod h1:2tVAtIY7SUyraSk0JxvwmONNPFL4ARavPuEsg5+KA28=
github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae h1:tpXvBXC3hpQBDCc9OojJZCQMVRAbT3TTdUMP8WguXkY=
github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae/go.mod h1:VTAq37rkGeV+WOybvZwjXiJOicICdpLCN8ifpISjK20=
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=
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
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/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-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/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/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=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/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=
nhooyr.io/websocket v1.7.4 h1:w/LGB2sZT0RV8lZYR7nfyaYz4PUbYZ5oF7NBon2M0NY=
nhooyr.io/websocket v1.7.4/go.mod h1:PxYxCwFdFYQ0yRvtQz3s/dC+VEm7CSuC/4b9t8MQQxw=

View File

@@ -1,4 +1,4 @@
// +build !desktop,!hybrid,!server,!dev
// +build !desktop,!hybrid,!server
package app
@@ -10,9 +10,7 @@ package app
import (
"os"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/internal/features"
)
// App defines a Wails application structure
@@ -22,15 +20,12 @@ type App struct {
Height int
Resizable bool
// Indicates if the app is running in debug mode
debug bool
logger *logger.Logger
Features *features.Features
}
// CreateApp returns a null application
func CreateApp(_ *options.App) (*App, error) {
return &App{}, nil
func CreateApp(options *Options) *App {
return &App{}
}
// Run the application
@@ -39,3 +34,8 @@ func (a *App) Run() error {
os.Exit(1)
return nil
}
// Bind the dummy interface
func (a *App) Bind(dummy interface{}) error {
return nil
}

View File

@@ -3,152 +3,117 @@
package app
import (
"context"
"sync"
"os"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/features"
"github.com/wailsapp/wails/v2/internal/ffenestri"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/internal/runtime"
"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"
)
// App defines a Wails application structure
type App struct {
appType string
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
log *subsystem.Log
runtime *subsystem.Runtime
event *subsystem.Event
binding *subsystem.Binding
call *subsystem.Call
menu *subsystem.Menu
url *subsystem.URL
dispatcher *messagedispatcher.Dispatcher
menuManager *menumanager.Manager
// Indicates if the app is in debug mode
debug bool
// This is our binding DB
bindings *binding.Bindings
// Application Stores
loglevelStore *runtime.Store
appconfigStore *runtime.Store
// Startup/Shutdown
startupCallback func(*runtime.Runtime)
shutdownCallback func()
// Feature flags
Features *features.Features
}
// Create App
func CreateApp(appoptions *options.App) (*App, error) {
func CreateApp(options *Options) *App {
// Merge default options
options.MergeDefaults(appoptions)
options.mergeDefaults()
// Set up logger
myLogger := logger.New(appoptions.Logger)
myLogger.SetLogLevel(appoptions.LogLevel)
myLogger := logger.New(os.Stdout)
myLogger.SetLogLevel(logger.TRACE)
// Create the menu manager
menuManager := menumanager.NewManager()
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,
Frameless: options.Frameless,
// Process the application menu
menuManager.SetApplicationMenu(options.GetApplicationMenu(appoptions))
// This should be controlled by the compile time flags...
DevTools: true,
// Process context menus
contextMenus := options.GetContextMenus(appoptions)
for _, contextMenu := range contextMenus {
menuManager.AddContextMenu(contextMenu)
}
// Process tray menus
trayMenus := options.GetTrayMenus(appoptions)
for _, trayMenu := range trayMenus {
menuManager.AddTrayMenu(trayMenu)
}
window := ffenestri.NewApplicationWithConfig(appoptions, myLogger, menuManager)
// Create binding exemptions - Ugly hack. There must be a better way
bindingExemptions := []interface{}{appoptions.Startup, appoptions.Shutdown}
Resizable: !options.DisableResize,
Fullscreen: options.Fullscreen,
}, myLogger)
result := &App{
appType: "desktop",
window: window,
servicebus: servicebus.New(myLogger),
logger: myLogger,
bindings: binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions),
menuManager: menuManager,
startupCallback: appoptions.Startup,
shutdownCallback: appoptions.Shutdown,
window: window,
servicebus: servicebus.New(myLogger),
logger: myLogger,
bindings: binding.NewBindings(myLogger),
Features: features.New(),
}
result.options = appoptions
// Initialise the app
err := result.Init()
return result, err
return result
}
// Run the application
func (a *App) Run() error {
var err error
// Setup a context
var subsystemWaitGroup sync.WaitGroup
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
ctx, cancel := context.WithCancel(parentContext)
// 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()
err = a.servicebus.Start()
if err != nil {
return err
}
a.servicebus.Start()
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback)
if err != nil {
return err
}
a.runtime = runtimesubsystem
err = a.runtime.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("wails:loglevel", a.options.LogLevel)
a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options)
// 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)
log, err := subsystem.NewLog(a.servicebus, a.logger)
if err != nil {
return err
}
a.log = log
err = a.log.Start()
if err != nil {
return err
}
a.log.Start()
// create the dispatcher
dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
@@ -156,56 +121,23 @@ func (a *App) Run() error {
return err
}
a.dispatcher = dispatcher
err = dispatcher.Start()
if err != nil {
return err
}
if a.options.Mac.URLHandlers != nil {
// Start the url handler subsystem
url, err := subsystem.NewURL(a.servicebus, a.logger, a.options.Mac.URLHandlers)
if err != nil {
return err
}
a.url = url
err = a.url.Start()
if err != nil {
return err
}
}
dispatcher.Start()
// Start the eventing subsystem
eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger)
if err != nil {
return err
}
a.event = eventsubsystem
err = a.event.Start()
if err != nil {
return err
}
// Start the menu subsystem
menusubsystem, err := subsystem.NewMenu(ctx, a.servicebus, a.logger, a.menuManager)
if err != nil {
return err
}
a.menu = menusubsystem
err = a.menu.Start()
event, err := subsystem.NewEvent(a.servicebus, a.logger)
if err != nil {
return err
}
a.event = event
a.event.Start()
// Start the call subsystem
callSubsystem, err := subsystem.NewCall(ctx, a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
if err != nil {
return err
}
a.call = callSubsystem
err = a.call.Start()
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB())
if err != nil {
return err
}
a.call = call
a.call.Start()
// Dump bindings as a debug
bindingDump, err := a.bindings.ToJSON()
@@ -213,42 +145,20 @@ func (a *App) Run() error {
return err
}
// Setup signal handler
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger)
if err != nil {
return err
}
a.signal = signalsubsystem
a.signal.Start()
err = a.window.Run(dispatcher, bindingDump, a.debug)
result := a.window.Run(dispatcher, bindingDump, a.Features)
a.logger.Trace("Ffenestri.Run() exited")
if err != nil {
return err
}
a.servicebus.Stop()
// Close down all the subsystems
a.logger.Trace("Cancelling subsystems")
cancel()
subsystemWaitGroup.Wait()
a.logger.Trace("Cancelling dispatcher")
dispatcher.Close()
// Close log
a.logger.Trace("Stopping log")
log.Close()
a.logger.Trace("Stopping Service bus")
err = a.servicebus.Stop()
if err != nil {
return err
}
// Shutdown callback
if a.shutdownCallback != nil {
a.shutdownCallback()
}
return nil
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())
}
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/features"
"github.com/wailsapp/wails/v2/internal/ffenestri"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
@@ -42,7 +43,8 @@ type App struct {
dispatcher *messagedispatcher.Dispatcher
servicebus *servicebus.ServiceBus
debug bool
// Feature flags
Features *features.Features
}
// Create App
@@ -56,15 +58,17 @@ func CreateApp(options *Options) *App {
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,
DevTools: options.DevTools,
Title: options.Title,
Width: options.Width,
Height: options.Height,
MinWidth: options.MinWidth,
MinHeight: options.MinHeight,
MaxWidth: options.MaxWidth,
MaxHeight: options.MaxHeight,
Frameless: options.Frameless,
// This should be controlled by the compile time flags...
DevTools: true,
Resizable: !options.DisableResize,
Fullscreen: options.Fullscreen,
@@ -75,12 +79,9 @@ func CreateApp(options *Options) *App {
webserver: webserver.NewWebServer(myLogger),
servicebus: servicebus.New(myLogger),
logger: myLogger,
bindings: binding.NewBindings(myLogger, options.Bind),
bindings: binding.NewBindings(myLogger),
}
// Initialise the app
app.Init()
return app
}
@@ -184,7 +185,7 @@ func (a *App) Run() error {
}
}()
result := a.window.Run(dispatcher, bindingDump)
result := a.window.Run(dispatcher, bindingDump, a.Features)
a.servicebus.Stop()
return result
@@ -192,3 +193,14 @@ func (a *App) Run() error {
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())
}
}

View File

@@ -0,0 +1,34 @@
package app
// Options for creating the App
type Options struct {
Title string
Width int
Height int
DisableResize bool
Fullscreen bool
Frameless bool
MinWidth int
MinHeight int
MaxWidth int
MaxHeight int
}
// mergeDefaults will set the minimum default values for an application
func (o *Options) mergeDefaults() {
// Create a default title
if len(o.Title) == 0 {
o.Title = "My Wails App"
}
// Default width
if o.Width == 0 {
o.Width = 1024
}
// Default height
if o.Height == 0 {
o.Height = 768
}
}

View File

@@ -6,13 +6,10 @@ import (
"os"
"path/filepath"
"github.com/wailsapp/wails/v2/pkg/options"
"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/runtime"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/internal/subsystem"
"github.com/wailsapp/wails/v2/internal/webserver"
@@ -20,57 +17,36 @@ import (
// App defines a Wails application structure
type App struct {
appType string
binding *subsystem.Binding
call *subsystem.Call
event *subsystem.Event
log *subsystem.Log
runtime *subsystem.Runtime
options *options.App
bindings *binding.Bindings
logger *logger.Logger
dispatcher *messagedispatcher.Dispatcher
servicebus *servicebus.ServiceBus
webserver *webserver.WebServer
debug bool
// Application Stores
loglevelStore *runtime.Store
appconfigStore *runtime.Store
// Startup/Shutdown
startupCallback func(*runtime.Runtime)
shutdownCallback func()
}
// Create App
func CreateApp(appoptions *options.App) (*App, error) {
func CreateApp(options *Options) *App {
options.mergeDefaults()
// We ignore the inputs (for now)
// Merge default options
options.MergeDefaults(appoptions)
// Set up logger
myLogger := logger.New(appoptions.Logger)
myLogger.SetLogLevel(appoptions.LogLevel)
// TODO: Allow logger output override on CLI
myLogger := logger.New(os.Stdout)
myLogger.SetLogLevel(logger.TRACE)
result := &App{
appType: "server",
bindings: binding.NewBindings(myLogger, options.Bind),
logger: myLogger,
servicebus: servicebus.New(myLogger),
webserver: webserver.NewWebServer(myLogger),
startupCallback: appoptions.Startup,
shutdownCallback: appoptions.Shutdown,
bindings: binding.NewBindings(myLogger),
logger: myLogger,
servicebus: servicebus.New(myLogger),
webserver: webserver.NewWebServer(myLogger),
}
// Initialise app
result.Init()
return result, nil
return result
}
// Run the application
@@ -107,21 +83,8 @@ func (a *App) Run() error {
if debugMode {
a.servicebus.Debug()
}
// Start the runtime
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
if err != nil {
return err
}
a.runtime = runtime
a.runtime.Start()
// Application Stores
a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options)
a.servicebus.Start()
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
log, err := subsystem.NewLog(a.servicebus, a.logger)
if err != nil {
return err
}
@@ -134,6 +97,14 @@ func (a *App) Run() error {
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 {
@@ -151,7 +122,7 @@ func (a *App) Run() error {
a.event.Start()
// Start the call subsystem
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB())
if err != nil {
return err
}
@@ -171,3 +142,14 @@ func (a *App) Run() error {
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())
}
}

View File

@@ -2,55 +2,30 @@ package binding
import (
"fmt"
"reflect"
"runtime"
"strings"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/logger"
)
type Bindings struct {
db *DB
logger logger.CustomLogger
exemptions slicer.StringSlicer
db *DB
logger logger.CustomLogger
}
// NewBindings returns a new Bindings object
func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}) *Bindings {
result := &Bindings{
func NewBindings(logger *logger.Logger) *Bindings {
return &Bindings{
db: newDB(),
logger: logger.CustomLogger("Bindings"),
}
for _, exemption := range exemptions {
if exemptions == nil {
continue
}
name := runtime.FuncForPC(reflect.ValueOf(exemption).Pointer()).Name()
// Yuk yuk yuk! Is there a better way?
name = strings.TrimSuffix(name, "-fm")
result.exemptions.Add(name)
}
// Add the structs to bind
for _, ptr := range structPointersToBind {
err := result.Add(ptr)
if err != nil {
logger.Fatal("Error during binding: " + err.Error())
}
}
return result
}
// Add the given struct methods to the Bindings
func (b *Bindings) Add(structPtr interface{}) error {
methods, err := b.getMethods(structPtr)
methods, err := getMethods(structPtr)
if err != nil {
return fmt.Errorf("cannot bind value to app: %s", err.Error())
return fmt.Errorf("unable to Add() - %s", err.Error())
}
for _, method := range methods {
@@ -59,8 +34,29 @@ func (b *Bindings) Add(structPtr interface{}) error {
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
}

View File

@@ -1,9 +1,9 @@
package binding
import (
"encoding/json"
"fmt"
"reflect"
"strings"
)
// BoundMethod defines all the data related to a Go method that is
@@ -16,6 +16,58 @@ type BoundMethod struct {
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("*goruntime.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)
@@ -26,29 +78,6 @@ 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())
if len(args) != b.InputCount() {
return nil, fmt.Errorf("received %d arguments to method '%s', expected %d", len(args), b.Name, 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
@@ -65,8 +94,17 @@ func (b *BoundMethod) Call(args []interface{}) (interface{}, error) {
// Iterate over given arguments
for index, arg := range args {
// Attempt to convert the argument to the type expected by the method
value, err := convertArgToValue(arg, b.Inputs[index])
// If it fails, return a suitable error
if err != nil {
return nil, fmt.Errorf("%s (parameter %d): %s", b.Name, index+1, err.Error())
}
// Save the converted argument
callArgs[index] = reflect.ValueOf(arg)
callArgs[index] = value
}
// Do the call

View File

@@ -15,6 +15,10 @@ type DB struct {
// 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
}
@@ -90,6 +94,38 @@ func (d *DB) AddMethod(packageName string, structName string, methodName string,
}
// 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) {
@@ -102,3 +138,13 @@ func (d *DB) ToJSON() (string, error) {
// 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
}

View File

@@ -3,7 +3,6 @@ package binding
import (
"fmt"
"reflect"
"runtime"
)
// isStructPtr returns true if the value given is a
@@ -13,35 +12,14 @@ func isStructPtr(value interface{}) bool {
reflect.ValueOf(value).Elem().Kind() == reflect.Struct
}
// isFunction returns true if the given value is a function
func isFunction(value interface{}) bool {
return reflect.ValueOf(value).Kind() == reflect.Func
}
// isStructPtr returns true if the value given is a struct
func isStruct(value interface{}) bool {
return reflect.ValueOf(value).Kind() == reflect.Struct
}
func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
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)
}
if isFunction(value) {
name := runtime.FuncForPC(reflect.ValueOf(value).Pointer()).Name()
return nil, fmt.Errorf("%s is a function, not a pointer to a struct. Wails v2 has deprecated the binding of functions. Please wrap your functions up in a struct and bind a pointer to that struct.", name)
}
return nil, fmt.Errorf("not a pointer to a struct.")
return nil, fmt.Errorf("not a pointer to an interface")
}
// Process Struct
@@ -56,11 +34,6 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
fullMethodName := baseName + "." + methodName
method := structValue.MethodByName(methodName)
methodReflectName := runtime.FuncForPC(methodDef.Func.Pointer()).Name()
if b.exemptions.Contains(methodReflectName) {
continue
}
// Create new method
boundMethod := &BoundMethod{
Name: fullMethodName,
@@ -83,8 +56,6 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
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++ {
@@ -100,3 +71,39 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
}
return result, nil
}
// convertArgToValue
func convertArgToValue(input interface{}, 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
}

View File

@@ -0,0 +1,13 @@
package features
// Features holds generic and platform specific feature flags
type Features struct {
Linux *Linux
}
// New creates a new Features object
func New() *Features {
return &Features{
Linux: &Linux{},
}
}

View File

@@ -0,0 +1,5 @@
package features
// Linux holds linux specific feature flags
type Linux struct {
}

View File

@@ -0,0 +1,23 @@
// +build linux
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 "github.com/wailsapp/wails/v2/internal/features"
func (a *Application) processOSFeatureFlags(features *features.Features) {
// Process Linux features
// linux := features.Linux
}

View File

@@ -5,11 +5,9 @@ import (
"strings"
"unsafe"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/internal/features"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/pkg/options"
)
/*
@@ -17,9 +15,6 @@ import (
#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"
@@ -27,16 +22,42 @@ import (
*/
import "C"
// DEBUG is the global Ffenestri debug flag.
// TODO: move to compile time.
var DEBUG bool = true
// Config defines how our application should be configured
type Config struct {
Title string
Width int
Height int
MinWidth int
MinHeight int
MaxWidth int
MaxHeight int
DevTools bool
Resizable bool
Fullscreen bool
Frameless bool
}
var defaultConfig = &Config{
Title: "My Wails App",
Width: 800,
Height: 600,
DevTools: true,
Resizable: true,
Fullscreen: false,
Frameless: false,
}
// Application is our main application object
type Application struct {
config *options.App
config *Config
memory []unsafe.Pointer
// This is the main app pointer
app *C.struct_Application
// Manages menus
menuManager *menumanager.Manager
app unsafe.Pointer
// Logger
logger logger.CustomLogger
@@ -57,18 +78,17 @@ func init() {
}
// NewApplicationWithConfig creates a new application based on the given config
func NewApplicationWithConfig(config *options.App, logger *logger.Logger, menuManager *menumanager.Manager) *Application {
func NewApplicationWithConfig(config *Config, logger *logger.Logger) *Application {
return &Application{
config: config,
logger: logger.CustomLogger("Ffenestri"),
menuManager: menuManager,
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,
config: defaultConfig,
logger: logger.CustomLogger("Ffenestri"),
}
}
@@ -101,29 +121,18 @@ 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, debug bool) error {
func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, features *features.Features) 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)
resizable := a.bool2Cint(a.config.Resizable)
devtools := a.bool2Cint(a.config.DevTools)
fullscreen := a.bool2Cint(a.config.Fullscreen)
startHidden := a.bool2Cint(a.config.StartHidden)
logLevel := C.int(a.config.LogLevel)
hideWindowOnClose := a.bool2Cint(a.config.HideWindowOnClose)
app := C.NewApplication(title, width, height, resizable, devtools, fullscreen, startHidden, logLevel, hideWindowOnClose)
app := C.NewApplication(title, width, height, resizable, devtools, fullscreen)
// Save app reference
a.app = (*C.struct_Application)(app)
a.app = unsafe.Pointer(app)
// Set Min Window Size
minWidth := C.int(a.config.MinWidth)
@@ -136,16 +145,11 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug
C.SetMaxWindowSize(a.app, maxWidth, maxHeight)
// Set debug if needed
C.SetDebug(app, a.bool2Cint(debug))
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)
// Set Frameless
if a.config.Frameless {
C.DisableFrame(a.app)
}
// Escape bindings so C doesn't freak out
@@ -154,16 +158,13 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug
// Set bindings
C.SetBindings(app, a.string2CString(bindings))
// Process feature flags
a.processFeatureFlags(features)
// save the dispatcher in a package variable so that the C callbacks
// can access it
dispatcher = incomingDispatcher.RegisterClient(newClient(a))
// Process platform settings
err := a.processPlatformSettings()
if err != nil {
return err
}
// Check we could initialise the application
if app != nil {
// Yes - Save memory reference and run app, cleaning up afterwards
@@ -185,3 +186,11 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug
func messageFromWindowCallback(data *C.char) {
dispatcher.DispatchMessage(C.GoString(data))
}
func (a *Application) processFeatureFlags(features *features.Features) {
// Process generic features
// Process OS Specific flags
a.processOSFeatureFlags(features)
}

View File

@@ -2,44 +2,23 @@
#define __FFENESTRI_H__
#include <stdio.h>
struct Application;
extern struct Application *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose);
extern void SetMinWindowSize(struct Application*, int minWidth, int minHeight);
extern void SetMaxWindowSize(struct Application*, int maxWidth, int maxHeight);
extern void Run(struct Application*, int argc, char **argv);
extern void DestroyApplication(struct Application*);
extern void SetDebug(struct Application*, int flag);
extern void SetBindings(struct Application*, const char *bindings);
extern void ExecJS(struct Application*, const char *script);
extern void Hide(struct Application*);
extern void Show(struct Application*);
extern void Center(struct Application*);
extern void Maximise(struct Application*);
extern void Unmaximise(struct Application*);
extern void ToggleMaximise(struct Application*);
extern void Minimise(struct Application*);
extern void Unminimise(struct Application*);
extern void ToggleMinimise(struct Application*);
extern void SetColour(struct Application*, int red, int green, int blue, int alpha);
extern void SetSize(struct Application*, int width, int height);
extern void SetPosition(struct Application*, int x, int y);
extern void Quit(struct Application*);
extern void SetTitle(struct Application*, const char *title);
extern void Fullscreen(struct Application*);
extern void UnFullscreen(struct Application*);
extern void ToggleFullscreen(struct Application*);
extern void DisableFrame(struct Application*);
extern void OpenDialog(struct Application*, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories);
extern void SaveDialog(struct Application*, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories);
extern void MessageDialog(struct Application*, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton);
extern void DarkModeEnabled(struct Application*, char *callbackID);
extern void SetApplicationMenu(struct Application*, const char *);
extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
extern void SetTrayMenu(struct Application*, const char *menuTrayJSON);
extern void DeleteTrayMenuByID(struct Application*, const char *id);
extern void UpdateTrayMenuLabel(struct Application*, const char* JSON);
extern void AddContextMenu(struct Application*, char *contextMenuJSON);
extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);
extern void *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen);
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 Quit(void *app);
extern void SetTitle(void *app, const char *title);
extern void Fullscreen(void *app);
extern void UnFullscreen(void *app);
extern int SetColour(void *app, const char *colourString);
extern void DisableFrame(void *app);
extern char *SaveFileDialogOnMainThread(void *appPointer, char *title);
extern char *OpenFileDialogOnMainThread(void *appPointer, char *title);
extern char *OpenDirectoryDialogOnMainThread(void *appPointer, char *title);
#endif

View File

@@ -13,8 +13,7 @@ import "C"
import (
"strconv"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"unsafe"
"github.com/wailsapp/wails/v2/internal/logger"
)
@@ -67,148 +66,44 @@ 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))
}
func (c *Client) WindowSetMinSize(width int, height int) {
C.SetMinWindowSize(c.app.app, C.int(width), C.int(height))
}
func (c *Client) WindowSetMaxSize(width int, height int) {
C.SetMaxWindowSize(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)
func (c *Client) WindowSetColour(colour string) bool {
result := C.SetColour(c.app.app, c.app.string2CString(colour))
return result == 1
}
// OpenDialog will open a dialog with the given title and filter
func (c *Client) OpenDialog(dialogOptions *dialog.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.ResolvesAliases),
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
)
}
// SaveDialog will open a dialog with the given title and filter
func (c *Client) SaveDialog(dialogOptions *dialog.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),
)
}
// MessageDialog will open a message dialog with the given options
func (c *Client) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
// Sanity check button length
if len(dialogOptions.Buttons) > 4 {
c.app.logger.Error("Given %d message dialog buttons. Maximum is 4", len(dialogOptions.Buttons))
return
// OpenFileDialog will open a file dialog with the given title
func (c *Client) OpenFileDialog(title string) string {
cstring := C.OpenFileDialogOnMainThread(c.app.app, c.app.string2CString(title))
var result string
if cstring != nil {
result = C.GoString(cstring)
// Free the C string that was allocated by the dialog
C.free(unsafe.Pointer(cstring))
}
return result
}
// Process buttons
buttons := []string{"", "", "", ""}
for i, button := range dialogOptions.Buttons {
buttons[i] = button
// SaveFileDialog will open a save file dialog with the given title
func (c *Client) SaveFileDialog(title string) string {
cstring := C.SaveFileDialogOnMainThread(c.app.app, c.app.string2CString(title))
var result string
if cstring != nil {
result = C.GoString(cstring)
// Free the C string that was allocated by the dialog
C.free(unsafe.Pointer(cstring))
}
C.MessageDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(string(dialogOptions.Type)),
c.app.string2CString(dialogOptions.Title),
c.app.string2CString(dialogOptions.Message),
c.app.string2CString(dialogOptions.Icon),
c.app.string2CString(buttons[0]),
c.app.string2CString(buttons[1]),
c.app.string2CString(buttons[2]),
c.app.string2CString(buttons[3]),
c.app.string2CString(dialogOptions.DefaultButton),
c.app.string2CString(dialogOptions.CancelButton))
return result
}
func (c *Client) DarkModeEnabled(callbackID string) {
C.DarkModeEnabled(c.app.app, c.app.string2CString(callbackID))
}
func (c *Client) SetApplicationMenu(applicationMenuJSON string) {
C.SetApplicationMenu(c.app.app, c.app.string2CString(applicationMenuJSON))
}
func (c *Client) SetTrayMenu(trayMenuJSON string) {
C.SetTrayMenu(c.app.app, c.app.string2CString(trayMenuJSON))
}
func (c *Client) UpdateTrayMenuLabel(JSON string) {
C.UpdateTrayMenuLabel(c.app.app, c.app.string2CString(JSON))
}
func (c *Client) UpdateContextMenu(contextMenuJSON string) {
C.UpdateContextMenu(c.app.app, c.app.string2CString(contextMenuJSON))
}
func (c *Client) DeleteTrayMenuByID(id string) {
C.DeleteTrayMenuByID(c.app.app, c.app.string2CString(id))
// OpenDirectoryDialog will open a directory dialog with the given title
func (c *Client) OpenDirectoryDialog(title string) string {
cstring := C.OpenDirectoryDialogOnMainThread(c.app.app, c.app.string2CString(title))
var result string
if cstring != nil {
result = C.GoString(cstring)
// Free the C string that was allocated by the dialog
C.free(unsafe.Pointer(cstring))
}
return result
}

View File

@@ -80,7 +80,6 @@ struct Application
int height;
int resizable;
int devtools;
int startHidden;
int fullscreen;
int minWidth;
int minHeight;
@@ -101,7 +100,7 @@ struct Application
int lock;
};
void *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden)
void *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen)
{
// Setup main application struct
struct Application *result = malloc(sizeof(struct Application));
@@ -116,11 +115,15 @@ void *NewApplication(const char *title, int width, int height, int resizable, in
result->maxWidth = 0;
result->maxHeight = 0;
result->frame = 1;
result->startHidden = startHidden;
// Default drag button is PRIMARY
result->dragButton = PRIMARY_MOUSE_BUTTON;
// printf("\n\nWidth: %d\n", result->width);
// printf("Height: %d\n\n", result->height);
// Features
result->sendMessageToBackend = (ffenestriCallback)messageFromWindowCallback;
// Create a unique ID based on the current unix timestamp
@@ -136,9 +139,10 @@ void *NewApplication(const char *title, int width, int height, int resizable, in
return (void *)result;
}
void DestroyApplication(struct Application *app)
void DestroyApplication(void *appPointer)
{
Debug("Destroying Application");
struct Application *app = (struct Application *)appPointer;
g_application_quit(G_APPLICATION(app->application));
@@ -167,11 +171,9 @@ void DestroyApplication(struct Application *app)
// 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(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
@@ -189,34 +191,45 @@ void DestroyApplication(struct Application *app)
// Quit will stop the gtk application and free up all the memory
// used by the application
void Quit(struct Application *app)
void Quit(void *appPointer)
{
Debug("Quit Called");
struct Application *app = (struct Application *)appPointer;
gtk_window_close((GtkWindow *)app->mainWindow);
g_application_quit((GApplication *)app->application);
DestroyApplication(app);
DestroyApplication(appPointer);
}
// SetTitle sets the main window title to the given string
void SetTitle(struct Application *app, const char *title)
void SetTitle(void *appPointer, const char *title)
{
struct Application *app = (struct Application *)appPointer;
gtk_window_set_title(app->mainWindow, title);
}
// Fullscreen sets the main window to be fullscreen
void Fullscreen(struct Application *app)
void Fullscreen(void *appPointer)
{
struct Application *app = (struct Application *)appPointer;
gtk_window_fullscreen(app->mainWindow);
}
// UnFullscreen resets the main window after a fullscreen
void UnFullscreen(struct Application *app)
void UnFullscreen(void *appPointer)
{
struct Application *app = (struct Application *)appPointer;
gtk_window_unfullscreen(app->mainWindow);
}
void setMinMaxSize(struct Application *app)
void Center(void *appPointer)
{
struct Application *app = (struct Application *)appPointer;
gtk_window_set_position(app->mainWindow, GTK_WIN_POS_CENTER);
}
void setMinMaxSize(void *appPointer)
{
struct Application *app = (struct Application *)appPointer;
GdkGeometry size;
size.min_width = size.min_height = size.max_width = size.max_height = 0;
int flags = 0;
@@ -232,43 +245,32 @@ void setMinMaxSize(struct Application *app)
size.min_width = app->minWidth;
flags |= GDK_HINT_MIN_SIZE;
}
Debug("size: %dx%d", app->width, app->height);
Debug("min: %dx%d", size.min_width, size.min_height);
Debug("max: %dx%d", size.max_width, size.max_height);
gtk_window_set_geometry_hints(app->mainWindow, NULL, &size, flags);
}
char *fileDialogInternal(struct Application *app, GtkFileChooserAction chooserAction, char **args) {
// OpenFileDialog opens a dialog to select a file
// NOTE: The result is a string that will need to be freed!
char *OpenFileDialog(void *appPointer, char *title)
{
struct Application *app = (struct Application *)appPointer;
GtkFileChooserNative *native;
GtkFileChooserAction action = chooserAction;
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
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)
{
GtkFileChooser *chooser = GTK_FILE_CHOOSER(native);
filename = gtk_file_chooser_get_filename(chooser);
}
@@ -277,65 +279,104 @@ char *fileDialogInternal(struct Application *app, GtkFileChooserAction chooserAc
return filename;
}
// openFileDialogInternal opens a dialog to select a file
// SaveFileDialog 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)
char *SaveFileDialog(void *appPointer, char *title)
{
return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_OPEN, args);
struct Application *app = (struct Application *)appPointer;
GtkFileChooserNative *native;
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE;
gint res;
char *filename;
native = gtk_file_chooser_native_new(title,
app->mainWindow,
action,
"_Save",
"_Cancel");
res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
if (res == GTK_RESPONSE_ACCEPT)
{
GtkFileChooser *chooser = GTK_FILE_CHOOSER(native);
filename = gtk_file_chooser_get_filename(chooser);
}
g_object_unref(native);
return filename;
}
// saveFileDialogInternal opens a dialog to select a file
// OpenDirectoryDialog opens a dialog to select a directory
// NOTE: The result is a string that will need to be freed!
char *saveFileDialogInternal(struct Application *app, char **args)
char *OpenDirectoryDialog(void *appPointer, char *title)
{
return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_SAVE, args);
struct Application *app = (struct Application *)appPointer;
GtkFileChooserNative *native;
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
gint res;
char *foldername;
native = gtk_file_chooser_native_new(title,
app->mainWindow,
action,
"_Open",
"_Cancel");
res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
if (res == GTK_RESPONSE_ACCEPT)
{
GtkFileChooser *chooser = GTK_FILE_CHOOSER(native);
foldername = gtk_file_chooser_get_filename(chooser);
}
g_object_unref(native);
return foldername;
}
// 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)
void SetMinWindowSize(void *appPointer, int minWidth, int minHeight)
{
struct Application *app = (struct Application *)appPointer;
app->minWidth = minWidth;
app->minHeight = minHeight;
}
void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight)
void SetMaxWindowSize(void *appPointer, int maxWidth, int maxHeight)
{
struct Application *app = (struct Application *)appPointer;
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)
int SetColour(void *appPointer, const char *colourString)
{
struct Application *app = (struct Application *)appPointer;
GdkRGBA rgba;
gboolean result = gdk_rgba_parse(&rgba, colourString);
if (result == FALSE)
{
return 0;
}
// Debug("Setting webview colour to: %s", colourString);
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)
void DisableFrame(void *appPointer)
{
struct Application *app = (struct Application *)appPointer;
app->frame = 0;
}
void syncCallback(GObject *source_object,
GAsyncResult *res,
void *data)
gpointer user_data)
{
struct Application *app = (struct Application *)data;
struct Application *app = (struct Application *)user_data;
app->lock = 0;
}
@@ -343,6 +384,8 @@ void syncEval(struct Application *app, const gchar *script)
{
WebKitWebView *webView = (WebKitWebView *)(app->webView);
// Debug("[%p] webview\n", webView);
// Debug("[%p] Running sync\n", script);
// wait for lock to free
while (app->lock == 1)
@@ -352,15 +395,18 @@ void syncEval(struct Application *app, const gchar *script)
// Set lock
app->lock = 1;
//
webkit_web_view_run_javascript(
webView,
script,
NULL, syncCallback, (void*)app);
NULL, syncCallback, app);
while (app->lock == 1)
{
// Debug("[%p] Waiting for callback\n", script);
g_main_context_iteration(0, true);
}
// Debug("[%p] Finished\n", script);
}
void asyncEval(WebKitWebView *webView, const gchar *script)
@@ -371,7 +417,7 @@ void asyncEval(WebKitWebView *webView, const gchar *script)
NULL, NULL, NULL);
}
typedef void (*dispatchMethod)(struct Application *app, void *);
typedef void (*dispatchMethod)(void *app, void *);
struct dispatchData
{
@@ -383,14 +429,20 @@ struct dispatchData
gboolean executeMethod(gpointer data)
{
struct dispatchData *d = (struct dispatchData *)data;
(d->method)(d->app, d->args);
struct Application *app = (struct Application *)(d->app);
// Debug("Webview %p\n", app->webView);
// Debug("Args %s\n", d->args);
// Debug("Method %p\n", (d->method));
(d->method)(app, d->args);
// Debug("Method Execute Complete. Freeing memory");
g_free(d);
return FALSE;
}
void ExecJS(struct Application *app, char *js)
void ExecJS(void *app, char *js)
{
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
struct dispatchData *data =
(struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)syncEval;
data->args = js;
data->app = app;
@@ -398,14 +450,13 @@ void ExecJS(struct Application *app, char *js)
gdk_threads_add_idle(executeMethod, data);
}
typedef char *(*dialogMethod)(struct Application *app, void *);
typedef char *(*dialogMethod)(void *app, void *);
struct dialogCall
{
struct Application *app;
dialogMethod method;
void *args;
void *filter;
char *result;
int done;
};
@@ -413,70 +464,79 @@ struct dialogCall
gboolean executeMethodWithReturn(gpointer data)
{
struct dialogCall *d = (struct dialogCall *)data;
d->result = (d->method)(d->app, d->args);
struct Application *app = (struct Application *)(d->app);
Debug("Webview %p\n", app->webView);
Debug("Args %s\n", d->args);
Debug("Method %p\n", (d->method));
d->result = (d->method)(app, d->args);
d->done = 1;
// Debug("Method Execute Complete. Freeing memory");
return FALSE;
}
char *OpenFileDialog(struct Application *app, char *title, char *filter)
char *OpenFileDialogOnMainThread(void *app, char *title)
{
struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
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->method = (dialogMethod)OpenFileDialog;
data->args = title;
data->app = app;
gdk_threads_add_idle(executeMethodWithReturn, data);
while (data->done == 0)
{
// Debug("Waiting for dialog");
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)
char *SaveFileDialogOnMainThread(void *app, char *title)
{
struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
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->method = (dialogMethod)SaveFileDialog;
data->args = title;
data->app = app;
gdk_threads_add_idle(executeMethodWithReturn, data);
while (data->done == 0)
{
// Debug("Waiting for dialog");
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 *OpenDirectoryDialogOnMainThread(void *app, char *title)
{
struct dialogCall *data =
(struct dialogCall *)g_new(struct dialogCall, 1);
data->result = NULL;
data->done = 0;
data->method = (dialogMethod)OpenDirectoryDialog;
data->args = title;
data->app = app;
gdk_threads_add_idle(executeMethodWithReturn, data);
while (data->done == 0)
{
// Debug("Waiting for dialog");
usleep(100000);
}
Debug("Directory Dialog done");
@@ -495,8 +555,10 @@ void setIcon(struct Application *app)
static void load_finished_cb(WebKitWebView *webView,
WebKitLoadEvent load_event,
struct Application *app)
gpointer userData)
{
struct Application *app;
switch (load_event)
{
// case WEBKIT_LOAD_STARTED:
@@ -518,6 +580,7 @@ static void load_finished_cb(WebKitWebView *webView,
case WEBKIT_LOAD_FINISHED:
/* Load finished, we can now stop the spinner */
// printf("Finished loading: %s\n", webkit_web_view_get_uri(web_view));
app = (struct Application *)userData;
// Bindings
Debug("Binding Methods");
@@ -574,23 +637,21 @@ static void load_finished_cb(WebKitWebView *webView,
setMinMaxSize(app);
// Centre by default
gtk_window_set_position(app->mainWindow, GTK_WIN_POS_CENTER);
Center(app);
// Show window and focus
if( app->startHidden == 0) {
gtk_widget_show_all(GTK_WIDGET(app->mainWindow));
gtk_widget_grab_focus(app->webView);
}
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)
static gboolean
disable_context_menu_cb(WebKitWebView *web_view,
WebKitContextMenu *context_menu,
GdkEvent *event,
WebKitHitTestResult *hit_test_result,
gpointer user_data)
{
return TRUE;
}
@@ -605,11 +666,12 @@ static void printEvent(const char *message, GdkEventButton *event)
event->time);
}
static void dragWindow(WebKitUserContentManager *contentManager,
WebKitJavascriptResult *result,
struct Application *app)
gpointer arg)
{
struct Application *app = (struct Application *)arg;
// If we get this message erroneously, ignore
if (app->dragButtonEvent == NULL)
{
@@ -634,22 +696,31 @@ static void dragWindow(WebKitUserContentManager *contentManager,
// Clear the event
app->dragButtonEvent = NULL;
return;
return FALSE;
}
gboolean buttonPress(GtkWidget *widget, GdkEventButton *event, struct Application *app)
gboolean buttonPress(GtkWidget *widget, GdkEventButton *event, gpointer arg)
{
struct Application *app = (struct Application *)arg;
Debug("I am pressing a button");
if (event->type == GDK_BUTTON_PRESS && event->button == app->dragButton)
{
printEvent("Drag button event was saved", event);
app->dragButtonEvent = event;
}
return FALSE;
}
gboolean buttonRelease(GtkWidget *widget, GdkEventButton *event, struct Application *app)
gboolean buttonRelease(GtkWidget *widget, GdkEventButton *event, gpointer arg)
{
struct Application *app = (struct Application *)arg;
Debug("I am releasing a button");
if (event->type == GDK_BUTTON_RELEASE && event->button == app->dragButton)
{
printEvent("Drag button event was reset", event);
app->dragButtonEvent = NULL;
}
return FALSE;
@@ -657,8 +728,9 @@ gboolean buttonRelease(GtkWidget *widget, GdkEventButton *event, struct Applicat
static void sendMessageToBackend(WebKitUserContentManager *contentManager,
WebKitJavascriptResult *result,
struct Application *app)
gpointer arg)
{
struct Application *app = (struct Application *)arg;
#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);
@@ -675,225 +747,16 @@ static void sendMessageToBackend(WebKitUserContentManager *contentManager,
g_free(message);
}
void SetDebug(struct Application *app, int flag)
void SetDebug(void *applicationPointer, int flag)
{
struct Application *app = (struct Application *)applicationPointer;
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)
void SetBindings(void *applicationPointer, const char *bindings)
{
// Get the geometry of the monitor
GdkRectangle m = getCurrentMonitorGeometry(app->mainWindow);
struct Application *app = (struct Application *)applicationPointer;
// 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);
@@ -903,15 +766,18 @@ void SetBindings(struct Application *app, const char *bindings)
// This is called when the close button on the window is pressed
gboolean close_button_pressed(GtkWidget *widget,
GdkEvent *event,
struct Application *app)
gpointer user_data)
{
struct Application *app = (struct Application *)user_data;
app->sendMessageToBackend("WC"); // Window Close
return TRUE;
}
static void setupWindow(struct Application *app)
static void setupWindow(void *applicationPointer)
{
struct Application *app = (struct Application *)applicationPointer;
// Create the window
GtkWidget *mainWindow = gtk_application_window_new(app->application);
// Save reference
@@ -930,14 +796,10 @@ static void setupWindow(struct Application *app)
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);
}
// Setup the window drag handler
webkit_user_content_manager_register_script_message_handler(contentManager, "windowDrag");
app->signalWindowDrag = g_signal_connect(contentManager, "script-message-received::windowDrag", G_CALLBACK(dragWindow), app);
GtkWidget *webView = webkit_web_view_new_with_user_content_manager(contentManager);
// Save reference
@@ -946,6 +808,9 @@ static void setupWindow(struct Application *app)
// Add the webview to the window
gtk_container_add(GTK_CONTAINER(mainWindow), webView);
// 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);
// Load default HTML
app->signalLoadChanged = g_signal_connect(G_OBJECT(webView), "load-changed", G_CALLBACK(load_finished_cb), app);
@@ -970,13 +835,20 @@ static void setupWindow(struct Application *app)
g_signal_connect(GTK_WIDGET(mainWindow), "delete-event", G_CALLBACK(close_button_pressed), app);
}
static void activate(GtkApplication* _, struct Application *app)
static void
activate(GtkApplication *app,
gpointer user_data)
{
setupWindow(app);
struct Application *mainApp = (struct Application *)user_data;
// Main Window
setupWindow(mainApp);
}
void Run(struct Application *app, int argc, char **argv)
void Run(void *applicationPointer, int argc, char **argv)
{
struct Application *app = (struct Application *)applicationPointer;
g_signal_connect(app->application, "activate", G_CALLBACK(activate), app);
g_application_run(G_APPLICATION(app->application), argc, argv);
}

View File

@@ -9,8 +9,6 @@ import (
"path/filepath"
"runtime"
"unsafe"
"github.com/leaanthony/slicer"
)
// LocalDirectory gets the caller's file directory
@@ -20,45 +18,11 @@ func LocalDirectory() string {
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)
}
// MkDirs creates the given nested directories.
// Returns error on failure
func MkDirs(fullPath string, mode ...os.FileMode) error {
var perms os.FileMode
perms = 0755
if len(mode) == 1 {
perms = mode[0]
}
return os.MkdirAll(fullPath, perms)
}
// MoveFile attempts to move the source file to the target
// Target is a fully qualified path to a file *name*, not a
// directory
func MoveFile(source string, target string) error {
return os.Rename(source, target)
}
// 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)
@@ -180,100 +144,3 @@ func fatal(message ...string) {
}
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
}
// Credit: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04
// CopyDir recursively copies a directory tree, attempting to preserve permissions.
// Source directory must exist, destination directory must *not* exist.
// Symlinks are ignored and skipped.
func CopyDir(src string, dst string) (err error) {
src = filepath.Clean(src)
dst = filepath.Clean(dst)
si, err := os.Stat(src)
if err != nil {
return err
}
if !si.IsDir() {
return fmt.Errorf("source is not a directory")
}
_, err = os.Stat(dst)
if err != nil && !os.IsNotExist(err) {
return
}
if err == nil {
return fmt.Errorf("destination already exists")
}
err = MkDirs(dst)
if err != nil {
return
}
entries, err := ioutil.ReadDir(src)
if err != nil {
return
}
for _, entry := range entries {
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())
if entry.IsDir() {
err = CopyDir(srcPath, dstPath)
if err != nil {
return
}
} else {
// Skip symlinks.
if entry.Mode()&os.ModeSymlink != 0 {
continue
}
err = CopyFile(srcPath, dstPath)
if err != nil {
return
}
}
}
return
}

View File

@@ -3,15 +3,11 @@ 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 {
@@ -67,10 +63,6 @@ func (a *Asset) AsCHexData() string {
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")
@@ -92,16 +84,6 @@ func (a *Asset) AsCHexData() string {
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
@@ -121,7 +103,6 @@ func (a *Asset) AsCHexData() string {
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])
}

View File

@@ -61,8 +61,6 @@ func (a *AssetBundle) processHTML(htmldata string) error {
buf := bytes.NewBufferString(htmldata)
tokenizer := html.NewTokenizer(buf)
paths := slicer.String()
for {
//get the next token type
tokenType := tokenizer.Next()
@@ -102,29 +100,19 @@ func (a *AssetBundle) processHTML(htmldata string) error {
if attr.Key == "href" {
asset.Path = attr.Val
}
// standard stylesheet
// stylesheet
if attr.Key == "rel" && attr.Val == "stylesheet" {
asset.Type = AssetTypes.CSS
}
if attr.Key == "as" && attr.Val == "style" {
asset.Type = AssetTypes.CSS
}
if attr.Key == "as" && attr.Val == "script" {
asset.Type = AssetTypes.JS
}
}
// Ensure we don't include duplicates
if !paths.Contains(asset.Path) {
err := asset.Load(a.basedirectory)
if err != nil {
return err
}
a.assets = append(a.assets, asset)
paths.Add(asset.Path)
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}
@@ -134,14 +122,11 @@ func (a *AssetBundle) processHTML(htmldata string) error {
break
}
}
if !paths.Contains(asset.Path) {
err := asset.Load(a.basedirectory)
if err != nil {
return err
}
a.assets = append(a.assets, asset)
paths.Add(asset.Path)
err := asset.Load(a.basedirectory)
if err != nil {
return err
}
a.assets = append(a.assets, asset)
}
}
}
@@ -156,7 +141,7 @@ func (a *AssetBundle) WriteToCFile(targetDir string) (string, error) {
var cdata strings.Builder
// Write header
header := `// assets.h
header := `// assets.c
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL.
// This file was auto-generated. DO NOT MODIFY.
@@ -168,10 +153,6 @@ func (a *AssetBundle) WriteToCFile(targetDir string) (string, 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)
@@ -179,13 +160,13 @@ func (a *AssetBundle) WriteToCFile(targetDir string) (string, error) {
}
if assetVariables.Length() > 0 {
cdata.WriteString(fmt.Sprintf("\nconst unsigned char *assets[] = { %s, 0x00 };", assetVariables.Join(", ")))
cdata.WriteString(fmt.Sprintf("\nconst char *assets[] = { %s, 0x00 };", assetVariables.Join(", ")))
} else {
cdata.WriteString("\nconst unsigned char *assets[] = { 0x00 };")
cdata.WriteString("\nconst char *assets[] = { 0x00 };")
}
// Save file
assetsFile := filepath.Join(targetDir, "assets.h")
assetsFile := filepath.Join(targetDir, "assets.c")
err = ioutil.WriteFile(assetsFile, []byte(cdata.String()), 0600)
if err != nil {
return "", err
@@ -196,17 +177,16 @@ func (a *AssetBundle) WriteToCFile(targetDir string) (string, error) {
// ConvertToAssetDB returns an assetdb.AssetDB initialized with
// the items in the AssetBundle
func (a *AssetBundle) ConvertToAssetDB() (*assetdb.AssetDB, error) {
theassetdb := assetdb.NewAssetDB()
assetdb := assetdb.NewAssetDB()
// Loop over the Assets
for _, asset := range a.assets {
theassetdb.AddAsset(asset.Path, []byte(asset.Data))
assetdb.AddAsset(asset.Path, []byte(asset.Data))
}
return theassetdb, nil
return assetdb, nil
}
// Dump will output the assets to the terminal
func (a *AssetBundle) Dump() {
println("Assets:")
for _, asset := range a.assets {

View File

@@ -2,30 +2,31 @@ package logger
import (
"fmt"
"os"
)
// 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)
Writeln(message string) error
// Write writes directly to the output with no log level
Write(message string)
Write(message string) error
// Trace level logging. Works like Sprintf.
Trace(format string, args ...interface{})
Trace(format string, args ...interface{}) error
// Debug level logging. Works like Sprintf.
Debug(format string, args ...interface{})
Debug(format string, args ...interface{}) error
// Info level logging. Works like Sprintf.
Info(format string, args ...interface{})
Info(format string, args ...interface{}) error
// Warning level logging. Works like Sprintf.
Warning(format string, args ...interface{})
Warning(format string, args ...interface{}) error
// Error level logging. Works like Sprintf.
Error(format string, args ...interface{})
Error(format string, args ...interface{}) error
// Fatal level logging. Works like Sprintf.
Fatal(format string, args ...interface{})
@@ -49,48 +50,49 @@ func newcustomLogger(logger *Logger, name string) *customLogger {
// Writeln writes directly to the output with no log level
// Appends a carriage return to the message
func (l *customLogger) Writeln(message string) {
l.logger.Writeln(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) {
l.logger.Write(message)
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{}) {
func (l *customLogger) Trace(format string, args ...interface{}) error {
format = fmt.Sprintf("%s | %s", l.name, format)
l.logger.Trace(format, args...)
return l.logger.processLogMessage(TRACE, format, args...)
}
// Debug level logging. Works like Sprintf.
func (l *customLogger) Debug(format string, args ...interface{}) {
func (l *customLogger) Debug(format string, args ...interface{}) error {
format = fmt.Sprintf("%s | %s", l.name, format)
l.logger.Debug(format, args...)
return l.logger.processLogMessage(DEBUG, format, args...)
}
// Info level logging. Works like Sprintf.
func (l *customLogger) Info(format string, args ...interface{}) {
func (l *customLogger) Info(format string, args ...interface{}) error {
format = fmt.Sprintf("%s | %s", l.name, format)
l.logger.Info(format, args...)
return l.logger.processLogMessage(INFO, format, args...)
}
// Warning level logging. Works like Sprintf.
func (l *customLogger) Warning(format string, args ...interface{}) {
func (l *customLogger) Warning(format string, args ...interface{}) error {
format = fmt.Sprintf("%s | %s", l.name, format)
l.logger.Warning(format, args...)
return l.logger.processLogMessage(WARNING, format, args...)
}
// Error level logging. Works like Sprintf.
func (l *customLogger) Error(format string, args ...interface{}) {
func (l *customLogger) Error(format string, args ...interface{}) error {
format = fmt.Sprintf("%s | %s", l.name, format)
l.logger.Error(format, args...)
return l.logger.processLogMessage(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...)
l.logger.processLogMessage(FATAL, format, args...)
os.Exit(1)
}

View File

@@ -2,32 +2,37 @@ package logger
import (
"fmt"
"io"
"os"
"github.com/wailsapp/wails/v2/pkg/logger"
"sync"
)
// 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
writers []io.Writer
logLevel uint8
showLevelInLog bool
lock sync.RWMutex
}
// 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 {
func New(writers ...io.Writer) *Logger {
result := &Logger{
logLevel: logger.INFO,
logLevel: INFO,
showLevelInLog: true,
output: output,
}
for _, writer := range writers {
result.AddOutput(writer)
}
return result
}
// Writers gets the log writers
func (l *Logger) Writers() []io.Writer {
return l.writers
}
// CustomLogger creates a new custom logger that prints out a name/id
// before the messages
func (l *Logger) CustomLogger(name string) CustomLogger {
@@ -40,66 +45,99 @@ func (l *Logger) HideLogLevel() {
}
// SetLogLevel sets the minimum level of logs that will be output
func (l *Logger) SetLogLevel(level LogLevel) {
func (l *Logger) SetLogLevel(level uint8) {
l.logLevel = level
}
// AddOutput adds the given `io.Writer` to the list of destinations
// that get logged to
func (l *Logger) AddOutput(writer io.Writer) {
l.writers = append(l.writers, writer)
}
func (l *Logger) write(loglevel uint8, message string) error {
// Don't print logs lower than the current log level
if loglevel < l.logLevel {
return nil
}
// Show log level text if enabled
if l.showLevelInLog {
message = mapLogLevel[loglevel] + message
}
// write out the logs
l.lock.Lock()
for _, writer := range l.writers {
_, err := writer.Write([]byte(message))
if err != nil {
l.lock.Unlock() // Because defer is slow
return err
}
}
l.lock.Unlock()
return nil
}
// writeln appends a newline character to the message before writing
func (l *Logger) writeln(loglevel uint8, message string) error {
return l.write(loglevel, message+"\n")
}
// Writeln writes directly to the output with no log level
// Appends a carriage return to the message
func (l *Logger) Writeln(message string) {
l.output.Print(message)
func (l *Logger) Writeln(message string) error {
return l.write(BYPASS, message+"\n")
}
// Write writes directly to the output with no log level
func (l *Logger) Write(message string) {
l.output.Print(message)
func (l *Logger) Write(message string) error {
return l.write(BYPASS, message)
}
// Print writes directly to the output with no log level
// Appends a carriage return to the message
func (l *Logger) Print(message string) {
l.Write(message)
// processLogMessage formats the given message before writing it out
func (l *Logger) processLogMessage(loglevel uint8, format string, args ...interface{}) error {
message := fmt.Sprintf(format, args...)
return l.writeln(loglevel, message)
}
// Trace level logging. Works like Sprintf.
func (l *Logger) Trace(format string, args ...interface{}) {
if l.logLevel <= logger.TRACE {
l.output.Trace(fmt.Sprintf(format, args...))
func (l *Logger) Trace(format string, args ...interface{}) error {
return l.processLogMessage(TRACE, format, args...)
}
// CustomTrace returns a custom Logging function that will insert the given name before the message
func (l *Logger) CustomTrace(name string) func(format string, args ...interface{}) {
return func(format string, args ...interface{}) {
format = name + " | " + format
l.processLogMessage(TRACE, format, args...)
}
}
// Debug level logging. Works like Sprintf.
func (l *Logger) Debug(format string, args ...interface{}) {
if l.logLevel <= logger.DEBUG {
l.output.Debug(fmt.Sprintf(format, args...))
}
func (l *Logger) Debug(format string, args ...interface{}) error {
return l.processLogMessage(DEBUG, format, args...)
}
// Info level logging. Works like Sprintf.
func (l *Logger) Info(format string, args ...interface{}) {
if l.logLevel <= logger.INFO {
l.output.Info(fmt.Sprintf(format, args...))
}
func (l *Logger) Info(format string, args ...interface{}) error {
return l.processLogMessage(INFO, format, args...)
}
// Warning level logging. Works like Sprintf.
func (l *Logger) Warning(format string, args ...interface{}) {
if l.logLevel <= logger.WARNING {
l.output.Warning(fmt.Sprintf(format, args...))
}
func (l *Logger) Warning(format string, args ...interface{}) error {
return l.processLogMessage(WARNING, format, args...)
}
// Error level logging. Works like Sprintf.
func (l *Logger) Error(format string, args ...interface{}) {
if l.logLevel <= logger.ERROR {
l.output.Error(fmt.Sprintf(format, args...))
}
func (l *Logger) Error(format string, args ...interface{}) error {
return l.processLogMessage(ERROR, format, args...)
}
// Fatal level logging. Works like Sprintf.
func (l *Logger) Fatal(format string, args ...interface{}) {
l.output.Fatal(fmt.Sprintf(format, args...))
l.processLogMessage(FATAL, format, args...)
os.Exit(1)
}

View File

@@ -0,0 +1,34 @@
package logger
const (
// TRACE level
TRACE uint8 = 0
// DEBUG level logging
DEBUG uint8 = 1
// INFO level logging
INFO uint8 = 2
// WARNING level logging
WARNING uint8 = 4
// ERROR level logging
ERROR uint8 = 8
// FATAL level logging
FATAL uint8 = 16
// BYPASS level logging - does not use a log level
BYPASS uint8 = 255
)
var mapLogLevel = map[uint8]string{
TRACE: "TRACE | ",
DEBUG: "DEBUG | ",
INFO: "INFO | ",
WARNING: "WARN | ",
ERROR: "ERROR | ",
FATAL: "FATAL | ",
BYPASS: "",
}

View File

@@ -0,0 +1,202 @@
package logger
import (
"bytes"
"io/ioutil"
"log"
"os"
"testing"
"github.com/matryer/is"
)
func TestByteBufferLogger(t *testing.T) {
is := is.New(t)
// Create new byte buffer logger
var buf bytes.Buffer
myLogger := New(&buf)
myLogger.SetLogLevel(TRACE)
tests := map[uint8]string{
TRACE: "TRACE | I am a message!\n",
DEBUG: "DEBUG | I am a message!\n",
WARNING: "WARN | I am a message!\n",
INFO: "INFO | I am a message!\n",
ERROR: "ERROR | I am a message!\n",
}
methods := map[uint8]func(string, ...interface{}) error{
TRACE: myLogger.Trace,
DEBUG: myLogger.Debug,
WARNING: myLogger.Warning,
INFO: myLogger.Info,
ERROR: myLogger.Error,
}
for level, expected := range tests {
buf.Reset()
method := methods[level]
// Write message
err := method("I am a message!")
if err != nil {
panic(err)
}
actual := buf.String()
is.Equal(actual, expected)
}
}
func TestCustomLogger(t *testing.T) {
is := is.New(t)
// Create new byte buffer logger
var buf bytes.Buffer
myLogger := New(&buf)
myLogger.SetLogLevel(TRACE)
customLogger := myLogger.CustomLogger("Test")
tests := map[uint8]string{
TRACE: "TRACE | Test | I am a message!\n",
DEBUG: "DEBUG | Test | I am a message!\n",
WARNING: "WARN | Test | I am a message!\n",
INFO: "INFO | Test | I am a message!\n",
ERROR: "ERROR | Test | I am a message!\n",
}
methods := map[uint8]func(string, ...interface{}) error{
TRACE: customLogger.Trace,
DEBUG: customLogger.Debug,
WARNING: customLogger.Warning,
INFO: customLogger.Info,
ERROR: customLogger.Error,
}
for level, expected := range tests {
buf.Reset()
method := methods[level]
// Write message
err := method("I am a message!")
if err != nil {
panic(err)
}
actual := buf.String()
is.Equal(actual, expected)
}
}
func TestWriteln(t *testing.T) {
is := is.New(t)
// Create new byte buffer logger
var buf bytes.Buffer
myLogger := New(&buf)
myLogger.SetLogLevel(DEBUG)
buf.Reset()
// Write message
err := myLogger.Writeln("I am a message!")
if err != nil {
panic(err)
}
actual := buf.String()
is.Equal(actual, "I am a message!\n")
buf.Reset()
// Write message
err = myLogger.Write("I am a message!")
if err != nil {
panic(err)
}
actual = buf.String()
is.Equal(actual, "I am a message!")
}
func TestLogLevel(t *testing.T) {
is := is.New(t)
// Create new byte buffer logger
var buf bytes.Buffer
myLogger := New(&buf)
myLogger.SetLogLevel(ERROR)
tests := map[uint8]string{
TRACE: "",
DEBUG: "",
WARNING: "",
INFO: "",
ERROR: "ERROR | I am a message!\n",
}
methods := map[uint8]func(string, ...interface{}) error{
TRACE: myLogger.Trace,
DEBUG: myLogger.Debug,
WARNING: myLogger.Warning,
INFO: myLogger.Info,
ERROR: myLogger.Error,
}
for level := range tests {
method := methods[level]
// Write message
err := method("I am a message!")
if err != nil {
panic(err)
}
}
actual := buf.String()
is.Equal(actual, "ERROR | I am a message!\n")
}
func TestFileLogger(t *testing.T) {
is := is.New(t)
// Create new byte buffer logger
file, err := ioutil.TempFile(".", "wailsv2test")
if err != nil {
log.Fatal(err)
}
defer os.Remove(file.Name())
myLogger := New(file)
myLogger.SetLogLevel(DEBUG)
// Write message
err = myLogger.Info("I am a message!")
if err != nil {
panic(err)
}
actual, err := ioutil.ReadFile(file.Name())
if err != nil {
panic(err)
}
is.Equal(string(actual), "INFO | I am a message!\n")
}

View File

@@ -6,38 +6,19 @@ import (
"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/dialog"
)
// Client defines what a frontend client can do
type Client interface {
Quit()
NotifyEvent(message string)
CallResult(message string)
OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string)
SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string)
MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string)
SaveFileDialog(title string) string
OpenFileDialog(title string) string
OpenDirectoryDialog(title string) string
WindowSetTitle(title string)
WindowShow()
WindowHide()
WindowCenter()
WindowMaximise()
WindowUnmaximise()
WindowMinimise()
WindowUnminimise()
WindowPosition(x int, y int)
WindowSize(width int, height int)
WindowSetMinSize(width int, height int)
WindowSetMaxSize(width int, height int)
WindowFullscreen()
WindowUnFullscreen()
WindowSetColour(colour int)
DarkModeEnabled(callbackID string)
SetApplicationMenu(menuJSON string)
SetTrayMenu(trayMenuJSON string)
UpdateTrayMenuLabel(JSON string)
UpdateContextMenu(contextMenuJSON string)
DeleteTrayMenuByID(id string)
WindowSetColour(colour string) bool
}
// DispatchClient is what the frontends use to interface with the
@@ -72,7 +53,7 @@ func (d *DispatchClient) DispatchMessage(incomingMessage string) {
d.logger.Trace(fmt.Sprintf("Received message: %+v", incomingMessage))
parsedMessage, err := message.Parse(incomingMessage)
if err != nil {
d.logger.Error(err.Error())
d.logger.Trace("Error: " + err.Error())
return
}
@@ -81,6 +62,14 @@ func (d *DispatchClient) DispatchMessage(incomingMessage string) {
d.logger.Trace("I got a parsedMessage: %+v", parsedMessage)
// Check error
if err != nil {
d.logger.Trace("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)

View File

@@ -6,9 +6,9 @@ import (
)
type CallMessage struct {
Name string `json:"name"`
Args []json.RawMessage `json:"args"`
CallbackID string `json:"callbackID,omitempty"`
Name string `json:"name"`
Args []interface{} `json:"args"`
CallbackID string `json:"callbackID,omitempty"`
}
// callMessageParser does what it says on the tin!
@@ -22,7 +22,6 @@ func callMessageParser(message string) (*parsedMessage, error) {
callMessage := new(CallMessage)
m := message[1:]
err := json.Unmarshal([]byte(m), callMessage)
if err != nil {
println(err.Error())

View File

@@ -1,9 +1,8 @@
package message
import (
"encoding/json"
"fmt"
)
import "fmt"
import "encoding/json"
type EventMessage struct {
Name string `json:"name"`
@@ -13,7 +12,6 @@ type EventMessage struct {
type OnEventMessage struct {
Name string
Callback func(optionalData ...interface{})
Counter int
}
// eventMessageParser does what it says on the tin!
@@ -29,6 +27,8 @@ func eventMessageParser(message string) (*parsedMessage, error) {
// Switch the event type (with or without data)
switch message[0] {
case 'e':
eventMessage.Name = message[2:]
case 'E':
m := message[2:]
err := json.Unmarshal([]byte(m), eventMessage)

View File

@@ -3,19 +3,16 @@ 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")
@@ -26,7 +23,7 @@ func logMessageParser(message string) (*parsedMessage, error) {
// If the type is invalid, raise error
if messageTopic == "" {
return nil, fmt.Errorf("log message type '%c' invalid", message[1])
return nil, fmt.Errorf("log message type '%b' invalid", message[1])
}
// Create a new parsed message struct

View File

@@ -14,26 +14,17 @@ var messageParsers = map[byte]func(string) (*parsedMessage, error){
'L': logMessageParser,
'R': runtimeMessageParser,
'E': eventMessageParser,
'e': eventMessageParser,
'C': callMessageParser,
'W': windowMessageParser,
'D': dialogMessageParser,
'S': systemMessageParser,
'M': menuMessageParser,
'T': trayMessageParser,
'X': contextMenusMessageParser,
'U': urlMessageParser,
}
// Parse will attempt to parse the given message
func Parse(message string) (*parsedMessage, error) {
if len(message) == 0 {
return nil, fmt.Errorf("MessageParser received blank message")
}
parseMethod := messageParsers[message[0]]
if parseMethod == nil {
return nil, fmt.Errorf("message type '%c' invalid", message[0])
return nil, fmt.Errorf("message type '%b' invalid", message[0])
}
return parseMethod(message)

View File

@@ -22,14 +22,13 @@ func runtimeMessageParser(message string) (*parsedMessage, error) {
// 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':
case 'U':
// Open URL
target := message[3:]
return &parsedMessage{Topic: "runtime:browser:open", Data: target}, nil
url := message[3:]
return &parsedMessage{Topic: "runtime:browser:openurl", Data: url}, nil
}
return nil, fmt.Errorf("unknown browser message: %s", message)

View File

@@ -22,66 +22,6 @@ func windowMessageParser(message string) (*parsedMessage, error) {
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 = ""
// Fullscreen window
case 'F':
parsedMessage.Topic = "window:fullscreen"
parsedMessage.Data = ""
// UnFullscreen window
case 'f':
parsedMessage.Topic = "window:unfullscreen"
parsedMessage.Data = ""
// Set Title
case 'T':
parsedMessage.Topic = "window:settitle"
parsedMessage.Data = message[2:]
// Unknown event type
default:
return nil, fmt.Errorf("unknown message: %s", message)

View File

@@ -1,14 +1,10 @@
package messagedispatcher
import (
"context"
"encoding/json"
"strconv"
"strings"
"sync"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/crypto"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
@@ -23,8 +19,7 @@ type Dispatcher struct {
eventChannel <-chan *servicebus.Message
windowChannel <-chan *servicebus.Message
dialogChannel <-chan *servicebus.Message
systemChannel <-chan *servicebus.Message
menuChannel <-chan *servicebus.Message
running bool
servicebus *servicebus.ServiceBus
logger logger.CustomLogger
@@ -32,13 +27,6 @@ type Dispatcher struct {
// Clients
clients map[string]*DispatchClient
lock sync.RWMutex
// Context for cancellation
ctx context.Context
cancel context.CancelFunc
// internal wait group
wg sync.WaitGroup
}
// New dispatcher. Needs a service bus to send to.
@@ -73,19 +61,6 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher,
return nil, err
}
systemChannel, err := servicebus.Subscribe("system:")
if err != nil {
return nil, err
}
menuChannel, err := servicebus.Subscribe("menufrontend:")
if err != nil {
return nil, err
}
// Create context
ctx, cancel := context.WithCancel(context.Background())
result := &Dispatcher{
servicebus: servicebus,
eventChannel: eventChannel,
@@ -95,10 +70,6 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher,
quitChannel: quitChannel,
windowChannel: windowChannel,
dialogChannel: dialogChannel,
systemChannel: systemChannel,
menuChannel: menuChannel,
ctx: ctx,
cancel: cancel,
}
return result, nil
@@ -109,18 +80,15 @@ func (d *Dispatcher) Start() error {
d.logger.Trace("Starting")
d.wg.Add(1)
d.running = true
// Spin off a go routine
go func() {
defer d.logger.Trace("Shutdown")
for {
for d.running {
select {
case <-d.ctx.Done():
d.wg.Done()
return
case <-d.quitChannel:
d.processQuit()
d.running = false
case resultMessage := <-d.resultChannel:
d.processCallResult(resultMessage)
case eventMessage := <-d.eventChannel:
@@ -129,12 +97,11 @@ func (d *Dispatcher) Start() error {
d.processWindowMessage(windowMessage)
case dialogMessage := <-d.dialogChannel:
d.processDialogMessage(dialogMessage)
case systemMessage := <-d.systemChannel:
d.processSystemMessage(systemMessage)
case menuMessage := <-d.menuChannel:
d.processMenuMessage(menuMessage)
}
}
// Call shutdown
d.shutdown()
}()
return nil
@@ -148,6 +115,10 @@ func (d *Dispatcher) processQuit() {
}
}
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 {
@@ -195,35 +166,12 @@ func (d *Dispatcher) processCallResult(result *servicebus.Message) {
if client == nil {
// This is fatal - unknown target!
d.logger.Fatal("Unknown target for call result: %+v", result)
return
}
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) {
@@ -282,7 +230,7 @@ func (d *Dispatcher) processWindowMessage(result *servicebus.Message) {
client.frontend.WindowUnFullscreen()
}
case "setcolour":
colour, ok := result.Data().(int)
colour, ok := result.Data().(string)
if !ok {
d.logger.Error("Invalid colour for 'window:setcolour' : %#v", result.Data())
return
@@ -291,105 +239,6 @@ func (d *Dispatcher) processWindowMessage(result *servicebus.Message) {
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)
}
case "minsize":
// We need 2 arguments
if len(splitTopic) != 4 {
d.logger.Error("Invalid number of parameters for 'window:minsize' : %#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:minsize' : %#v", result.Data())
return
}
// Notifh clients
for _, client := range d.clients {
client.frontend.WindowSetMinSize(w, h)
}
case "maxsize":
// We need 2 arguments
if len(splitTopic) != 4 {
d.logger.Error("Invalid number of parameters for 'window:maxsize' : %#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:maxsize' : %#v", result.Data())
return
}
// Notifh clients
for _, client := range d.clients {
client.frontend.WindowSetMaxSize(w, h)
}
default:
d.logger.Error("Unknown window command: %s", command)
}
@@ -400,6 +249,7 @@ func (d *Dispatcher) processWindowMessage(result *servicebus.Message) {
// 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
@@ -409,142 +259,61 @@ func (d *Dispatcher) processDialogMessage(result *servicebus.Message) {
switch command {
case "select":
dialogType := splitTopic[2]
title := splitTopic[3]
switch dialogType {
case "open":
dialogOptions, ok := result.Data().(*dialog.OpenDialog)
case "file":
responseTopic, ok := result.Data().(string)
if !ok {
d.logger.Error("Invalid data for 'dialog:select:open' : %#v", result.Data())
d.logger.Error("Invalid responseTopic for 'dialog:select:file' : %#v", result.Data())
return
}
// This is hardcoded in the sender too
callbackID := splitTopic[3]
d.logger.Info("Opening File dialog! responseTopic = %s", responseTopic)
// TODO: Work out what we mean in a multi window environment...
// For now we will just pick the first one
var result string
for _, client := range d.clients {
client.frontend.OpenDialog(dialogOptions, callbackID)
result = client.frontend.OpenFileDialog(title)
}
case "save":
dialogOptions, ok := result.Data().(*dialog.SaveDialog)
// Send dummy response
d.servicebus.Publish(responseTopic, result)
case "filesave":
responseTopic, ok := result.Data().(string)
if !ok {
d.logger.Error("Invalid data for 'dialog:select:save' : %#v", result.Data())
d.logger.Error("Invalid responseTopic for 'dialog:select:filesave' : %#v", result.Data())
return
}
// This is hardcoded in the sender too
callbackID := splitTopic[3]
d.logger.Info("Opening Save File dialog! responseTopic = %s", responseTopic)
// TODO: Work out what we mean in a multi window environment...
// For now we will just pick the first one
var result string
for _, client := range d.clients {
client.frontend.SaveDialog(dialogOptions, callbackID)
result = client.frontend.SaveFileDialog(title)
}
case "message":
dialogOptions, ok := result.Data().(*dialog.MessageDialog)
// Send dummy response
d.servicebus.Publish(responseTopic, result)
case "directory":
responseTopic, ok := result.Data().(string)
if !ok {
d.logger.Error("Invalid data for 'dialog:select:message' : %#v", result.Data())
d.logger.Error("Invalid responseTopic for 'dialog:select:directory' : %#v", result.Data())
return
}
// This is hardcoded in the sender too
callbackID := splitTopic[3]
d.logger.Info("Opening Directory dialog! responseTopic = %s", responseTopic)
// TODO: Work out what we mean in a multi window environment...
// For now we will just pick the first one
var result string
for _, client := range d.clients {
client.frontend.MessageDialog(dialogOptions, callbackID)
result = client.frontend.OpenDirectoryDialog(title)
}
// Send dummy response
d.servicebus.Publish(responseTopic, result)
default:
d.logger.Error("Unknown dialog type: %s", dialogType)
d.logger.Error("Unknown dialog command: %s", command)
}
default:
d.logger.Error("Unknown dialog command: %s", command)
}
}
func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
splitTopic := strings.Split(result.Topic(), ":")
if len(splitTopic) < 2 {
d.logger.Error("Invalid menu message : %#v", result.Data())
return
}
command := splitTopic[1]
switch command {
case "updateappmenu":
updatedMenu, ok := result.Data().(string)
if !ok {
d.logger.Error("Invalid data for 'menufrontend:updateappmenu' : %#v",
result.Data())
return
}
// 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.SetApplicationMenu(updatedMenu)
}
case "settraymenu":
trayMenuJSON, ok := result.Data().(string)
if !ok {
d.logger.Error("Invalid data for 'menufrontend:settraymenu' : %#v",
result.Data())
return
}
// 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.SetTrayMenu(trayMenuJSON)
}
case "updatecontextmenu":
updatedContextMenu, ok := result.Data().(string)
if !ok {
d.logger.Error("Invalid data for 'menufrontend:updatecontextmenu' : %#v",
result.Data())
return
}
// 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.UpdateContextMenu(updatedContextMenu)
}
case "updatetraymenulabel":
updatedTrayMenuLabel, ok := result.Data().(string)
if !ok {
d.logger.Error("Invalid data for 'menufrontend:updatetraymenulabel' : %#v",
result.Data())
return
}
// 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.UpdateTrayMenuLabel(updatedTrayMenuLabel)
}
case "deletetraymenu":
traymenuid, ok := result.Data().(string)
if !ok {
d.logger.Error("Invalid data for 'menufrontend:updatetraymenulabel' : %#v",
result.Data())
return
}
for _, client := range d.clients {
client.frontend.DeleteTrayMenuByID(traymenuid)
}
default:
d.logger.Error("Unknown menufrontend command: %s", command)
}
}
func (d *Dispatcher) Close() {
d.cancel()
d.wg.Wait()
}

View File

@@ -10,6 +10,8 @@ import (
"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{}
@@ -47,7 +49,7 @@ 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)
fmt.Fprintf(os.Stderr, "load: %v\n", err)
os.Exit(1)
}
if packages.PrintErrors(pkgs) > 0 {
@@ -63,6 +65,7 @@ func ParseProject(projectPath string) (BoundStructs, error) {
var wailsPkgVar = ""
ast.Inspect(file, func(n ast.Node) bool {
var s string
switch x := n.(type) {
// Parse import declarations
case *ast.ImportSpec:
@@ -201,6 +204,10 @@ func ParseProject(projectPath string) (BoundStructs, error) {
// 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]
@@ -431,6 +438,4 @@ func ParseProject(projectPath string) (BoundStructs, error) {
println()
println("}")
println()
return nil, nil
}

View File

@@ -1,30 +1,26 @@
package process
import (
"os"
"os/exec"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/internal/logger"
)
// Process defines a process that can be executed
type Process struct {
logger *clilogger.CLILogger
logger *logger.Logger
cmd *exec.Cmd
exitChannel chan bool
Running bool
}
// NewProcess creates a new process struct
func NewProcess(logger *clilogger.CLILogger, cmd string, args ...string) *Process {
result := &Process{
func NewProcess(logger *logger.Logger, cmd string, args ...string) *Process {
return &Process{
logger: logger,
cmd: exec.Command(cmd, args...),
exitChannel: make(chan bool, 1),
}
result.cmd.Stdout = os.Stdout
result.cmd.Stderr = os.Stderr
return result
}
// Start the process
@@ -37,15 +33,10 @@ func (p *Process) Start() error {
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)
err := cmd.Wait()
if err != nil {
if err.Error() != "signal: killed" {
logger.Fatal("Fatal error from app: " + err.Error())
}
}
logger.Println("Exiting process (PID: %d)", cmd.Process.Pid)
go func(cmd *exec.Cmd, running *bool, logger *logger.Logger, exitChannel chan bool) {
logger.Info("Starting process (PID: %d)", cmd.Process.Pid)
cmd.Wait()
logger.Info("Exiting process (PID: %d)", cmd.Process.Pid)
*running = false
exitChannel <- true
}(p.cmd, &p.Running, p.logger, p.exitChannel)

View File

@@ -25,9 +25,6 @@ type Project struct {
// The path to the project directory
Path string
// Assets directory
AssetsDir string `json:"assetsdir"`
// The output filename
OutputFilename string `json:"outputfilename"`
@@ -36,15 +33,6 @@ type Project struct {
// The platform to target
Platform string
// The application author
Author Author
}
// Author stores details about the application author
type Author struct {
Name string `json:"name"`
Email string `json:"email"`
}
// Load the project from the current working directory
@@ -75,11 +63,6 @@ func Load(projectPath string) (*Project, error) {
result.Name = "wailsapp"
}
// Set default assets directory if none given
if result.AssetsDir == "" {
result.AssetsDir = filepath.Join(result.Path, "assets")
}
// Fix up OutputFilename
switch runtime.GOOS {
case "windows":

View File

@@ -0,0 +1,36 @@
package goruntime
import (
"fmt"
"os/exec"
"runtime"
)
// Browser defines all browser related operations
type Browser interface {
Open(url 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(url string) error {
var err error
switch runtime.GOOS {
case "linux":
err = exec.Command("xdg-open", url).Start()
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
err = exec.Command("open", url).Start()
default:
err = fmt.Errorf("unsupported platform")
}
return err
}
func newBrowser() *browser {
return &browser{}
}

View File

@@ -0,0 +1,16 @@
package goruntime
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")
}

View File

@@ -0,0 +1,105 @@
package goruntime
import (
"fmt"
"github.com/wailsapp/wails/v2/internal/crypto"
"github.com/wailsapp/wails/v2/internal/servicebus"
)
// Dialog defines all Dialog related operations
type Dialog interface {
SaveFile(title string) string
SelectFile(title string) string
SelectDirectory(title string) 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,
}
}
// SelectFile prompts the user to select a file
func (r *dialog) SelectFile(title string) string {
// Create unique dialog callback
uniqueCallback := crypto.RandomID()
// Subscribe to the respose channel
responseTopic := "dialog:fileselected:" + uniqueCallback
dialogResponseChannel, err := r.bus.Subscribe(responseTopic)
if err != nil {
fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error())
}
// Publish dialog request
// TODO: Add title to request
r.bus.Publish("dialog:select:file:"+title, responseTopic)
// Wait for result
result := <-dialogResponseChannel
// Delete subscription to response topic
r.bus.UnSubscribe(responseTopic)
return result.Data().(string)
}
// SaveFile prompts the user to select a file to save to
func (r *dialog) SaveFile(title string) string {
// Create unique dialog callback
uniqueCallback := crypto.RandomID()
// Subscribe to the respose channel
responseTopic := "dialog:filesaveselected:" + uniqueCallback
dialogResponseChannel, err := r.bus.Subscribe(responseTopic)
if err != nil {
fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error())
}
// Publish dialog request
// TODO: Add title to request
r.bus.Publish("dialog:select:filesave:"+title, responseTopic)
// Wait for result
result := <-dialogResponseChannel
// Delete subscription to response topic
r.bus.UnSubscribe(responseTopic)
return result.Data().(string)
}
// SelectDirectory prompts the user to select a file
func (r *dialog) SelectDirectory(title string) string {
// Create unique dialog callback
uniqueCallback := crypto.RandomID()
// Subscribe to the respose channel
responseTopic := "dialog:directoryselected:" + uniqueCallback
dialogResponseChannel, err := r.bus.Subscribe(responseTopic)
if err != nil {
fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error())
}
// Publish dialog request
// TODO: Add title to request
r.bus.Publish("dialog:select:directory:"+title, responseTopic)
// Wait for result
var result *servicebus.Message = <-dialogResponseChannel
// Delete subscription to response topic
r.bus.UnSubscribe(responseTopic)
return result.Data().(string)
}

View File

@@ -0,0 +1,43 @@
package goruntime
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{}))
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 pass through
func (r *event) On(eventName string, callback func(optionalData ...interface{})) {
eventMessage := &message.OnEventMessage{
Name: eventName,
Callback: callback,
}
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)
}

View File

@@ -0,0 +1,28 @@
package goruntime
import "github.com/wailsapp/wails/v2/internal/servicebus"
// Runtime is a means for the user to interact with the application at runtime
type Runtime struct {
Browser Browser
Events Events
Window Window
Dialog Dialog
bus *servicebus.ServiceBus
}
// New creates a new runtime
func New(serviceBus *servicebus.ServiceBus) *Runtime {
return &Runtime{
Browser: newBrowser(),
Events: newEvents(serviceBus),
Window: newWindow(serviceBus),
Dialog: newDialog(serviceBus),
bus: serviceBus,
}
}
// Quit the application
func (r *Runtime) Quit() {
r.bus.Publish("quit", "runtime.Quit()")
}

View File

@@ -0,0 +1,54 @@
package goruntime
import (
"github.com/wailsapp/wails/v2/internal/servicebus"
)
// Window defines all Window related operations
type Window interface {
Close()
SetTitle(title string)
Fullscreen()
UnFullscreen()
SetColour(colour string)
}
// Window exposes the Windows interface
type window struct {
bus *servicebus.ServiceBus
}
// newWindow creates a new window struct
func newWindow(bus *servicebus.ServiceBus) Window {
return &window{
bus: bus,
}
}
// Close the Window
// DISCUSSION:
// Should we even be doing this now we have a server build?
// Runtime.Quit() makes more sense than closing a window...
func (w *window) Close() {
w.bus.Publish("quit", "runtime.Close()")
}
// SetTitle sets the title of the window
func (w *window) SetTitle(title string) {
w.bus.Publish("window:settitle", title)
}
// Fullscreen makes the window fullscreen
func (w *window) Fullscreen() {
w.bus.Publish("window:fullscreen", "")
}
// UnFullscreen makes the window UnFullscreen
func (w *window) UnFullscreen() {
w.bus.Publish("window:unfullscreen", "")
}
// SetColour sets the window colour to the given string
func (w *window) SetColour(colour string) {
w.bus.Publish("window:setcolour", colour)
}

View File

@@ -65,7 +65,7 @@ export function SetBindings(bindingsMap) {
window.backend[packageName][structName][methodName] = function () {
// No timeout by default
let timeout = 0;
var timeout = 0;
// Actual function
function dynamic() {
@@ -89,3 +89,19 @@ export function SetBindings(bindingsMap) {
});
});
}
// /**
// * 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;
// }
// }

View File

@@ -12,13 +12,23 @@ The lightweight framework for web-like apps
import { SendMessage } from 'ipc';
/**
* Opens the given URL / filename in the system browser
* Opens the given URL in the system browser
*
* @export
* @param {string} target
* @param {string} url
* @returns
*/
export function Open(target) {
return SendMessage('RBO' + target);
export function OpenURL(url) {
return SendMessage('RBU' + url);
}
/**
* Opens the given filename using the system's default file handler
*
* @export
* @param {sting} filename
* @returns
*/
export function OpenFile(filename) {
return SendMessage('runtime:browser:openfile', filename);
}

View File

@@ -151,7 +151,6 @@ export function Callback(incomingMessage) {
* @param {any[]=} data
* @returns
*/
export function SystemCall(method) {
var data = [].slice.apply(arguments).slice(1);
export function SystemCall(method, data) {
return Call('.wails.' + method, data);
}

View File

@@ -10,19 +10,16 @@ The lightweight framework for web-like apps
/* jshint esversion: 6 */
import { SetBindings } from './bindings';
import { Init } from './main';
import {RaiseError} from '../desktop/darwin';
// Setup global error handler
window.onerror = function (msg, url, lineNo, columnNo, error) {
const errorMessage = {
message: msg,
url: url,
line: lineNo,
column: columnNo,
error: JSON.stringify(error),
stack: function() { return JSON.stringify(new Error().stack); }(),
};
RaiseError(errorMessage);
window.onerror = function (/*msg, url, lineNo, columnNo, error*/) {
// window.wails.Log.Error('**** Caught Unhandled Error ****');
// window.wails.Log.Error('Message: ' + msg);
// window.wails.Log.Error('URL: ' + url);
// window.wails.Log.Error('Line No: ' + lineNo);
// window.wails.Log.Error('Column No: ' + columnNo);
// window.wails.Log.Error('error: ' + error);
(function () { window.wails.Log.Error(new Error().stack); })();
};
// Initialise the Runtime

View File

@@ -13,7 +13,6 @@ import { Error } from './log';
import { SendMessage } from 'ipc';
// Defines a single listener with a maximum number of times to callback
/**
* The Listener class defines a listener! :-)
*
@@ -44,7 +43,7 @@ class Listener {
}
}
let eventListeners = {};
var eventListeners = {};
/**
* Registers an event listener that will be invoked `maxCallbacks` times before being destroyed
@@ -72,17 +71,6 @@ export function On(eventName, callback) {
OnMultiple(eventName, callback);
}
/**
* Registers listeners for when the system theme changes from light/dark. A bool is
* sent to the listener, true if it is dark mode.
*
* @export
* @param {function} callback
*/
export function OnThemeChange(callback) {
On('wails:system:themechange', callback);
}
/**
* Registers an event listener that will be invoked once then destroyed
*
@@ -94,43 +82,11 @@ export function Once(eventName, callback) {
OnMultiple(eventName, callback, 1);
}
function notifyListeners(eventData) {
// Get the event name
let eventName = eventData.name;
// Check if we have any listeners for this event
if (eventListeners[eventName]) {
// Keep a list of listener indexes to destroy
const newEventListenerList = eventListeners[eventName].slice();
// Iterate listeners
for (let count = 0; count < eventListeners[eventName].length; count += 1) {
// Get next listener
const listener = eventListeners[eventName][count];
let data = eventData.data;
// Do the callback
const destroy = listener.Callback(data);
if (destroy) {
// if the listener indicated to destroy itself, add it to the destroy list
newEventListenerList.splice(count, 1);
}
}
// Update callbacks with new list of listeners
eventListeners[eventName] = newEventListenerList;
}
}
/**
* Notify informs frontend listeners that an event was emitted with the given data
*
* @export
* @param {string} notifyMessage - encoded notification message
* @param {string} encoded notification message
*/
export function Notify(notifyMessage) {
@@ -144,7 +100,33 @@ export function Notify(notifyMessage) {
throw new Error(error);
}
notifyListeners(message);
var eventName = message.name;
// Check if we have any listeners for this event
if (eventListeners[eventName]) {
// Keep a list of listener indexes to destroy
const newEventListenerList = eventListeners[eventName].slice();
// Iterate listeners
for (let count = 0; count < eventListeners[eventName].length; count += 1) {
// Get next listener
const listener = eventListeners[eventName][count];
var data = message.data;
// Do the callback
const destroy = listener.Callback(data);
if (destroy) {
// if the listener indicated to destroy itself, add it to the destroy list
newEventListenerList.splice(count, 1);
}
}
// Update callbacks with new list of listners
eventListeners[eventName] = newEventListenerList;
}
}
/**
@@ -155,15 +137,66 @@ export function Notify(notifyMessage) {
*/
export function Emit(eventName) {
const payload = {
name: eventName,
data: [].slice.apply(arguments).slice(1),
};
// Calculate the data
if (arguments.length > 1) {
// Notify backend
const payload = {
name: eventName,
data: [].slice.apply(arguments).slice(1),
};
SendMessage('Ej' + JSON.stringify(payload));
} else {
SendMessage('ej' + eventName);
}
// Notify JS listeners
notifyListeners(payload);
}
// Notify Go listeners
SendMessage('Ej' + JSON.stringify(payload));
// Callbacks for the heartbeat calls
const heartbeatCallbacks = {};
}
/**
* Heartbeat emits the event `eventName`, every `timeInMilliseconds` milliseconds until
* the event is acknowledged via `Event.Acknowledge`. Once this happens, `callback` is invoked ONCE
*
* @export
* @param {string} eventName
* @param {number} timeInMilliseconds
* @param {function} callback
*/
export function Heartbeat(eventName, timeInMilliseconds, callback) {
// Declare interval variable
let interval = null;
// Setup callback
function dynamicCallback() {
// Kill interval
clearInterval(interval);
// Callback
callback();
}
// Register callback
heartbeatCallbacks[eventName] = dynamicCallback;
// Start emitting the event
interval = setInterval(function () {
Emit(eventName);
}, timeInMilliseconds);
}
/**
* Acknowledges a heartbeat event by name
*
* @export
* @param {string} eventName
*/
export function Acknowledge(eventName) {
// If we are waiting for acknowledgement for this event type
if (heartbeatCallbacks[eventName]) {
// Acknowledge!
heartbeatCallbacks[eventName]();
} else {
throw new Error(`Cannot acknowledge unknown heartbeat '${eventName}'`);
}
}

View File

@@ -25,26 +25,6 @@ function sendLogMessage(level, 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
*
@@ -94,22 +74,3 @@ export function Error(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

@@ -10,15 +10,11 @@ The lightweight framework for web-like apps
/* jshint esversion: 6 */
import * as Log from './log';
import * as Browser from './browser';
import * as Window from './window';
import * as Dialog from './dialog';
import { On, Once, OnMultiple, Emit, Notify } from './events';
import { Callback, SystemCall } from './calls';
import { AddScript, InjectCSS, DisableDefaultContextMenu } from './utils';
import { On, OnMultiple, Emit, Notify, Heartbeat, Acknowledge } from './events';
import { Callback } from './calls';
import { AddScript, InjectCSS } from './utils';
import { AddIPCListener } from 'ipc';
import * as Platform from 'platform';
import * as Store from './store';
import * as Tray from './tray';
export function Init() {
// Backend is where the Go struct wrappers get bound to
@@ -29,37 +25,23 @@ export function Init() {
System: Platform.System,
Log,
Browser,
Window,
Tray,
Dialog,
Events: {
On,
Once,
OnMultiple,
Emit,
Heartbeat,
Acknowledge,
},
_: {
Callback,
Notify,
AddScript,
InjectCSS,
DisableDefaultContextMenu,
// Init,
AddIPCListener,
SystemCall,
},
Store,
Init,
AddIPCListener
}
};
// Setup system. Store uses window.wails so needs to be setup after that
window.wails.System = {
IsDarkMode: Store.New('wails:isdarkmode'),
LogLevel: Store.New('wails:loglevel'),
AppConfig: Store.New('wails:appconfig'),
};
// Copy platform specific information into it
Object.assign(window.wails.System, Platform.System);
// Do platform specific Init
Platform.Init();
}

View File

@@ -36,7 +36,3 @@ export function InjectCSS(css) {
console.log(e);
}
}
export function DisableDefaultContextMenu() {
window.disableWailsDefaultContextMenu = true;
}

View File

@@ -14,8 +14,8 @@ The lightweight framework for web-like apps
*/
export const System = {
...common,
Platform: () => "linux",
Platform: "linux",
AppType: "desktop"
}
export function SendMessage(message) {
@@ -27,7 +27,7 @@ export function Init() {
// Setup drag handler
// Based on code from: https://github.com/patr0nus/DeskGap
window.addEventListener('mousedown', function (e) {
let currentElement = e.target;
var currentElement = e.target;
while (currentElement != null) {
if (currentElement.hasAttribute('data-wails-no-drag')) {
break;

View File

@@ -1923,9 +1923,9 @@
"dev": true
},
"caniuse-lite": {
"version": "1.0.30001171",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001171.tgz",
"integrity": "sha512-5Alrh8TTYPG9IH4UkRqEBZoEToWRLvPbSQokvzSz0lii8/FOWKG4keO1HoYfPWs8IF/NH/dyNPg1cmJGvV3Zlg==",
"version": "1.0.30001040",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001040.tgz",
"integrity": "sha512-Ep0tEPeI5wCvmJNrXjE3etgfI+lkl1fTDU6Y3ZH1mhrjkPlVI9W4pcKbMo+BQLpEWKVYYp2EmYaRsqpPC3k7lQ==",
"dev": true
},
"chalk": {

View File

@@ -39,6 +39,5 @@
"eslint": "^6.8.0",
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10"
},
"dependencies": {}
}
}

View File

@@ -1 +1 @@
7a2c438e79cf603ba763055e515650be
77a32d4461a2cad598edfd551fe64dcd

View File

@@ -1 +1 @@
src
bridge.js

View File

@@ -1,3 +1,3 @@
# Wails Runtime
This module is the Javascript runtime library for the [Wails](https://wails.app) framework. It is intended to be installed as part of a [Wails](https://wails.app) V2 project, not a standalone module.
This module is the Javascript runtime library for the [Wails](https://wails.app) framework. It is intended to be installed as part of a [Wails](https://wails.app) project, not a standalone module.

View File

@@ -10,12 +10,28 @@ The lightweight framework for web-like apps
/* jshint esversion: 6 */
/**
* Opens the given URL or Filename in the system browser
* Opens the given URL in the system browser
*
* @export
* @param {string} target
* @param {string} url
* @returns
*/
export function Open(target) {
return window.wails.Browser.Open(target);
function OpenURL(url) {
return window.wails.Browser.OpenURL(url);
}
/**
* Opens the given filename using the system's default file handler
*
* @export
* @param {sting} filename
* @returns
*/
function OpenFile(filename) {
return window.wails.Browser.OpenFile(filename);
}
module.exports = {
OpenURL: OpenURL,
OpenFile: OpenFile
};

View File

@@ -56,14 +56,28 @@ function Emit(eventName) {
return window.wails.Events.Emit.apply(null, args);
}
/**
* Registers listeners for when the system theme changes
* Heartbeat emits the event `eventName`, every `timeInMilliseconds` milliseconds until
* the event is acknowledged via `Event.Acknowledge`. Once this happens, `callback` is invoked ONCE
*
* @export
* @param {string} eventName
* @param {number} timeInMilliseconds
* @param {function} callback
*/
function OnThemeChange(callback) {
On('wails:system:themechange', callback);
function Heartbeat(eventName, timeInMilliseconds, callback) {
window.wails.Events.Heartbeat(eventName, timeInMilliseconds, callback);
}
/**
* Acknowledges a heartbeat event by name
*
* @export
* @param {string} eventName
*/
function Acknowledge(eventName) {
return window.wails.Events.Acknowledge(eventName);
}
module.exports = {
@@ -71,5 +85,6 @@ module.exports = {
On: On,
Once: Once,
Emit: Emit,
OnThemeChange: OnThemeChange,
Heartbeat: Heartbeat,
Acknowledge: Acknowledge
};

View File

@@ -9,25 +9,13 @@ The lightweight framework for web-like apps
*/
/* jshint esversion: 6 */
import bridge from './bridge';
/**
* ready will execute the callback when Wails has loaded
* and initialised.
* Initialises the Wails runtime
*
* @param {function} callback
*/
function ready(callback) {
// If window.wails exists, we are ready
if( window.wails ) {
return callback();
}
// If not we need to setup the bridge
bridge.InitBridge(callback);
function Init(callback) {
window.wails._.Init(callback);
}
module.exports = {
ready: ready,
};
module.exports = Init;

View File

@@ -11,26 +11,6 @@ The lightweight framework for web-like apps
/* jshint esversion: 6 */
/**
* Log the given message with the backend
*
* @export
* @param {string} message
*/
function Print(message) {
window.wails.Log.Print(message);
}
/**
* Log the given trace message with the backend
*
* @export
* @param {string} message
*/
function Trace(message) {
window.wails.Log.Trace(message);
}
/**
* Log the given debug message with the backend
*
@@ -74,40 +54,17 @@ function Error(message) {
/**
* Log the given fatal message with the backend
*
* @export
* @param {string} message
*/
function Fatal(message) {
window.wails.Log.Fatal(message);
}
/**
* Sets the Log level to the given log level
*
* @param {number} loglevel
*/
function SetLogLevel(loglevel) {
window.wails.Log.SetLogLevel(loglevel);
}
// Log levels
const Level = {
TRACE: 1,
DEBUG: 2,
INFO: 3,
WARNING: 4,
ERROR: 5,
};
module.exports = {
Print: Print,
Trace: Trace,
Debug: Debug,
Info: Info,
Warning: Warning,
Error: Error,
Fatal: Fatal,
SetLogLevel: SetLogLevel,
Level: Level,
Fatal: Fatal
};

View File

@@ -11,22 +11,12 @@ The lightweight framework for web-like apps
const Log = require('./log');
const Browser = require('./browser');
const Dialog = require('./dialog');
const Events = require('./events');
const Init = require('./init');
const System = require('./system');
const Store = require('./store');
const Window = require('./window');
const Tray = require('./tray');
module.exports = {
Browser: Browser,
Dialog: Dialog,
Events: Events,
ready: Init.ready,
Log: Log,
System: System,
Store: Store,
Window: Window,
Tray: Tray,
Browser: Browser,
Events: Events,
Init: Init
};

View File

@@ -1,7 +1,7 @@
{
"name": "@wails/runtime",
"version": "1.3.12",
"description": "Wails V2 Javascript runtime library",
"name": "@wailsapp/runtime",
"version": "1.0.10",
"description": "Wails Javascript runtime library",
"main": "main.js",
"types": "runtime.d.ts",
"scripts": {

View File

@@ -1,137 +1,26 @@
export = wailsapp__runtime;
interface Store {
get(): any;
set(value: any): void;
subscribe(callback: (newvalue: any) => void): void;
update(callback: (currentvalue: any) => any): void;
}
interface MacTitleBar {
TitleBarAppearsTransparent: boolean; // NSWindow.titleBarAppearsTransparent
HideTitle: boolean; // NSWindow.hideTitle
HideTitleBar: boolean; // NSWindow.hideTitleBar
FullSizeContent: boolean; // Makes the webview portion of the window the full size of the window, even over the titlebar
UseToolbar: boolean; // Set true to add a blank toolbar to the window (makes the title bar larger)
HideToolbarSeparator: boolean; // Set true to remove the separator between the toolbar and the main content area
}
interface MacAppConfig {
TitleBar: MacTitleBar;
}
interface LinuxAppConfig {
}
interface WindowsAppConfig {
}
interface AppConfig {
Title: string; // Application Title
Width: number; // Window Width
Height: number; // Window Height
DisableResize: boolean; // True if resize is disabled
Fullscreen: boolean; // App started in fullscreen
MinWidth: number; // Window Minimum Width
MinHeight: number; // Window Minimum Height
MaxWidth: number; // Window Maximum Width
MaxHeight: number; // Window Maximum Height
StartHidden: boolean; // Start with window hidden
DevTools: boolean; // Enables the window devtools
RBGA: number; // The initial window colour. Convert to hex then it'll mean 0xRRGGBBAA
Mac?: MacAppConfig; // - Configuration when running on Mac
Linux?: LinuxAppConfig; // - Configuration when running on Linux
Windows?: WindowsAppConfig; // - Configuration when running on Windows
Appearance: string; // The default application appearance. Use the values listed here: https://developer.apple.com/documentation/appkit/nsappearance?language=objc
WebviewIsTransparent: number; // Makes the background of the webview content transparent. Use this with the Alpha part of the window colour to make parts of your application transparent.
WindowBackgroundIsTranslucent: number; // Makes the transparent parts of the application window translucent. Example: https://en.wikipedia.org/wiki/MacOS_Big_Sur#/media/File:MacOS_Big_Sur_-_Safari_Extensions_category_in_App_Store.jpg
LogLevel: number; // The initial log level (lower is more verbose)
}
interface Level {
TRACE: 1,
DEBUG: 2,
INFO: 3,
WARNING: 4,
ERROR: 5,
}
interface OpenDialogOptions {
DefaultDirectory: string;
DefaultFilename: string;
Title: string;
Filters: string;
AllowFiles: boolean;
AllowDirectories: boolean;
AllowMultiple: boolean;
ShowHiddenFiles: boolean;
CanCreateDirectories: boolean;
ResolvesAliases: boolean;
TreatPackagesAsDirectories: boolean;
}
interface SaveDialogOptions {
DefaultDirectory: string;
DefaultFilename: string;
Title: string;
Filters: string;
ShowHiddenFiles: boolean;
CanCreateDirectories: boolean;
TreatPackagesAsDirectories: boolean;
}
interface DialogType {
InfoDialog: 'info',
WarningDialog: 'warning',
ErrorDialog: 'error',
QuestionDialog: 'question',
}
interface MessageDialogOptions {
Type: DialogType;
Title: string;
Message: string;
Buttons: string[];
DefaultButton: string;
CancelButton: string;
Icon: string;
}
declare const wailsapp__runtime: {
Browser: {
Open(target: string): Promise<any>;
OpenFile(filename: string): Promise<any>;
OpenURL(url: string): Promise<any>;
};
Events: {
Emit(eventName: string, data?: any): void;
On(eventName: string, callback: (data?: any) => void): void;
OnMultiple(eventName: string, callback: (data?: any) => void, maxCallbacks: number): void;
Once(eventName: string, callback: (data?: any) => void): void;
Acknowledge(eventName: string): void;
Emit(eventName: string): void;
Heartbeat(eventName: string, timeInMilliseconds: number, callback: () => void): void;
On(eventName: string, callback: () => void): void;
OnMultiple(eventName: string, callback: () => void, maxCallbacks: number): void;
Once(eventName: string, callback: () => void): void;
};
// Init(callback: () => void): void;
Init(callback: () => void): void;
Log: {
Debug(message: string): void;
Error(message: string): void;
Fatal(message: string): void;
Info(message: string): void;
Warning(message: string): void;
Level: Level;
};
System: {
DarkModeEnabled(): Promise<boolean>;
OnThemeChange(callback: (darkModeEnabled: boolean) => void): void;
LogLevel(): Store;
Platform(): string;
AppType(): string;
AppConfig(): AppConfig;
};
Store: {
New(name: string, defaultValue?: any): Store;
};
Dialog: {
Open(options: OpenDialogOptions): Promise<Array<string>>;
Save(options: SaveDialogOptions): Promise<string>;
Message(options: MessageDialogOptions): Promise<string>;
};
Tray: {
SetIcon(trayIconID: string): void;
}
};

View File

@@ -13,7 +13,7 @@ module.exports = {
mode: 'production',
output: {
path: path.resolve(__dirname, '..', 'assets'),
filename: 'desktop_'+platform+'.js',
filename: 'desktop.js',
library: 'Wails'
},
resolve: {

View File

@@ -1,7 +1,6 @@
package servicebus
import (
"context"
"fmt"
"strings"
"sync"
@@ -13,26 +12,23 @@ import (
type ServiceBus struct {
listeners map[string][]chan *Message
messageQueue chan *Message
quitChannel chan struct{}
wg sync.WaitGroup
lock sync.RWMutex
closed bool
debug bool
logger logger.CustomLogger
ctx context.Context
cancel context.CancelFunc
}
// New creates a new ServiceBus
// The internal message queue is set to 100 messages
// Listener queues are set to 10
func New(logger *logger.Logger) *ServiceBus {
ctx, cancel := context.WithCancel(context.Background())
return &ServiceBus{
listeners: make(map[string][]chan *Message),
messageQueue: make(chan *Message, 100),
quitChannel: make(chan struct{}, 1),
logger: logger.CustomLogger("Service Bus"),
ctx: ctx,
cancel: cancel,
}
}
@@ -67,22 +63,23 @@ func (s *ServiceBus) Debug() {
// Start the service bus
func (s *ServiceBus) Start() error {
s.logger.Trace("Starting")
// Prevent starting when closed
if s.closed {
return fmt.Errorf("cannot call start on closed servicebus")
}
s.logger.Trace("Starting")
// We run in a different thread
go func() {
defer s.logger.Trace("Stopped")
quit := false
s.wg.Add(1)
// Loop until we get a quit message
for {
for !quit {
select {
case <-s.ctx.Done():
return
// Listen for messages
case message := <-s.messageQueue:
@@ -93,9 +90,16 @@ func (s *ServiceBus) Start() error {
}
// Dispatch message
s.dispatchMessage(message)
// Listen for quit messages
case <-s.quitChannel:
quit = true
}
}
// Indicate we have shut down
s.wg.Done()
}()
return nil
@@ -112,7 +116,10 @@ func (s *ServiceBus) Stop() error {
s.closed = true
// Send quit message
s.cancel()
s.quitChannel <- struct{}{}
// Wait for dispatcher to stop
s.wg.Wait()
// Close down subscriber channels
s.lock.Lock()
@@ -127,6 +134,7 @@ func (s *ServiceBus) Stop() error {
// Close message queue
close(s.messageQueue)
s.logger.Trace("Stopped")
return nil
}
@@ -160,22 +168,25 @@ func (s *ServiceBus) Subscribe(topic string) (<-chan *Message, error) {
}
// Publish sends the given message on the service bus
func (s *ServiceBus) Publish(topic string, data interface{}) {
func (s *ServiceBus) Publish(topic string, data interface{}) error {
// Prevent publish when closed
if s.closed {
return
return fmt.Errorf("cannot call publish on closed servicebus")
}
message := NewMessage(topic, data)
s.messageQueue <- message
return nil
}
// PublishForTarget sends the given message on the service bus for the given target
func (s *ServiceBus) PublishForTarget(topic string, data interface{}, target string) {
func (s *ServiceBus) PublishForTarget(topic string, data interface{}, target string) error {
// Prevent publish when closed
if s.closed {
return
return fmt.Errorf("cannot call publish on closed servicebus")
}
message := NewMessageForTarget(topic, data, target)
s.messageQueue <- message
return nil
}

View File

@@ -1,10 +1,8 @@
package signal
import (
"context"
"os"
gosignal "os/signal"
"sync"
"syscall"
"github.com/wailsapp/wails/v2/internal/logger"
@@ -22,24 +20,24 @@ type Manager struct {
// signalChannel
signalchannel chan os.Signal
// ctx
ctx context.Context
cancel context.CancelFunc
// Parent waitgroup
wg *sync.WaitGroup
// Quit channel
quitChannel <-chan *servicebus.Message
}
// NewManager creates a new signal manager
func NewManager(ctx context.Context, cancel context.CancelFunc, bus *servicebus.ServiceBus, logger *logger.Logger) (*Manager, error) {
func NewManager(bus *servicebus.ServiceBus, logger *logger.Logger) (*Manager, error) {
// Register quit channel
quitChannel, err := bus.Subscribe("quit")
if err != nil {
return nil, err
}
result := &Manager{
bus: bus,
logger: logger.CustomLogger("Event Manager"),
signalchannel: make(chan os.Signal, 2),
ctx: ctx,
cancel: cancel,
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
quitChannel: quitChannel,
}
return result, nil
@@ -51,23 +49,20 @@ func (m *Manager) Start() {
// Hook into interrupts
gosignal.Notify(m.signalchannel, os.Interrupt, syscall.SIGTERM)
m.wg.Add(1)
// Spin off signal listener and wait for either a cancellation
// or signal
// Spin off signal listener
go func() {
select {
case <-m.signalchannel:
println()
m.logger.Trace("Ctrl+C detected. Shutting down...")
m.bus.Publish("quit", "ctrl-c pressed")
// Start shutdown of Wails
m.cancel()
case <-m.ctx.Done():
running := true
for running {
select {
case <-m.signalchannel:
println()
m.logger.Trace("Ctrl+C detected. Shutting down...")
m.bus.Publish("quit", "ctrl-c pressed")
case <-m.quitChannel:
running = false
break
}
}
m.logger.Trace("Shutdown")
m.wg.Done()
}()
}

View File

@@ -3,16 +3,16 @@ package subsystem
import (
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/internal/runtime/goruntime"
"github.com/wailsapp/wails/v2/internal/servicebus"
)
// Binding is the Binding subsystem. It manages all service bus messages
// starting with "binding".
type Binding struct {
quitChannel <-chan *servicebus.Message
bindingChannel <-chan *servicebus.Message
running bool
running bool
// Binding db
bindings *binding.Bindings
@@ -21,11 +21,17 @@ type Binding struct {
logger logger.CustomLogger
// runtime
runtime *runtime.Runtime
runtime *goruntime.Runtime
}
// NewBinding creates a new binding subsystem. Uses the given bindings db for reference.
func NewBinding(bus *servicebus.ServiceBus, logger *logger.Logger, bindings *binding.Bindings, runtime *runtime.Runtime) (*Binding, error) {
func NewBinding(bus *servicebus.ServiceBus, logger *logger.Logger, bindings *binding.Bindings, runtime *goruntime.Runtime) (*Binding, error) {
// Register quit channel
quitChannel, err := bus.Subscribe("quit")
if err != nil {
return nil, err
}
// Subscribe to event messages
bindingChannel, err := bus.Subscribe("binding")
@@ -34,12 +40,21 @@ func NewBinding(bus *servicebus.ServiceBus, logger *logger.Logger, bindings *bin
}
result := &Binding{
quitChannel: quitChannel,
bindingChannel: bindingChannel,
logger: logger.CustomLogger("Binding Subsystem"),
bindings: bindings,
runtime: runtime,
}
// Call WailsInit methods once the frontend is loaded
// TODO: Double check that this is actually being emitted
// when we want it to be
runtime.Events.On("wails:loaded", func(...interface{}) {
result.logger.Trace("Calling WailsInit() methods")
result.CallWailsInit()
})
return result, nil
}
@@ -54,16 +69,45 @@ func (b *Binding) Start() error {
go func() {
for b.running {
select {
case <-b.quitChannel:
b.running = false
case bindingMessage := <-b.bindingChannel:
b.logger.Trace("Got binding message: %+v", bindingMessage)
}
}
b.logger.Trace("Shutdown")
// Call shutdown
b.shutdown()
}()
return nil
}
func (b *Binding) Close() {
b.running = false
// CallWailsInit will callback to the registered WailsInit
// methods with the runtime object
func (b *Binding) CallWailsInit() error {
for _, wailsinit := range b.bindings.DB().WailsInitMethods() {
_, err := wailsinit.Call([]interface{}{b.runtime})
if err != nil {
return err
}
}
return nil
}
// CallWailsShutdown will callback to the registered WailsShutdown
// methods with the runtime object
func (b *Binding) CallWailsShutdown() error {
for _, wailsshutdown := range b.bindings.DB().WailsShutdownMethods() {
_, err := wailsshutdown.Call([]interface{}{})
if err != nil {
return err
}
}
return nil
}
func (b *Binding) shutdown() {
b.CallWailsShutdown()
b.logger.Trace("Shutdown")
}

View File

@@ -1,28 +1,21 @@
package subsystem
import (
"context"
"encoding/json"
"fmt"
"strings"
"sync"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
"github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/internal/servicebus"
)
// Call is the Call subsystem. It manages all service bus messages
// starting with "call".
type Call struct {
quitChannel <-chan *servicebus.Message
callChannel <-chan *servicebus.Message
// quit flag
shouldQuit bool
running bool
// bindings DB
DB *binding.DB
@@ -32,19 +25,16 @@ type Call struct {
// logger
logger logger.CustomLogger
// runtime
runtime *runtime.Runtime
// context
ctx context.Context
// parent waitgroup
wg *sync.WaitGroup
}
// NewCall creates a new call subsystem
func NewCall(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger, DB *binding.DB, runtime *runtime.Runtime) (*Call, error) {
// NewCall creates a new log subsystem
func NewCall(bus *servicebus.ServiceBus, logger *logger.Logger, DB *binding.DB) (*Call, error) {
// Register quit channel
quitChannel, err := bus.Subscribe("quit")
if err != nil {
return nil, err
}
// Subscribe to event messages
callChannel, err := bus.Subscribe("call:invoke")
@@ -53,13 +43,11 @@ func NewCall(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Log
}
result := &Call{
quitChannel: quitChannel,
callChannel: callChannel,
logger: logger.CustomLogger("Call Subsystem"),
DB: DB,
bus: bus,
runtime: runtime,
ctx: ctx,
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
}
return result, nil
@@ -68,21 +56,22 @@ func NewCall(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Log
// Start the subsystem
func (c *Call) Start() error {
c.wg.Add(1)
c.running = true
// Spin off a go routine
go func() {
defer c.logger.Trace("Shutdown")
for {
for c.running {
select {
case <-c.ctx.Done():
c.wg.Done()
return
case <-c.quitChannel:
c.running = false
case callMessage := <-c.callChannel:
c.processCall(callMessage)
}
}
// Call shutdown
c.shutdown()
}()
return nil
@@ -98,12 +87,6 @@ func (c *Call) processCall(callMessage *servicebus.Message) {
// Lookup method
registeredMethod := c.DB.GetMethod(payload.Name)
// Check if it's a system call
if strings.HasPrefix(payload.Name, ".wails.") {
c.processSystemCall(payload, callMessage.Target())
return
}
// Check we have it
if registeredMethod == nil {
c.sendError(fmt.Errorf("Method not registered"), payload, callMessage.Target())
@@ -111,12 +94,7 @@ func (c *Call) processCall(callMessage *servicebus.Message) {
}
c.logger.Trace("Got registered method: %+v", registeredMethod)
args, err := registeredMethod.ParseArgs(payload.Args)
if err != nil {
c.sendError(fmt.Errorf("Error parsing arguments: %s", err.Error()), payload, callMessage.Target())
}
result, err := registeredMethod.Call(args)
result, err := registeredMethod.Call(payload.Args)
if err != nil {
c.sendError(err, payload, callMessage.Target())
return
@@ -127,50 +105,14 @@ func (c *Call) processCall(callMessage *servicebus.Message) {
}
func (c *Call) processSystemCall(payload *message.CallMessage, clientID string) {
c.logger.Trace("Got internal System call: %+v", payload)
callName := strings.TrimPrefix(payload.Name, ".wails.")
switch callName {
case "IsDarkMode":
darkModeEnabled := c.runtime.System.IsDarkMode()
c.sendResult(darkModeEnabled, payload, clientID)
case "Dialog.Open":
dialogOptions := new(dialog.OpenDialog)
err := json.Unmarshal(payload.Args[0], dialogOptions)
if err != nil {
c.logger.Error("Error decoding: %s", err)
}
result := c.runtime.Dialog.Open(dialogOptions)
c.sendResult(result, payload, clientID)
case "Dialog.Save":
dialogOptions := new(dialog.SaveDialog)
err := json.Unmarshal(payload.Args[0], dialogOptions)
if err != nil {
c.logger.Error("Error decoding: %s", err)
}
result := c.runtime.Dialog.Save(dialogOptions)
c.sendResult(result, payload, clientID)
case "Dialog.Message":
dialogOptions := new(dialog.MessageDialog)
err := json.Unmarshal(payload.Args[0], dialogOptions)
if err != nil {
c.logger.Error("Error decoding: %s", err)
}
result := c.runtime.Dialog.Message(dialogOptions)
c.sendResult(result, payload, clientID)
default:
c.logger.Error("Unknown system call: %+v", callName)
}
}
func (c *Call) sendResult(result interface{}, payload *message.CallMessage, clientID string) {
c.logger.Trace("Sending success result with CallbackID '%s' : %+v\n", payload.CallbackID, result)
incomingMessage := &CallbackMessage{
message := &CallbackMessage{
Result: result,
CallbackID: payload.CallbackID,
}
messageData, err := json.Marshal(incomingMessage)
c.logger.Trace("json incomingMessage data: %+v\n", string(messageData))
messageData, err := json.Marshal(message)
c.logger.Trace("json message data: %+v\n", string(messageData))
if err != nil {
// what now?
c.logger.Fatal(err.Error())
@@ -180,13 +122,13 @@ func (c *Call) sendResult(result interface{}, payload *message.CallMessage, clie
func (c *Call) sendError(err error, payload *message.CallMessage, clientID string) {
c.logger.Trace("Sending error result with CallbackID '%s' : %+v\n", payload.CallbackID, err.Error())
incomingMessage := &CallbackMessage{
message := &CallbackMessage{
Err: err.Error(),
CallbackID: payload.CallbackID,
}
messageData, err := json.Marshal(incomingMessage)
c.logger.Trace("json incomingMessage data: %+v\n", string(messageData))
messageData, err := json.Marshal(message)
c.logger.Trace("json message data: %+v\n", string(messageData))
if err != nil {
// what now?
c.logger.Fatal(err.Error())
@@ -194,6 +136,10 @@ func (c *Call) sendError(err error, payload *message.CallMessage, clientID strin
c.bus.PublishForTarget("call:result", string(messageData), clientID)
}
func (c *Call) shutdown() {
c.logger.Trace("Shutdown")
}
// CallbackMessage defines a message that contains the result of a call
type CallbackMessage struct {
Result interface{} `json:"result"`

View File

@@ -1,7 +1,6 @@
package subsystem
import (
"context"
"strings"
"sync"
@@ -16,14 +15,16 @@ import (
// means it does not expire (default).
type eventListener struct {
callback func(...interface{}) // Function to call with emitted event data
counter int // The number of times this callback may be called. -1 = infinite
counter int64 // The number of times this callback may be called. -1 = infinite
delete bool // Flag to indicate that this listener should be deleted
}
// Event is the Eventing subsystem. It manages all service bus messages
// starting with "event".
type Event struct {
quitChannel <-chan *servicebus.Message
eventChannel <-chan *servicebus.Message
running bool
// Event listeners
listeners map[string][]*eventListener
@@ -31,16 +32,16 @@ type Event struct {
// logger
logger logger.CustomLogger
// ctx
ctx context.Context
// parent waitgroup
wg *sync.WaitGroup
}
// NewEvent creates a new log subsystem
func NewEvent(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger) (*Event, error) {
func NewEvent(bus *servicebus.ServiceBus, logger *logger.Logger) (*Event, error) {
// Register quit channel
quitChannel, err := bus.Subscribe("quit")
if err != nil {
return nil, err
}
// Subscribe to event messages
eventChannel, err := bus.Subscribe("event")
@@ -49,23 +50,22 @@ func NewEvent(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Lo
}
result := &Event{
quitChannel: quitChannel,
eventChannel: eventChannel,
logger: logger.CustomLogger("Event Subsystem"),
listeners: make(map[string][]*eventListener),
ctx: ctx,
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
}
return result, nil
}
// RegisterListener provides a means of subscribing to events of type "eventName"
func (e *Event) RegisterListener(eventName string, callback func(...interface{}), counter int) {
func (e *Event) RegisterListener(eventName string, callback func(...interface{})) {
// Create new eventListener
thisListener := &eventListener{
callback: callback,
counter: counter,
counter: 0,
delete: false,
}
@@ -80,16 +80,15 @@ func (e *Event) Start() error {
e.logger.Trace("Starting")
e.wg.Add(1)
e.running = true
// Spin off a go routine
go func() {
defer e.logger.Trace("Shutdown")
for {
for e.running {
select {
case <-e.ctx.Done():
e.wg.Done()
return
case <-e.quitChannel:
e.running = false
break
case eventMessage := <-e.eventChannel:
splitTopic := strings.Split(eventMessage.Topic(), ":")
eventType := splitTopic[1]
@@ -121,7 +120,7 @@ func (e *Event) Start() error {
var message *message.OnEventMessage = eventMessage.Data().(*message.OnEventMessage)
eventName := message.Name
callback := message.Callback
e.RegisterListener(eventName, callback, message.Counter)
e.RegisterListener(eventName, callback)
e.logger.Trace("Registered listener for event '%s' with callback %p", eventName, callback)
default:
e.logger.Error("unknown event message: %+v", eventMessage)
@@ -129,6 +128,8 @@ func (e *Event) Start() error {
}
}
// Call shutdown
e.shutdown()
}()
return nil
@@ -140,7 +141,7 @@ func (e *Event) notifyListeners(eventName string, message *message.EventMessage)
// Get list of event listeners
listeners := e.listeners[eventName]
if listeners == nil {
e.logger.Trace("No listeners for event '%s'", eventName)
println("no listeners for", eventName)
return
}
@@ -178,14 +179,15 @@ func (e *Event) notifyListeners(eventName string, message *message.EventMessage)
}
}
// Save new listeners or remove entry
if len(newListeners) > 0 {
e.listeners[eventName] = newListeners
} else {
delete(e.listeners, eventName)
}
// Save new listeners
e.listeners[eventName] = newListeners
}
// Unlock
e.notifyLock.Unlock()
}
func (e *Event) shutdown() {
e.logger.Trace("Shutdown")
}

View File

@@ -1,40 +1,25 @@
package subsystem
import (
"context"
"strconv"
"strings"
"sync"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/internal/servicebus"
)
// Log is the Logging subsystem. It handles messages with topics starting
// with "log:"
type Log struct {
logChannel <-chan *servicebus.Message
// quit flag
shouldQuit bool
logChannel <-chan *servicebus.Message
quitChannel <-chan *servicebus.Message
running bool
// Logger!
logger *logger.Logger
// Loglevel store
logLevelStore *runtime.Store
// Context for shutdown
ctx context.Context
cancel context.CancelFunc
// internal waitgroup
wg sync.WaitGroup
}
// NewLog creates a new log subsystem
func NewLog(bus *servicebus.ServiceBus, logger *logger.Logger, logLevelStore *runtime.Store) (*Log, error) {
func NewLog(bus *servicebus.ServiceBus, logger *logger.Logger) (*Log, error) {
// Subscribe to log messages
logChannel, err := bus.Subscribe("log")
@@ -42,14 +27,16 @@ func NewLog(bus *servicebus.ServiceBus, logger *logger.Logger, logLevelStore *ru
return nil, err
}
ctx, cancel := context.WithCancel(context.Background())
// Subscribe to quit messages
quitChannel, err := bus.Subscribe("quit")
if err != nil {
return nil, err
}
result := &Log{
logChannel: logChannel,
logger: logger,
logLevelStore: logLevelStore,
ctx: ctx,
cancel: cancel,
logChannel: logChannel,
quitChannel: quitChannel,
logger: logger,
}
return result, nil
@@ -58,24 +45,18 @@ func NewLog(bus *servicebus.ServiceBus, logger *logger.Logger, logLevelStore *ru
// Start the subsystem
func (l *Log) Start() error {
l.wg.Add(1)
l.running = true
// Spin off a go routine
go func() {
defer l.logger.Trace("Logger Shutdown")
for l.shouldQuit == false {
for l.running {
select {
case <-l.ctx.Done():
l.wg.Done()
return
case <-l.quitChannel:
l.running = false
break
case logMessage := <-l.logChannel:
logType := strings.TrimPrefix(logMessage.Topic(), "log:")
switch logType {
case "print":
l.logger.Print(logMessage.Data().(string))
case "trace":
l.logger.Trace(logMessage.Data().(string))
case "debug":
l.logger.Debug(logMessage.Data().(string))
case "info":
@@ -86,33 +67,13 @@ func (l *Log) Start() error {
l.logger.Error(logMessage.Data().(string))
case "fatal":
l.logger.Fatal(logMessage.Data().(string))
case "setlevel":
switch inLevel := logMessage.Data().(type) {
case logger.LogLevel:
l.logger.SetLogLevel(inLevel)
l.logLevelStore.Set(inLevel)
case string:
uint64level, err := strconv.ParseUint(inLevel, 10, 8)
if err != nil {
l.logger.Error("Error parsing log level: %+v", inLevel)
continue
}
level := logger.LogLevel(uint64level)
l.logLevelStore.Set(level)
l.logger.SetLogLevel(level)
}
default:
l.logger.Error("unknown log message: %+v", logMessage)
}
}
}
l.logger.Trace("Logger Shutdown")
}()
return nil
}
func (l *Log) Close() {
l.cancel()
l.wg.Wait()
}

View File

@@ -1,67 +1,47 @@
package subsystem
import (
"context"
"fmt"
"strings"
"sync"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/internal/runtime/goruntime"
"github.com/wailsapp/wails/v2/internal/servicebus"
)
// Runtime is the Runtime subsystem. It handles messages with topics starting
// with "runtime:"
type Runtime struct {
quitChannel <-chan *servicebus.Message
runtimeChannel <-chan *servicebus.Message
// The hooks channel allows us to hook into frontend startup
hooksChannel <-chan *servicebus.Message
startupCallback func(*runtime.Runtime)
shutdownCallback func()
// quit flag
shouldQuit bool
running bool
logger logger.CustomLogger
// Runtime library
runtime *runtime.Runtime
//ctx
ctx context.Context
// Startup Hook
startupOnce sync.Once
// Service bus
bus *servicebus.ServiceBus
runtime *goruntime.Runtime
}
// NewRuntime creates a new runtime subsystem
func NewRuntime(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger, startupCallback func(*runtime.Runtime)) (*Runtime, error) {
func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger) (*Runtime, error) {
// Subscribe to log messages
runtimeChannel, err := bus.Subscribe("runtime:")
// Register quit channel
quitChannel, err := bus.Subscribe("quit")
if err != nil {
return nil, err
}
// Subscribe to log messages
hooksChannel, err := bus.Subscribe("hooks:")
runtimeChannel, err := bus.Subscribe("runtime")
if err != nil {
return nil, err
}
result := &Runtime{
runtimeChannel: runtimeChannel,
hooksChannel: hooksChannel,
logger: logger.CustomLogger("Runtime Subsystem"),
runtime: runtime.New(bus),
startupCallback: startupCallback,
ctx: ctx,
bus: bus,
quitChannel: quitChannel,
runtimeChannel: runtimeChannel,
logger: logger.CustomLogger("Runtime Subsystem"),
runtime: goruntime.New(bus),
}
return result, nil
@@ -70,36 +50,15 @@ func NewRuntime(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.
// Start the subsystem
func (r *Runtime) Start() error {
r.running = true
// Spin off a go routine
go func() {
defer r.logger.Trace("Shutdown")
for {
for r.running {
select {
case hooksMessage := <-r.hooksChannel:
r.logger.Trace(fmt.Sprintf("Received hooksmessage: %+v", hooksMessage))
messageSlice := strings.Split(hooksMessage.Topic(), ":")
hook := messageSlice[1]
switch hook {
case "startup":
if r.startupCallback != nil {
r.startupOnce.Do(func() {
go func() {
r.startupCallback(r.runtime)
// If we got a url, publish it now startup completed
url, ok := hooksMessage.Data().(string)
if ok && len(url) > 0 {
r.bus.Publish("url:handler", url)
}
}()
})
} else {
r.logger.Warning("no startup callback registered!")
}
default:
r.logger.Error("unknown hook message: %+v", hooksMessage)
continue
}
case <-r.quitChannel:
r.running = false
break
case runtimeMessage := <-r.runtimeChannel:
r.logger.Trace(fmt.Sprintf("Received message: %+v", runtimeMessage))
// Topics have the format: "runtime:category:call"
@@ -116,41 +75,40 @@ func (r *Runtime) Start() error {
case "browser":
err = r.processBrowserMessage(method, runtimeMessage.Data())
default:
err = fmt.Errorf("unknown runtime message: %+v",
runtimeMessage)
fmt.Errorf("unknown log message: %+v", runtimeMessage)
}
// If we had an error, log it
if err != nil {
r.logger.Error(err.Error())
}
case <-r.ctx.Done():
return
}
}
// Call shutdown
r.shutdown()
}()
return nil
}
// GoRuntime returns the Go Runtime object
func (r *Runtime) GoRuntime() *runtime.Runtime {
func (r *Runtime) GoRuntime() *goruntime.Runtime {
return r.runtime
}
func (r *Runtime) shutdown() {
r.logger.Trace("Shutdown")
}
func (r *Runtime) processBrowserMessage(method string, data interface{}) error {
switch method {
case "open":
target, ok := data.(string)
case "openurl":
url, ok := data.(string)
if !ok {
return fmt.Errorf("expected 1 string parameter for runtime:browser:open")
return fmt.Errorf("expected 1 string parameter for runtime:browser:openurl")
}
go func() {
err := r.runtime.Browser.Open(target)
if err != nil {
r.logger.Error(err.Error())
}
}()
go r.runtime.Browser.Open(url)
default:
return fmt.Errorf("unknown method runtime:browser:%s", method)
}

View File

@@ -9,6 +9,24 @@ import (
"github.com/wailsapp/wails/v2/internal/shell"
)
// PackageManager is a common interface across all package managers
type PackageManager interface {
Name() string
Packages() packagemap
PackageInstalled(*Package) (bool, error)
PackageAvailable(*Package) (bool, error)
InstallCommand(*Package) string
}
// Package contains information about a system package
type Package struct {
Name string
Version string
InstallCommand map[string]string
SystemPackage bool
Library bool
Optional bool
}
// A list of package manager commands
var pmcommands = []string{
@@ -20,6 +38,8 @@ var pmcommands = []string{
"zypper",
}
type packagemap = map[string][]*Package
// Find will attempt to find the system package manager
func Find(osid string) PackageManager {
@@ -50,6 +70,58 @@ func newPackageManager(pmname string, osid string) PackageManager {
return nil
}
// Dependancy represents a system package that we require
type Dependancy struct {
Name string
PackageName string
Installed bool
InstallCommand string
Version string
Optional bool
External bool
}
// DependencyList is a list of Dependency instances
type DependencyList []*Dependancy
// InstallAllRequiredCommand returns the command you need to use to install all required dependencies
func (d DependencyList) InstallAllRequiredCommand() string {
result := ""
for _, dependency := range d {
if dependency.PackageName != "" {
if !dependency.Installed && !dependency.Optional {
if result == "" {
result = dependency.InstallCommand
} else {
result += " " + dependency.PackageName
}
}
}
}
return result
}
// InstallAllOptionalCommand returns the command you need to use to install all optional dependencies
func (d DependencyList) InstallAllOptionalCommand() string {
result := ""
for _, dependency := range d {
if dependency.PackageName != "" {
if !dependency.Installed && dependency.Optional {
if result == "" {
result = dependency.InstallCommand
} else {
result += " " + dependency.PackageName
}
}
}
}
return result
}
// Dependancies scans the system for required dependancies
// Returns a list of dependancies search for, whether they were found
// and whether they were installed

View File

@@ -2,20 +2,14 @@
package system
import "github.com/wailsapp/wails/v2/internal/system/operatingsystem"
import (
"fmt"
"syscall"
)
func (i *Info) discover() error {
var err error
osinfo, err := operatingsystem.Info()
if err != nil {
return err
}
i.OS = osinfo
// dll := syscall.MustLoadDLL("kernel32.dll")
// p := dll.MustFindProc("GetVersion")
// v, _, _ := p.Call()
// fmt.Printf("Windows version %d.%d (Build %d)\n", byte(v), uint8(v>>8), uint16(v>>16))
return nil
func (i *Info) discover() {
dll := syscall.MustLoadDLL("kernel32.dll")
p := dll.MustFindProc("GetVersion")
v, _, _ := p.Call()
fmt.Printf("Windows version %d.%d (Build %d)\n", byte(v), uint8(v>>8), uint16(v>>16))
}

View File

@@ -6,14 +6,13 @@ import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/leaanthony/gosod"
"github.com/leaanthony/slicer"
"github.com/olekukonko/tablewriter"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/internal/logger"
)
// Cahce for the templates
@@ -22,29 +21,21 @@ var templateCache []Template = nil
// Data contains the data we wish to embed during template installation
type Data struct {
ProjectName string
BinaryName string
WailsVersion string
NPMProjectName string
AuthorName string
AuthorEmail string
AuthorNameAndEmail string
WailsDirectory string
ProjectName string
BinaryName string
WailsVersion string
NPMProjectName string
Author string
WailsDirectory string
}
// Options for installing a template
type Options struct {
ProjectName string
TemplateName string
BinaryName string
TargetDir string
Logger *clilogger.CLILogger
GenerateVSCode bool
PathToDesktopBinary string
PathToServerBinary string
InitGit bool
AuthorName string
AuthorEmail string
ProjectName string
TemplateName string
BinaryName string
TargetDir string
Logger *logger.Logger
}
// Template holds data relating to a template
@@ -171,24 +162,9 @@ func Install(options *Options) error {
}
// Did the user want to install in current directory?
if options.TargetDir == "" {
// If the current directory is empty, use it
isEmpty, err := fs.DirIsEmpty(cwd)
if err != nil {
return err
}
if isEmpty {
// Yes - use cwd
options.TargetDir = cwd
} else {
options.TargetDir = filepath.Join(cwd, options.ProjectName)
if fs.DirExists(options.TargetDir) {
return fmt.Errorf("cannot create project directory. Dir exists: %s", options.TargetDir)
}
}
if options.TargetDir == "." {
// Yes - use cwd
options.TargetDir = cwd
} else {
// Get the absolute path of the given directory
targetDir, err := filepath.Abs(filepath.Join(cwd, options.TargetDir))
@@ -229,93 +205,43 @@ func Install(options *Options) error {
BinaryName: filepath.Base(options.TargetDir),
NPMProjectName: NPMProjectName,
WailsDirectory: localWailsDirectory,
AuthorEmail: options.AuthorEmail,
AuthorName: options.AuthorName,
}
// Create a formatted name and email combo.
if options.AuthorName != "" {
templateData.AuthorNameAndEmail = options.AuthorName + " "
}
if options.AuthorEmail != "" {
templateData.AuthorNameAndEmail += "<" + options.AuthorEmail + ">"
}
templateData.AuthorNameAndEmail = strings.TrimSpace(templateData.AuthorNameAndEmail)
// Extract the template
err = installer.Extract(options.TargetDir, templateData)
if err != nil {
return err
}
err = generateIDEFiles(options)
if err != nil {
return err
}
// Calculate the directory name
return nil
}
// OutputList prints the list of available tempaltes to the given logger
func OutputList(logger *clilogger.CLILogger) error {
func OutputList(logger *logger.Logger) error {
templates, err := List()
if err != nil {
return err
}
table := tablewriter.NewWriter(logger.Writer)
table.SetHeader([]string{"Template", "Short Name", "Description"})
table.SetAutoWrapText(false)
table.SetAutoFormatHeaders(true)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetRowSeparator("")
table.SetHeaderLine(false)
table.SetBorder(false)
table.SetTablePadding("\t") // pad with tabs
table.SetNoWhiteSpace(true)
for _, template := range templates {
table.Append([]string{template.Name, template.ShortName, template.Description})
for _, writer := range logger.Writers() {
table := tablewriter.NewWriter(writer)
table.SetHeader([]string{"Template", "Short Name", "Description"})
table.SetAutoWrapText(false)
table.SetAutoFormatHeaders(true)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetCenterSeparator("")
table.SetColumnSeparator("")
table.SetRowSeparator("")
table.SetHeaderLine(false)
table.SetBorder(false)
table.SetTablePadding("\t") // pad with tabs
table.SetNoWhiteSpace(true)
for _, template := range templates {
table.Append([]string{template.Name, template.ShortName, template.Description})
}
table.Render()
}
table.Render()
return nil
}
func generateIDEFiles(options *Options) error {
if options.GenerateVSCode {
return generateVSCodeFiles(options)
}
return nil
}
func generateVSCodeFiles(options *Options) error {
targetDir := filepath.Join(options.TargetDir, ".vscode")
sourceDir := fs.RelativePath(filepath.Join("./ides/vscode"))
// Use Gosod to install the template
installer, err := gosod.TemplateDir(sourceDir)
if err != nil {
return err
}
binaryName := filepath.Base(options.TargetDir)
if runtime.GOOS == "windows" {
// yay windows
binaryName += ".exe"
}
options.PathToDesktopBinary = filepath.Join("build", runtime.GOOS, "desktop", binaryName)
options.PathToServerBinary = filepath.Join("build", runtime.GOOS, "server", binaryName)
err = installer.Extract(targetDir, options)
if err != nil {
return err
}
return nil
}

View File

@@ -3,7 +3,7 @@ package main
import (
"fmt"
"github.com/wailsapp/wails/v2"
wails "github.com/wailsapp/wails/v2"
)
// Basic application struct

View File

@@ -2,21 +2,21 @@
"name": "svelte-app",
"version": "1.0.0",
"scripts": {
"build": "npx rollup -c",
"dev": "npx rollup -c -w",
"start": "npx sirv public",
"start:dev": "npx sirv public --single --host 0.0.0.0 --dev"
"build": "rollup -c",
"dev": "rollup -c -w",
"start": "sirv public",
"start:dev": "sirv public --single --host 0.0.0.0 --dev"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-node-resolve": "^11.0.1",
"focus-visible": "^5.2.0",
"rollup": "^2.35.1",
"rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-svelte": "^7.0.0",
"rollup-plugin-terser": "^7.0.2",
"svelte": "^3.31.1",
"svelte-mui": "^0.3.3-5"
"@rollup/plugin-commonjs": "^11.0.0",
"@rollup/plugin-node-resolve": "^7.0.0",
"focus-visible": "^5.0.2",
"rollup": "^1.20.0",
"rollup-plugin-livereload": "^1.0.0",
"rollup-plugin-svelte": "^5.0.3",
"rollup-plugin-terser": "^5.1.2",
"svelte": "^3.0.0",
"svelte-mui": "^0.3.3"
},
"dependencies": {
"sirv-cli": "^0.4.4"

View File

@@ -1,22 +1,15 @@
package main
import (
"github.com/wailsapp/wails/v2"
"log"
wails "github.com/wailsapp/wails/v2"
)
func main() {
// Create application with options
app, err := wails.CreateApp("{{.ProjectName}}", 1024, 768)
if err != nil {
log.Fatal(err)
}
app := wails.CreateApp("{{.ProjectName}}", 1024, 768)
app.Bind(newBasic())
err = app.Run()
if err != nil {
log.Fatal(err)
}
app.Run()
}

View File

@@ -3,7 +3,7 @@ package main
import (
"fmt"
"github.com/wailsapp/wails/v2"
wails "github.com/wailsapp/wails/v2"
)
// Basic application struct
@@ -12,19 +12,20 @@ type Basic struct {
}
// newBasic creates a new Basic application struct
func NewBasic() *Basic {
func newBasic() *Basic {
return &Basic{}
}
// startup is called at application startup
func (b *Basic) startup(runtime *wails.Runtime) {
// WailsInit is called at application startup
func (b *Basic) WailsInit(runtime *wails.Runtime) error {
// Perform your setup here
b.runtime = runtime
runtime.Window.SetTitle("{{.ProjectName}}")
return nil
}
// shutdown is called at application termination
func (b *Basic) shutdown() {
// WailsShutdown is called at application termination
func (b *Basic) WailsShutdown() {
// Perform your teardown here
}

View File

@@ -4,7 +4,7 @@
<link rel="stylesheet" href="/main.css">
</head>
<body data-wails-drag>
<body>
<div id="logo"></div>
<div id="input">
<input id="name" type="text"></input>

File diff suppressed because one or more lines are too long

View File

@@ -1,39 +1,15 @@
package main
import (
"log"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/mac"
wails "github.com/wailsapp/wails/v2"
)
func main() {
// Create application with options
app := NewBasic()
app := wails.CreateApp("{{.ProjectName}}", 1024, 768)
err := wails.Run(&options.App{
Title: "{{.ProjectName}}",
Width: 800,
Height: 600,
DisableResize: true,
Mac: &mac.Options{
WebviewIsTransparent: true,
WindowBackgroundIsTranslucent: true,
TitleBar: mac.TitleBarHiddenInset(),
Menu: menu.DefaultMacMenu(),
},
LogLevel: logger.DEBUG,
Startup: app.startup,
Shutdown: app.shutdown,
Bind: []interface{}{
app,
},
})
if err != nil {
log.Fatal(err)
}
app.Bind(newBasic())
app.Run()
}

View File

@@ -1,9 +1,5 @@
{
"name": "{{.ProjectName}}",
"outputfilename": "{{.BinaryName}}",
"html": "frontend/index.html",
"author": {
"name": "{{.AuthorName}}",
"email": "{{.AuthorEmail}}"
}
"html": "frontend/index.html"
}

View File

@@ -3,7 +3,7 @@ package main
import (
"fmt"
"github.com/wailsapp/wails/v2"
wails "github.com/wailsapp/wails/v2"
)
// Basic application struct

View File

@@ -0,0 +1,52 @@
{
"name": "vuetify2",
"author": "Travis McLane<tmclane@gmail.com>",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.6.1",
"regenerator-runtime": "^0.13.3",
"vue": "^2.5.22",
"vuetify": "^2.2.17"
},
"devDependencies": {
"@mdi/font": "^4.3.95",
"@vue/cli-plugin-babel": "^3.4.0",
"@vue/cli-plugin-eslint": "^3.4.0",
"@vue/cli-service": "^3.4.0",
"babel-eslint": "^10.0.1",
"eslint": "^5.8.0",
"eslint-plugin-vue": "^5.0.0",
"eventsource-polyfill": "^0.9.6",
"vue-template-compiler": "^2.5.21",
"webpack-hot-middleware": "^2.24.3"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
}
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}

View File

@@ -1,7 +1,7 @@
package main
import (
"github.com/wailsapp/wails/v2"
wails "github.com/wailsapp/wails/v2"
)
func main() {

View File

@@ -5,9 +5,5 @@
"js": "frontend/dist/app.js",
"css": "frontend/dist/app.css",
"frontend:build": "npm run build",
"frontend:install": "npm install",
"author": {
"name": "{{.AuthorName}}",
"email": "{{.AuthorEmail}}"
}
"frontend:install": "npm install"
}

View File

@@ -2,8 +2,6 @@ package webserver
import (
"context"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"net/http"
"strings"
@@ -20,94 +18,6 @@ type WebClient struct {
running bool
}
func (wc *WebClient) SetTrayMenu(trayMenuJSON string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) UpdateTrayMenuLabel(trayMenuJSON string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) SetApplicationMenu(menuJSON string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) UpdateTrayMenu(trayMenuJSON string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) UpdateContextMenu(contextMenuJSON string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) WindowShow() {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) WindowHide() {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) WindowCenter() {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) WindowMaximise() {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) WindowUnmaximise() {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) WindowMinimise() {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) WindowUnminimise() {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) WindowPosition(x int, y int) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) WindowSize(width int, height int) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) DarkModeEnabled(callbackID string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) UpdateMenu(menu *menu.Menu) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) UpdateTray(menu *menu.Menu) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) UpdateTrayLabel(label string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) UpdateTrayIcon(name string) {
wc.logger.Info("Not implemented in server build")
}
// Quit terminates the webclient session
func (wc *WebClient) Quit() {
wc.running = false
@@ -124,6 +34,21 @@ func (wc *WebClient) CallResult(message string) {
wc.SendMessage("R" + message)
}
// SaveFileDialog is a noop in the webclient
func (wc *WebClient) SaveFileDialog(title string) string {
return ""
}
// OpenFileDialog is a noop in the webclient
func (wc *WebClient) OpenFileDialog(title string) string {
return ""
}
// OpenDirectoryDialog is a noop in the webclient
func (wc *WebClient) OpenDirectoryDialog(title string) string {
return ""
}
// WindowSetTitle is a noop in the webclient
func (wc *WebClient) WindowSetTitle(title string) {}
@@ -134,7 +59,8 @@ func (wc *WebClient) WindowFullscreen() {}
func (wc *WebClient) WindowUnFullscreen() {}
// WindowSetColour is a noop in the webclient
func (wc *WebClient) WindowSetColour(colour int) {
func (wc *WebClient) WindowSetColour(colour string) bool {
return false
}
// Run processes messages from the remote webclient
@@ -165,11 +91,7 @@ func (wc *WebClient) Run(w *WebServer) {
dispatcher.DispatchMessage(v.(string))
}
err := wc.conn.Close(ws.StatusNormalClosure, "Goodbye")
if err != nil {
w.logger.Error("Error encountered on socket: %v", err)
return
}
wc.conn.Close(ws.StatusNormalClosure, "Goodbye")
w.logger.Debug("Connection closed: %v", wc.identifier)
}
@@ -180,10 +102,7 @@ func (wc *WebClient) SendMessage(message string) {
wc.logger.Debug("WebClient.SendMessage() - %s", message)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
err := wc.conn.Write(ctx, ws.MessageText, []byte(message))
if err != nil {
wc.logger.Error("Error encountered writing to webclient: %v", err)
}
wc.conn.Write(ctx, ws.MessageText, []byte(message))
}
// unregisterClient is called automatically by a WebClient session during termination

View File

@@ -14,23 +14,20 @@ import (
"github.com/wailsapp/wails/v2/internal/assetdb"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/html"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/internal/shell"
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
// BaseBuilder is the common builder struct
type BaseBuilder struct {
filesToDelete slicer.StringSlicer
projectData *project.Project
options *Options
}
// NewBaseBuilder creates a new BaseBuilder
func NewBaseBuilder(options *Options) *BaseBuilder {
result := &BaseBuilder{
options: options,
}
func NewBaseBuilder() *BaseBuilder {
result := &BaseBuilder{}
return result
}
@@ -40,9 +37,7 @@ func (b *BaseBuilder) SetProjectData(projectData *project.Project) {
}
func (b *BaseBuilder) addFileToDelete(filename string) {
if !b.options.KeepAssets {
b.filesToDelete.Add(filename)
}
b.filesToDelete.Add(filename)
}
func (b *BaseBuilder) fileExists(path string) bool {
@@ -54,43 +49,39 @@ func (b *BaseBuilder) fileExists(path string) bool {
return true
}
// buildCustomAssets will iterate through the projects static directory and add all files
// buildStaticAssets will iterate through the projects static directory and add all files
// to the application wide asset database.
func (b *BaseBuilder) buildCustomAssets(projectData *project.Project) error {
func (b *BaseBuilder) buildStaticAssets(projectData *project.Project) error {
// Add trailing slash to Asset directory
customAssetsDir := filepath.Join(projectData.Path, "assets", "custom") + "/"
if !b.fileExists(customAssetsDir) {
err := fs.MkDirs(customAssetsDir)
if err != nil {
return err
}
}
assetsDir := filepath.Join(projectData.Path, "assets") + "/"
assets := assetdb.NewAssetDB()
err := filepath.Walk(customAssetsDir, func(path string, info os.FileInfo, err error) error {
if b.fileExists(assetsDir) {
err := filepath.Walk(assetsDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
normalisedPath := filepath.ToSlash(path)
localPath := strings.TrimPrefix(normalisedPath, assetsDir)
if len(localPath) == 0 {
return nil
}
if data, err := ioutil.ReadFile(filepath.Join(assetsDir, localPath)); err == nil {
assets.AddAsset(localPath, data)
}
return nil
})
if err != nil {
return err
}
normalisedPath := filepath.ToSlash(path)
localPath := strings.TrimPrefix(normalisedPath, customAssetsDir)
if len(localPath) == 0 {
return nil
}
if data, err := ioutil.ReadFile(filepath.Join(customAssetsDir, localPath)); err == nil {
assets.AddAsset(localPath, data)
}
return nil
})
if err != nil {
return err
}
// Write assetdb out to root directory
assetsDbFilename := fs.RelativePath("../../../assetsdb.go")
b.addFileToDelete(assetsDbFilename)
err = ioutil.WriteFile(assetsDbFilename, []byte(assets.Serialize("assets", "wails")), 0644)
err := ioutil.WriteFile(assetsDbFilename, []byte(assets.Serialize("assets", "wails")), 0644)
if err != nil {
return err
}
@@ -137,7 +128,7 @@ func (b *BaseBuilder) CleanUp() {
// Delete file. We ignore errors because these files will be overwritten
// by the next build anyway.
_ = os.Remove(filename)
os.Remove(filename)
})
}
@@ -145,39 +136,12 @@ func (b *BaseBuilder) CleanUp() {
// CompileProject compiles the project
func (b *BaseBuilder) CompileProject(options *Options) error {
// Run go mod tidy first
cmd := exec.Command(options.Compiler, "mod", "tidy")
err := cmd.Run()
if err != nil {
return err
}
// Default go build command
commands := slicer.String([]string{"build"})
// Add better debugging flags
if options.Mode == Debug {
commands.Add("-gcflags")
commands.Add(`"all=-N -l"`)
}
// TODO: Work out if we can make this more efficient
// We need to do a full build as CGO doesn't detect updates
// to .h files, and we package assets into .h file. We could
// potentially try and see if the assets have changed but will
// this take as much time as a `-a` build?
commands.Add("-a")
var tags slicer.StringSlicer
tags.Add(options.OutputType)
if options.Mode == Debug {
tags.Add("debug")
}
// Add the output type build tag
commands.Add("-tags")
commands.Add(tags.Join(","))
commands.Add(options.OutputType)
// Strip binary in Production mode
if options.Mode == Production {
@@ -194,8 +158,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
}
// Get application build directory
appDir := options.BuildDirectory
err = cleanBuildDirectory(options)
appDir, err := getApplicationBuildDirectory(options, options.Platform)
if err != nil {
return err
}
@@ -206,40 +169,20 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
}
// Set up output filename
outputFile := options.OutputFile
if outputFile == "" {
outputFile = b.projectData.OutputFilename
}
compiledBinary := filepath.Join(appDir, outputFile)
outputFilePath := filepath.Join(appDir, b.projectData.OutputFilename)
commands.Add("-o")
commands.Add(compiledBinary)
commands.Add(outputFilePath)
b.projectData.OutputFilename = strings.TrimPrefix(compiledBinary, options.ProjectData.Path)
options.CompiledBinary = compiledBinary
b.projectData.OutputFilename = strings.TrimPrefix(outputFilePath, options.ProjectData.Path)
// Create the command
cmd = exec.Command(options.Compiler, commands.AsSlice()...)
cmd := exec.Command(options.Compiler, commands.AsSlice()...)
// Set the directory
cmd.Dir = b.projectData.Path
// Add CGO flags
// We use the project/build dir as a temporary place for our generated c headers
buildBaseDir, err := fs.RelativeToCwd("build")
if err != nil {
return err
}
cmd.Env = os.Environ() // inherit env
// Use upsertEnv so we don't overwrite user's CGO_CFLAGS
cmd.Env = upsertEnv(cmd.Env, "CGO_CFLAGS", func(v string) string {
if v != "" {
v += " "
}
v += "-I" + buildBaseDir
return v
})
// Set GO111MODULE environment variable
cmd.Env = append(os.Environ(), "GO111MODULE=on")
// Setup buffers
var stdo, stde bytes.Buffer
@@ -351,9 +294,7 @@ func (b *BaseBuilder) NpmRunWithEnvironment(projectDir, buildTarget string, verb
}
// BuildFrontend executes the `npm build` command for the frontend directory
func (b *BaseBuilder) BuildFrontend(outputLogger *clilogger.CLILogger) error {
// TODO: Fix this up from the CLI
func (b *BaseBuilder) BuildFrontend(outputLogger *logger.Logger) error {
verbose := false
frontendDir := filepath.Join(b.projectData.Path, "frontend")
@@ -361,10 +302,10 @@ func (b *BaseBuilder) BuildFrontend(outputLogger *clilogger.CLILogger) error {
// Check there is an 'InstallCommand' provided in wails.json
if b.projectData.InstallCommand == "" {
// No - don't install
outputLogger.Println(" - No Install command. Skipping.")
outputLogger.Writeln(" - No Install command. Skipping.")
} else {
// Do install if needed
outputLogger.Println(" - Installing dependencies...")
outputLogger.Writeln(" - Installing dependencies...")
if err := b.NpmInstallUsingCommand(frontendDir, b.projectData.InstallCommand); err != nil {
return err
}
@@ -372,12 +313,12 @@ func (b *BaseBuilder) BuildFrontend(outputLogger *clilogger.CLILogger) error {
// Check if there is a build command
if b.projectData.BuildCommand == "" {
outputLogger.Println(" - No Build command. Skipping.")
outputLogger.Writeln(" - No Build command. Skipping.")
// No - ignore
return nil
}
outputLogger.Println(" - Compiling Frontend Project")
outputLogger.Writeln(" - Compiling Frontend Project")
cmd := strings.Split(b.projectData.BuildCommand, " ")
stdout, stderr, err := shell.RunCommand(frontendDir, cmd[0], cmd[1:]...)
if verbose || err != nil {
@@ -397,22 +338,3 @@ func (b *BaseBuilder) ExtractAssets() (*html.AssetBundle, error) {
// Read in html
return html.NewAssetBundle(b.projectData.HTML)
}
func upsertEnv(env []string, key string, update func(v string) string) []string {
newEnv := make([]string, len(env), len(env)+1)
found := false
for i := range env {
if strings.HasPrefix(env[i], key+"=") {
eqIndex := strings.Index(env[i], "=")
val := env[i][eqIndex+1:]
newEnv[i] = fmt.Sprintf("%s=%v", key, update(val))
found = true
continue
}
newEnv[i] = env[i]
}
if !found {
newEnv = append(newEnv, fmt.Sprintf("%s=%v", key, update("")))
}
return newEnv
}

View File

@@ -3,12 +3,11 @@ package build
import (
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
// Mode is the type used to indicate the build modes
@@ -25,20 +24,14 @@ var modeMap = []string{"Debug", "Production"}
// Options contains all the build options as well as the project data
type Options struct {
LDFlags string // Optional flags to pass to linker
Logger *clilogger.CLILogger // All output to the logger
OutputType string // EG: desktop, server....
Mode Mode // release or debug
ProjectData *project.Project // The project data
Pack bool // Create a package for the app after building
Platform string // The platform to build for
Compiler string // The compiler command to use
IgnoreFrontend bool // Indicates if the frontend does not need building
OutputFile string // Override the output filename
BuildDirectory string // Directory to use for building the application
CompiledBinary string // Fully qualified path to the compiled binary
KeepAssets bool // /Keep the generated assets/files
AppleIdentity string
LDFlags string // Optional flags to pass to linker
Logger *logger.Logger // All output to the logger
OutputType string // EG: desktop, server....
Mode Mode // release or debug
ProjectData *project.Project // The project data
Pack bool // Create a package for the app after building
Platform string // The platform to build for
Compiler string // The compiler command to use
}
// GetModeAsString returns the current mode as a string
@@ -52,6 +45,11 @@ func Build(options *Options) (string, error) {
// Extract logger
outputLogger := options.Logger
// Create a default logger if it doesn't exist
if outputLogger == nil {
outputLogger = logger.New()
}
// Get working directory
cwd, err := os.Getwd()
if err != nil {
@@ -59,7 +57,7 @@ func Build(options *Options) (string, error) {
}
// Check platform
validPlatforms := slicer.String([]string{"linux", "darwin"})
validPlatforms := slicer.String([]string{"linux"})
if !validPlatforms.Contains(options.Platform) {
return "", fmt.Errorf("platform %s not supported", options.Platform)
}
@@ -71,9 +69,6 @@ func Build(options *Options) (string, error) {
}
options.ProjectData = projectData
// Calculate build dir
options.BuildDirectory = filepath.Join(options.ProjectData.Path, "build", options.Platform, options.OutputType)
// Save the project type
projectData.OutputType = options.OutputType
@@ -82,13 +77,11 @@ func Build(options *Options) (string, error) {
switch projectData.OutputType {
case "desktop":
builder = newDesktopBuilder(options)
builder = newDesktopBuilder()
case "hybrid":
builder = newHybridBuilder(options)
builder = newHybridBuilder()
case "server":
builder = newServerBuilder(options)
case "dev":
builder = newDesktopBuilder(options)
builder = newServerBuilder()
default:
return "", fmt.Errorf("cannot build assets for output type %s", projectData.OutputType)
}
@@ -99,40 +92,34 @@ func Build(options *Options) (string, error) {
// Initialise Builder
builder.SetProjectData(projectData)
// Generate Frontend JS Package
// outputLogger.Println(" - Generating Backend JS Package")
// // Ignore the parser report coming back
// _, err = parser.GenerateWailsFrontendPackage()
// if err != nil {
// return "", err
// }
if !options.IgnoreFrontend {
outputLogger.Println(" - Building Project Frontend")
err = builder.BuildFrontend(outputLogger)
if err != nil {
return "", err
}
outputLogger.Writeln(" - Building Wails Frontend")
err = builder.BuildFrontend(outputLogger)
if err != nil {
return "", err
}
// Build the base assets
outputLogger.Println(" - Compiling Assets")
outputLogger.Writeln(" - Compiling Assets")
err = builder.BuildRuntime(options)
if err != nil {
return "", err
}
err = builder.BuildAssets(options)
if err != nil {
return "", err
}
// Compile the application
outputLogger.Print(" - Compiling Application in " + GetModeAsString(options.Mode) + " mode...")
outputLogger.Write(" - Compiling Application in " + GetModeAsString(options.Mode) + " mode...")
err = builder.CompileProject(options)
if err != nil {
return "", err
}
outputLogger.Println("done.")
outputLogger.Writeln("done.")
// Do we need to pack the app?
if options.Pack {
outputLogger.Println(" - Packaging Application")
outputLogger.Writeln(" - Packaging Application")
// TODO: Allow cross platform build
err = packageProject(options, runtime.GOOS)

View File

@@ -1,15 +1,15 @@
package build
import (
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
// Builder defines a builder that can build Wails applications
type Builder interface {
SetProjectData(projectData *project.Project)
BuildAssets(*Options) error
BuildFrontend(*clilogger.CLILogger) error
BuildFrontend(*logger.Logger) error
BuildRuntime(*Options) error
CompileProject(*Options) error
CleanUp()

View File

@@ -7,6 +7,7 @@ import (
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/html"
"github.com/wailsapp/wails/v2/internal/logger"
)
// DesktopBuilder builds applications for the desktop
@@ -14,9 +15,9 @@ type DesktopBuilder struct {
*BaseBuilder
}
func newDesktopBuilder(options *Options) *DesktopBuilder {
func newDesktopBuilder() *DesktopBuilder {
return &DesktopBuilder{
BaseBuilder: NewBaseBuilder(options),
BaseBuilder: NewBaseBuilder(),
}
}
@@ -24,17 +25,6 @@ func newDesktopBuilder(options *Options) *DesktopBuilder {
func (d *DesktopBuilder) BuildAssets(options *Options) error {
var err error
// Check assets directory exists
if !fs.DirExists(options.ProjectData.AssetsDir) {
// Path to default assets
defaultAssets := fs.RelativePath("./internal/assets")
// Copy the default assets directory
err := fs.CopyDir(defaultAssets, options.ProjectData.AssetsDir)
if err != nil {
return err
}
}
// Get a list of assets from the HTML
assets, err := d.BaseBuilder.ExtractAssets()
if err != nil {
@@ -42,7 +32,13 @@ func (d *DesktopBuilder) BuildAssets(options *Options) error {
}
// Build base assets (HTML/JS/CSS/etc)
err = d.BuildBaseAssets(assets, options)
err = d.BuildBaseAssets(assets, options.Logger)
if err != nil {
return err
}
// Build static assets
err = d.buildStaticAssets(d.projectData)
if err != nil {
return err
}
@@ -51,25 +47,13 @@ func (d *DesktopBuilder) BuildAssets(options *Options) error {
}
// BuildBaseAssets builds the assets for the desktop application
func (d *DesktopBuilder) BuildBaseAssets(assets *html.AssetBundle, options *Options) error {
func (d *DesktopBuilder) BuildBaseAssets(assets *html.AssetBundle, outputLogger *logger.Logger) error {
var err error
outputLogger := options.Logger
outputLogger.Print(" - Embedding Assets...")
outputLogger.Write(" - Embedding Assets...")
// Get target asset directory
assetDir, err := fs.RelativeToCwd("build")
if err != nil {
return err
}
// Make dir if it doesn't exist
if !fs.DirExists(assetDir) {
err := fs.Mkdir(assetDir)
if err != nil {
return err
}
}
assetDir := fs.RelativePath("../../../internal/ffenestri")
// Dump assets as C
assetsFile, err := assets.WriteToCFile(assetDir)
@@ -79,34 +63,22 @@ func (d *DesktopBuilder) BuildBaseAssets(assets *html.AssetBundle, options *Opti
d.addFileToDelete(assetsFile)
// Process Icon
err = d.processApplicationIcon(assetDir)
err = d.processIcon(assetDir)
if err != nil {
return err
}
// Process Tray Icons
err = d.processTrayIcons(assetDir, options)
if err != nil {
return err
}
// Process Dialog Icons
err = d.processDialogIcons(assetDir, options)
if err != nil {
return err
}
outputLogger.Println("done.")
outputLogger.Writeln("done.")
return nil
}
// processApplicationIcon will copy a default icon if one doesn't exist, then, if
// processIcon will copy a default icon if one doesn't exist, then, if
// needed, will compile the icon
func (d *DesktopBuilder) processApplicationIcon(assetDir string) error {
func (d *DesktopBuilder) processIcon(assetDir string) error {
// Copy default icon if one doesn't exist
iconFile := filepath.Join(d.projectData.AssetsDir, "appicon.png")
iconFile := filepath.Join(d.projectData.Path, "icon.png")
if !fs.FileExists(iconFile) {
err := fs.CopyFile(defaultIconPath(), iconFile)
if err != nil {
@@ -129,7 +101,7 @@ func (d *DesktopBuilder) BuildRuntime(options *Options) error {
return err
}
outputLogger.Print(" - Embedding Runtime...")
outputLogger.Write(" - Embedding Runtime...")
envvars := []string{"WAILSPLATFORM=" + options.Platform}
if err := d.NpmRunWithEnvironment(sourceDir, "build:desktop", false, envvars); err != nil {
return err
@@ -140,7 +112,7 @@ func (d *DesktopBuilder) BuildRuntime(options *Options) error {
if err != nil {
return err
}
outputLogger.Println("done.")
outputLogger.Writeln("done.")
// Convert to C structure
runtimeC := `

View File

@@ -2,7 +2,6 @@ package build
import (
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
// HybridBuilder builds applications as a server
@@ -12,11 +11,11 @@ type HybridBuilder struct {
server *ServerBuilder
}
func newHybridBuilder(options *Options) Builder {
func newHybridBuilder() Builder {
result := &HybridBuilder{
BaseBuilder: NewBaseBuilder(options),
desktop: newDesktopBuilder(options),
server: newServerBuilder(options),
BaseBuilder: NewBaseBuilder(),
desktop: newDesktopBuilder(),
server: newServerBuilder(),
}
return result
}
@@ -31,7 +30,7 @@ func (b *HybridBuilder) BuildAssets(options *Options) error {
return err
}
// Build static assets
err = b.buildCustomAssets(b.projectData)
err = b.buildStaticAssets(b.projectData)
if err != nil {
return err
}
@@ -39,12 +38,6 @@ func (b *HybridBuilder) BuildAssets(options *Options) error {
return nil
}
// BuildFrontend builds the assets for the desktop application
func (b *HybridBuilder) BuildFrontend(_ *clilogger.CLILogger) error {
panic("To be implemented")
return nil
}
// BuildAssets builds the assets for the desktop application
func (b *HybridBuilder) BuildBaseAssets(options *Options) error {
@@ -53,7 +46,7 @@ func (b *HybridBuilder) BuildBaseAssets(options *Options) error {
return err
}
err = b.desktop.BuildBaseAssets(assets, options)
err = b.desktop.BuildBaseAssets(assets, options.Logger)
if err != nil {
return err
}
@@ -64,13 +57,13 @@ func (b *HybridBuilder) BuildBaseAssets(options *Options) error {
}
// Build desktop static assets
err = b.desktop.buildCustomAssets(b.projectData)
err = b.desktop.buildStaticAssets(b.projectData)
if err != nil {
return err
}
// Build server static assets
err = b.server.buildCustomAssets(b.projectData)
err = b.server.buildStaticAssets(b.projectData)
if err != nil {
return err
}

View File

@@ -14,8 +14,8 @@ func packageProject(options *Options, platform string) error {
var err error
switch platform {
case "linux", "darwin":
err = packageApplication(options)
case "linux":
err = packageLinuxApplication(options)
default:
err = fmt.Errorf("packing not supported for %s yet", platform)
}
@@ -27,28 +27,29 @@ func packageProject(options *Options, platform string) error {
return nil
}
// cleanBuildDirectory will remove an existing build directory and recreate it
func cleanBuildDirectory(options *Options) error {
buildDirectory := options.BuildDirectory
// Gets (and creates) the platform/target build directory
func getApplicationBuildDirectory(options *Options, platform string) (string, error) {
buildDirectory := filepath.Join(options.ProjectData.Path, "build", platform, options.OutputType)
// Clear out old builds
if fs.DirExists(buildDirectory) {
err := os.RemoveAll(buildDirectory)
if err != nil {
return err
return "", err
}
}
// Create clean directory
err := os.MkdirAll(buildDirectory, 0700)
if err != nil {
return err
return "", err
}
return nil
return buildDirectory, nil
}
func copyFileToBuildDirectory() {}
// Gets (and creates) the build base directory
func getBuildBaseDirectory(options *Options) (string, error) {
buildDirectory := filepath.Join(options.ProjectData.Path, "build")
@@ -63,7 +64,7 @@ func getBuildBaseDirectory(options *Options) (string, error) {
// Gets the path to the default icon
func defaultIconPath() string {
return fs.RelativePath("internal/packager/icon1024.png")
return fs.RelativePath("internal/packager/icon64.png")
}
// Gets the platform dependent package assets directory

View File

@@ -17,7 +17,7 @@ func deleteLinuxPackFiles(appDirBase string) {
os.RemoveAll(appDir)
}
func packageApplication(options *Options) error {
func packageLinuxApplication(options *Options) error {
// Check we have AppImage tools

View File

@@ -15,15 +15,15 @@ type ServerBuilder struct {
*BaseBuilder
}
func newServerBuilder(options *Options) *ServerBuilder {
func newServerBuilder() *ServerBuilder {
result := &ServerBuilder{
BaseBuilder: NewBaseBuilder(options),
BaseBuilder: NewBaseBuilder(),
}
return result
}
// BuildAssets builds the assets for the desktop application
func (s *ServerBuilder) BuildAssets(_ *Options) error {
func (s *ServerBuilder) BuildAssets(options *Options) error {
var err error
assets, err := s.BaseBuilder.ExtractAssets()
@@ -38,7 +38,7 @@ func (s *ServerBuilder) BuildAssets(_ *Options) error {
}
// Build static assets
err = s.buildCustomAssets(s.projectData)
err = s.buildStaticAssets(s.projectData)
if err != nil {
return err
}
@@ -56,11 +56,11 @@ func (s *ServerBuilder) BuildBaseAssets(assets *html.AssetBundle) error {
// Fetch, update, and reinject index.html
index, err := db.Read("index.html")
if err != nil {
return fmt.Errorf(`failed to locate "index.html"`)
return fmt.Errorf(`Failed to locate "index.html"`)
}
splits := strings.Split(string(index), "</body>")
if len(splits) != 2 {
return fmt.Errorf("unable to locate a </body> tag in your frontend/index.html")
return fmt.Errorf("Unable to locate a </body> tag in your frontend/index.html")
}
injectScript := `<script defer src="/wails.js"></script><script defer src="/bindings.js"></script>`
result := []string{}
@@ -84,14 +84,11 @@ func (s *ServerBuilder) BuildBaseAssets(assets *html.AssetBundle) error {
if err != nil {
return err
}
defer f.Close()
_, err = f.WriteString(db.Serialize("db", "webserver"))
if err != nil {
// Ignore error - we already have one!
_ = f.Close()
return err
}
return f.Close()
f.WriteString(db.Serialize("db", "webserver"))
return nil
}
// BuildRuntime builds the javascript runtime used by the HTML client to connect to the websocket
@@ -102,13 +99,13 @@ func (s *ServerBuilder) BuildRuntime(options *Options) error {
return err
}
options.Logger.Print(" - Embedding Runtime...")
options.Logger.Write(" - Embedding Runtime...")
envvars := []string{"WAILSPLATFORM=" + options.Platform}
var err error
if err = s.NpmRunWithEnvironment(sourceDir, "build:server", false, envvars); err != nil {
return err
}
options.Logger.Println("done.")
options.Logger.Writeln("done.")
return nil
}

Some files were not shown because too many files have changed in this diff Show More