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 ( import (
"fmt" "fmt"
"io" "os"
"runtime" "runtime"
"strings" "strings"
"time" "time"
"github.com/leaanthony/clir" "github.com/leaanthony/clir"
"github.com/leaanthony/slicer" "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" "github.com/wailsapp/wails/v2/pkg/commands/build"
) )
// AddBuildSubcommand adds the `build` command for the Wails application // AddBuildSubcommand adds the `build` command for the Wails application
func AddBuildSubcommand(app *clir.Cli, w io.Writer) { func AddBuildSubcommand(app *clir.Cli) {
outputType := "desktop" outputType := "desktop"
@@ -32,7 +32,7 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
// Setup pack flag // Setup pack flag
pack := false pack := false
command.BoolFlag("package", "Create a platform specific package", &pack) command.BoolFlag("pack", "Create a platform specific package", &pack)
compilerCommand := "go" compilerCommand := "go"
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand) 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 Build
quiet := false quiet := false
command.BoolFlag("q", "Suppress output to console", &quiet) command.BoolFlag("q", "Supress output to console", &quiet)
// ldflags to pass to `go` // ldflags to pass to `go`
ldflags := "" ldflags := ""
@@ -53,38 +53,28 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
logFile := "" logFile := ""
command.StringFlag("l", "Log to file", &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 { command.Action(func() error {
// Create logger // Create logger
logger := clilogger.New(w) logger := logger.New()
logger.Mute(quiet)
if !quiet {
logger.AddOutput(os.Stdout)
}
// Validate output type // Validate output type
if !validTargetTypes.Contains(outputType) { 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 { if !quiet {
app.PrintBanner() 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)) task := fmt.Sprintf("Building %s Application", strings.Title(outputType))
logger.Println(task) logger.Writeln(task)
logger.Println(strings.Repeat("-", len(task))) logger.Writeln(strings.Repeat("-", len(task)))
// Setup mode // Setup mode
mode := build.Debug mode := build.Debug
@@ -94,15 +84,13 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
// Create BuildOptions // Create BuildOptions
buildOptions := &build.Options{ buildOptions := &build.Options{
Logger: logger, Logger: logger,
OutputType: outputType, OutputType: outputType,
Mode: mode, Mode: mode,
Pack: pack, Pack: pack,
Platform: platform, Platform: platform,
LDFlags: ldflags, LDFlags: ldflags,
Compiler: compilerCommand, Compiler: compilerCommand,
KeepAssets: keepAssets,
AppleIdentity: appleIdentity,
} }
return doBuild(buildOptions) return doBuild(buildOptions)
@@ -119,12 +107,11 @@ func doBuild(buildOptions *build.Options) error {
if err != nil { if err != nil {
return err return err
} }
// Output stats // Output stats
elapsed := time.Since(start) elapsed := time.Since(start)
buildOptions.Logger.Println("") buildOptions.Logger.Writeln("")
buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.", outputFilename, elapsed.Round(time.Millisecond).String())) buildOptions.Logger.Writeln(fmt.Sprintf("Built '%s' in %s.", outputFilename, elapsed.Round(time.Millisecond).String()))
buildOptions.Logger.Println("") buildOptions.Logger.Writeln("")
return nil return nil
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,29 +1,14 @@
module github.com/wailsapp/wails/v2 module github.com/wailsapp/wails/v2
go 1.16 go 1.13
require ( require (
github.com/Masterminds/semver v1.5.0 github.com/leaanthony/clir v1.0.2
github.com/davecgh/go-spew v1.1.1 github.com/leaanthony/gosod v0.0.3
github.com/fatih/structtag v1.2.0 github.com/leaanthony/slicer v1.4.1
github.com/fsnotify/fsnotify v1.4.9 github.com/matryer/is v1.3.0
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/olekukonko/tablewriter v0.0.4 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 github.com/xyproto/xpm v1.2.1
golang.org/x/net v0.0.0-20200822124328-c89045814202 golang.org/x/net v0.0.0-20200301022130-244492dfa37a
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c nhooyr.io/websocket v1.7.4
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82
nhooyr.io/websocket v1.8.6
) )

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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= 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 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= 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 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= 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.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmAMQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/jackmordaunt/icns v1.0.0/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/leaanthony/clir v1.0.2 h1:Ham9Ep7NH95il0x/ci5s2ExJa/K4B2dE8L2uaSj2cxo=
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8= github.com/leaanthony/clir v1.0.2/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/leaanthony/gosod v0.0.3 h1:VlPilye0zoH4I0WihShyoiTHyiAN1v6dcBW4mMvGJao=
github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU= github.com/leaanthony/gosod v0.0.3/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0= github.com/leaanthony/slicer v1.4.1 h1:X/SmRIDhkUAolP79mSTO0jTcVX1k504PJBqvV6TwP0w=
github.com/leaanthony/gosod v0.0.4 h1:v4hepo4IyL8E8c9qzDsvYcA0KGh7Npf8As74K5ibQpI= github.com/leaanthony/slicer v1.4.1/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leaanthony/gosod v0.0.4/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU= github.com/matryer/is v1.3.0 h1:9qiso3jaJrOe6qBRJRBt2Ldht05qDiFP9le0JOIhRSI=
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY= github.com/matryer/is v1.3.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 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 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/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/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 h1:trdvGjjWBsOOKzBBUPT6JvaIQM3acJEEYfbxN7M96wg=
github.com/xyproto/xpm v1.2.1/go.mod h1:cMnesLsD0PBXLgjDfTDEaKr8XyTFsnP1QycSqRw7BiY= 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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-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.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-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= nhooyr.io/websocket v1.7.4 h1:w/LGB2sZT0RV8lZYR7nfyaYz4PUbYZ5oF7NBon2M0NY=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= nhooyr.io/websocket v1.7.4/go.mod h1:PxYxCwFdFYQ0yRvtQz3s/dC+VEm7CSuC/4b9t8MQQxw=
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=

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ import (
"github.com/leaanthony/clir" "github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/binding" "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/ffenestri"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher" "github.com/wailsapp/wails/v2/internal/messagedispatcher"
@@ -42,7 +43,8 @@ type App struct {
dispatcher *messagedispatcher.Dispatcher dispatcher *messagedispatcher.Dispatcher
servicebus *servicebus.ServiceBus servicebus *servicebus.ServiceBus
debug bool // Feature flags
Features *features.Features
} }
// Create App // Create App
@@ -56,15 +58,17 @@ func CreateApp(options *Options) *App {
myLogger.SetLogLevel(logger.INFO) myLogger.SetLogLevel(logger.INFO)
window := ffenestri.NewApplicationWithConfig(&ffenestri.Config{ window := ffenestri.NewApplicationWithConfig(&ffenestri.Config{
Title: options.Title, Title: options.Title,
Width: options.Width, Width: options.Width,
Height: options.Height, Height: options.Height,
MinWidth: options.MinWidth, MinWidth: options.MinWidth,
MinHeight: options.MinHeight, MinHeight: options.MinHeight,
MaxWidth: options.MaxWidth, MaxWidth: options.MaxWidth,
MaxHeight: options.MaxHeight, MaxHeight: options.MaxHeight,
StartHidden: options.StartHidden, Frameless: options.Frameless,
DevTools: options.DevTools,
// This should be controlled by the compile time flags...
DevTools: true,
Resizable: !options.DisableResize, Resizable: !options.DisableResize,
Fullscreen: options.Fullscreen, Fullscreen: options.Fullscreen,
@@ -75,12 +79,9 @@ func CreateApp(options *Options) *App {
webserver: webserver.NewWebServer(myLogger), webserver: webserver.NewWebServer(myLogger),
servicebus: servicebus.New(myLogger), servicebus: servicebus.New(myLogger),
logger: myLogger, logger: myLogger,
bindings: binding.NewBindings(myLogger, options.Bind), bindings: binding.NewBindings(myLogger),
} }
// Initialise the app
app.Init()
return app 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() a.servicebus.Stop()
return result return result
@@ -192,3 +193,14 @@ func (a *App) Run() error {
return cli.Run() 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" "os"
"path/filepath" "path/filepath"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/leaanthony/clir" "github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/binding" "github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher" "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/servicebus"
"github.com/wailsapp/wails/v2/internal/subsystem" "github.com/wailsapp/wails/v2/internal/subsystem"
"github.com/wailsapp/wails/v2/internal/webserver" "github.com/wailsapp/wails/v2/internal/webserver"
@@ -20,57 +17,36 @@ import (
// App defines a Wails application structure // App defines a Wails application structure
type App struct { type App struct {
appType string
binding *subsystem.Binding binding *subsystem.Binding
call *subsystem.Call call *subsystem.Call
event *subsystem.Event event *subsystem.Event
log *subsystem.Log log *subsystem.Log
runtime *subsystem.Runtime runtime *subsystem.Runtime
options *options.App
bindings *binding.Bindings bindings *binding.Bindings
logger *logger.Logger logger *logger.Logger
dispatcher *messagedispatcher.Dispatcher dispatcher *messagedispatcher.Dispatcher
servicebus *servicebus.ServiceBus servicebus *servicebus.ServiceBus
webserver *webserver.WebServer webserver *webserver.WebServer
debug bool
// Application Stores
loglevelStore *runtime.Store
appconfigStore *runtime.Store
// Startup/Shutdown
startupCallback func(*runtime.Runtime)
shutdownCallback func()
} }
// Create App // 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 // TODO: Allow logger output override on CLI
options.MergeDefaults(appoptions) myLogger := logger.New(os.Stdout)
myLogger.SetLogLevel(logger.TRACE)
// Set up logger
myLogger := logger.New(appoptions.Logger)
myLogger.SetLogLevel(appoptions.LogLevel)
result := &App{ result := &App{
appType: "server", bindings: binding.NewBindings(myLogger),
bindings: binding.NewBindings(myLogger, options.Bind), logger: myLogger,
logger: myLogger, servicebus: servicebus.New(myLogger),
servicebus: servicebus.New(myLogger), webserver: webserver.NewWebServer(myLogger),
webserver: webserver.NewWebServer(myLogger),
startupCallback: appoptions.Startup,
shutdownCallback: appoptions.Shutdown,
} }
// Initialise app return result
result.Init()
return result, nil
} }
// Run the application // Run the application
@@ -107,21 +83,8 @@ func (a *App) Run() error {
if debugMode { if debugMode {
a.servicebus.Debug() 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() a.servicebus.Start()
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore) log, err := subsystem.NewLog(a.servicebus, a.logger)
if err != nil { if err != nil {
return err return err
} }
@@ -134,6 +97,14 @@ func (a *App) Run() error {
a.dispatcher = dispatcher a.dispatcher = dispatcher
a.dispatcher.Start() 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 // Start the binding subsystem
binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, runtime.GoRuntime()) binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, runtime.GoRuntime())
if err != nil { if err != nil {
@@ -151,7 +122,7 @@ func (a *App) Run() error {
a.event.Start() a.event.Start()
// Start the call subsystem // 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 { if err != nil {
return err return err
} }
@@ -171,3 +142,14 @@ func (a *App) Run() error {
return cli.Run() 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 ( import (
"fmt" "fmt"
"reflect"
"runtime"
"strings" "strings"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
) )
type Bindings struct { type Bindings struct {
db *DB db *DB
logger logger.CustomLogger logger logger.CustomLogger
exemptions slicer.StringSlicer
} }
// NewBindings returns a new Bindings object // NewBindings returns a new Bindings object
func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}) *Bindings { func NewBindings(logger *logger.Logger) *Bindings {
result := &Bindings{ return &Bindings{
db: newDB(), db: newDB(),
logger: logger.CustomLogger("Bindings"), 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 // Add the given struct methods to the Bindings
func (b *Bindings) Add(structPtr interface{}) error { func (b *Bindings) Add(structPtr interface{}) error {
methods, err := b.getMethods(structPtr) methods, err := getMethods(structPtr)
if err != nil { 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 { for _, method := range methods {
@@ -59,8 +34,29 @@ func (b *Bindings) Add(structPtr interface{}) error {
structName := splitName[1] structName := splitName[1]
methodName := splitName[2] 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 // Add it as a regular method
b.db.AddMethod(packageName, structName, methodName, method) b.db.AddMethod(packageName, structName, methodName, method)
} }
return nil return nil
} }

View File

@@ -1,9 +1,9 @@
package binding package binding
import ( import (
"encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"strings"
) )
// BoundMethod defines all the data related to a Go method that is // BoundMethod defines all the data related to a Go method that is
@@ -16,6 +16,58 @@ type BoundMethod struct {
Method reflect.Value `json:"-"` 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 // InputCount returns the number of inputs this bound method has
func (b *BoundMethod) InputCount() int { func (b *BoundMethod) InputCount() int {
return len(b.Inputs) return len(b.Inputs)
@@ -26,29 +78,6 @@ func (b *BoundMethod) OutputCount() int {
return len(b.Outputs) 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 // Call will attempt to call this bound method with the given args
func (b *BoundMethod) Call(args []interface{}) (interface{}, error) { func (b *BoundMethod) Call(args []interface{}) (interface{}, error) {
// Check inputs // Check inputs
@@ -65,8 +94,17 @@ func (b *BoundMethod) Call(args []interface{}) (interface{}, error) {
// Iterate over given arguments // Iterate over given arguments
for index, arg := range args { 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 // Save the converted argument
callArgs[index] = reflect.ValueOf(arg) callArgs[index] = value
} }
// Do the call // Do the call

View File

@@ -15,6 +15,10 @@ type DB struct {
// It used for performance gains at runtime // It used for performance gains at runtime
methodMap map[string]*BoundMethod 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 to ensure sync access to the data
lock sync.RWMutex 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 // ToJSON converts the method map to JSON
func (d *DB) ToJSON() (string, error) { 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 zero copy string as this string will be read only
return *(*string)(unsafe.Pointer(&bytes)), err 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 ( import (
"fmt" "fmt"
"reflect" "reflect"
"runtime"
) )
// isStructPtr returns true if the value given is a // 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 reflect.ValueOf(value).Elem().Kind() == reflect.Struct
} }
// isFunction returns true if the given value is a function func getMethods(value interface{}) ([]*BoundMethod, error) {
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) {
// Create result placeholder // Create result placeholder
var result []*BoundMethod var result []*BoundMethod
// Check type // Check type
if !isStructPtr(value) { if !isStructPtr(value) {
return nil, fmt.Errorf("not a pointer to an interface")
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.")
} }
// Process Struct // Process Struct
@@ -56,11 +34,6 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
fullMethodName := baseName + "." + methodName fullMethodName := baseName + "." + methodName
method := structValue.MethodByName(methodName) method := structValue.MethodByName(methodName)
methodReflectName := runtime.FuncForPC(methodDef.Func.Pointer()).Name()
if b.exemptions.Contains(methodReflectName) {
continue
}
// Create new method // Create new method
boundMethod := &BoundMethod{ boundMethod := &BoundMethod{
Name: fullMethodName, Name: fullMethodName,
@@ -83,8 +56,6 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
boundMethod.Inputs = inputs boundMethod.Inputs = inputs
// Iterate outputs // Iterate outputs
// TODO: Determine what to do about limiting return types
// especially around errors.
outputParamCount := methodType.NumOut() outputParamCount := methodType.NumOut()
var outputs []*Parameter var outputs []*Parameter
for outputIndex := 0; outputIndex < outputParamCount; outputIndex++ { for outputIndex := 0; outputIndex < outputParamCount; outputIndex++ {
@@ -100,3 +71,39 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
} }
return result, nil 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" "strings"
"unsafe" "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/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher" "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 CFLAGS: -DFFENESTRI_LINUX=1
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0 #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 <stdlib.h>
#include "ffenestri.h" #include "ffenestri.h"
@@ -27,16 +22,42 @@ import (
*/ */
import "C" 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 // Application is our main application object
type Application struct { type Application struct {
config *options.App config *Config
memory []unsafe.Pointer memory []unsafe.Pointer
// This is the main app pointer // This is the main app pointer
app *C.struct_Application app unsafe.Pointer
// Manages menus
menuManager *menumanager.Manager
// Logger // Logger
logger logger.CustomLogger logger logger.CustomLogger
@@ -57,18 +78,17 @@ func init() {
} }
// NewApplicationWithConfig creates a new application based on the given config // 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{ return &Application{
config: config, config: config,
logger: logger.CustomLogger("Ffenestri"), logger: logger.CustomLogger("Ffenestri"),
menuManager: menuManager,
} }
} }
// NewApplication creates a new Application with the default config // NewApplication creates a new Application with the default config
func NewApplication(logger *logger.Logger) *Application { func NewApplication(logger *logger.Logger) *Application {
return &Application{ return &Application{
config: options.Default, config: defaultConfig,
logger: logger.CustomLogger("Ffenestri"), logger: logger.CustomLogger("Ffenestri"),
} }
} }
@@ -101,29 +121,18 @@ type DispatchClient interface {
SendMessage(string) 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 // 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) title := a.string2CString(a.config.Title)
width := C.int(a.config.Width) width := C.int(a.config.Width)
height := C.int(a.config.Height) height := C.int(a.config.Height)
resizable := a.bool2Cint(!a.config.DisableResize) resizable := a.bool2Cint(a.config.Resizable)
devtools := a.bool2Cint(a.config.DevTools) devtools := a.bool2Cint(a.config.DevTools)
fullscreen := a.bool2Cint(a.config.Fullscreen) fullscreen := a.bool2Cint(a.config.Fullscreen)
startHidden := a.bool2Cint(a.config.StartHidden) app := C.NewApplication(title, width, height, resizable, devtools, fullscreen)
logLevel := C.int(a.config.LogLevel)
hideWindowOnClose := a.bool2Cint(a.config.HideWindowOnClose)
app := C.NewApplication(title, width, height, resizable, devtools, fullscreen, startHidden, logLevel, hideWindowOnClose)
// Save app reference // Save app reference
a.app = (*C.struct_Application)(app) a.app = unsafe.Pointer(app)
// Set Min Window Size // Set Min Window Size
minWidth := C.int(a.config.MinWidth) 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) C.SetMaxWindowSize(a.app, maxWidth, maxHeight)
// Set debug if needed // Set debug if needed
C.SetDebug(app, a.bool2Cint(debug)) C.SetDebug(app, a.bool2Cint(DEBUG))
// TODO: Move frameless to Linux options // Set Frameless
// if a.config.Frameless { if a.config.Frameless {
// C.DisableFrame(a.app) C.DisableFrame(a.app)
// }
if a.config.RGBA != 0 {
r, g, b, alpha := intToColour(a.config.RGBA)
C.SetColour(a.app, r, g, b, alpha)
} }
// Escape bindings so C doesn't freak out // Escape bindings so C doesn't freak out
@@ -154,16 +158,13 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug
// Set bindings // Set bindings
C.SetBindings(app, a.string2CString(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 // save the dispatcher in a package variable so that the C callbacks
// can access it // can access it
dispatcher = incomingDispatcher.RegisterClient(newClient(a)) dispatcher = incomingDispatcher.RegisterClient(newClient(a))
// Process platform settings
err := a.processPlatformSettings()
if err != nil {
return err
}
// Check we could initialise the application // Check we could initialise the application
if app != nil { if app != nil {
// Yes - Save memory reference and run app, cleaning up afterwards // 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) { func messageFromWindowCallback(data *C.char) {
dispatcher.DispatchMessage(C.GoString(data)) 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__ #define __FFENESTRI_H__
#include <stdio.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 *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen);
extern void SetMinWindowSize(struct Application*, int minWidth, int minHeight); extern void SetMinWindowSize(void *app, int minWidth, int minHeight);
extern void SetMaxWindowSize(struct Application*, int maxWidth, int maxHeight); extern void SetMaxWindowSize(void *app, int maxWidth, int maxHeight);
extern void Run(struct Application*, int argc, char **argv); extern void Run(void *app, int argc, char **argv);
extern void DestroyApplication(struct Application*); extern void DestroyApplication(void *app);
extern void SetDebug(struct Application*, int flag); extern void SetDebug(void *app, int flag);
extern void SetBindings(struct Application*, const char *bindings); extern void SetBindings(void *app, const char *bindings);
extern void ExecJS(struct Application*, const char *script); extern void ExecJS(void *app, const char *script);
extern void Hide(struct Application*); extern void Quit(void *app);
extern void Show(struct Application*); extern void SetTitle(void *app, const char *title);
extern void Center(struct Application*); extern void Fullscreen(void *app);
extern void Maximise(struct Application*); extern void UnFullscreen(void *app);
extern void Unmaximise(struct Application*); extern int SetColour(void *app, const char *colourString);
extern void ToggleMaximise(struct Application*); extern void DisableFrame(void *app);
extern void Minimise(struct Application*); extern char *SaveFileDialogOnMainThread(void *appPointer, char *title);
extern void Unminimise(struct Application*); extern char *OpenFileDialogOnMainThread(void *appPointer, char *title);
extern void ToggleMinimise(struct Application*); extern char *OpenDirectoryDialogOnMainThread(void *appPointer, char *title);
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);
#endif #endif

View File

@@ -13,8 +13,7 @@ import "C"
import ( import (
"strconv" "strconv"
"unsafe"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
) )
@@ -67,148 +66,44 @@ func (c *Client) WindowUnFullscreen() {
C.UnFullscreen(c.app.app) 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 // WindowSetColour sets the window colour
func (c *Client) WindowSetColour(colour int) { func (c *Client) WindowSetColour(colour string) bool {
r, g, b, a := intToColour(colour) result := C.SetColour(c.app.app, c.app.string2CString(colour))
C.SetColour(c.app.app, r, g, b, a) return result == 1
} }
// OpenDialog will open a dialog with the given title and filter // OpenFileDialog will open a file dialog with the given title
func (c *Client) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) { func (c *Client) OpenFileDialog(title string) string {
C.OpenDialog(c.app.app, cstring := C.OpenFileDialogOnMainThread(c.app.app, c.app.string2CString(title))
c.app.string2CString(callbackID), var result string
c.app.string2CString(dialogOptions.Title), if cstring != nil {
c.app.string2CString(dialogOptions.Filters), result = C.GoString(cstring)
c.app.string2CString(dialogOptions.DefaultFilename), // Free the C string that was allocated by the dialog
c.app.string2CString(dialogOptions.DefaultDirectory), C.free(unsafe.Pointer(cstring))
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
} }
return result
}
// Process buttons // SaveFileDialog will open a save file dialog with the given title
buttons := []string{"", "", "", ""} func (c *Client) SaveFileDialog(title string) string {
for i, button := range dialogOptions.Buttons { cstring := C.SaveFileDialogOnMainThread(c.app.app, c.app.string2CString(title))
buttons[i] = button 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
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))
} }
func (c *Client) DarkModeEnabled(callbackID string) { // OpenDirectoryDialog will open a directory dialog with the given title
C.DarkModeEnabled(c.app.app, c.app.string2CString(callbackID)) func (c *Client) OpenDirectoryDialog(title string) string {
} cstring := C.OpenDirectoryDialogOnMainThread(c.app.app, c.app.string2CString(title))
var result string
func (c *Client) SetApplicationMenu(applicationMenuJSON string) { if cstring != nil {
C.SetApplicationMenu(c.app.app, c.app.string2CString(applicationMenuJSON)) result = C.GoString(cstring)
} // Free the C string that was allocated by the dialog
C.free(unsafe.Pointer(cstring))
func (c *Client) SetTrayMenu(trayMenuJSON string) { }
C.SetTrayMenu(c.app.app, c.app.string2CString(trayMenuJSON)) return result
}
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))
} }

View File

@@ -80,7 +80,6 @@ struct Application
int height; int height;
int resizable; int resizable;
int devtools; int devtools;
int startHidden;
int fullscreen; int fullscreen;
int minWidth; int minWidth;
int minHeight; int minHeight;
@@ -101,7 +100,7 @@ struct Application
int lock; 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 // Setup main application struct
struct Application *result = malloc(sizeof(struct Application)); 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->maxWidth = 0;
result->maxHeight = 0; result->maxHeight = 0;
result->frame = 1; result->frame = 1;
result->startHidden = startHidden;
// Default drag button is PRIMARY // Default drag button is PRIMARY
result->dragButton = PRIMARY_MOUSE_BUTTON; result->dragButton = PRIMARY_MOUSE_BUTTON;
// printf("\n\nWidth: %d\n", result->width);
// printf("Height: %d\n\n", result->height);
// Features
result->sendMessageToBackend = (ffenestriCallback)messageFromWindowCallback; result->sendMessageToBackend = (ffenestriCallback)messageFromWindowCallback;
// Create a unique ID based on the current unix timestamp // 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; return (void *)result;
} }
void DestroyApplication(struct Application *app) void DestroyApplication(void *appPointer)
{ {
Debug("Destroying Application"); Debug("Destroying Application");
struct Application *app = (struct Application *)appPointer;
g_application_quit(G_APPLICATION(app->application)); g_application_quit(G_APPLICATION(app->application));
@@ -167,11 +171,9 @@ void DestroyApplication(struct Application *app)
// Disconnect signal handlers // Disconnect signal handlers
WebKitUserContentManager *manager = webkit_web_view_get_user_content_manager((WebKitWebView *)app->webView); WebKitUserContentManager *manager = webkit_web_view_get_user_content_manager((WebKitWebView *)app->webView);
g_signal_handler_disconnect(manager, app->signalInvoke); g_signal_handler_disconnect(manager, app->signalInvoke);
if( app->frame == 0) { g_signal_handler_disconnect(manager, app->signalWindowDrag);
g_signal_handler_disconnect(manager, app->signalWindowDrag); g_signal_handler_disconnect(app->webView, app->signalButtonPressed);
g_signal_handler_disconnect(app->webView, app->signalButtonPressed); g_signal_handler_disconnect(app->webView, app->signalButtonReleased);
g_signal_handler_disconnect(app->webView, app->signalButtonReleased);
}
g_signal_handler_disconnect(app->webView, app->signalLoadChanged); g_signal_handler_disconnect(app->webView, app->signalLoadChanged);
// Release the main GTK Application // 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 // Quit will stop the gtk application and free up all the memory
// used by the application // used by the application
void Quit(struct Application *app) void Quit(void *appPointer)
{ {
Debug("Quit Called"); Debug("Quit Called");
struct Application *app = (struct Application *)appPointer;
gtk_window_close((GtkWindow *)app->mainWindow); gtk_window_close((GtkWindow *)app->mainWindow);
g_application_quit((GApplication *)app->application); g_application_quit((GApplication *)app->application);
DestroyApplication(app); DestroyApplication(appPointer);
} }
// SetTitle sets the main window title to the given string // 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); gtk_window_set_title(app->mainWindow, title);
} }
// Fullscreen sets the main window to be fullscreen // 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); gtk_window_fullscreen(app->mainWindow);
} }
// UnFullscreen resets the main window after a fullscreen // 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); 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; GdkGeometry size;
size.min_width = size.min_height = size.max_width = size.max_height = 0; size.min_width = size.min_height = size.max_width = size.max_height = 0;
int flags = 0; int flags = 0;
@@ -232,43 +245,32 @@ void setMinMaxSize(struct Application *app)
size.min_width = app->minWidth; size.min_width = app->minWidth;
flags |= GDK_HINT_MIN_SIZE; 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); 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; GtkFileChooserNative *native;
GtkFileChooserAction action = chooserAction; GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
gint res; gint res;
char *filename; char *filename;
char *title = args[0];
char *filter = args[1];
native = gtk_file_chooser_native_new(title, native = gtk_file_chooser_native_new(title,
app->mainWindow, app->mainWindow,
action, action,
"_Open", "_Open",
"_Cancel"); "_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)); res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
if (res == GTK_RESPONSE_ACCEPT) if (res == GTK_RESPONSE_ACCEPT)
{ {
GtkFileChooser *chooser = GTK_FILE_CHOOSER(native);
filename = gtk_file_chooser_get_filename(chooser); filename = gtk_file_chooser_get_filename(chooser);
} }
@@ -277,65 +279,104 @@ char *fileDialogInternal(struct Application *app, GtkFileChooserAction chooserAc
return filename; 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! // 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! // 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;
} }
void SetMinWindowSize(void *appPointer, int minWidth, int minHeight)
// 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)
{ {
struct Application *app = (struct Application *)appPointer;
app->minWidth = minWidth; app->minWidth = minWidth;
app->minHeight = minHeight; 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->maxWidth = maxWidth;
app->maxHeight = maxHeight; app->maxHeight = maxHeight;
} }
// SetColour sets the colour of the webview to the given colour string // 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; GdkRGBA rgba;
gboolean result = gdk_rgba_parse(&rgba, colourString); gboolean result = gdk_rgba_parse(&rgba, colourString);
if (result == FALSE) if (result == FALSE)
{ {
return 0; 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); webkit_web_view_get_background_color((WebKitWebView *)(app->webView), &rgba);
return 1; return 1;
} }
// DisableFrame disables the window frame // DisableFrame disables the window frame
void DisableFrame(struct Application *app) void DisableFrame(void *appPointer)
{ {
struct Application *app = (struct Application *)appPointer;
app->frame = 0; app->frame = 0;
} }
void syncCallback(GObject *source_object, void syncCallback(GObject *source_object,
GAsyncResult *res, GAsyncResult *res,
void *data) gpointer user_data)
{ {
struct Application *app = (struct Application *)data;
struct Application *app = (struct Application *)user_data;
app->lock = 0; app->lock = 0;
} }
@@ -343,6 +384,8 @@ void syncEval(struct Application *app, const gchar *script)
{ {
WebKitWebView *webView = (WebKitWebView *)(app->webView); WebKitWebView *webView = (WebKitWebView *)(app->webView);
// Debug("[%p] webview\n", webView);
// Debug("[%p] Running sync\n", script);
// wait for lock to free // wait for lock to free
while (app->lock == 1) while (app->lock == 1)
@@ -352,15 +395,18 @@ void syncEval(struct Application *app, const gchar *script)
// Set lock // Set lock
app->lock = 1; app->lock = 1;
//
webkit_web_view_run_javascript( webkit_web_view_run_javascript(
webView, webView,
script, script,
NULL, syncCallback, (void*)app); NULL, syncCallback, app);
while (app->lock == 1) while (app->lock == 1)
{ {
// Debug("[%p] Waiting for callback\n", script);
g_main_context_iteration(0, true); g_main_context_iteration(0, true);
} }
// Debug("[%p] Finished\n", script);
} }
void asyncEval(WebKitWebView *webView, const gchar *script) void asyncEval(WebKitWebView *webView, const gchar *script)
@@ -371,7 +417,7 @@ void asyncEval(WebKitWebView *webView, const gchar *script)
NULL, NULL, NULL); NULL, NULL, NULL);
} }
typedef void (*dispatchMethod)(struct Application *app, void *); typedef void (*dispatchMethod)(void *app, void *);
struct dispatchData struct dispatchData
{ {
@@ -383,14 +429,20 @@ struct dispatchData
gboolean executeMethod(gpointer data) gboolean executeMethod(gpointer data)
{ {
struct dispatchData *d = (struct dispatchData *)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); g_free(d);
return FALSE; 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->method = (dispatchMethod)syncEval;
data->args = js; data->args = js;
data->app = app; data->app = app;
@@ -398,14 +450,13 @@ void ExecJS(struct Application *app, char *js)
gdk_threads_add_idle(executeMethod, data); gdk_threads_add_idle(executeMethod, data);
} }
typedef char *(*dialogMethod)(struct Application *app, void *); typedef char *(*dialogMethod)(void *app, void *);
struct dialogCall struct dialogCall
{ {
struct Application *app; struct Application *app;
dialogMethod method; dialogMethod method;
void *args; void *args;
void *filter;
char *result; char *result;
int done; int done;
}; };
@@ -413,70 +464,79 @@ struct dialogCall
gboolean executeMethodWithReturn(gpointer data) gboolean executeMethodWithReturn(gpointer data)
{ {
struct dialogCall *d = (struct dialogCall *)data; struct dialogCall *d = (struct dialogCall *)data;
struct Application *app = (struct Application *)(d->app);
d->result = (d->method)(d->app, d->args); 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; d->done = 1;
// Debug("Method Execute Complete. Freeing memory");
return FALSE; 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->result = NULL;
data->done = 0; data->done = 0;
data->method = (dialogMethod)openFileDialogInternal; data->method = (dialogMethod)OpenFileDialog;
const char* dialogArgs[]={ title, filter }; data->args = title;
data->args = dialogArgs;
data->app = app;
gdk_threads_add_idle(executeMethodWithReturn, data);
while (data->done == 0)
{
usleep(100000);
}
g_free(data);
return data->result;
}
char *SaveFileDialog(struct Application *app, char *title, char *filter)
{
struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
data->result = NULL;
data->done = 0;
data->method = (dialogMethod)saveFileDialogInternal;
const char* dialogArgs[]={ title, filter };
data->args = dialogArgs;
data->app = app; data->app = app;
gdk_threads_add_idle(executeMethodWithReturn, data); gdk_threads_add_idle(executeMethodWithReturn, data);
while (data->done == 0) while (data->done == 0)
{ {
// Debug("Waiting for dialog");
usleep(100000); usleep(100000);
} }
Debug("Dialog done"); Debug("Dialog done");
Debug("Result = %s\n", data->result); Debug("Result = %s\n", data->result);
g_free(data); g_free(data);
// Fingers crossed this wasn't freed by g_free above // Fingers crossed this wasn't freed by g_free above
return data->result; 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->result = NULL;
data->done = 0; data->done = 0;
data->method = (dialogMethod)openDirectoryDialogInternal; data->method = (dialogMethod)SaveFileDialog;
const char* dialogArgs[]={ title, filter }; data->args = title;
data->args = dialogArgs;
data->app = app; data->app = app;
gdk_threads_add_idle(executeMethodWithReturn, data); gdk_threads_add_idle(executeMethodWithReturn, data);
while (data->done == 0) 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); usleep(100000);
} }
Debug("Directory Dialog done"); Debug("Directory Dialog done");
@@ -495,8 +555,10 @@ void setIcon(struct Application *app)
static void load_finished_cb(WebKitWebView *webView, static void load_finished_cb(WebKitWebView *webView,
WebKitLoadEvent load_event, WebKitLoadEvent load_event,
struct Application *app) gpointer userData)
{ {
struct Application *app;
switch (load_event) switch (load_event)
{ {
// case WEBKIT_LOAD_STARTED: // case WEBKIT_LOAD_STARTED:
@@ -518,6 +580,7 @@ static void load_finished_cb(WebKitWebView *webView,
case WEBKIT_LOAD_FINISHED: case WEBKIT_LOAD_FINISHED:
/* Load finished, we can now stop the spinner */ /* Load finished, we can now stop the spinner */
// printf("Finished loading: %s\n", webkit_web_view_get_uri(web_view)); // printf("Finished loading: %s\n", webkit_web_view_get_uri(web_view));
app = (struct Application *)userData;
// Bindings // Bindings
Debug("Binding Methods"); Debug("Binding Methods");
@@ -574,23 +637,21 @@ static void load_finished_cb(WebKitWebView *webView,
setMinMaxSize(app); setMinMaxSize(app);
// Centre by default // Centre by default
gtk_window_set_position(app->mainWindow, GTK_WIN_POS_CENTER); Center(app);
// Show window and focus // Show window and focus
if( app->startHidden == 0) { gtk_widget_show_all(GTK_WIDGET(app->mainWindow));
gtk_widget_show_all(GTK_WIDGET(app->mainWindow)); gtk_widget_grab_focus(app->webView);
gtk_widget_grab_focus(app->webView);
}
break; break;
} }
} }
static gboolean disable_context_menu_cb( static gboolean
WebKitWebView *web_view, disable_context_menu_cb(WebKitWebView *web_view,
WebKitContextMenu *context_menu, WebKitContextMenu *context_menu,
GdkEvent *event, GdkEvent *event,
WebKitHitTestResult *hit_test_result, WebKitHitTestResult *hit_test_result,
gpointer user_data) gpointer user_data)
{ {
return TRUE; return TRUE;
} }
@@ -605,11 +666,12 @@ static void printEvent(const char *message, GdkEventButton *event)
event->time); event->time);
} }
static void dragWindow(WebKitUserContentManager *contentManager, static void dragWindow(WebKitUserContentManager *contentManager,
WebKitJavascriptResult *result, WebKitJavascriptResult *result,
struct Application *app) gpointer arg)
{ {
struct Application *app = (struct Application *)arg;
// If we get this message erroneously, ignore // If we get this message erroneously, ignore
if (app->dragButtonEvent == NULL) if (app->dragButtonEvent == NULL)
{ {
@@ -634,22 +696,31 @@ static void dragWindow(WebKitUserContentManager *contentManager,
// Clear the event // Clear the event
app->dragButtonEvent = NULL; 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) if (event->type == GDK_BUTTON_PRESS && event->button == app->dragButton)
{ {
printEvent("Drag button event was saved", event);
app->dragButtonEvent = event; app->dragButtonEvent = event;
} }
return FALSE; 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) if (event->type == GDK_BUTTON_RELEASE && event->button == app->dragButton)
{ {
printEvent("Drag button event was reset", event);
app->dragButtonEvent = NULL; app->dragButtonEvent = NULL;
} }
return FALSE; return FALSE;
@@ -657,8 +728,9 @@ gboolean buttonRelease(GtkWidget *widget, GdkEventButton *event, struct Applicat
static void sendMessageToBackend(WebKitUserContentManager *contentManager, static void sendMessageToBackend(WebKitUserContentManager *contentManager,
WebKitJavascriptResult *result, WebKitJavascriptResult *result,
struct Application *app) gpointer arg)
{ {
struct Application *app = (struct Application *)arg;
#if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22 #if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22
JSCValue *value = webkit_javascript_result_get_js_value(result); JSCValue *value = webkit_javascript_result_get_js_value(result);
char *message = jsc_value_to_string(value); char *message = jsc_value_to_string(value);
@@ -675,225 +747,16 @@ static void sendMessageToBackend(WebKitUserContentManager *contentManager,
g_free(message); g_free(message);
} }
void SetDebug(struct Application *app, int flag) void SetDebug(void *applicationPointer, int flag)
{ {
struct Application *app = (struct Application *)applicationPointer;
debug = flag; debug = flag;
} }
// getCurrentMonitorGeometry gets the geometry of the monitor void SetBindings(void *applicationPointer, const char *bindings)
// that the window is mostly on.
GdkRectangle getCurrentMonitorGeometry(GtkWindow *window) {
// Get the monitor that the window is currently on
GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(window));
GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
GdkMonitor *monitor = gdk_display_get_monitor_at_window (display, gdk_window);
// Get the geometry of the monitor
GdkRectangle result;
gdk_monitor_get_geometry (monitor,&result);
return result;
}
/*******************
* Window Position *
*******************/
// Position holds an x/y corrdinate
struct Position {
int x;
int y;
};
// Internal call for setting the position of the window.
void setPositionInternal(struct Application *app, struct Position *pos) {
// Get the monitor geometry
GdkRectangle m = getCurrentMonitorGeometry(app->mainWindow);
// Move the window relative to the monitor
gtk_window_move(app->mainWindow, m.x + pos->x, m.y + pos->y);
// Free memory
free(pos);
}
// SetPosition sets the position of the window to the given x/y
// coordinates. The x/y values are relative to the monitor
// the window is mostly on.
void SetPosition(struct Application *app, int x, int y) {
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)setPositionInternal;
struct Position *pos = malloc(sizeof(struct Position));
pos->x = x;
pos->y = y;
data->args = pos;
data->app = app;
gdk_threads_add_idle(executeMethod, data);
}
/***************
* Window Size *
***************/
// Size holds a width/height
struct Size {
int width;
int height;
};
// Internal call for setting the size of the window.
void setSizeInternal(struct Application *app, struct Size *size) {
gtk_window_resize(app->mainWindow, size->width, size->height);
free(size);
}
// SetSize sets the size of the window to the given width/height
void SetSize(struct Application *app, int width, int height) {
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)setSizeInternal;
struct Size *size = malloc(sizeof(struct Size));
size->width = width;
size->height = height;
data->args = size;
data->app = app;
gdk_threads_add_idle(executeMethod, data);
}
// centerInternal will center the main window on the monitor it is mostly in
void centerInternal(struct Application *app)
{ {
// Get the geometry of the monitor struct Application *app = (struct Application *)applicationPointer;
GdkRectangle m = getCurrentMonitorGeometry(app->mainWindow);
// Get the window width/height
int windowWidth, windowHeight;
gtk_window_get_size(app->mainWindow, &windowWidth, &windowHeight);
// Place the window at the center of the monitor
gtk_window_move(app->mainWindow, ((m.width - windowWidth) / 2) + m.x, ((m.height - windowHeight) / 2) + m.y);
}
// Center the window
void Center(struct Application *app) {
// Setup a call to centerInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)centerInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
// hideInternal hides the main window
void hideInternal(struct Application *app) {
gtk_widget_hide (GTK_WIDGET(app->mainWindow));
}
// Hide places the hideInternal method onto the main thread for execution
void Hide(struct Application *app) {
// Setup a call to hideInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)hideInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
// showInternal shows the main window
void showInternal(struct Application *app) {
gtk_widget_show_all(GTK_WIDGET(app->mainWindow));
gtk_widget_grab_focus(app->webView);
}
// Show places the showInternal method onto the main thread for execution
void Show(struct Application *app) {
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)showInternal;
data->app = app;
gdk_threads_add_idle(executeMethod, data);
}
// maximiseInternal maximises the main window
void maximiseInternal(struct Application *app) {
gtk_window_maximize(GTK_WIDGET(app->mainWindow));
}
// Maximise places the maximiseInternal method onto the main thread for execution
void Maximise(struct Application *app) {
// Setup a call to maximiseInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)maximiseInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
// unmaximiseInternal unmaximises the main window
void unmaximiseInternal(struct Application *app) {
gtk_window_unmaximize(GTK_WIDGET(app->mainWindow));
}
// Unmaximise places the unmaximiseInternal method onto the main thread for execution
void Unmaximise(struct Application *app) {
// Setup a call to unmaximiseInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)unmaximiseInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
// minimiseInternal minimises the main window
void minimiseInternal(struct Application *app) {
gtk_window_iconify(app->mainWindow);
}
// Minimise places the minimiseInternal method onto the main thread for execution
void Minimise(struct Application *app) {
// Setup a call to minimiseInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)minimiseInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
// unminimiseInternal unminimises the main window
void unminimiseInternal(struct Application *app) {
gtk_window_present(app->mainWindow);
}
// Unminimise places the unminimiseInternal method onto the main thread for execution
void Unminimise(struct Application *app) {
// Setup a call to unminimiseInternal on the main thread
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
data->method = (dispatchMethod)unminimiseInternal;
data->app = app;
// Add call to main thread
gdk_threads_add_idle(executeMethod, data);
}
void SetBindings(struct Application *app, const char *bindings)
{
const char *temp = concat("window.wailsbindings = \"", bindings); const char *temp = concat("window.wailsbindings = \"", bindings);
const char *jscall = concat(temp, "\";"); const char *jscall = concat(temp, "\";");
free((void *)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 // This is called when the close button on the window is pressed
gboolean close_button_pressed(GtkWidget *widget, gboolean close_button_pressed(GtkWidget *widget,
GdkEvent *event, GdkEvent *event,
struct Application *app) gpointer user_data)
{ {
struct Application *app = (struct Application *)user_data;
app->sendMessageToBackend("WC"); // Window Close app->sendMessageToBackend("WC"); // Window Close
return TRUE; return TRUE;
} }
static void setupWindow(struct Application *app) static void setupWindow(void *applicationPointer)
{ {
struct Application *app = (struct Application *)applicationPointer;
// Create the window // Create the window
GtkWidget *mainWindow = gtk_application_window_new(app->application); GtkWidget *mainWindow = gtk_application_window_new(app->application);
// Save reference // Save reference
@@ -930,14 +796,10 @@ static void setupWindow(struct Application *app)
webkit_user_content_manager_register_script_message_handler(contentManager, "external"); webkit_user_content_manager_register_script_message_handler(contentManager, "external");
app->signalInvoke = g_signal_connect(contentManager, "script-message-received::external", G_CALLBACK(sendMessageToBackend), app); 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 // Setup the window drag handler
if ( app->frame == 0 ) { webkit_user_content_manager_register_script_message_handler(contentManager, "windowDrag");
webkit_user_content_manager_register_script_message_handler(contentManager, "windowDrag"); app->signalWindowDrag = g_signal_connect(contentManager, "script-message-received::windowDrag", G_CALLBACK(dragWindow), app);
app->signalWindowDrag = g_signal_connect(contentManager, "script-message-received::windowDrag", G_CALLBACK(dragWindow), app);
// Setup the mouse handlers
app->signalButtonPressed = g_signal_connect(app->webView, "button-press-event", G_CALLBACK(buttonPress), app);
app->signalButtonReleased = g_signal_connect(app->webView, "button-release-event", G_CALLBACK(buttonRelease), app);
}
GtkWidget *webView = webkit_web_view_new_with_user_content_manager(contentManager); GtkWidget *webView = webkit_web_view_new_with_user_content_manager(contentManager);
// Save reference // Save reference
@@ -946,6 +808,9 @@ static void setupWindow(struct Application *app)
// Add the webview to the window // Add the webview to the window
gtk_container_add(GTK_CONTAINER(mainWindow), webView); 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 // Load default HTML
app->signalLoadChanged = g_signal_connect(G_OBJECT(webView), "load-changed", G_CALLBACK(load_finished_cb), app); 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); 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_signal_connect(app->application, "activate", G_CALLBACK(activate), app);
g_application_run(G_APPLICATION(app->application), argc, argv); g_application_run(G_APPLICATION(app->application), argc, argv);
} }

View File

@@ -9,8 +9,6 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"unsafe" "unsafe"
"github.com/leaanthony/slicer"
) )
// LocalDirectory gets the caller's file directory // LocalDirectory gets the caller's file directory
@@ -20,45 +18,11 @@ func LocalDirectory() string {
return filepath.Dir(thisFile) 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 // Mkdir will create the given directory
func Mkdir(dirname string) error { func Mkdir(dirname string) error {
return os.Mkdir(dirname, 0755) 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 // CopyFile from source to target
func CopyFile(source string, target string) error { func CopyFile(source string, target string) error {
s, err := os.Open(source) s, err := os.Open(source)
@@ -180,100 +144,3 @@ func fatal(message ...string) {
} }
os.Exit(1) 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 ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"net/url" "net/url"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
"unsafe" "unsafe"
"github.com/tdewolff/minify"
"github.com/tdewolff/minify/js"
) )
type assetTypes struct { type assetTypes struct {
@@ -67,10 +63,6 @@ func (a *Asset) AsCHexData() string {
result = strings.ReplaceAll(result, "\n", "") result = strings.ReplaceAll(result, "\n", "")
result = strings.ReplaceAll(result, "\r\n", "") result = strings.ReplaceAll(result, "\r\n", "")
result = strings.ReplaceAll(result, "\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} url := url.URL{Path: result}
urlString := strings.ReplaceAll(url.String(), "/", "%2f") urlString := strings.ReplaceAll(url.String(), "/", "%2f")
@@ -92,16 +84,6 @@ func (a *Asset) AsCHexData() string {
result = strings.ReplaceAll(result, ` {`, `{`) result = strings.ReplaceAll(result, ` {`, `{`)
result = strings.ReplaceAll(result, `: `, `:`) result = strings.ReplaceAll(result, `: `, `:`)
dataString = fmt.Sprintf("window.wails._.InjectCSS(\"%s\");", 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 // Get byte data of the string
@@ -121,7 +103,6 @@ func (a *Asset) AsCHexData() string {
return cdata.String() return cdata.String()
} }
// Dump will output the asset to the terminal
func (a *Asset) Dump() { func (a *Asset) Dump() {
fmt.Printf("{ Type: %s, Path: %s, Data: %+v }\n", a.Type, a.Path, a.Data[:10]) 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) buf := bytes.NewBufferString(htmldata)
tokenizer := html.NewTokenizer(buf) tokenizer := html.NewTokenizer(buf)
paths := slicer.String()
for { for {
//get the next token type //get the next token type
tokenType := tokenizer.Next() tokenType := tokenizer.Next()
@@ -102,29 +100,19 @@ func (a *AssetBundle) processHTML(htmldata string) error {
if attr.Key == "href" { if attr.Key == "href" {
asset.Path = attr.Val asset.Path = attr.Val
} }
// standard stylesheet // stylesheet
if attr.Key == "rel" && attr.Val == "stylesheet" { if attr.Key == "rel" && attr.Val == "stylesheet" {
asset.Type = AssetTypes.CSS 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
}
} }
err := asset.Load(a.basedirectory)
// Ensure we don't include duplicates if err != nil {
if !paths.Contains(asset.Path) { return err
err := asset.Load(a.basedirectory)
if err != nil {
return err
}
a.assets = append(a.assets, asset)
paths.Add(asset.Path)
} }
a.assets = append(a.assets, asset)
} }
if "script" == token.Data { if "script" == token.Data {
tokenType = tokenizer.Next() tokenType = tokenizer.Next()
//just make sure it's actually a text token //just make sure it's actually a text token
asset := &Asset{Type: AssetTypes.JS} asset := &Asset{Type: AssetTypes.JS}
@@ -134,14 +122,11 @@ func (a *AssetBundle) processHTML(htmldata string) error {
break break
} }
} }
if !paths.Contains(asset.Path) { err := asset.Load(a.basedirectory)
err := asset.Load(a.basedirectory) if err != nil {
if err != nil { return err
return err
}
a.assets = append(a.assets, asset)
paths.Add(asset.Path)
} }
a.assets = append(a.assets, asset)
} }
} }
} }
@@ -156,7 +141,7 @@ func (a *AssetBundle) WriteToCFile(targetDir string) (string, error) {
var cdata strings.Builder var cdata strings.Builder
// Write header // Write header
header := `// assets.h header := `// assets.c
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL. // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL.
// This file was auto-generated. DO NOT MODIFY. // This file was auto-generated. DO NOT MODIFY.
@@ -168,10 +153,6 @@ func (a *AssetBundle) WriteToCFile(targetDir string) (string, error) {
assetVariables := slicer.String() assetVariables := slicer.String()
var variableName string var variableName string
for index, asset := range a.assets { 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) variableName = fmt.Sprintf("%s%d", asset.Type, index)
assetCdata := fmt.Sprintf("const unsigned char %s[]={ %s0x00 };\n", variableName, asset.AsCHexData()) assetCdata := fmt.Sprintf("const unsigned char %s[]={ %s0x00 };\n", variableName, asset.AsCHexData())
cdata.WriteString(assetCdata) cdata.WriteString(assetCdata)
@@ -179,13 +160,13 @@ func (a *AssetBundle) WriteToCFile(targetDir string) (string, error) {
} }
if assetVariables.Length() > 0 { 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 { } else {
cdata.WriteString("\nconst unsigned char *assets[] = { 0x00 };") cdata.WriteString("\nconst char *assets[] = { 0x00 };")
} }
// Save file // Save file
assetsFile := filepath.Join(targetDir, "assets.h") assetsFile := filepath.Join(targetDir, "assets.c")
err = ioutil.WriteFile(assetsFile, []byte(cdata.String()), 0600) err = ioutil.WriteFile(assetsFile, []byte(cdata.String()), 0600)
if err != nil { if err != nil {
return "", err return "", err
@@ -196,17 +177,16 @@ func (a *AssetBundle) WriteToCFile(targetDir string) (string, error) {
// ConvertToAssetDB returns an assetdb.AssetDB initialized with // ConvertToAssetDB returns an assetdb.AssetDB initialized with
// the items in the AssetBundle // the items in the AssetBundle
func (a *AssetBundle) ConvertToAssetDB() (*assetdb.AssetDB, error) { func (a *AssetBundle) ConvertToAssetDB() (*assetdb.AssetDB, error) {
theassetdb := assetdb.NewAssetDB() assetdb := assetdb.NewAssetDB()
// Loop over the Assets // Loop over the Assets
for _, asset := range a.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() { func (a *AssetBundle) Dump() {
println("Assets:") println("Assets:")
for _, asset := range a.assets { for _, asset := range a.assets {

View File

@@ -2,30 +2,31 @@ package logger
import ( import (
"fmt" "fmt"
"os"
) )
// CustomLogger defines what a user can do with a logger // CustomLogger defines what a user can do with a logger
type CustomLogger interface { type CustomLogger interface {
// Writeln writes directly to the output with no log level plus line ending // 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 writes directly to the output with no log level
Write(message string) Write(message string) error
// Trace level logging. Works like Sprintf. // Trace level logging. Works like Sprintf.
Trace(format string, args ...interface{}) Trace(format string, args ...interface{}) error
// Debug level logging. Works like Sprintf. // Debug level logging. Works like Sprintf.
Debug(format string, args ...interface{}) Debug(format string, args ...interface{}) error
// Info level logging. Works like Sprintf. // Info level logging. Works like Sprintf.
Info(format string, args ...interface{}) Info(format string, args ...interface{}) error
// Warning level logging. Works like Sprintf. // Warning level logging. Works like Sprintf.
Warning(format string, args ...interface{}) Warning(format string, args ...interface{}) error
// Error level logging. Works like Sprintf. // Error level logging. Works like Sprintf.
Error(format string, args ...interface{}) Error(format string, args ...interface{}) error
// Fatal level logging. Works like Sprintf. // Fatal level logging. Works like Sprintf.
Fatal(format string, args ...interface{}) 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 // Writeln writes directly to the output with no log level
// Appends a carriage return to the message // Appends a carriage return to the message
func (l *customLogger) Writeln(message string) { func (l *customLogger) Writeln(message string) error {
l.logger.Writeln(message) return l.logger.Writeln(message)
} }
// Write writes directly to the output with no log level // Write writes directly to the output with no log level
func (l *customLogger) Write(message string) { func (l *customLogger) Write(message string) error {
l.logger.Write(message) return l.logger.Write(message)
} }
// Trace level logging. Works like Sprintf. // 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) 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. // 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) 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. // 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) 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. // 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) 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. // 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) 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. // Fatal level logging. Works like Sprintf.
func (l *customLogger) Fatal(format string, args ...interface{}) { func (l *customLogger) Fatal(format string, args ...interface{}) {
format = fmt.Sprintf("%s | %s", l.name, format) 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 ( import (
"fmt" "fmt"
"io"
"os" "os"
"sync"
"github.com/wailsapp/wails/v2/pkg/logger"
) )
// LogLevel is an alias for the public LogLevel
type LogLevel = logger.LogLevel
// Logger is a utlility to log messages to a number of destinations // Logger is a utlility to log messages to a number of destinations
type Logger struct { type Logger struct {
output logger.Logger writers []io.Writer
logLevel LogLevel logLevel uint8
showLevelInLog bool showLevelInLog bool
lock sync.RWMutex
} }
// New creates a new Logger. You may pass in a number of `io.Writer`s that // New creates a new Logger. You may pass in a number of `io.Writer`s that
// are the targets for the logs // are the targets for the logs
func New(output logger.Logger) *Logger { func New(writers ...io.Writer) *Logger {
result := &Logger{ result := &Logger{
logLevel: logger.INFO, logLevel: INFO,
showLevelInLog: true, showLevelInLog: true,
output: output, }
for _, writer := range writers {
result.AddOutput(writer)
} }
return result 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 // CustomLogger creates a new custom logger that prints out a name/id
// before the messages // before the messages
func (l *Logger) CustomLogger(name string) CustomLogger { 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 // 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 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 // Writeln writes directly to the output with no log level
// Appends a carriage return to the message // Appends a carriage return to the message
func (l *Logger) Writeln(message string) { func (l *Logger) Writeln(message string) error {
l.output.Print(message) return l.write(BYPASS, message+"\n")
} }
// Write writes directly to the output with no log level // Write writes directly to the output with no log level
func (l *Logger) Write(message string) { func (l *Logger) Write(message string) error {
l.output.Print(message) return l.write(BYPASS, message)
} }
// Print writes directly to the output with no log level // processLogMessage formats the given message before writing it out
// Appends a carriage return to the message func (l *Logger) processLogMessage(loglevel uint8, format string, args ...interface{}) error {
func (l *Logger) Print(message string) { message := fmt.Sprintf(format, args...)
l.Write(message) return l.writeln(loglevel, message)
} }
// Trace level logging. Works like Sprintf. // Trace level logging. Works like Sprintf.
func (l *Logger) Trace(format string, args ...interface{}) { func (l *Logger) Trace(format string, args ...interface{}) error {
if l.logLevel <= logger.TRACE { return l.processLogMessage(TRACE, format, args...)
l.output.Trace(fmt.Sprintf(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. // Debug level logging. Works like Sprintf.
func (l *Logger) Debug(format string, args ...interface{}) { func (l *Logger) Debug(format string, args ...interface{}) error {
if l.logLevel <= logger.DEBUG { return l.processLogMessage(DEBUG, format, args...)
l.output.Debug(fmt.Sprintf(format, args...))
}
} }
// Info level logging. Works like Sprintf. // Info level logging. Works like Sprintf.
func (l *Logger) Info(format string, args ...interface{}) { func (l *Logger) Info(format string, args ...interface{}) error {
if l.logLevel <= logger.INFO { return l.processLogMessage(INFO, format, args...)
l.output.Info(fmt.Sprintf(format, args...))
}
} }
// Warning level logging. Works like Sprintf. // Warning level logging. Works like Sprintf.
func (l *Logger) Warning(format string, args ...interface{}) { func (l *Logger) Warning(format string, args ...interface{}) error {
if l.logLevel <= logger.WARNING { return l.processLogMessage(WARNING, format, args...)
l.output.Warning(fmt.Sprintf(format, args...))
}
} }
// Error level logging. Works like Sprintf. // Error level logging. Works like Sprintf.
func (l *Logger) Error(format string, args ...interface{}) { func (l *Logger) Error(format string, args ...interface{}) error {
if l.logLevel <= logger.ERROR { return l.processLogMessage(ERROR, format, args...)
l.output.Error(fmt.Sprintf(format, args...))
}
} }
// Fatal level logging. Works like Sprintf. // Fatal level logging. Works like Sprintf.
func (l *Logger) Fatal(format string, args ...interface{}) { func (l *Logger) Fatal(format string, args ...interface{}) {
l.output.Fatal(fmt.Sprintf(format, args...)) l.processLogMessage(FATAL, format, args...)
os.Exit(1) 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/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message" "github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
"github.com/wailsapp/wails/v2/internal/servicebus" "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 { type Client interface {
Quit() Quit()
NotifyEvent(message string) NotifyEvent(message string)
CallResult(message string) CallResult(message string)
OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) SaveFileDialog(title string) string
SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) OpenFileDialog(title string) string
MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) OpenDirectoryDialog(title string) string
WindowSetTitle(title 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() WindowFullscreen()
WindowUnFullscreen() WindowUnFullscreen()
WindowSetColour(colour int) WindowSetColour(colour string) bool
DarkModeEnabled(callbackID string)
SetApplicationMenu(menuJSON string)
SetTrayMenu(trayMenuJSON string)
UpdateTrayMenuLabel(JSON string)
UpdateContextMenu(contextMenuJSON string)
DeleteTrayMenuByID(id string)
} }
// DispatchClient is what the frontends use to interface with the // 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)) d.logger.Trace(fmt.Sprintf("Received message: %+v", incomingMessage))
parsedMessage, err := message.Parse(incomingMessage) parsedMessage, err := message.Parse(incomingMessage)
if err != nil { if err != nil {
d.logger.Error(err.Error()) d.logger.Trace("Error: " + err.Error())
return return
} }
@@ -81,6 +62,14 @@ func (d *DispatchClient) DispatchMessage(incomingMessage string) {
d.logger.Trace("I got a parsedMessage: %+v", parsedMessage) 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 // Publish the parsed message
d.bus.PublishForTarget(parsedMessage.Topic, parsedMessage.Data, d.id) d.bus.PublishForTarget(parsedMessage.Topic, parsedMessage.Data, d.id)

View File

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

View File

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

View File

@@ -3,19 +3,16 @@ package message
import "fmt" import "fmt"
var logMessageMap = map[byte]string{ var logMessageMap = map[byte]string{
'P': "log:print",
'T': "log:trace",
'D': "log:debug", 'D': "log:debug",
'I': "log:info", 'I': "log:info",
'W': "log:warning", 'W': "log:warning",
'E': "log:error", 'E': "log:error",
'F': "log:fatal", 'F': "log:fatal",
'S': "log:setlevel",
} }
// logMessageParser does what it says on the tin! // logMessageParser does what it says on the tin!
func logMessageParser(message string) (*parsedMessage, error) { func logMessageParser(message string) (*parsedMessage, error) {
// Sanity check: Log messages must be at least 2 bytes // Sanity check: Log messages must be at least 2 bytes
if len(message) < 2 { if len(message) < 2 {
return nil, fmt.Errorf("log message was an invalid length") 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 the type is invalid, raise error
if messageTopic == "" { 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 // Create a new parsed message struct

View File

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

View File

@@ -22,14 +22,13 @@ func runtimeMessageParser(message string) (*parsedMessage, error) {
// processBrowserMessage expects messages of the following format: // processBrowserMessage expects messages of the following format:
// RB<METHOD><DATA> // RB<METHOD><DATA>
// O = Open
func processBrowserMessage(message string) (*parsedMessage, error) { func processBrowserMessage(message string) (*parsedMessage, error) {
method := message[2] method := message[2]
switch method { switch method {
case 'O': case 'U':
// Open URL // Open URL
target := message[3:] url := message[3:]
return &parsedMessage{Topic: "runtime:browser:open", Data: target}, nil return &parsedMessage{Topic: "runtime:browser:openurl", Data: url}, nil
} }
return nil, fmt.Errorf("unknown browser message: %s", message) 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.Topic = "quit"
parsedMessage.Data = "Window Closed" 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 // Unknown event type
default: default:
return nil, fmt.Errorf("unknown message: %s", message) return nil, fmt.Errorf("unknown message: %s", message)

View File

@@ -1,14 +1,10 @@
package messagedispatcher package messagedispatcher
import ( import (
"context"
"encoding/json" "encoding/json"
"strconv"
"strings" "strings"
"sync" "sync"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/crypto" "github.com/wailsapp/wails/v2/internal/crypto"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message" "github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
@@ -23,8 +19,7 @@ type Dispatcher struct {
eventChannel <-chan *servicebus.Message eventChannel <-chan *servicebus.Message
windowChannel <-chan *servicebus.Message windowChannel <-chan *servicebus.Message
dialogChannel <-chan *servicebus.Message dialogChannel <-chan *servicebus.Message
systemChannel <-chan *servicebus.Message running bool
menuChannel <-chan *servicebus.Message
servicebus *servicebus.ServiceBus servicebus *servicebus.ServiceBus
logger logger.CustomLogger logger logger.CustomLogger
@@ -32,13 +27,6 @@ type Dispatcher struct {
// Clients // Clients
clients map[string]*DispatchClient clients map[string]*DispatchClient
lock sync.RWMutex 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. // 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 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{ result := &Dispatcher{
servicebus: servicebus, servicebus: servicebus,
eventChannel: eventChannel, eventChannel: eventChannel,
@@ -95,10 +70,6 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher,
quitChannel: quitChannel, quitChannel: quitChannel,
windowChannel: windowChannel, windowChannel: windowChannel,
dialogChannel: dialogChannel, dialogChannel: dialogChannel,
systemChannel: systemChannel,
menuChannel: menuChannel,
ctx: ctx,
cancel: cancel,
} }
return result, nil return result, nil
@@ -109,18 +80,15 @@ func (d *Dispatcher) Start() error {
d.logger.Trace("Starting") d.logger.Trace("Starting")
d.wg.Add(1) d.running = true
// Spin off a go routine // Spin off a go routine
go func() { go func() {
defer d.logger.Trace("Shutdown") for d.running {
for {
select { select {
case <-d.ctx.Done():
d.wg.Done()
return
case <-d.quitChannel: case <-d.quitChannel:
d.processQuit() d.processQuit()
d.running = false
case resultMessage := <-d.resultChannel: case resultMessage := <-d.resultChannel:
d.processCallResult(resultMessage) d.processCallResult(resultMessage)
case eventMessage := <-d.eventChannel: case eventMessage := <-d.eventChannel:
@@ -129,12 +97,11 @@ func (d *Dispatcher) Start() error {
d.processWindowMessage(windowMessage) d.processWindowMessage(windowMessage)
case dialogMessage := <-d.dialogChannel: case dialogMessage := <-d.dialogChannel:
d.processDialogMessage(dialogMessage) d.processDialogMessage(dialogMessage)
case systemMessage := <-d.systemChannel:
d.processSystemMessage(systemMessage)
case menuMessage := <-d.menuChannel:
d.processMenuMessage(menuMessage)
} }
} }
// Call shutdown
d.shutdown()
}() }()
return nil 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 // RegisterClient will register the given callback with the dispatcher
// and return a DispatchClient that the caller can use to send messages // and return a DispatchClient that the caller can use to send messages
func (d *Dispatcher) RegisterClient(client Client) *DispatchClient { func (d *Dispatcher) RegisterClient(client Client) *DispatchClient {
@@ -195,35 +166,12 @@ func (d *Dispatcher) processCallResult(result *servicebus.Message) {
if client == nil { if client == nil {
// This is fatal - unknown target! // This is fatal - unknown target!
d.logger.Fatal("Unknown target for call result: %+v", result) 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)) d.logger.Trace("Sending message to client %s: R%s", target, result.Data().(string))
client.frontend.CallResult(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 // processEvent will
func (d *Dispatcher) processEvent(result *servicebus.Message) { func (d *Dispatcher) processEvent(result *servicebus.Message) {
@@ -282,7 +230,7 @@ func (d *Dispatcher) processWindowMessage(result *servicebus.Message) {
client.frontend.WindowUnFullscreen() client.frontend.WindowUnFullscreen()
} }
case "setcolour": case "setcolour":
colour, ok := result.Data().(int) colour, ok := result.Data().(string)
if !ok { if !ok {
d.logger.Error("Invalid colour for 'window:setcolour' : %#v", result.Data()) d.logger.Error("Invalid colour for 'window:setcolour' : %#v", result.Data())
return return
@@ -291,105 +239,6 @@ func (d *Dispatcher) processWindowMessage(result *servicebus.Message) {
for _, client := range d.clients { for _, client := range d.clients {
client.frontend.WindowSetColour(colour) 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: default:
d.logger.Error("Unknown window command: %s", command) d.logger.Error("Unknown window command: %s", command)
} }
@@ -400,6 +249,7 @@ func (d *Dispatcher) processWindowMessage(result *servicebus.Message) {
// processDialogMessage processes dialog messages // processDialogMessage processes dialog messages
func (d *Dispatcher) processDialogMessage(result *servicebus.Message) { func (d *Dispatcher) processDialogMessage(result *servicebus.Message) {
splitTopic := strings.Split(result.Topic(), ":") splitTopic := strings.Split(result.Topic(), ":")
if len(splitTopic) < 4 { if len(splitTopic) < 4 {
d.logger.Error("Invalid dialog message : %#v", result.Data()) d.logger.Error("Invalid dialog message : %#v", result.Data())
return return
@@ -409,142 +259,61 @@ func (d *Dispatcher) processDialogMessage(result *servicebus.Message) {
switch command { switch command {
case "select": case "select":
dialogType := splitTopic[2] dialogType := splitTopic[2]
title := splitTopic[3]
switch dialogType { switch dialogType {
case "open": case "file":
dialogOptions, ok := result.Data().(*dialog.OpenDialog) responseTopic, ok := result.Data().(string)
if !ok { 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 return
} }
// This is hardcoded in the sender too d.logger.Info("Opening File dialog! responseTopic = %s", responseTopic)
callbackID := splitTopic[3]
// TODO: Work out what we mean in a multi window environment... // TODO: Work out what we mean in a multi window environment...
// For now we will just pick the first one // For now we will just pick the first one
var result string
for _, client := range d.clients { 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 { 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 return
} }
// This is hardcoded in the sender too d.logger.Info("Opening Save File dialog! responseTopic = %s", responseTopic)
callbackID := splitTopic[3]
// TODO: Work out what we mean in a multi window environment... // TODO: Work out what we mean in a multi window environment...
// For now we will just pick the first one // For now we will just pick the first one
var result string
for _, client := range d.clients { 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 { 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 return
} }
// This is hardcoded in the sender too d.logger.Info("Opening Directory dialog! responseTopic = %s", responseTopic)
callbackID := splitTopic[3]
// TODO: Work out what we mean in a multi window environment... // TODO: Work out what we mean in a multi window environment...
// For now we will just pick the first one // For now we will just pick the first one
var result string
for _, client := range d.clients { for _, client := range d.clients {
client.frontend.MessageDialog(dialogOptions, callbackID) result = client.frontend.OpenDirectoryDialog(title)
} }
// Send dummy response
d.servicebus.Publish(responseTopic, result)
default: 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" "golang.org/x/tools/go/packages"
) )
var internalMethods = slicer.String([]string{"WailsInit", "Wails Shutdown"})
var structCache = make(map[string]*ParsedStruct) var structCache = make(map[string]*ParsedStruct)
var boundStructs = make(map[string]*ParsedStruct) var boundStructs = make(map[string]*ParsedStruct)
var boundMethods = []string{} var boundMethods = []string{}
@@ -47,7 +49,7 @@ func ParseProject(projectPath string) (BoundStructs, error) {
cfg := &packages.Config{Mode: packages.NeedFiles | packages.NeedSyntax | packages.NeedTypesInfo} cfg := &packages.Config{Mode: packages.NeedFiles | packages.NeedSyntax | packages.NeedTypesInfo}
pkgs, err := packages.Load(cfg, projectPath) pkgs, err := packages.Load(cfg, projectPath)
if err != nil { if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "load: %v\n", err) fmt.Fprintf(os.Stderr, "load: %v\n", err)
os.Exit(1) os.Exit(1)
} }
if packages.PrintErrors(pkgs) > 0 { if packages.PrintErrors(pkgs) > 0 {
@@ -63,6 +65,7 @@ func ParseProject(projectPath string) (BoundStructs, error) {
var wailsPkgVar = "" var wailsPkgVar = ""
ast.Inspect(file, func(n ast.Node) bool { ast.Inspect(file, func(n ast.Node) bool {
var s string
switch x := n.(type) { switch x := n.(type) {
// Parse import declarations // Parse import declarations
case *ast.ImportSpec: case *ast.ImportSpec:
@@ -201,6 +204,10 @@ func ParseProject(projectPath string) (BoundStructs, error) {
// This is a struct pointer method // This is a struct pointer method
i, ok := se.X.(*ast.Ident) i, ok := se.X.(*ast.Ident)
if ok { if ok {
// We want to ignore Internal functions
if internalMethods.Contains(x.Name.Name) {
continue
}
// If we haven't already found this struct, // If we haven't already found this struct,
// Create a placeholder in the cache // Create a placeholder in the cache
parsedStruct := structCache[i.Name] parsedStruct := structCache[i.Name]
@@ -431,6 +438,4 @@ func ParseProject(projectPath string) (BoundStructs, error) {
println() println()
println("}") println("}")
println() println()
return nil, nil
} }

View File

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

View File

@@ -25,9 +25,6 @@ type Project struct {
// The path to the project directory // The path to the project directory
Path string Path string
// Assets directory
AssetsDir string `json:"assetsdir"`
// The output filename // The output filename
OutputFilename string `json:"outputfilename"` OutputFilename string `json:"outputfilename"`
@@ -36,15 +33,6 @@ type Project struct {
// The platform to target // The platform to target
Platform string 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 // Load the project from the current working directory
@@ -75,11 +63,6 @@ func Load(projectPath string) (*Project, error) {
result.Name = "wailsapp" result.Name = "wailsapp"
} }
// Set default assets directory if none given
if result.AssetsDir == "" {
result.AssetsDir = filepath.Join(result.Path, "assets")
}
// Fix up OutputFilename // Fix up OutputFilename
switch runtime.GOOS { switch runtime.GOOS {
case "windows": 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 () { window.backend[packageName][structName][methodName] = function () {
// No timeout by default // No timeout by default
let timeout = 0; var timeout = 0;
// Actual function // Actual function
function dynamic() { 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'; import { SendMessage } from 'ipc';
/** /**
* Opens the given URL / filename in the system browser * Opens the given URL in the system browser
* *
* @export * @export
* @param {string} target * @param {string} url
* @returns * @returns
*/ */
export function Open(target) { export function OpenURL(url) {
return SendMessage('RBO' + target); 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 * @param {any[]=} data
* @returns * @returns
*/ */
export function SystemCall(method) { export function SystemCall(method, data) {
var data = [].slice.apply(arguments).slice(1);
return Call('.wails.' + method, data); return Call('.wails.' + method, data);
} }

View File

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

View File

@@ -13,7 +13,6 @@ import { Error } from './log';
import { SendMessage } from 'ipc'; import { SendMessage } from 'ipc';
// Defines a single listener with a maximum number of times to callback // Defines a single listener with a maximum number of times to callback
/** /**
* The Listener class defines a listener! :-) * 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 * 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); 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 * Registers an event listener that will be invoked once then destroyed
* *
@@ -94,43 +82,11 @@ export function Once(eventName, callback) {
OnMultiple(eventName, callback, 1); 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 * Notify informs frontend listeners that an event was emitted with the given data
* *
* @export * @export
* @param {string} notifyMessage - encoded notification message * @param {string} encoded notification message
*/ */
export function Notify(notifyMessage) { export function Notify(notifyMessage) {
@@ -144,7 +100,33 @@ export function Notify(notifyMessage) {
throw new Error(error); 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) { export function Emit(eventName) {
const payload = { // Calculate the data
name: eventName, if (arguments.length > 1) {
data: [].slice.apply(arguments).slice(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 // Callbacks for the heartbeat calls
SendMessage('Ej' + JSON.stringify(payload)); 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); 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 * Log the given debug message with the backend
* *
@@ -94,22 +74,3 @@ export function Error(message) {
export function Fatal(message) { export function Fatal(message) {
sendLogMessage('F', 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 */ /* jshint esversion: 6 */
import * as Log from './log'; import * as Log from './log';
import * as Browser from './browser'; import * as Browser from './browser';
import * as Window from './window'; import { On, OnMultiple, Emit, Notify, Heartbeat, Acknowledge } from './events';
import * as Dialog from './dialog'; import { Callback } from './calls';
import { On, Once, OnMultiple, Emit, Notify } from './events'; import { AddScript, InjectCSS } from './utils';
import { Callback, SystemCall } from './calls';
import { AddScript, InjectCSS, DisableDefaultContextMenu } from './utils';
import { AddIPCListener } from 'ipc'; import { AddIPCListener } from 'ipc';
import * as Platform from 'platform'; import * as Platform from 'platform';
import * as Store from './store';
import * as Tray from './tray';
export function Init() { export function Init() {
// Backend is where the Go struct wrappers get bound to // Backend is where the Go struct wrappers get bound to
@@ -29,37 +25,23 @@ export function Init() {
System: Platform.System, System: Platform.System,
Log, Log,
Browser, Browser,
Window,
Tray,
Dialog,
Events: { Events: {
On, On,
Once,
OnMultiple, OnMultiple,
Emit, Emit,
Heartbeat,
Acknowledge,
}, },
_: { _: {
Callback, Callback,
Notify, Notify,
AddScript, AddScript,
InjectCSS, InjectCSS,
DisableDefaultContextMenu, Init,
// Init, AddIPCListener
AddIPCListener, }
SystemCall,
},
Store,
}; };
// 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 // Do platform specific Init
Platform.Init(); Platform.Init();
} }

View File

@@ -36,7 +36,3 @@ export function InjectCSS(css) {
console.log(e); 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 = { export const System = {
...common, Platform: "linux",
Platform: () => "linux", AppType: "desktop"
} }
export function SendMessage(message) { export function SendMessage(message) {
@@ -27,7 +27,7 @@ export function Init() {
// Setup drag handler // Setup drag handler
// Based on code from: https://github.com/patr0nus/DeskGap // Based on code from: https://github.com/patr0nus/DeskGap
window.addEventListener('mousedown', function (e) { window.addEventListener('mousedown', function (e) {
let currentElement = e.target; var currentElement = e.target;
while (currentElement != null) { while (currentElement != null) {
if (currentElement.hasAttribute('data-wails-no-drag')) { if (currentElement.hasAttribute('data-wails-no-drag')) {
break; break;

View File

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

View File

@@ -39,6 +39,5 @@
"eslint": "^6.8.0", "eslint": "^6.8.0",
"webpack": "^4.41.5", "webpack": "^4.41.5",
"webpack-cli": "^3.3.10" "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 # 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 */ /* jshint esversion: 6 */
/** /**
* Opens the given URL or Filename in the system browser * Opens the given URL in the system browser
* *
* @export * @export
* @param {string} target * @param {string} url
* @returns * @returns
*/ */
export function Open(target) { function OpenURL(url) {
return window.wails.Browser.Open(target); 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); 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 * @export
* @param {string} eventName
* @param {number} timeInMilliseconds
* @param {function} callback * @param {function} callback
*/ */
function OnThemeChange(callback) { function Heartbeat(eventName, timeInMilliseconds, callback) {
On('wails:system:themechange', 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 = { module.exports = {
@@ -71,5 +85,6 @@ module.exports = {
On: On, On: On,
Once: Once, Once: Once,
Emit: Emit, Emit: Emit,
OnThemeChange: OnThemeChange, Heartbeat: Heartbeat,
Acknowledge: Acknowledge
}; };

View File

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

View File

@@ -11,26 +11,6 @@ The lightweight framework for web-like apps
/* jshint esversion: 6 */ /* 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 * Log the given debug message with the backend
* *
@@ -74,40 +54,17 @@ function Error(message) {
/** /**
* Log the given fatal message with the backend * Log the given fatal message with the backend
* *
* @export
* @param {string} message * @param {string} message
*/ */
function Fatal(message) { function Fatal(message) {
window.wails.Log.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 = { module.exports = {
Print: Print,
Trace: Trace,
Debug: Debug, Debug: Debug,
Info: Info, Info: Info,
Warning: Warning, Warning: Warning,
Error: Error, Error: Error,
Fatal: Fatal, Fatal: Fatal
SetLogLevel: SetLogLevel,
Level: Level,
}; };

View File

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

View File

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

View File

@@ -1,137 +1,26 @@
export = wailsapp__runtime; 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: { declare const wailsapp__runtime: {
Browser: { Browser: {
Open(target: string): Promise<any>; OpenFile(filename: string): Promise<any>;
OpenURL(url: string): Promise<any>;
}; };
Events: { Events: {
Emit(eventName: string, data?: any): void; Acknowledge(eventName: string): void;
On(eventName: string, callback: (data?: any) => void): void; Emit(eventName: string): void;
OnMultiple(eventName: string, callback: (data?: any) => void, maxCallbacks: number): void; Heartbeat(eventName: string, timeInMilliseconds: number, callback: () => void): void;
Once(eventName: string, callback: (data?: any) => 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: { Log: {
Debug(message: string): void; Debug(message: string): void;
Error(message: string): void; Error(message: string): void;
Fatal(message: string): void; Fatal(message: string): void;
Info(message: string): void; Info(message: string): void;
Warning(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', mode: 'production',
output: { output: {
path: path.resolve(__dirname, '..', 'assets'), path: path.resolve(__dirname, '..', 'assets'),
filename: 'desktop_'+platform+'.js', filename: 'desktop.js',
library: 'Wails' library: 'Wails'
}, },
resolve: { resolve: {

View File

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

View File

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

View File

@@ -3,16 +3,16 @@ package subsystem
import ( import (
"github.com/wailsapp/wails/v2/internal/binding" "github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/logger" "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" "github.com/wailsapp/wails/v2/internal/servicebus"
) )
// Binding is the Binding subsystem. It manages all service bus messages // Binding is the Binding subsystem. It manages all service bus messages
// starting with "binding". // starting with "binding".
type Binding struct { type Binding struct {
quitChannel <-chan *servicebus.Message
bindingChannel <-chan *servicebus.Message bindingChannel <-chan *servicebus.Message
running bool
running bool
// Binding db // Binding db
bindings *binding.Bindings bindings *binding.Bindings
@@ -21,11 +21,17 @@ type Binding struct {
logger logger.CustomLogger logger logger.CustomLogger
// runtime // runtime
runtime *runtime.Runtime runtime *goruntime.Runtime
} }
// NewBinding creates a new binding subsystem. Uses the given bindings db for reference. // 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 // Subscribe to event messages
bindingChannel, err := bus.Subscribe("binding") bindingChannel, err := bus.Subscribe("binding")
@@ -34,12 +40,21 @@ func NewBinding(bus *servicebus.ServiceBus, logger *logger.Logger, bindings *bin
} }
result := &Binding{ result := &Binding{
quitChannel: quitChannel,
bindingChannel: bindingChannel, bindingChannel: bindingChannel,
logger: logger.CustomLogger("Binding Subsystem"), logger: logger.CustomLogger("Binding Subsystem"),
bindings: bindings, bindings: bindings,
runtime: runtime, 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 return result, nil
} }
@@ -54,16 +69,45 @@ func (b *Binding) Start() error {
go func() { go func() {
for b.running { for b.running {
select { select {
case <-b.quitChannel:
b.running = false
case bindingMessage := <-b.bindingChannel: case bindingMessage := <-b.bindingChannel:
b.logger.Trace("Got binding message: %+v", bindingMessage) b.logger.Trace("Got binding message: %+v", bindingMessage)
} }
} }
b.logger.Trace("Shutdown")
// Call shutdown
b.shutdown()
}() }()
return nil return nil
} }
func (b *Binding) Close() { // CallWailsInit will callback to the registered WailsInit
b.running = false // 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 package subsystem
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
"sync"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/binding" "github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message" "github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
"github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/internal/servicebus" "github.com/wailsapp/wails/v2/internal/servicebus"
) )
// Call is the Call subsystem. It manages all service bus messages // Call is the Call subsystem. It manages all service bus messages
// starting with "call". // starting with "call".
type Call struct { type Call struct {
quitChannel <-chan *servicebus.Message
callChannel <-chan *servicebus.Message callChannel <-chan *servicebus.Message
running bool
// quit flag
shouldQuit bool
// bindings DB // bindings DB
DB *binding.DB DB *binding.DB
@@ -32,19 +25,16 @@ type Call struct {
// logger // logger
logger logger.CustomLogger logger logger.CustomLogger
// runtime
runtime *runtime.Runtime
// context
ctx context.Context
// parent waitgroup
wg *sync.WaitGroup
} }
// NewCall creates a new call subsystem // NewCall creates a new log subsystem
func NewCall(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger, DB *binding.DB, runtime *runtime.Runtime) (*Call, error) { 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 // Subscribe to event messages
callChannel, err := bus.Subscribe("call:invoke") callChannel, err := bus.Subscribe("call:invoke")
@@ -53,13 +43,11 @@ func NewCall(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Log
} }
result := &Call{ result := &Call{
quitChannel: quitChannel,
callChannel: callChannel, callChannel: callChannel,
logger: logger.CustomLogger("Call Subsystem"), logger: logger.CustomLogger("Call Subsystem"),
DB: DB, DB: DB,
bus: bus, bus: bus,
runtime: runtime,
ctx: ctx,
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
} }
return result, nil return result, nil
@@ -68,21 +56,22 @@ func NewCall(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Log
// Start the subsystem // Start the subsystem
func (c *Call) Start() error { func (c *Call) Start() error {
c.wg.Add(1) c.running = true
// Spin off a go routine // Spin off a go routine
go func() { go func() {
defer c.logger.Trace("Shutdown") for c.running {
for {
select { select {
case <-c.ctx.Done(): case <-c.quitChannel:
c.wg.Done() c.running = false
return
case callMessage := <-c.callChannel: case callMessage := <-c.callChannel:
c.processCall(callMessage) c.processCall(callMessage)
} }
} }
// Call shutdown
c.shutdown()
}() }()
return nil return nil
@@ -98,12 +87,6 @@ func (c *Call) processCall(callMessage *servicebus.Message) {
// Lookup method // Lookup method
registeredMethod := c.DB.GetMethod(payload.Name) 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 // Check we have it
if registeredMethod == nil { if registeredMethod == nil {
c.sendError(fmt.Errorf("Method not registered"), payload, callMessage.Target()) 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) c.logger.Trace("Got registered method: %+v", registeredMethod)
args, err := registeredMethod.ParseArgs(payload.Args) result, err := registeredMethod.Call(payload.Args)
if err != nil {
c.sendError(fmt.Errorf("Error parsing arguments: %s", err.Error()), payload, callMessage.Target())
}
result, err := registeredMethod.Call(args)
if err != nil { if err != nil {
c.sendError(err, payload, callMessage.Target()) c.sendError(err, payload, callMessage.Target())
return 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) { 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) c.logger.Trace("Sending success result with CallbackID '%s' : %+v\n", payload.CallbackID, result)
incomingMessage := &CallbackMessage{ message := &CallbackMessage{
Result: result, Result: result,
CallbackID: payload.CallbackID, CallbackID: payload.CallbackID,
} }
messageData, err := json.Marshal(incomingMessage) messageData, err := json.Marshal(message)
c.logger.Trace("json incomingMessage data: %+v\n", string(messageData)) c.logger.Trace("json message data: %+v\n", string(messageData))
if err != nil { if err != nil {
// what now? // what now?
c.logger.Fatal(err.Error()) 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) { 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()) c.logger.Trace("Sending error result with CallbackID '%s' : %+v\n", payload.CallbackID, err.Error())
incomingMessage := &CallbackMessage{ message := &CallbackMessage{
Err: err.Error(), Err: err.Error(),
CallbackID: payload.CallbackID, CallbackID: payload.CallbackID,
} }
messageData, err := json.Marshal(incomingMessage) messageData, err := json.Marshal(message)
c.logger.Trace("json incomingMessage data: %+v\n", string(messageData)) c.logger.Trace("json message data: %+v\n", string(messageData))
if err != nil { if err != nil {
// what now? // what now?
c.logger.Fatal(err.Error()) 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) 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 // CallbackMessage defines a message that contains the result of a call
type CallbackMessage struct { type CallbackMessage struct {
Result interface{} `json:"result"` Result interface{} `json:"result"`

View File

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

View File

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

View File

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

View File

@@ -9,6 +9,24 @@ import (
"github.com/wailsapp/wails/v2/internal/shell" "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 // A list of package manager commands
var pmcommands = []string{ var pmcommands = []string{
@@ -20,6 +38,8 @@ var pmcommands = []string{
"zypper", "zypper",
} }
type packagemap = map[string][]*Package
// Find will attempt to find the system package manager // Find will attempt to find the system package manager
func Find(osid string) PackageManager { func Find(osid string) PackageManager {
@@ -50,6 +70,58 @@ func newPackageManager(pmname string, osid string) PackageManager {
return nil 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 // Dependancies scans the system for required dependancies
// Returns a list of dependancies search for, whether they were found // Returns a list of dependancies search for, whether they were found
// and whether they were installed // and whether they were installed

View File

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

View File

@@ -6,14 +6,13 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
"github.com/leaanthony/gosod" "github.com/leaanthony/gosod"
"github.com/leaanthony/slicer" "github.com/leaanthony/slicer"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
"github.com/wailsapp/wails/v2/internal/fs" "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 // Cahce for the templates
@@ -22,29 +21,21 @@ var templateCache []Template = nil
// Data contains the data we wish to embed during template installation // Data contains the data we wish to embed during template installation
type Data struct { type Data struct {
ProjectName string ProjectName string
BinaryName string BinaryName string
WailsVersion string WailsVersion string
NPMProjectName string NPMProjectName string
AuthorName string Author string
AuthorEmail string WailsDirectory string
AuthorNameAndEmail string
WailsDirectory string
} }
// Options for installing a template // Options for installing a template
type Options struct { type Options struct {
ProjectName string ProjectName string
TemplateName string TemplateName string
BinaryName string BinaryName string
TargetDir string TargetDir string
Logger *clilogger.CLILogger Logger *logger.Logger
GenerateVSCode bool
PathToDesktopBinary string
PathToServerBinary string
InitGit bool
AuthorName string
AuthorEmail string
} }
// Template holds data relating to a template // 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? // Did the user want to install in current directory?
if options.TargetDir == "" { if options.TargetDir == "." {
// Yes - use cwd
// If the current directory is empty, use it options.TargetDir = cwd
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)
}
}
} else { } else {
// Get the absolute path of the given directory // Get the absolute path of the given directory
targetDir, err := filepath.Abs(filepath.Join(cwd, options.TargetDir)) targetDir, err := filepath.Abs(filepath.Join(cwd, options.TargetDir))
@@ -229,93 +205,43 @@ func Install(options *Options) error {
BinaryName: filepath.Base(options.TargetDir), BinaryName: filepath.Base(options.TargetDir),
NPMProjectName: NPMProjectName, NPMProjectName: NPMProjectName,
WailsDirectory: localWailsDirectory, 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 // Extract the template
err = installer.Extract(options.TargetDir, templateData) err = installer.Extract(options.TargetDir, templateData)
if err != nil { if err != nil {
return err return err
} }
err = generateIDEFiles(options) // Calculate the directory name
if err != nil {
return err
}
return nil return nil
} }
// OutputList prints the list of available tempaltes to the given logger // 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() templates, err := List()
if err != nil { if err != nil {
return err return err
} }
table := tablewriter.NewWriter(logger.Writer) for _, writer := range logger.Writers() {
table.SetHeader([]string{"Template", "Short Name", "Description"}) table := tablewriter.NewWriter(writer)
table.SetAutoWrapText(false) table.SetHeader([]string{"Template", "Short Name", "Description"})
table.SetAutoFormatHeaders(true) table.SetAutoWrapText(false)
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) table.SetAutoFormatHeaders(true)
table.SetAlignment(tablewriter.ALIGN_LEFT) table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
table.SetCenterSeparator("") table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetColumnSeparator("") table.SetCenterSeparator("")
table.SetRowSeparator("") table.SetColumnSeparator("")
table.SetHeaderLine(false) table.SetRowSeparator("")
table.SetBorder(false) table.SetHeaderLine(false)
table.SetTablePadding("\t") // pad with tabs table.SetBorder(false)
table.SetNoWhiteSpace(true) table.SetTablePadding("\t") // pad with tabs
for _, template := range templates { table.SetNoWhiteSpace(true)
table.Append([]string{template.Name, template.ShortName, template.Description}) 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 return nil
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@
<link rel="stylesheet" href="/main.css"> <link rel="stylesheet" href="/main.css">
</head> </head>
<body data-wails-drag> <body>
<div id="logo"></div> <div id="logo"></div>
<div id="input"> <div id="input">
<input id="name" type="text"></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 package main
import ( import (
"log" wails "github.com/wailsapp/wails/v2"
"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"
) )
func main() { func main() {
// Create application with options // Create application with options
app := NewBasic() app := wails.CreateApp("{{.ProjectName}}", 1024, 768)
err := wails.Run(&options.App{ app.Bind(newBasic())
Title: "{{.ProjectName}}",
Width: 800, app.Run()
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)
}
} }

View File

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

View File

@@ -3,7 +3,7 @@ package main
import ( import (
"fmt" "fmt"
"github.com/wailsapp/wails/v2" wails "github.com/wailsapp/wails/v2"
) )
// Basic application struct // 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 package main
import ( import (
"github.com/wailsapp/wails/v2" wails "github.com/wailsapp/wails/v2"
) )
func main() { func main() {

View File

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

View File

@@ -2,8 +2,6 @@ package webserver
import ( import (
"context" "context"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"net/http" "net/http"
"strings" "strings"
@@ -20,94 +18,6 @@ type WebClient struct {
running bool 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 // Quit terminates the webclient session
func (wc *WebClient) Quit() { func (wc *WebClient) Quit() {
wc.running = false wc.running = false
@@ -124,6 +34,21 @@ func (wc *WebClient) CallResult(message string) {
wc.SendMessage("R" + message) 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 // WindowSetTitle is a noop in the webclient
func (wc *WebClient) WindowSetTitle(title string) {} func (wc *WebClient) WindowSetTitle(title string) {}
@@ -134,7 +59,8 @@ func (wc *WebClient) WindowFullscreen() {}
func (wc *WebClient) WindowUnFullscreen() {} func (wc *WebClient) WindowUnFullscreen() {}
// WindowSetColour is a noop in the webclient // 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 // Run processes messages from the remote webclient
@@ -165,11 +91,7 @@ func (wc *WebClient) Run(w *WebServer) {
dispatcher.DispatchMessage(v.(string)) dispatcher.DispatchMessage(v.(string))
} }
err := wc.conn.Close(ws.StatusNormalClosure, "Goodbye") wc.conn.Close(ws.StatusNormalClosure, "Goodbye")
if err != nil {
w.logger.Error("Error encountered on socket: %v", err)
return
}
w.logger.Debug("Connection closed: %v", wc.identifier) 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) wc.logger.Debug("WebClient.SendMessage() - %s", message)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
err := wc.conn.Write(ctx, ws.MessageText, []byte(message)) wc.conn.Write(ctx, ws.MessageText, []byte(message))
if err != nil {
wc.logger.Error("Error encountered writing to webclient: %v", err)
}
} }
// unregisterClient is called automatically by a WebClient session during termination // 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/assetdb"
"github.com/wailsapp/wails/v2/internal/fs" "github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/html" "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/project"
"github.com/wailsapp/wails/v2/internal/shell" "github.com/wailsapp/wails/v2/internal/shell"
"github.com/wailsapp/wails/v2/pkg/clilogger"
) )
// BaseBuilder is the common builder struct // BaseBuilder is the common builder struct
type BaseBuilder struct { type BaseBuilder struct {
filesToDelete slicer.StringSlicer filesToDelete slicer.StringSlicer
projectData *project.Project projectData *project.Project
options *Options
} }
// NewBaseBuilder creates a new BaseBuilder // NewBaseBuilder creates a new BaseBuilder
func NewBaseBuilder(options *Options) *BaseBuilder { func NewBaseBuilder() *BaseBuilder {
result := &BaseBuilder{ result := &BaseBuilder{}
options: options,
}
return result return result
} }
@@ -40,9 +37,7 @@ func (b *BaseBuilder) SetProjectData(projectData *project.Project) {
} }
func (b *BaseBuilder) addFileToDelete(filename string) { 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 { func (b *BaseBuilder) fileExists(path string) bool {
@@ -54,43 +49,39 @@ func (b *BaseBuilder) fileExists(path string) bool {
return true 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. // 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 // Add trailing slash to Asset directory
customAssetsDir := filepath.Join(projectData.Path, "assets", "custom") + "/" assetsDir := filepath.Join(projectData.Path, "assets") + "/"
if !b.fileExists(customAssetsDir) {
err := fs.MkDirs(customAssetsDir)
if err != nil {
return err
}
}
assets := assetdb.NewAssetDB() 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 { if err != nil {
return err 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 // Write assetdb out to root directory
assetsDbFilename := fs.RelativePath("../../../assetsdb.go") assetsDbFilename := fs.RelativePath("../../../assetsdb.go")
b.addFileToDelete(assetsDbFilename) 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 { if err != nil {
return err return err
} }
@@ -137,7 +128,7 @@ func (b *BaseBuilder) CleanUp() {
// Delete file. We ignore errors because these files will be overwritten // Delete file. We ignore errors because these files will be overwritten
// by the next build anyway. // by the next build anyway.
_ = os.Remove(filename) os.Remove(filename)
}) })
} }
@@ -145,39 +136,12 @@ func (b *BaseBuilder) CleanUp() {
// CompileProject compiles the project // CompileProject compiles the project
func (b *BaseBuilder) CompileProject(options *Options) error { 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 // Default go build command
commands := slicer.String([]string{"build"}) 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 // Add the output type build tag
commands.Add("-tags") commands.Add("-tags")
commands.Add(tags.Join(",")) commands.Add(options.OutputType)
// Strip binary in Production mode // Strip binary in Production mode
if options.Mode == Production { if options.Mode == Production {
@@ -194,8 +158,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
} }
// Get application build directory // Get application build directory
appDir := options.BuildDirectory appDir, err := getApplicationBuildDirectory(options, options.Platform)
err = cleanBuildDirectory(options)
if err != nil { if err != nil {
return err return err
} }
@@ -206,40 +169,20 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
} }
// Set up output filename // Set up output filename
outputFile := options.OutputFile outputFilePath := filepath.Join(appDir, b.projectData.OutputFilename)
if outputFile == "" {
outputFile = b.projectData.OutputFilename
}
compiledBinary := filepath.Join(appDir, outputFile)
commands.Add("-o") commands.Add("-o")
commands.Add(compiledBinary) commands.Add(outputFilePath)
b.projectData.OutputFilename = strings.TrimPrefix(compiledBinary, options.ProjectData.Path) b.projectData.OutputFilename = strings.TrimPrefix(outputFilePath, options.ProjectData.Path)
options.CompiledBinary = compiledBinary
// Create the command // Create the command
cmd = exec.Command(options.Compiler, commands.AsSlice()...) cmd := exec.Command(options.Compiler, commands.AsSlice()...)
// Set the directory // Set the directory
cmd.Dir = b.projectData.Path cmd.Dir = b.projectData.Path
// Add CGO flags // Set GO111MODULE environment variable
// We use the project/build dir as a temporary place for our generated c headers cmd.Env = append(os.Environ(), "GO111MODULE=on")
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
})
// Setup buffers // Setup buffers
var stdo, stde bytes.Buffer 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 // BuildFrontend executes the `npm build` command for the frontend directory
func (b *BaseBuilder) BuildFrontend(outputLogger *clilogger.CLILogger) error { func (b *BaseBuilder) BuildFrontend(outputLogger *logger.Logger) error {
// TODO: Fix this up from the CLI
verbose := false verbose := false
frontendDir := filepath.Join(b.projectData.Path, "frontend") 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 // Check there is an 'InstallCommand' provided in wails.json
if b.projectData.InstallCommand == "" { if b.projectData.InstallCommand == "" {
// No - don't install // No - don't install
outputLogger.Println(" - No Install command. Skipping.") outputLogger.Writeln(" - No Install command. Skipping.")
} else { } else {
// Do install if needed // Do install if needed
outputLogger.Println(" - Installing dependencies...") outputLogger.Writeln(" - Installing dependencies...")
if err := b.NpmInstallUsingCommand(frontendDir, b.projectData.InstallCommand); err != nil { if err := b.NpmInstallUsingCommand(frontendDir, b.projectData.InstallCommand); err != nil {
return err return err
} }
@@ -372,12 +313,12 @@ func (b *BaseBuilder) BuildFrontend(outputLogger *clilogger.CLILogger) error {
// Check if there is a build command // Check if there is a build command
if b.projectData.BuildCommand == "" { if b.projectData.BuildCommand == "" {
outputLogger.Println(" - No Build command. Skipping.") outputLogger.Writeln(" - No Build command. Skipping.")
// No - ignore // No - ignore
return nil return nil
} }
outputLogger.Println(" - Compiling Frontend Project") outputLogger.Writeln(" - Compiling Frontend Project")
cmd := strings.Split(b.projectData.BuildCommand, " ") cmd := strings.Split(b.projectData.BuildCommand, " ")
stdout, stderr, err := shell.RunCommand(frontendDir, cmd[0], cmd[1:]...) stdout, stderr, err := shell.RunCommand(frontendDir, cmd[0], cmd[1:]...)
if verbose || err != nil { if verbose || err != nil {
@@ -397,22 +338,3 @@ func (b *BaseBuilder) ExtractAssets() (*html.AssetBundle, error) {
// Read in html // Read in html
return html.NewAssetBundle(b.projectData.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 ( import (
"fmt" "fmt"
"os" "os"
"path/filepath"
"runtime" "runtime"
"github.com/leaanthony/slicer" "github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/project" "github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/pkg/clilogger"
) )
// Mode is the type used to indicate the build modes // 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 // Options contains all the build options as well as the project data
type Options struct { type Options struct {
LDFlags string // Optional flags to pass to linker LDFlags string // Optional flags to pass to linker
Logger *clilogger.CLILogger // All output to the logger Logger *logger.Logger // All output to the logger
OutputType string // EG: desktop, server.... OutputType string // EG: desktop, server....
Mode Mode // release or debug Mode Mode // release or debug
ProjectData *project.Project // The project data ProjectData *project.Project // The project data
Pack bool // Create a package for the app after building Pack bool // Create a package for the app after building
Platform string // The platform to build for Platform string // The platform to build for
Compiler string // The compiler command to use 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
} }
// GetModeAsString returns the current mode as a string // GetModeAsString returns the current mode as a string
@@ -52,6 +45,11 @@ func Build(options *Options) (string, error) {
// Extract logger // Extract logger
outputLogger := options.Logger outputLogger := options.Logger
// Create a default logger if it doesn't exist
if outputLogger == nil {
outputLogger = logger.New()
}
// Get working directory // Get working directory
cwd, err := os.Getwd() cwd, err := os.Getwd()
if err != nil { if err != nil {
@@ -59,7 +57,7 @@ func Build(options *Options) (string, error) {
} }
// Check platform // Check platform
validPlatforms := slicer.String([]string{"linux", "darwin"}) validPlatforms := slicer.String([]string{"linux"})
if !validPlatforms.Contains(options.Platform) { if !validPlatforms.Contains(options.Platform) {
return "", fmt.Errorf("platform %s not supported", options.Platform) return "", fmt.Errorf("platform %s not supported", options.Platform)
} }
@@ -71,9 +69,6 @@ func Build(options *Options) (string, error) {
} }
options.ProjectData = projectData options.ProjectData = projectData
// Calculate build dir
options.BuildDirectory = filepath.Join(options.ProjectData.Path, "build", options.Platform, options.OutputType)
// Save the project type // Save the project type
projectData.OutputType = options.OutputType projectData.OutputType = options.OutputType
@@ -82,13 +77,11 @@ func Build(options *Options) (string, error) {
switch projectData.OutputType { switch projectData.OutputType {
case "desktop": case "desktop":
builder = newDesktopBuilder(options) builder = newDesktopBuilder()
case "hybrid": case "hybrid":
builder = newHybridBuilder(options) builder = newHybridBuilder()
case "server": case "server":
builder = newServerBuilder(options) builder = newServerBuilder()
case "dev":
builder = newDesktopBuilder(options)
default: default:
return "", fmt.Errorf("cannot build assets for output type %s", projectData.OutputType) return "", fmt.Errorf("cannot build assets for output type %s", projectData.OutputType)
} }
@@ -99,40 +92,34 @@ func Build(options *Options) (string, error) {
// Initialise Builder // Initialise Builder
builder.SetProjectData(projectData) builder.SetProjectData(projectData)
// Generate Frontend JS Package outputLogger.Writeln(" - Building Wails Frontend")
// outputLogger.Println(" - Generating Backend JS Package") err = builder.BuildFrontend(outputLogger)
// // Ignore the parser report coming back if err != nil {
// _, err = parser.GenerateWailsFrontendPackage() return "", err
// if err != nil {
// return "", err
// }
if !options.IgnoreFrontend {
outputLogger.Println(" - Building Project Frontend")
err = builder.BuildFrontend(outputLogger)
if err != nil {
return "", err
}
} }
// Build the base assets // 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) err = builder.BuildAssets(options)
if err != nil { if err != nil {
return "", err return "", err
} }
// Compile the application // 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) err = builder.CompileProject(options)
if err != nil { if err != nil {
return "", err return "", err
} }
outputLogger.Println("done.") outputLogger.Writeln("done.")
// Do we need to pack the app? // Do we need to pack the app?
if options.Pack { if options.Pack {
outputLogger.Println(" - Packaging Application") outputLogger.Writeln(" - Packaging Application")
// TODO: Allow cross platform build // TODO: Allow cross platform build
err = packageProject(options, runtime.GOOS) err = packageProject(options, runtime.GOOS)

View File

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

View File

@@ -7,6 +7,7 @@ import (
"github.com/wailsapp/wails/v2/internal/fs" "github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/html" "github.com/wailsapp/wails/v2/internal/html"
"github.com/wailsapp/wails/v2/internal/logger"
) )
// DesktopBuilder builds applications for the desktop // DesktopBuilder builds applications for the desktop
@@ -14,9 +15,9 @@ type DesktopBuilder struct {
*BaseBuilder *BaseBuilder
} }
func newDesktopBuilder(options *Options) *DesktopBuilder { func newDesktopBuilder() *DesktopBuilder {
return &DesktopBuilder{ return &DesktopBuilder{
BaseBuilder: NewBaseBuilder(options), BaseBuilder: NewBaseBuilder(),
} }
} }
@@ -24,17 +25,6 @@ func newDesktopBuilder(options *Options) *DesktopBuilder {
func (d *DesktopBuilder) BuildAssets(options *Options) error { func (d *DesktopBuilder) BuildAssets(options *Options) error {
var err 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 // Get a list of assets from the HTML
assets, err := d.BaseBuilder.ExtractAssets() assets, err := d.BaseBuilder.ExtractAssets()
if err != nil { if err != nil {
@@ -42,7 +32,13 @@ func (d *DesktopBuilder) BuildAssets(options *Options) error {
} }
// Build base assets (HTML/JS/CSS/etc) // 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 { if err != nil {
return err return err
} }
@@ -51,25 +47,13 @@ func (d *DesktopBuilder) BuildAssets(options *Options) error {
} }
// BuildBaseAssets builds the assets for the desktop application // 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 var err error
outputLogger := options.Logger outputLogger.Write(" - Embedding Assets...")
outputLogger.Print(" - Embedding Assets...")
// Get target asset directory // Get target asset directory
assetDir, err := fs.RelativeToCwd("build") assetDir := fs.RelativePath("../../../internal/ffenestri")
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
}
}
// Dump assets as C // Dump assets as C
assetsFile, err := assets.WriteToCFile(assetDir) assetsFile, err := assets.WriteToCFile(assetDir)
@@ -79,34 +63,22 @@ func (d *DesktopBuilder) BuildBaseAssets(assets *html.AssetBundle, options *Opti
d.addFileToDelete(assetsFile) d.addFileToDelete(assetsFile)
// Process Icon // Process Icon
err = d.processApplicationIcon(assetDir) err = d.processIcon(assetDir)
if err != nil { if err != nil {
return err return err
} }
// Process Tray Icons outputLogger.Writeln("done.")
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.")
return nil 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 // 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 // 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) { if !fs.FileExists(iconFile) {
err := fs.CopyFile(defaultIconPath(), iconFile) err := fs.CopyFile(defaultIconPath(), iconFile)
if err != nil { if err != nil {
@@ -129,7 +101,7 @@ func (d *DesktopBuilder) BuildRuntime(options *Options) error {
return err return err
} }
outputLogger.Print(" - Embedding Runtime...") outputLogger.Write(" - Embedding Runtime...")
envvars := []string{"WAILSPLATFORM=" + options.Platform} envvars := []string{"WAILSPLATFORM=" + options.Platform}
if err := d.NpmRunWithEnvironment(sourceDir, "build:desktop", false, envvars); err != nil { if err := d.NpmRunWithEnvironment(sourceDir, "build:desktop", false, envvars); err != nil {
return err return err
@@ -140,7 +112,7 @@ func (d *DesktopBuilder) BuildRuntime(options *Options) error {
if err != nil { if err != nil {
return err return err
} }
outputLogger.Println("done.") outputLogger.Writeln("done.")
// Convert to C structure // Convert to C structure
runtimeC := ` runtimeC := `

View File

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

View File

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

View File

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

View File

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

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