mirror of
https://github.com/taigrr/wails.git
synced 2026-04-04 14:12:40 -07:00
Compare commits
36 Commits
v2.0.0-alp
...
v2-alpha-o
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
febd867fa7 | ||
|
|
cbd98b5a1a | ||
|
|
c8e0aea69c | ||
|
|
7c0b236eb0 | ||
|
|
16debbd109 | ||
|
|
39bfa5d910 | ||
|
|
6424579a9e | ||
|
|
a962ae6f63 | ||
|
|
c7dee158ba | ||
|
|
237d25089d | ||
|
|
265328d648 | ||
|
|
6f40e85a6e | ||
|
|
96d8509da3 | ||
|
|
598445ab0f | ||
|
|
24788e2fd3 | ||
|
|
bbf4dde43f | ||
|
|
0afd27ab45 | ||
|
|
d498423ec2 | ||
|
|
66ce84973c | ||
|
|
55e6a0f312 | ||
|
|
81e83fdf18 | ||
|
|
f9b79d24f8 | ||
|
|
0599a47bfe | ||
|
|
817c55d318 | ||
|
|
14146c8c0c | ||
|
|
18adac20d4 | ||
|
|
eb4bff89da | ||
|
|
c66dc777f3 | ||
|
|
9003462457 | ||
|
|
e124f0a220 | ||
|
|
9806b9c651 | ||
|
|
2a20867d00 | ||
|
|
48ff661150 | ||
|
|
19d59bef51 | ||
|
|
bdcb2fe810 | ||
|
|
25a157e661 |
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "**** Checking if Wails passes unit tests ****"
|
||||
if ! go test ./...
|
||||
if ! go test ./lib/... ./runtime/... ./cmd/...
|
||||
then
|
||||
echo ""
|
||||
echo "ERROR: Unit tests failed!"
|
||||
|
||||
@@ -2,19 +2,19 @@ package build
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/leaanthony/clir"
|
||||
"github.com/leaanthony/slicer"
|
||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/commands/build"
|
||||
)
|
||||
|
||||
// AddBuildSubcommand adds the `build` command for the Wails application
|
||||
func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
||||
func AddBuildSubcommand(app *clir.Cli) {
|
||||
|
||||
outputType := "desktop"
|
||||
|
||||
@@ -32,7 +32,7 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
||||
|
||||
// Setup pack flag
|
||||
pack := false
|
||||
command.BoolFlag("package", "Create a platform specific package", &pack)
|
||||
command.BoolFlag("pack", "Create a platform specific package", &pack)
|
||||
|
||||
compilerCommand := "go"
|
||||
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
|
||||
@@ -43,7 +43,7 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
||||
|
||||
// Quiet Build
|
||||
quiet := false
|
||||
command.BoolFlag("q", "Suppress output to console", &quiet)
|
||||
command.BoolFlag("q", "Supress output to console", &quiet)
|
||||
|
||||
// ldflags to pass to `go`
|
||||
ldflags := ""
|
||||
@@ -53,38 +53,28 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
||||
logFile := ""
|
||||
command.StringFlag("l", "Log to file", &logFile)
|
||||
|
||||
// Retain assets
|
||||
keepAssets := false
|
||||
command.BoolFlag("k", "Keep generated assets", &keepAssets)
|
||||
|
||||
appleIdentity := ""
|
||||
if runtime.GOOS == "darwin" {
|
||||
command.StringFlag("sign", "Signs your app with the given identity.", &appleIdentity)
|
||||
}
|
||||
|
||||
command.Action(func() error {
|
||||
|
||||
// Create logger
|
||||
logger := clilogger.New(w)
|
||||
logger.Mute(quiet)
|
||||
logger := logger.New()
|
||||
|
||||
if !quiet {
|
||||
logger.AddOutput(os.Stdout)
|
||||
}
|
||||
|
||||
// Validate output type
|
||||
if !validTargetTypes.Contains(outputType) {
|
||||
return fmt.Errorf("output type '%s' is not valid", outputType)
|
||||
logger.Fatal(fmt.Sprintf("Output type '%s' is not valid.", outputType))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if !quiet {
|
||||
app.PrintBanner()
|
||||
}
|
||||
|
||||
// Ensure package is used with apple identity
|
||||
if appleIdentity != "" && pack == false {
|
||||
return fmt.Errorf("must use `-package` flag when using `-sign`")
|
||||
}
|
||||
|
||||
task := fmt.Sprintf("Building %s Application", strings.Title(outputType))
|
||||
logger.Println(task)
|
||||
logger.Println(strings.Repeat("-", len(task)))
|
||||
logger.Writeln(task)
|
||||
logger.Writeln(strings.Repeat("-", len(task)))
|
||||
|
||||
// Setup mode
|
||||
mode := build.Debug
|
||||
@@ -94,15 +84,13 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
||||
|
||||
// Create BuildOptions
|
||||
buildOptions := &build.Options{
|
||||
Logger: logger,
|
||||
OutputType: outputType,
|
||||
Mode: mode,
|
||||
Pack: pack,
|
||||
Platform: platform,
|
||||
LDFlags: ldflags,
|
||||
Compiler: compilerCommand,
|
||||
KeepAssets: keepAssets,
|
||||
AppleIdentity: appleIdentity,
|
||||
Logger: logger,
|
||||
OutputType: outputType,
|
||||
Mode: mode,
|
||||
Pack: pack,
|
||||
Platform: platform,
|
||||
LDFlags: ldflags,
|
||||
Compiler: compilerCommand,
|
||||
}
|
||||
|
||||
return doBuild(buildOptions)
|
||||
@@ -119,12 +107,11 @@ func doBuild(buildOptions *build.Options) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Output stats
|
||||
elapsed := time.Since(start)
|
||||
buildOptions.Logger.Println("")
|
||||
buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.", outputFilename, elapsed.Round(time.Millisecond).String()))
|
||||
buildOptions.Logger.Println("")
|
||||
buildOptions.Logger.Writeln("")
|
||||
buildOptions.Logger.Writeln(fmt.Sprintf("Built '%s' in %s.", outputFilename, elapsed.Round(time.Millisecond).String()))
|
||||
buildOptions.Logger.Writeln("")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,45 +2,35 @@ package dev
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/colour"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/leaanthony/clir"
|
||||
"github.com/leaanthony/slicer"
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/process"
|
||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
||||
"github.com/wailsapp/wails/v2/pkg/commands/build"
|
||||
)
|
||||
|
||||
func LogGreen(message string, args ...interface{}) {
|
||||
text := fmt.Sprintf(message, args...)
|
||||
println(colour.Green(text))
|
||||
}
|
||||
|
||||
func LogRed(message string, args ...interface{}) {
|
||||
text := fmt.Sprintf(message, args...)
|
||||
println(colour.Red(text))
|
||||
}
|
||||
|
||||
func LogDarkYellow(message string, args ...interface{}) {
|
||||
text := fmt.Sprintf(message, args...)
|
||||
println(colour.DarkYellow(text))
|
||||
}
|
||||
|
||||
// AddSubcommand adds the `dev` command for the Wails application
|
||||
func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
func AddSubcommand(app *clir.Cli) error {
|
||||
|
||||
command := app.NewSubCommand("dev", "Development mode")
|
||||
|
||||
outputType := "desktop"
|
||||
|
||||
validTargetTypes := slicer.String([]string{"desktop", "hybrid", "server"})
|
||||
|
||||
// Setup target type flag
|
||||
description := "Type of application to develop. Valid types: " + validTargetTypes.Join(",")
|
||||
command.StringFlag("t", description, &outputType)
|
||||
|
||||
// Passthrough ldflags
|
||||
ldflags := ""
|
||||
command.StringFlag("ldflags", "optional ldflags", &ldflags)
|
||||
@@ -51,19 +41,18 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
|
||||
// extensions to trigger rebuilds
|
||||
extensions := "go"
|
||||
command.StringFlag("e", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions)
|
||||
|
||||
// extensions to trigger rebuilds
|
||||
showWarnings := false
|
||||
command.BoolFlag("w", "Show warnings", &showWarnings)
|
||||
|
||||
loglevel := ""
|
||||
command.StringFlag("loglevel", "Loglevel to use - Trace, Debug, Info, Warning, Error", &loglevel)
|
||||
command.StringFlag("m", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions)
|
||||
|
||||
command.Action(func() error {
|
||||
|
||||
// Validate inputs
|
||||
if !validTargetTypes.Contains(outputType) {
|
||||
return fmt.Errorf("output type '%s' is not valid", outputType)
|
||||
}
|
||||
|
||||
// Create logger
|
||||
logger := clilogger.New(w)
|
||||
logger := logger.New()
|
||||
logger.AddOutput(os.Stdout)
|
||||
app.PrintBanner()
|
||||
|
||||
// TODO: Check you are in a project directory
|
||||
@@ -75,6 +64,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
defer watcher.Close()
|
||||
|
||||
var debugBinaryProcess *process.Process = nil
|
||||
var buildFrontend bool = true
|
||||
var extensionsThatTriggerARebuild = strings.Split(extensions, ",")
|
||||
|
||||
// Setup signal handler
|
||||
@@ -84,27 +74,19 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
debounceQuit := make(chan bool, 1)
|
||||
|
||||
// Do initial build
|
||||
logger.Println("Building application for development...")
|
||||
newProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess, loglevel)
|
||||
if newProcess != nil {
|
||||
debugBinaryProcess = newProcess
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Info("Building application for development...")
|
||||
debugBinaryProcess = restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess)
|
||||
|
||||
go debounce(100*time.Millisecond, watcher.Events, debounceQuit, func(event fsnotify.Event) {
|
||||
// logger.Println("event: %+v", event)
|
||||
// logger.Info("event: %+v", event)
|
||||
|
||||
// Check for new directories
|
||||
if event.Op&fsnotify.Create == fsnotify.Create {
|
||||
// If this is a folder, add it to our watch list
|
||||
if fs.DirExists(event.Name) {
|
||||
if !strings.Contains(event.Name, "node_modules") {
|
||||
err := watcher.Add(event.Name)
|
||||
if err != nil {
|
||||
logger.Fatal("%s", err.Error())
|
||||
}
|
||||
LogGreen("[New Directory] Watching new directory: %s", event.Name)
|
||||
watcher.Add(event.Name)
|
||||
logger.Info("Watching directory: %s", event.Name)
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -113,33 +95,38 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
// Check for file writes
|
||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||
|
||||
// logger.Info("modified file: %s", event.Name)
|
||||
var rebuild bool = false
|
||||
|
||||
// Iterate all file patterns
|
||||
for _, pattern := range extensionsThatTriggerARebuild {
|
||||
if strings.HasSuffix(event.Name, pattern) {
|
||||
rebuild = true
|
||||
rebuild = strings.HasSuffix(event.Name, pattern)
|
||||
if err != nil {
|
||||
logger.Fatal(err.Error())
|
||||
}
|
||||
if rebuild {
|
||||
// Only build frontend when the file isn't a Go file
|
||||
buildFrontend = !strings.HasSuffix(event.Name, "go")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !rebuild {
|
||||
if showWarnings {
|
||||
LogDarkYellow("[File change] %s did not match extension list (%s)", event.Name, extensions)
|
||||
}
|
||||
logger.Info("Filename change: %s did not match extension list %s", event.Name, extensions)
|
||||
return
|
||||
}
|
||||
|
||||
LogGreen("[Attempting rebuild] %s updated", event.Name)
|
||||
if buildFrontend {
|
||||
logger.Info("Full rebuild triggered: %s updated", event.Name)
|
||||
} else {
|
||||
logger.Info("Partial build triggered: %s updated", event.Name)
|
||||
}
|
||||
|
||||
// Do a rebuild
|
||||
|
||||
// Try and build the app
|
||||
newBinaryProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess, loglevel)
|
||||
if err != nil {
|
||||
fmt.Printf("Error during build: %s", err.Error())
|
||||
return
|
||||
}
|
||||
newBinaryProcess := restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess)
|
||||
|
||||
// If we have a new process, save it
|
||||
if newBinaryProcess != nil {
|
||||
debugBinaryProcess = newBinaryProcess
|
||||
@@ -149,28 +136,23 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
})
|
||||
|
||||
// Get project dir
|
||||
projectDir, err := os.Getwd()
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get all subdirectories
|
||||
dirs, err := fs.GetSubdirectories(projectDir)
|
||||
dirs, err := fs.GetSubdirectories(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
LogGreen("Watching (sub)/directory: %s", projectDir)
|
||||
|
||||
// Setup a watcher for non-node_modules directories
|
||||
dirs.Each(func(dir string) {
|
||||
if strings.Contains(dir, "node_modules") {
|
||||
return
|
||||
}
|
||||
// Ignore build directory
|
||||
if strings.HasPrefix(dir, filepath.Join(projectDir, "build")) {
|
||||
return
|
||||
}
|
||||
logger.Info("Watching directory: %s", dir)
|
||||
err = watcher.Add(dir)
|
||||
if err != nil {
|
||||
logger.Fatal(err.Error())
|
||||
@@ -182,7 +164,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
for quit == false {
|
||||
select {
|
||||
case <-quitChannel:
|
||||
LogGreen("\nCaught quit")
|
||||
println()
|
||||
// Notify debouncer to quit
|
||||
debounceQuit <- true
|
||||
quit = true
|
||||
@@ -191,13 +173,10 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
|
||||
// Kill the current program if running
|
||||
if debugBinaryProcess != nil {
|
||||
err := debugBinaryProcess.Kill()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
debugBinaryProcess.Kill()
|
||||
}
|
||||
|
||||
LogGreen("Development mode exited")
|
||||
logger.Info("Development mode exited")
|
||||
|
||||
return nil
|
||||
})
|
||||
@@ -224,15 +203,15 @@ exit:
|
||||
}
|
||||
}
|
||||
|
||||
func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, debugBinaryProcess *process.Process, loglevel string) (*process.Process, error) {
|
||||
func restartApp(logger *logger.Logger, outputType string, ldflags string, compilerCommand string, buildFrontend bool, debugBinaryProcess *process.Process) *process.Process {
|
||||
|
||||
appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand)
|
||||
appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand, buildFrontend)
|
||||
println()
|
||||
if err != nil {
|
||||
LogRed("Build error - continuing to run current version")
|
||||
LogDarkYellow(err.Error())
|
||||
return nil, nil
|
||||
logger.Error("Build Failed: %s", err.Error())
|
||||
return nil
|
||||
}
|
||||
logger.Info("Build new binary: %s", appBinary)
|
||||
|
||||
// Kill existing binary if need be
|
||||
if debugBinaryProcess != nil {
|
||||
@@ -248,24 +227,21 @@ func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string,
|
||||
// TODO: Generate `backend.js`
|
||||
|
||||
// Start up new binary
|
||||
newProcess := process.NewProcess(logger, appBinary, "-loglevel", loglevel)
|
||||
newProcess := process.NewProcess(logger, appBinary)
|
||||
err = newProcess.Start()
|
||||
if err != nil {
|
||||
// Remove binary
|
||||
deleteError := fs.DeleteFile(appBinary)
|
||||
if deleteError != nil {
|
||||
logger.Fatal("Unable to delete app binary: " + appBinary)
|
||||
}
|
||||
fs.DeleteFile(appBinary)
|
||||
logger.Fatal("Unable to start application: %s", err.Error())
|
||||
}
|
||||
|
||||
return newProcess, nil
|
||||
return newProcess
|
||||
}
|
||||
|
||||
func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string) (string, error) {
|
||||
func buildApp(logger *logger.Logger, outputType string, ldflags string, compilerCommand string, buildFrontend bool) (string, error) {
|
||||
|
||||
// Create random output file
|
||||
outputFile := fmt.Sprintf("dev-%d", time.Now().Unix())
|
||||
outputFile := fmt.Sprintf("debug-%d", time.Now().Unix())
|
||||
|
||||
// Create BuildOptions
|
||||
buildOptions := &build.Options{
|
||||
@@ -277,7 +253,7 @@ func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, co
|
||||
LDFlags: ldflags,
|
||||
Compiler: compilerCommand,
|
||||
OutputFile: outputFile,
|
||||
IgnoreFrontend: true,
|
||||
IgnoreFrontend: !buildFrontend,
|
||||
}
|
||||
|
||||
return build.Build(buildOptions)
|
||||
|
||||
@@ -2,37 +2,37 @@ package doctor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/leaanthony/clir"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/system"
|
||||
"github.com/wailsapp/wails/v2/internal/system/packagemanager"
|
||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
||||
)
|
||||
|
||||
// AddSubcommand adds the `doctor` command for the Wails application
|
||||
func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
func AddSubcommand(app *clir.Cli) error {
|
||||
|
||||
command := app.NewSubCommand("doctor", "Diagnose your environment")
|
||||
|
||||
command.Action(func() error {
|
||||
|
||||
logger := clilogger.New(w)
|
||||
// Create logger
|
||||
logger := logger.New()
|
||||
logger.AddOutput(os.Stdout)
|
||||
|
||||
app.PrintBanner()
|
||||
logger.Print("Scanning system - please wait...")
|
||||
print("Scanning system - please wait...")
|
||||
|
||||
// Get system info
|
||||
info, err := system.GetInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Println("Done.")
|
||||
println("Done.")
|
||||
|
||||
// Start a new tabwriter
|
||||
w := new(tabwriter.Writer)
|
||||
@@ -112,22 +112,22 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
fmt.Fprintf(w, "\n")
|
||||
fmt.Fprintf(w, "* - Optional Dependency\n")
|
||||
w.Flush()
|
||||
logger.Println("")
|
||||
logger.Println("Diagnosis")
|
||||
logger.Println("---------\n")
|
||||
println()
|
||||
println("Diagnosis")
|
||||
println("---------\n")
|
||||
|
||||
// Generate an appropriate diagnosis
|
||||
|
||||
if len(dependenciesMissing) == 0 && dependenciesAvailableRequired == 0 {
|
||||
logger.Println("Your system is ready for Wails development!")
|
||||
println("Your system is ready for Wails development!")
|
||||
}
|
||||
|
||||
if dependenciesAvailableRequired != 0 {
|
||||
log.Println("Install required packages using: " + info.Dependencies.InstallAllRequiredCommand())
|
||||
println("Install required packages using: " + info.Dependencies.InstallAllRequiredCommand())
|
||||
}
|
||||
|
||||
if dependenciesAvailableOptional != 0 {
|
||||
log.Println("Install optional packages using: " + info.Dependencies.InstallAllOptionalCommand())
|
||||
println("Install optional packages using: " + info.Dependencies.InstallAllOptionalCommand())
|
||||
}
|
||||
|
||||
if len(externalPackages) > 0 {
|
||||
@@ -135,18 +135,18 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
if p.Optional {
|
||||
print("[Optional] ")
|
||||
}
|
||||
log.Println("Install " + p.Name + ": " + p.InstallCommand)
|
||||
println("Install " + p.Name + ": " + p.InstallCommand)
|
||||
}
|
||||
}
|
||||
|
||||
if len(dependenciesMissing) != 0 {
|
||||
// TODO: Check if apps are available locally and if so, adjust the diagnosis
|
||||
log.Println("Fatal:")
|
||||
log.Println("Required dependencies missing: " + strings.Join(dependenciesMissing, " "))
|
||||
log.Println("Please read this article on how to resolve this: https://wails.app/guides/resolving-missing-packages")
|
||||
println("Fatal:")
|
||||
println("Required dependencies missing: " + strings.Join(dependenciesMissing, " "))
|
||||
println("Please read this article on how to resolve this: https://wails.app/guides/resolving-missing-packages")
|
||||
}
|
||||
|
||||
log.Println("")
|
||||
println()
|
||||
return nil
|
||||
})
|
||||
|
||||
|
||||
@@ -2,19 +2,17 @@ package initialise
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/leaanthony/clir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/templates"
|
||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
||||
"github.com/wailsapp/wails/v2/pkg/git"
|
||||
)
|
||||
|
||||
// AddSubcommand adds the `init` command for the Wails application
|
||||
func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
func AddSubcommand(app *clir.Cli) error {
|
||||
|
||||
// Load the template shortnames
|
||||
validShortNames, err := templates.TemplateShortNames()
|
||||
@@ -34,24 +32,13 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
command.StringFlag("n", "Name of project", &projectName)
|
||||
|
||||
// Setup project directory
|
||||
projectDirectory := ""
|
||||
projectDirectory := "."
|
||||
command.StringFlag("d", "Project directory", &projectDirectory)
|
||||
|
||||
// Quiet Init
|
||||
quiet := false
|
||||
command.BoolFlag("q", "Supress output to console", &quiet)
|
||||
|
||||
initGit := false
|
||||
gitInstalled := git.IsInstalled()
|
||||
if gitInstalled {
|
||||
// Git Init
|
||||
command.BoolFlag("g", "Initialise git repository", &initGit)
|
||||
}
|
||||
|
||||
// VSCode project files
|
||||
vscode := false
|
||||
command.BoolFlag("vscode", "Generate VSCode project files", &vscode)
|
||||
|
||||
// List templates
|
||||
list := false
|
||||
command.BoolFlag("l", "List templates", &list)
|
||||
@@ -59,29 +46,32 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
command.Action(func() error {
|
||||
|
||||
// Create logger
|
||||
logger := clilogger.New(w)
|
||||
logger.Mute(quiet)
|
||||
logger := logger.New()
|
||||
|
||||
if !quiet {
|
||||
logger.AddOutput(os.Stdout)
|
||||
}
|
||||
|
||||
// Are we listing templates?
|
||||
if list {
|
||||
app.PrintBanner()
|
||||
err := templates.OutputList(logger)
|
||||
logger.Println("")
|
||||
logger.Writeln("")
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate output type
|
||||
if !validShortNames.Contains(templateName) {
|
||||
logger.Print(fmt.Sprintf("[ERROR] Template '%s' is not valid", templateName))
|
||||
logger.Println("")
|
||||
logger.Write(fmt.Sprintf("ERROR: Template '%s' is not valid", templateName))
|
||||
logger.Writeln("")
|
||||
command.PrintHelp()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate name
|
||||
if len(projectName) == 0 {
|
||||
logger.Println("ERROR: Project name required")
|
||||
logger.Println("")
|
||||
logger.Writeln("ERROR: Project name required")
|
||||
logger.Writeln("")
|
||||
command.PrintHelp()
|
||||
return nil
|
||||
}
|
||||
@@ -91,23 +81,15 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
}
|
||||
|
||||
task := fmt.Sprintf("Initialising Project %s", strings.Title(projectName))
|
||||
logger.Println(task)
|
||||
logger.Println(strings.Repeat("-", len(task)))
|
||||
logger.Writeln(task)
|
||||
logger.Writeln(strings.Repeat("-", len(task)))
|
||||
|
||||
// Create Template Options
|
||||
options := &templates.Options{
|
||||
ProjectName: projectName,
|
||||
TargetDir: projectDirectory,
|
||||
TemplateName: templateName,
|
||||
Logger: logger,
|
||||
GenerateVSCode: vscode,
|
||||
InitGit: initGit,
|
||||
}
|
||||
|
||||
// Try to discover author details from git config
|
||||
err := findAuthorDetails(options)
|
||||
if err != nil {
|
||||
return err
|
||||
ProjectName: projectName,
|
||||
TargetDir: projectDirectory,
|
||||
TemplateName: templateName,
|
||||
Logger: logger,
|
||||
}
|
||||
|
||||
return initProject(options)
|
||||
@@ -128,55 +110,11 @@ func initProject(options *templates.Options) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if options.InitGit {
|
||||
err = initGit(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Output stats
|
||||
elapsed := time.Since(start)
|
||||
options.Logger.Println("")
|
||||
options.Logger.Println("Project Name: " + options.ProjectName)
|
||||
options.Logger.Println("Project Directory: " + options.TargetDir)
|
||||
options.Logger.Println("Project Template: " + options.TemplateName)
|
||||
if options.GenerateVSCode {
|
||||
options.Logger.Println("VSCode config files generated.")
|
||||
}
|
||||
if options.InitGit {
|
||||
options.Logger.Println("Git repository initialised.")
|
||||
}
|
||||
options.Logger.Println("")
|
||||
options.Logger.Println(fmt.Sprintf("Initialised project '%s' in %s.", options.ProjectName, elapsed.Round(time.Millisecond).String()))
|
||||
options.Logger.Println("")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initGit(options *templates.Options) error {
|
||||
err := git.InitRepo(options.TargetDir)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Unable to initialise git repository:")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func findAuthorDetails(options *templates.Options) error {
|
||||
if git.IsInstalled() {
|
||||
name, err := git.Name()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options.AuthorName = strings.TrimSpace(name)
|
||||
|
||||
email, err := git.Email()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options.AuthorEmail = strings.TrimSpace(email)
|
||||
}
|
||||
options.Logger.Writeln("")
|
||||
options.Logger.Writeln(fmt.Sprintf("Initialised project '%s' in %s.", options.ProjectName, elapsed.Round(time.Millisecond).String()))
|
||||
options.Logger.Writeln("")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/colour"
|
||||
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/update"
|
||||
|
||||
"github.com/leaanthony/clir"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/debug"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/dev"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/doctor"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/generate"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/initialise"
|
||||
)
|
||||
|
||||
@@ -22,44 +14,19 @@ func fatal(message string) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func banner(_ *clir.Cli) string {
|
||||
return fmt.Sprintf("%s %s - Go/HTML Application Framework", colour.Yellow("Wails"), colour.DarkRed(version))
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
var err error
|
||||
version := "v2.0.0-alpha"
|
||||
|
||||
app := clir.NewCli("Wails", "Go/HTML Application Framework", version)
|
||||
|
||||
app.SetBannerFunction(banner)
|
||||
|
||||
build.AddBuildSubcommand(app, os.Stdout)
|
||||
err = initialise.AddSubcommand(app, os.Stdout)
|
||||
build.AddBuildSubcommand(app)
|
||||
err = initialise.AddSubcommand(app)
|
||||
if err != nil {
|
||||
fatal(err.Error())
|
||||
}
|
||||
|
||||
err = debug.AddSubcommand(app, os.Stdout)
|
||||
if err != nil {
|
||||
fatal(err.Error())
|
||||
}
|
||||
err = doctor.AddSubcommand(app, os.Stdout)
|
||||
if err != nil {
|
||||
fatal(err.Error())
|
||||
}
|
||||
|
||||
err = dev.AddSubcommand(app, os.Stdout)
|
||||
if err != nil {
|
||||
fatal(err.Error())
|
||||
}
|
||||
|
||||
err = generate.AddSubcommand(app, os.Stdout)
|
||||
if err != nil {
|
||||
fatal(err.Error())
|
||||
}
|
||||
|
||||
err = update.AddSubcommand(app, os.Stdout, version)
|
||||
err = doctor.AddSubcommand(app)
|
||||
if err != nil {
|
||||
fatal(err.Error())
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v2.0.0-alpha.42"
|
||||
var version = "v2.0.0-alpha.54"
|
||||
|
||||
29
v2/go.mod
29
v2/go.mod
@@ -1,29 +1,14 @@
|
||||
module github.com/wailsapp/wails/v2
|
||||
|
||||
go 1.16
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/fatih/structtag v1.2.0
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/jackmordaunt/icns v1.0.0
|
||||
github.com/leaanthony/clir v1.0.4
|
||||
github.com/leaanthony/gosod v0.0.4
|
||||
github.com/leaanthony/slicer v1.5.0
|
||||
github.com/matryer/is v1.4.0
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/leaanthony/clir v1.0.2
|
||||
github.com/leaanthony/gosod v0.0.3
|
||||
github.com/leaanthony/slicer v1.4.1
|
||||
github.com/matryer/is v1.3.0
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/tdewolff/minify v2.3.6+incompatible
|
||||
github.com/tdewolff/parse v2.3.4+incompatible // indirect
|
||||
github.com/tdewolff/test v1.0.6 // indirect
|
||||
github.com/wzshiming/ctc v1.2.3
|
||||
github.com/xyproto/xpm v1.2.1
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
|
||||
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82
|
||||
nhooyr.io/websocket v1.8.6
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
|
||||
nhooyr.io/websocket v1.7.4
|
||||
)
|
||||
|
||||
132
v2/go.sum
132
v2/go.sum
@@ -1,125 +1,57 @@
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
||||
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmAMQ=
|
||||
github.com/jackmordaunt/icns v1.0.0/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo=
|
||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU=
|
||||
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
|
||||
github.com/leaanthony/gosod v0.0.4 h1:v4hepo4IyL8E8c9qzDsvYcA0KGh7Npf8As74K5ibQpI=
|
||||
github.com/leaanthony/gosod v0.0.4/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
|
||||
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
|
||||
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/leaanthony/clir v1.0.2 h1:Ham9Ep7NH95il0x/ci5s2ExJa/K4B2dE8L2uaSj2cxo=
|
||||
github.com/leaanthony/clir v1.0.2/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
|
||||
github.com/leaanthony/gosod v0.0.3 h1:VlPilye0zoH4I0WihShyoiTHyiAN1v6dcBW4mMvGJao=
|
||||
github.com/leaanthony/gosod v0.0.3/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
|
||||
github.com/leaanthony/slicer v1.4.1 h1:X/SmRIDhkUAolP79mSTO0jTcVX1k504PJBqvV6TwP0w=
|
||||
github.com/leaanthony/slicer v1.4.1/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
|
||||
github.com/matryer/is v1.3.0 h1:9qiso3jaJrOe6qBRJRBt2Ldht05qDiFP9le0JOIhRSI=
|
||||
github.com/matryer/is v1.3.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo=
|
||||
github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs=
|
||||
github.com/tdewolff/parse v2.3.4+incompatible h1:x05/cnGwIMf4ceLuDMBOdQ1qGniMoxpP46ghf0Qzh38=
|
||||
github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ=
|
||||
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
|
||||
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/wzshiming/ctc v1.2.3 h1:q+hW3IQNsjIlOFBTGZZZeIXTElFM4grF4spW/errh/c=
|
||||
github.com/wzshiming/ctc v1.2.3/go.mod h1:2tVAtIY7SUyraSk0JxvwmONNPFL4ARavPuEsg5+KA28=
|
||||
github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae h1:tpXvBXC3hpQBDCc9OojJZCQMVRAbT3TTdUMP8WguXkY=
|
||||
github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae/go.mod h1:VTAq37rkGeV+WOybvZwjXiJOicICdpLCN8ifpISjK20=
|
||||
github.com/xyproto/xpm v1.2.1 h1:trdvGjjWBsOOKzBBUPT6JvaIQM3acJEEYfbxN7M96wg=
|
||||
github.com/xyproto/xpm v1.2.1/go.mod h1:cMnesLsD0PBXLgjDfTDEaKr8XyTFsnP1QycSqRw7BiY=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82 h1:shxDsb9Dz27xzk3A0DxP0JuJnZMpKrdg8+E14eiUAX4=
|
||||
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
|
||||
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
nhooyr.io/websocket v1.7.4 h1:w/LGB2sZT0RV8lZYR7nfyaYz4PUbYZ5oF7NBon2M0NY=
|
||||
nhooyr.io/websocket v1.7.4/go.mod h1:PxYxCwFdFYQ0yRvtQz3s/dC+VEm7CSuC/4b9t8MQQxw=
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build !desktop,!hybrid,!server,!dev
|
||||
// +build !desktop,!hybrid,!server
|
||||
|
||||
package app
|
||||
|
||||
@@ -10,9 +10,7 @@ package app
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/internal/features"
|
||||
)
|
||||
|
||||
// App defines a Wails application structure
|
||||
@@ -22,15 +20,12 @@ type App struct {
|
||||
Height int
|
||||
Resizable bool
|
||||
|
||||
// Indicates if the app is running in debug mode
|
||||
debug bool
|
||||
|
||||
logger *logger.Logger
|
||||
Features *features.Features
|
||||
}
|
||||
|
||||
// CreateApp returns a null application
|
||||
func CreateApp(_ *options.App) (*App, error) {
|
||||
return &App{}, nil
|
||||
func CreateApp(options *Options) *App {
|
||||
return &App{}
|
||||
}
|
||||
|
||||
// Run the application
|
||||
@@ -39,3 +34,8 @@ func (a *App) Run() error {
|
||||
os.Exit(1)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bind the dummy interface
|
||||
func (a *App) Bind(dummy interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,152 +3,117 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"os"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/features"
|
||||
"github.com/wailsapp/wails/v2/internal/ffenestri"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/menumanager"
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
|
||||
"github.com/wailsapp/wails/v2/internal/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
"github.com/wailsapp/wails/v2/internal/signal"
|
||||
"github.com/wailsapp/wails/v2/internal/subsystem"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
// App defines a Wails application structure
|
||||
type App struct {
|
||||
appType string
|
||||
|
||||
window *ffenestri.Application
|
||||
servicebus *servicebus.ServiceBus
|
||||
logger *logger.Logger
|
||||
signal *signal.Manager
|
||||
options *options.App
|
||||
|
||||
// Subsystems
|
||||
log *subsystem.Log
|
||||
runtime *subsystem.Runtime
|
||||
event *subsystem.Event
|
||||
//binding *subsystem.Binding
|
||||
log *subsystem.Log
|
||||
runtime *subsystem.Runtime
|
||||
event *subsystem.Event
|
||||
binding *subsystem.Binding
|
||||
call *subsystem.Call
|
||||
menu *subsystem.Menu
|
||||
url *subsystem.URL
|
||||
dispatcher *messagedispatcher.Dispatcher
|
||||
|
||||
menuManager *menumanager.Manager
|
||||
|
||||
// Indicates if the app is in debug mode
|
||||
debug bool
|
||||
|
||||
// This is our binding DB
|
||||
bindings *binding.Bindings
|
||||
|
||||
// Application Stores
|
||||
loglevelStore *runtime.Store
|
||||
appconfigStore *runtime.Store
|
||||
|
||||
// Startup/Shutdown
|
||||
startupCallback func(*runtime.Runtime)
|
||||
shutdownCallback func()
|
||||
// Feature flags
|
||||
Features *features.Features
|
||||
}
|
||||
|
||||
// Create App
|
||||
func CreateApp(appoptions *options.App) (*App, error) {
|
||||
func CreateApp(options *Options) *App {
|
||||
|
||||
// Merge default options
|
||||
options.MergeDefaults(appoptions)
|
||||
options.mergeDefaults()
|
||||
|
||||
// Set up logger
|
||||
myLogger := logger.New(appoptions.Logger)
|
||||
myLogger.SetLogLevel(appoptions.LogLevel)
|
||||
myLogger := logger.New(os.Stdout)
|
||||
myLogger.SetLogLevel(logger.TRACE)
|
||||
|
||||
// Create the menu manager
|
||||
menuManager := menumanager.NewManager()
|
||||
window := ffenestri.NewApplicationWithConfig(&ffenestri.Config{
|
||||
Title: options.Title,
|
||||
Width: options.Width,
|
||||
Height: options.Height,
|
||||
MinWidth: options.MinWidth,
|
||||
MinHeight: options.MinHeight,
|
||||
MaxWidth: options.MaxWidth,
|
||||
MaxHeight: options.MaxHeight,
|
||||
Frameless: options.Frameless,
|
||||
|
||||
// Process the application menu
|
||||
menuManager.SetApplicationMenu(options.GetApplicationMenu(appoptions))
|
||||
// This should be controlled by the compile time flags...
|
||||
DevTools: true,
|
||||
|
||||
// Process context menus
|
||||
contextMenus := options.GetContextMenus(appoptions)
|
||||
for _, contextMenu := range contextMenus {
|
||||
menuManager.AddContextMenu(contextMenu)
|
||||
}
|
||||
|
||||
// Process tray menus
|
||||
trayMenus := options.GetTrayMenus(appoptions)
|
||||
for _, trayMenu := range trayMenus {
|
||||
menuManager.AddTrayMenu(trayMenu)
|
||||
}
|
||||
|
||||
window := ffenestri.NewApplicationWithConfig(appoptions, myLogger, menuManager)
|
||||
|
||||
// Create binding exemptions - Ugly hack. There must be a better way
|
||||
bindingExemptions := []interface{}{appoptions.Startup, appoptions.Shutdown}
|
||||
Resizable: !options.DisableResize,
|
||||
Fullscreen: options.Fullscreen,
|
||||
}, myLogger)
|
||||
|
||||
result := &App{
|
||||
appType: "desktop",
|
||||
window: window,
|
||||
servicebus: servicebus.New(myLogger),
|
||||
logger: myLogger,
|
||||
bindings: binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions),
|
||||
menuManager: menuManager,
|
||||
startupCallback: appoptions.Startup,
|
||||
shutdownCallback: appoptions.Shutdown,
|
||||
window: window,
|
||||
servicebus: servicebus.New(myLogger),
|
||||
logger: myLogger,
|
||||
bindings: binding.NewBindings(myLogger),
|
||||
Features: features.New(),
|
||||
}
|
||||
|
||||
result.options = appoptions
|
||||
|
||||
// Initialise the app
|
||||
err := result.Init()
|
||||
|
||||
return result, err
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
// Run the application
|
||||
func (a *App) Run() error {
|
||||
|
||||
var err error
|
||||
|
||||
// Setup a context
|
||||
var subsystemWaitGroup sync.WaitGroup
|
||||
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
|
||||
ctx, cancel := context.WithCancel(parentContext)
|
||||
// Setup signal handler
|
||||
signal, err := signal.NewManager(a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.signal = signal
|
||||
a.signal.Start()
|
||||
|
||||
// Start the service bus
|
||||
a.servicebus.Debug()
|
||||
err = a.servicebus.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.servicebus.Start()
|
||||
|
||||
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.runtime = runtimesubsystem
|
||||
err = a.runtime.Start()
|
||||
// Start the runtime
|
||||
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.runtime = runtime
|
||||
a.runtime.Start()
|
||||
|
||||
// Application Stores
|
||||
a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
|
||||
a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options)
|
||||
// Start the binding subsystem
|
||||
binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, a.runtime.GoRuntime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.binding = binding
|
||||
a.binding.Start()
|
||||
|
||||
// Start the logging subsystem
|
||||
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
|
||||
log, err := subsystem.NewLog(a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.log = log
|
||||
err = a.log.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.log.Start()
|
||||
|
||||
// create the dispatcher
|
||||
dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
|
||||
@@ -156,56 +121,23 @@ func (a *App) Run() error {
|
||||
return err
|
||||
}
|
||||
a.dispatcher = dispatcher
|
||||
err = dispatcher.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if a.options.Mac.URLHandlers != nil {
|
||||
// Start the url handler subsystem
|
||||
url, err := subsystem.NewURL(a.servicebus, a.logger, a.options.Mac.URLHandlers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.url = url
|
||||
err = a.url.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
dispatcher.Start()
|
||||
|
||||
// Start the eventing subsystem
|
||||
eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.event = eventsubsystem
|
||||
err = a.event.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Start the menu subsystem
|
||||
menusubsystem, err := subsystem.NewMenu(ctx, a.servicebus, a.logger, a.menuManager)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.menu = menusubsystem
|
||||
err = a.menu.Start()
|
||||
event, err := subsystem.NewEvent(a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.event = event
|
||||
a.event.Start()
|
||||
|
||||
// Start the call subsystem
|
||||
callSubsystem, err := subsystem.NewCall(ctx, a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.call = callSubsystem
|
||||
err = a.call.Start()
|
||||
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.call = call
|
||||
a.call.Start()
|
||||
|
||||
// Dump bindings as a debug
|
||||
bindingDump, err := a.bindings.ToJSON()
|
||||
@@ -213,42 +145,20 @@ func (a *App) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Setup signal handler
|
||||
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.signal = signalsubsystem
|
||||
a.signal.Start()
|
||||
|
||||
err = a.window.Run(dispatcher, bindingDump, a.debug)
|
||||
result := a.window.Run(dispatcher, bindingDump, a.Features)
|
||||
a.logger.Trace("Ffenestri.Run() exited")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.servicebus.Stop()
|
||||
|
||||
// Close down all the subsystems
|
||||
a.logger.Trace("Cancelling subsystems")
|
||||
cancel()
|
||||
subsystemWaitGroup.Wait()
|
||||
|
||||
a.logger.Trace("Cancelling dispatcher")
|
||||
dispatcher.Close()
|
||||
|
||||
// Close log
|
||||
a.logger.Trace("Stopping log")
|
||||
log.Close()
|
||||
|
||||
a.logger.Trace("Stopping Service bus")
|
||||
err = a.servicebus.Stop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Shutdown callback
|
||||
if a.shutdownCallback != nil {
|
||||
a.shutdownCallback()
|
||||
}
|
||||
|
||||
return nil
|
||||
return result
|
||||
}
|
||||
|
||||
// Bind a struct to the application by passing in
|
||||
// a pointer to it
|
||||
func (a *App) Bind(structPtr interface{}) {
|
||||
|
||||
// Add the struct to the bindings
|
||||
err := a.bindings.Add(structPtr)
|
||||
if err != nil {
|
||||
a.logger.Fatal("Error during binding: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/leaanthony/clir"
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/features"
|
||||
"github.com/wailsapp/wails/v2/internal/ffenestri"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
|
||||
@@ -42,7 +43,8 @@ type App struct {
|
||||
dispatcher *messagedispatcher.Dispatcher
|
||||
servicebus *servicebus.ServiceBus
|
||||
|
||||
debug bool
|
||||
// Feature flags
|
||||
Features *features.Features
|
||||
}
|
||||
|
||||
// Create App
|
||||
@@ -56,15 +58,17 @@ func CreateApp(options *Options) *App {
|
||||
myLogger.SetLogLevel(logger.INFO)
|
||||
|
||||
window := ffenestri.NewApplicationWithConfig(&ffenestri.Config{
|
||||
Title: options.Title,
|
||||
Width: options.Width,
|
||||
Height: options.Height,
|
||||
MinWidth: options.MinWidth,
|
||||
MinHeight: options.MinHeight,
|
||||
MaxWidth: options.MaxWidth,
|
||||
MaxHeight: options.MaxHeight,
|
||||
StartHidden: options.StartHidden,
|
||||
DevTools: options.DevTools,
|
||||
Title: options.Title,
|
||||
Width: options.Width,
|
||||
Height: options.Height,
|
||||
MinWidth: options.MinWidth,
|
||||
MinHeight: options.MinHeight,
|
||||
MaxWidth: options.MaxWidth,
|
||||
MaxHeight: options.MaxHeight,
|
||||
Frameless: options.Frameless,
|
||||
|
||||
// This should be controlled by the compile time flags...
|
||||
DevTools: true,
|
||||
|
||||
Resizable: !options.DisableResize,
|
||||
Fullscreen: options.Fullscreen,
|
||||
@@ -75,12 +79,9 @@ func CreateApp(options *Options) *App {
|
||||
webserver: webserver.NewWebServer(myLogger),
|
||||
servicebus: servicebus.New(myLogger),
|
||||
logger: myLogger,
|
||||
bindings: binding.NewBindings(myLogger, options.Bind),
|
||||
bindings: binding.NewBindings(myLogger),
|
||||
}
|
||||
|
||||
// Initialise the app
|
||||
app.Init()
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
@@ -184,7 +185,7 @@ func (a *App) Run() error {
|
||||
}
|
||||
}()
|
||||
|
||||
result := a.window.Run(dispatcher, bindingDump)
|
||||
result := a.window.Run(dispatcher, bindingDump, a.Features)
|
||||
a.servicebus.Stop()
|
||||
|
||||
return result
|
||||
@@ -192,3 +193,14 @@ func (a *App) Run() error {
|
||||
|
||||
return cli.Run()
|
||||
}
|
||||
|
||||
// Bind a struct to the application by passing in
|
||||
// a pointer to it
|
||||
func (a *App) Bind(structPtr interface{}) {
|
||||
|
||||
// Add the struct to the bindings
|
||||
err := a.bindings.Add(structPtr)
|
||||
if err != nil {
|
||||
a.logger.Fatal("Error during binding: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
34
v2/internal/app/options.go
Normal file
34
v2/internal/app/options.go
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,10 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
|
||||
"github.com/leaanthony/clir"
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
|
||||
"github.com/wailsapp/wails/v2/internal/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
"github.com/wailsapp/wails/v2/internal/subsystem"
|
||||
"github.com/wailsapp/wails/v2/internal/webserver"
|
||||
@@ -20,57 +17,36 @@ import (
|
||||
|
||||
// App defines a Wails application structure
|
||||
type App struct {
|
||||
appType string
|
||||
|
||||
binding *subsystem.Binding
|
||||
call *subsystem.Call
|
||||
event *subsystem.Event
|
||||
log *subsystem.Log
|
||||
runtime *subsystem.Runtime
|
||||
|
||||
options *options.App
|
||||
|
||||
bindings *binding.Bindings
|
||||
logger *logger.Logger
|
||||
dispatcher *messagedispatcher.Dispatcher
|
||||
servicebus *servicebus.ServiceBus
|
||||
webserver *webserver.WebServer
|
||||
|
||||
debug bool
|
||||
|
||||
// Application Stores
|
||||
loglevelStore *runtime.Store
|
||||
appconfigStore *runtime.Store
|
||||
|
||||
// Startup/Shutdown
|
||||
startupCallback func(*runtime.Runtime)
|
||||
shutdownCallback func()
|
||||
}
|
||||
|
||||
// Create App
|
||||
func CreateApp(appoptions *options.App) (*App, error) {
|
||||
func CreateApp(options *Options) *App {
|
||||
options.mergeDefaults()
|
||||
// We ignore the inputs (for now)
|
||||
|
||||
// Merge default options
|
||||
options.MergeDefaults(appoptions)
|
||||
|
||||
// Set up logger
|
||||
myLogger := logger.New(appoptions.Logger)
|
||||
myLogger.SetLogLevel(appoptions.LogLevel)
|
||||
// TODO: Allow logger output override on CLI
|
||||
myLogger := logger.New(os.Stdout)
|
||||
myLogger.SetLogLevel(logger.TRACE)
|
||||
|
||||
result := &App{
|
||||
appType: "server",
|
||||
bindings: binding.NewBindings(myLogger, options.Bind),
|
||||
logger: myLogger,
|
||||
servicebus: servicebus.New(myLogger),
|
||||
webserver: webserver.NewWebServer(myLogger),
|
||||
startupCallback: appoptions.Startup,
|
||||
shutdownCallback: appoptions.Shutdown,
|
||||
bindings: binding.NewBindings(myLogger),
|
||||
logger: myLogger,
|
||||
servicebus: servicebus.New(myLogger),
|
||||
webserver: webserver.NewWebServer(myLogger),
|
||||
}
|
||||
|
||||
// Initialise app
|
||||
result.Init()
|
||||
|
||||
return result, nil
|
||||
return result
|
||||
}
|
||||
|
||||
// Run the application
|
||||
@@ -107,21 +83,8 @@ func (a *App) Run() error {
|
||||
if debugMode {
|
||||
a.servicebus.Debug()
|
||||
}
|
||||
|
||||
// Start the runtime
|
||||
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.runtime = runtime
|
||||
a.runtime.Start()
|
||||
|
||||
// Application Stores
|
||||
a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
|
||||
a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options)
|
||||
|
||||
a.servicebus.Start()
|
||||
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
|
||||
log, err := subsystem.NewLog(a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -134,6 +97,14 @@ func (a *App) Run() error {
|
||||
a.dispatcher = dispatcher
|
||||
a.dispatcher.Start()
|
||||
|
||||
// Start the runtime
|
||||
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.runtime = runtime
|
||||
a.runtime.Start()
|
||||
|
||||
// Start the binding subsystem
|
||||
binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, runtime.GoRuntime())
|
||||
if err != nil {
|
||||
@@ -151,7 +122,7 @@ func (a *App) Run() error {
|
||||
a.event.Start()
|
||||
|
||||
// Start the call subsystem
|
||||
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
|
||||
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -171,3 +142,14 @@ func (a *App) Run() error {
|
||||
|
||||
return cli.Run()
|
||||
}
|
||||
|
||||
// Bind a struct to the application by passing in
|
||||
// a pointer to it
|
||||
func (a *App) Bind(structPtr interface{}) {
|
||||
|
||||
// Add the struct to the bindings
|
||||
err := a.bindings.Add(structPtr)
|
||||
if err != nil {
|
||||
a.logger.Fatal("Error during binding: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,55 +2,30 @@ package binding
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
)
|
||||
|
||||
type Bindings struct {
|
||||
db *DB
|
||||
logger logger.CustomLogger
|
||||
exemptions slicer.StringSlicer
|
||||
db *DB
|
||||
logger logger.CustomLogger
|
||||
}
|
||||
|
||||
// NewBindings returns a new Bindings object
|
||||
func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}) *Bindings {
|
||||
result := &Bindings{
|
||||
func NewBindings(logger *logger.Logger) *Bindings {
|
||||
return &Bindings{
|
||||
db: newDB(),
|
||||
logger: logger.CustomLogger("Bindings"),
|
||||
}
|
||||
|
||||
for _, exemption := range exemptions {
|
||||
if exemptions == nil {
|
||||
continue
|
||||
}
|
||||
name := runtime.FuncForPC(reflect.ValueOf(exemption).Pointer()).Name()
|
||||
// Yuk yuk yuk! Is there a better way?
|
||||
name = strings.TrimSuffix(name, "-fm")
|
||||
result.exemptions.Add(name)
|
||||
}
|
||||
|
||||
// Add the structs to bind
|
||||
for _, ptr := range structPointersToBind {
|
||||
err := result.Add(ptr)
|
||||
if err != nil {
|
||||
logger.Fatal("Error during binding: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Add the given struct methods to the Bindings
|
||||
func (b *Bindings) Add(structPtr interface{}) error {
|
||||
|
||||
methods, err := b.getMethods(structPtr)
|
||||
methods, err := getMethods(structPtr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot bind value to app: %s", err.Error())
|
||||
return fmt.Errorf("unable to Add() - %s", err.Error())
|
||||
}
|
||||
|
||||
for _, method := range methods {
|
||||
@@ -59,8 +34,29 @@ func (b *Bindings) Add(structPtr interface{}) error {
|
||||
structName := splitName[1]
|
||||
methodName := splitName[2]
|
||||
|
||||
// Is this WailsInit?
|
||||
if method.IsWailsInit() {
|
||||
err := b.db.AddWailsInit(method)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.logger.Trace("Registered WailsInit method: %s", method.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
// Is this WailsShutdown?
|
||||
if method.IsWailsShutdown() {
|
||||
err := b.db.AddWailsShutdown(method)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.logger.Trace("Registered WailsShutdown method: %s", method.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
// Add it as a regular method
|
||||
b.db.AddMethod(packageName, structName, methodName, method)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BoundMethod defines all the data related to a Go method that is
|
||||
@@ -16,6 +16,58 @@ type BoundMethod struct {
|
||||
Method reflect.Value `json:"-"`
|
||||
}
|
||||
|
||||
// IsWailsInit returns true if the method name is "WailsInit"
|
||||
func (b *BoundMethod) IsWailsInit() bool {
|
||||
return strings.HasSuffix(b.Name, "WailsInit")
|
||||
}
|
||||
|
||||
// IsWailsShutdown returns true if the method name is "WailsShutdown"
|
||||
func (b *BoundMethod) IsWailsShutdown() bool {
|
||||
return strings.HasSuffix(b.Name, "WailsShutdown")
|
||||
}
|
||||
|
||||
// VerifyWailsInit checks if the WailsInit signature is correct
|
||||
func (b *BoundMethod) VerifyWailsInit() error {
|
||||
// Must only have 1 input
|
||||
if b.InputCount() != 1 {
|
||||
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
|
||||
}
|
||||
|
||||
// Check input type
|
||||
if !b.Inputs[0].IsType("*goruntime.Runtime") {
|
||||
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
|
||||
}
|
||||
|
||||
// Must only have 1 output
|
||||
if b.OutputCount() != 1 {
|
||||
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
|
||||
}
|
||||
|
||||
// Check output type
|
||||
if !b.Outputs[0].IsError() {
|
||||
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
|
||||
}
|
||||
|
||||
// Input must be of type Runtime
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyWailsShutdown checks if the WailsShutdown signature is correct
|
||||
func (b *BoundMethod) VerifyWailsShutdown() error {
|
||||
// Must have no inputs
|
||||
if b.InputCount() != 0 {
|
||||
return fmt.Errorf("invalid method signature for WailsShutdown: expected `WailsShutdown()`")
|
||||
}
|
||||
|
||||
// Must have no outputs
|
||||
if b.OutputCount() != 0 {
|
||||
return fmt.Errorf("invalid method signature for WailsShutdown: expected `WailsShutdown()`")
|
||||
}
|
||||
|
||||
// Input must be of type Runtime
|
||||
return nil
|
||||
}
|
||||
|
||||
// InputCount returns the number of inputs this bound method has
|
||||
func (b *BoundMethod) InputCount() int {
|
||||
return len(b.Inputs)
|
||||
@@ -26,29 +78,6 @@ func (b *BoundMethod) OutputCount() int {
|
||||
return len(b.Outputs)
|
||||
}
|
||||
|
||||
// ParseArgs method converts the input json into the types expected by the method
|
||||
func (b *BoundMethod) ParseArgs(args []json.RawMessage) ([]interface{}, error) {
|
||||
|
||||
result := make([]interface{}, b.InputCount())
|
||||
if len(args) != b.InputCount() {
|
||||
return nil, fmt.Errorf("received %d arguments to method '%s', expected %d", len(args), b.Name, b.InputCount())
|
||||
}
|
||||
for index, arg := range args {
|
||||
typ := b.Inputs[index].reflectType
|
||||
inputValue := reflect.New(typ).Interface()
|
||||
err := json.Unmarshal(arg, inputValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if inputValue == nil {
|
||||
result[index] = reflect.Zero(typ).Interface()
|
||||
} else {
|
||||
result[index] = reflect.ValueOf(inputValue).Elem().Interface()
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Call will attempt to call this bound method with the given args
|
||||
func (b *BoundMethod) Call(args []interface{}) (interface{}, error) {
|
||||
// Check inputs
|
||||
@@ -65,8 +94,17 @@ func (b *BoundMethod) Call(args []interface{}) (interface{}, error) {
|
||||
|
||||
// Iterate over given arguments
|
||||
for index, arg := range args {
|
||||
|
||||
// Attempt to convert the argument to the type expected by the method
|
||||
value, err := convertArgToValue(arg, b.Inputs[index])
|
||||
|
||||
// If it fails, return a suitable error
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s (parameter %d): %s", b.Name, index+1, err.Error())
|
||||
}
|
||||
|
||||
// Save the converted argument
|
||||
callArgs[index] = reflect.ValueOf(arg)
|
||||
callArgs[index] = value
|
||||
}
|
||||
|
||||
// Do the call
|
||||
|
||||
@@ -15,6 +15,10 @@ type DB struct {
|
||||
// It used for performance gains at runtime
|
||||
methodMap map[string]*BoundMethod
|
||||
|
||||
// These are slices of methods registered using WailsInit and WailsShutdown
|
||||
wailsInitMethods []*BoundMethod
|
||||
wailsShutdownMethods []*BoundMethod
|
||||
|
||||
// Lock to ensure sync access to the data
|
||||
lock sync.RWMutex
|
||||
}
|
||||
@@ -90,6 +94,38 @@ func (d *DB) AddMethod(packageName string, structName string, methodName string,
|
||||
|
||||
}
|
||||
|
||||
// AddWailsInit checks the given method is a WailsInit method and if it
|
||||
// is, adds it to the list of WailsInit methods
|
||||
func (d *DB) AddWailsInit(method *BoundMethod) error {
|
||||
err := method.VerifyWailsInit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Lock the db whilst processing and unlock on return
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
|
||||
d.wailsInitMethods = append(d.wailsInitMethods, method)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddWailsShutdown checks the given method is a WailsInit method and if it
|
||||
// is, adds it to the list of WailsShutdown methods
|
||||
func (d *DB) AddWailsShutdown(method *BoundMethod) error {
|
||||
err := method.VerifyWailsShutdown()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Lock the db whilst processing and unlock on return
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
|
||||
d.wailsShutdownMethods = append(d.wailsShutdownMethods, method)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToJSON converts the method map to JSON
|
||||
func (d *DB) ToJSON() (string, error) {
|
||||
|
||||
@@ -102,3 +138,13 @@ func (d *DB) ToJSON() (string, error) {
|
||||
// Return zero copy string as this string will be read only
|
||||
return *(*string)(unsafe.Pointer(&bytes)), err
|
||||
}
|
||||
|
||||
// WailsInitMethods returns the list of registered WailsInit methods
|
||||
func (d *DB) WailsInitMethods() []*BoundMethod {
|
||||
return d.wailsInitMethods
|
||||
}
|
||||
|
||||
// WailsShutdownMethods returns the list of registered WailsInit methods
|
||||
func (d *DB) WailsShutdownMethods() []*BoundMethod {
|
||||
return d.wailsShutdownMethods
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package binding
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// isStructPtr returns true if the value given is a
|
||||
@@ -13,35 +12,14 @@ func isStructPtr(value interface{}) bool {
|
||||
reflect.ValueOf(value).Elem().Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
// isFunction returns true if the given value is a function
|
||||
func isFunction(value interface{}) bool {
|
||||
return reflect.ValueOf(value).Kind() == reflect.Func
|
||||
}
|
||||
|
||||
// isStructPtr returns true if the value given is a struct
|
||||
func isStruct(value interface{}) bool {
|
||||
return reflect.ValueOf(value).Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
|
||||
func getMethods(value interface{}) ([]*BoundMethod, error) {
|
||||
|
||||
// Create result placeholder
|
||||
var result []*BoundMethod
|
||||
|
||||
// Check type
|
||||
if !isStructPtr(value) {
|
||||
|
||||
if isStruct(value) {
|
||||
name := reflect.ValueOf(value).Type().Name()
|
||||
return nil, fmt.Errorf("%s is a struct, not a pointer to a struct", name)
|
||||
}
|
||||
|
||||
if isFunction(value) {
|
||||
name := runtime.FuncForPC(reflect.ValueOf(value).Pointer()).Name()
|
||||
return nil, fmt.Errorf("%s is a function, not a pointer to a struct. Wails v2 has deprecated the binding of functions. Please wrap your functions up in a struct and bind a pointer to that struct.", name)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("not a pointer to a struct.")
|
||||
return nil, fmt.Errorf("not a pointer to an interface")
|
||||
}
|
||||
|
||||
// Process Struct
|
||||
@@ -56,11 +34,6 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
|
||||
fullMethodName := baseName + "." + methodName
|
||||
method := structValue.MethodByName(methodName)
|
||||
|
||||
methodReflectName := runtime.FuncForPC(methodDef.Func.Pointer()).Name()
|
||||
if b.exemptions.Contains(methodReflectName) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Create new method
|
||||
boundMethod := &BoundMethod{
|
||||
Name: fullMethodName,
|
||||
@@ -83,8 +56,6 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
|
||||
boundMethod.Inputs = inputs
|
||||
|
||||
// Iterate outputs
|
||||
// TODO: Determine what to do about limiting return types
|
||||
// especially around errors.
|
||||
outputParamCount := methodType.NumOut()
|
||||
var outputs []*Parameter
|
||||
for outputIndex := 0; outputIndex < outputParamCount; outputIndex++ {
|
||||
@@ -100,3 +71,39 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// convertArgToValue
|
||||
func convertArgToValue(input interface{}, target *Parameter) (result reflect.Value, err error) {
|
||||
|
||||
// Catch type conversion panics thrown by convert
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// Modify error
|
||||
err = fmt.Errorf("%s", r.(string)[23:])
|
||||
}
|
||||
}()
|
||||
|
||||
// Do the conversion
|
||||
|
||||
// Handle nil values
|
||||
if input == nil {
|
||||
switch target.reflectType.Kind() {
|
||||
case reflect.Chan,
|
||||
reflect.Func,
|
||||
reflect.Interface,
|
||||
reflect.Map,
|
||||
reflect.Ptr,
|
||||
reflect.Slice:
|
||||
result = reflect.ValueOf(input).Convert(target.reflectType)
|
||||
default:
|
||||
return reflect.Zero(target.reflectType), fmt.Errorf("Unable to use null value")
|
||||
}
|
||||
} else {
|
||||
result = reflect.ValueOf(input).Convert(target.reflectType)
|
||||
}
|
||||
|
||||
// We don't like doing this but it's the only way to
|
||||
// handle recover() correctly
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
13
v2/internal/features/features.go
Normal file
13
v2/internal/features/features.go
Normal 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{},
|
||||
}
|
||||
}
|
||||
5
v2/internal/features/features_linux.go
Normal file
5
v2/internal/features/features_linux.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package features
|
||||
|
||||
// Linux holds linux specific feature flags
|
||||
type Linux struct {
|
||||
}
|
||||
23
v2/internal/ffenestri/features_linux.go
Normal file
23
v2/internal/ffenestri/features_linux.go
Normal 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
|
||||
|
||||
}
|
||||
@@ -5,11 +5,9 @@ import (
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/menumanager"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/features"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -17,9 +15,6 @@ import (
|
||||
#cgo linux CFLAGS: -DFFENESTRI_LINUX=1
|
||||
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
|
||||
|
||||
#cgo darwin CFLAGS: -DFFENESTRI_DARWIN=1
|
||||
#cgo darwin LDFLAGS: -framework WebKit -lobjc
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "ffenestri.h"
|
||||
|
||||
@@ -27,16 +22,42 @@ import (
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// DEBUG is the global Ffenestri debug flag.
|
||||
// TODO: move to compile time.
|
||||
var DEBUG bool = true
|
||||
|
||||
// Config defines how our application should be configured
|
||||
type Config struct {
|
||||
Title string
|
||||
Width int
|
||||
Height int
|
||||
MinWidth int
|
||||
MinHeight int
|
||||
MaxWidth int
|
||||
MaxHeight int
|
||||
DevTools bool
|
||||
Resizable bool
|
||||
Fullscreen bool
|
||||
Frameless bool
|
||||
}
|
||||
|
||||
var defaultConfig = &Config{
|
||||
Title: "My Wails App",
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
DevTools: true,
|
||||
Resizable: true,
|
||||
Fullscreen: false,
|
||||
Frameless: false,
|
||||
}
|
||||
|
||||
// Application is our main application object
|
||||
type Application struct {
|
||||
config *options.App
|
||||
config *Config
|
||||
memory []unsafe.Pointer
|
||||
|
||||
// This is the main app pointer
|
||||
app *C.struct_Application
|
||||
|
||||
// Manages menus
|
||||
menuManager *menumanager.Manager
|
||||
app unsafe.Pointer
|
||||
|
||||
// Logger
|
||||
logger logger.CustomLogger
|
||||
@@ -57,18 +78,17 @@ func init() {
|
||||
}
|
||||
|
||||
// NewApplicationWithConfig creates a new application based on the given config
|
||||
func NewApplicationWithConfig(config *options.App, logger *logger.Logger, menuManager *menumanager.Manager) *Application {
|
||||
func NewApplicationWithConfig(config *Config, logger *logger.Logger) *Application {
|
||||
return &Application{
|
||||
config: config,
|
||||
logger: logger.CustomLogger("Ffenestri"),
|
||||
menuManager: menuManager,
|
||||
config: config,
|
||||
logger: logger.CustomLogger("Ffenestri"),
|
||||
}
|
||||
}
|
||||
|
||||
// NewApplication creates a new Application with the default config
|
||||
func NewApplication(logger *logger.Logger) *Application {
|
||||
return &Application{
|
||||
config: options.Default,
|
||||
config: defaultConfig,
|
||||
logger: logger.CustomLogger("Ffenestri"),
|
||||
}
|
||||
}
|
||||
@@ -101,29 +121,18 @@ type DispatchClient interface {
|
||||
SendMessage(string)
|
||||
}
|
||||
|
||||
func intToColour(colour int) (C.int, C.int, C.int, C.int) {
|
||||
var alpha = C.int(colour & 0xFF)
|
||||
var blue = C.int((colour >> 8) & 0xFF)
|
||||
var green = C.int((colour >> 16) & 0xFF)
|
||||
var red = C.int((colour >> 24) & 0xFF)
|
||||
return red, green, blue, alpha
|
||||
}
|
||||
|
||||
// Run the application
|
||||
func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug bool) error {
|
||||
func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, features *features.Features) error {
|
||||
title := a.string2CString(a.config.Title)
|
||||
width := C.int(a.config.Width)
|
||||
height := C.int(a.config.Height)
|
||||
resizable := a.bool2Cint(!a.config.DisableResize)
|
||||
resizable := a.bool2Cint(a.config.Resizable)
|
||||
devtools := a.bool2Cint(a.config.DevTools)
|
||||
fullscreen := a.bool2Cint(a.config.Fullscreen)
|
||||
startHidden := a.bool2Cint(a.config.StartHidden)
|
||||
logLevel := C.int(a.config.LogLevel)
|
||||
hideWindowOnClose := a.bool2Cint(a.config.HideWindowOnClose)
|
||||
app := C.NewApplication(title, width, height, resizable, devtools, fullscreen, startHidden, logLevel, hideWindowOnClose)
|
||||
app := C.NewApplication(title, width, height, resizable, devtools, fullscreen)
|
||||
|
||||
// Save app reference
|
||||
a.app = (*C.struct_Application)(app)
|
||||
a.app = unsafe.Pointer(app)
|
||||
|
||||
// Set Min Window Size
|
||||
minWidth := C.int(a.config.MinWidth)
|
||||
@@ -136,16 +145,11 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug
|
||||
C.SetMaxWindowSize(a.app, maxWidth, maxHeight)
|
||||
|
||||
// Set debug if needed
|
||||
C.SetDebug(app, a.bool2Cint(debug))
|
||||
C.SetDebug(app, a.bool2Cint(DEBUG))
|
||||
|
||||
// TODO: Move frameless to Linux options
|
||||
// if a.config.Frameless {
|
||||
// C.DisableFrame(a.app)
|
||||
// }
|
||||
|
||||
if a.config.RGBA != 0 {
|
||||
r, g, b, alpha := intToColour(a.config.RGBA)
|
||||
C.SetColour(a.app, r, g, b, alpha)
|
||||
// Set Frameless
|
||||
if a.config.Frameless {
|
||||
C.DisableFrame(a.app)
|
||||
}
|
||||
|
||||
// Escape bindings so C doesn't freak out
|
||||
@@ -154,16 +158,13 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug
|
||||
// Set bindings
|
||||
C.SetBindings(app, a.string2CString(bindings))
|
||||
|
||||
// Process feature flags
|
||||
a.processFeatureFlags(features)
|
||||
|
||||
// save the dispatcher in a package variable so that the C callbacks
|
||||
// can access it
|
||||
dispatcher = incomingDispatcher.RegisterClient(newClient(a))
|
||||
|
||||
// Process platform settings
|
||||
err := a.processPlatformSettings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check we could initialise the application
|
||||
if app != nil {
|
||||
// Yes - Save memory reference and run app, cleaning up afterwards
|
||||
@@ -185,3 +186,11 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug
|
||||
func messageFromWindowCallback(data *C.char) {
|
||||
dispatcher.DispatchMessage(C.GoString(data))
|
||||
}
|
||||
|
||||
func (a *Application) processFeatureFlags(features *features.Features) {
|
||||
|
||||
// Process generic features
|
||||
|
||||
// Process OS Specific flags
|
||||
a.processOSFeatureFlags(features)
|
||||
}
|
||||
|
||||
@@ -2,44 +2,23 @@
|
||||
#define __FFENESTRI_H__
|
||||
|
||||
#include <stdio.h>
|
||||
struct Application;
|
||||
|
||||
extern struct Application *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose);
|
||||
extern void SetMinWindowSize(struct Application*, int minWidth, int minHeight);
|
||||
extern void SetMaxWindowSize(struct Application*, int maxWidth, int maxHeight);
|
||||
extern void Run(struct Application*, int argc, char **argv);
|
||||
extern void DestroyApplication(struct Application*);
|
||||
extern void SetDebug(struct Application*, int flag);
|
||||
extern void SetBindings(struct Application*, const char *bindings);
|
||||
extern void ExecJS(struct Application*, const char *script);
|
||||
extern void Hide(struct Application*);
|
||||
extern void Show(struct Application*);
|
||||
extern void Center(struct Application*);
|
||||
extern void Maximise(struct Application*);
|
||||
extern void Unmaximise(struct Application*);
|
||||
extern void ToggleMaximise(struct Application*);
|
||||
extern void Minimise(struct Application*);
|
||||
extern void Unminimise(struct Application*);
|
||||
extern void ToggleMinimise(struct Application*);
|
||||
extern void SetColour(struct Application*, int red, int green, int blue, int alpha);
|
||||
extern void SetSize(struct Application*, int width, int height);
|
||||
extern void SetPosition(struct Application*, int x, int y);
|
||||
extern void Quit(struct Application*);
|
||||
extern void SetTitle(struct Application*, const char *title);
|
||||
extern void Fullscreen(struct Application*);
|
||||
extern void UnFullscreen(struct Application*);
|
||||
extern void ToggleFullscreen(struct Application*);
|
||||
extern void DisableFrame(struct Application*);
|
||||
extern void OpenDialog(struct Application*, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories);
|
||||
extern void SaveDialog(struct Application*, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories);
|
||||
extern void MessageDialog(struct Application*, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton);
|
||||
extern void DarkModeEnabled(struct Application*, char *callbackID);
|
||||
extern void SetApplicationMenu(struct Application*, const char *);
|
||||
extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
|
||||
extern void SetTrayMenu(struct Application*, const char *menuTrayJSON);
|
||||
extern void DeleteTrayMenuByID(struct Application*, const char *id);
|
||||
extern void UpdateTrayMenuLabel(struct Application*, const char* JSON);
|
||||
extern void AddContextMenu(struct Application*, char *contextMenuJSON);
|
||||
extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);
|
||||
extern void *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen);
|
||||
extern void SetMinWindowSize(void *app, int minWidth, int minHeight);
|
||||
extern void SetMaxWindowSize(void *app, int maxWidth, int maxHeight);
|
||||
extern void Run(void *app, int argc, char **argv);
|
||||
extern void DestroyApplication(void *app);
|
||||
extern void SetDebug(void *app, int flag);
|
||||
extern void SetBindings(void *app, const char *bindings);
|
||||
extern void ExecJS(void *app, const char *script);
|
||||
extern void Quit(void *app);
|
||||
extern void SetTitle(void *app, const char *title);
|
||||
extern void Fullscreen(void *app);
|
||||
extern void UnFullscreen(void *app);
|
||||
extern int SetColour(void *app, const char *colourString);
|
||||
extern void DisableFrame(void *app);
|
||||
extern char *SaveFileDialogOnMainThread(void *appPointer, char *title);
|
||||
extern char *OpenFileDialogOnMainThread(void *appPointer, char *title);
|
||||
extern char *OpenDirectoryDialogOnMainThread(void *appPointer, char *title);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -13,8 +13,7 @@ import "C"
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
)
|
||||
@@ -67,148 +66,44 @@ func (c *Client) WindowUnFullscreen() {
|
||||
C.UnFullscreen(c.app.app)
|
||||
}
|
||||
|
||||
// WindowShow will show the window
|
||||
func (c *Client) WindowShow() {
|
||||
C.Show(c.app.app)
|
||||
}
|
||||
|
||||
// WindowHide will hide the window
|
||||
func (c *Client) WindowHide() {
|
||||
C.Hide(c.app.app)
|
||||
}
|
||||
|
||||
// WindowCenter will hide the window
|
||||
func (c *Client) WindowCenter() {
|
||||
C.Center(c.app.app)
|
||||
}
|
||||
|
||||
// WindowMaximise will maximise the window
|
||||
func (c *Client) WindowMaximise() {
|
||||
C.Maximise(c.app.app)
|
||||
}
|
||||
|
||||
// WindowMinimise will minimise the window
|
||||
func (c *Client) WindowMinimise() {
|
||||
C.Minimise(c.app.app)
|
||||
}
|
||||
|
||||
// WindowUnmaximise will unmaximise the window
|
||||
func (c *Client) WindowUnmaximise() {
|
||||
C.Unmaximise(c.app.app)
|
||||
}
|
||||
|
||||
// WindowUnminimise will unminimise the window
|
||||
func (c *Client) WindowUnminimise() {
|
||||
C.Unminimise(c.app.app)
|
||||
}
|
||||
|
||||
// WindowPosition will position the window to x,y on the
|
||||
// monitor that the window is mostly on
|
||||
func (c *Client) WindowPosition(x int, y int) {
|
||||
C.SetPosition(c.app.app, C.int(x), C.int(y))
|
||||
}
|
||||
|
||||
// WindowSize will resize the window to the given
|
||||
// width and height
|
||||
func (c *Client) WindowSize(width int, height int) {
|
||||
C.SetSize(c.app.app, C.int(width), C.int(height))
|
||||
}
|
||||
|
||||
func (c *Client) WindowSetMinSize(width int, height int) {
|
||||
C.SetMinWindowSize(c.app.app, C.int(width), C.int(height))
|
||||
}
|
||||
|
||||
func (c *Client) WindowSetMaxSize(width int, height int) {
|
||||
C.SetMaxWindowSize(c.app.app, C.int(width), C.int(height))
|
||||
}
|
||||
|
||||
// WindowSetColour sets the window colour
|
||||
func (c *Client) WindowSetColour(colour int) {
|
||||
r, g, b, a := intToColour(colour)
|
||||
C.SetColour(c.app.app, r, g, b, a)
|
||||
func (c *Client) WindowSetColour(colour string) bool {
|
||||
result := C.SetColour(c.app.app, c.app.string2CString(colour))
|
||||
return result == 1
|
||||
}
|
||||
|
||||
// OpenDialog will open a dialog with the given title and filter
|
||||
func (c *Client) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
|
||||
C.OpenDialog(c.app.app,
|
||||
c.app.string2CString(callbackID),
|
||||
c.app.string2CString(dialogOptions.Title),
|
||||
c.app.string2CString(dialogOptions.Filters),
|
||||
c.app.string2CString(dialogOptions.DefaultFilename),
|
||||
c.app.string2CString(dialogOptions.DefaultDirectory),
|
||||
c.app.bool2Cint(dialogOptions.AllowFiles),
|
||||
c.app.bool2Cint(dialogOptions.AllowDirectories),
|
||||
c.app.bool2Cint(dialogOptions.AllowMultiple),
|
||||
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
|
||||
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
|
||||
c.app.bool2Cint(dialogOptions.ResolvesAliases),
|
||||
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
|
||||
)
|
||||
}
|
||||
|
||||
// SaveDialog will open a dialog with the given title and filter
|
||||
func (c *Client) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
|
||||
C.SaveDialog(c.app.app,
|
||||
c.app.string2CString(callbackID),
|
||||
c.app.string2CString(dialogOptions.Title),
|
||||
c.app.string2CString(dialogOptions.Filters),
|
||||
c.app.string2CString(dialogOptions.DefaultFilename),
|
||||
c.app.string2CString(dialogOptions.DefaultDirectory),
|
||||
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
|
||||
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
|
||||
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
|
||||
)
|
||||
}
|
||||
|
||||
// MessageDialog will open a message dialog with the given options
|
||||
func (c *Client) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
|
||||
|
||||
// Sanity check button length
|
||||
if len(dialogOptions.Buttons) > 4 {
|
||||
c.app.logger.Error("Given %d message dialog buttons. Maximum is 4", len(dialogOptions.Buttons))
|
||||
return
|
||||
// OpenFileDialog will open a file dialog with the given title
|
||||
func (c *Client) OpenFileDialog(title string) string {
|
||||
cstring := C.OpenFileDialogOnMainThread(c.app.app, c.app.string2CString(title))
|
||||
var result string
|
||||
if cstring != nil {
|
||||
result = C.GoString(cstring)
|
||||
// Free the C string that was allocated by the dialog
|
||||
C.free(unsafe.Pointer(cstring))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Process buttons
|
||||
buttons := []string{"", "", "", ""}
|
||||
for i, button := range dialogOptions.Buttons {
|
||||
buttons[i] = button
|
||||
// SaveFileDialog will open a save file dialog with the given title
|
||||
func (c *Client) SaveFileDialog(title string) string {
|
||||
cstring := C.SaveFileDialogOnMainThread(c.app.app, c.app.string2CString(title))
|
||||
var result string
|
||||
if cstring != nil {
|
||||
result = C.GoString(cstring)
|
||||
// Free the C string that was allocated by the dialog
|
||||
C.free(unsafe.Pointer(cstring))
|
||||
}
|
||||
|
||||
C.MessageDialog(c.app.app,
|
||||
c.app.string2CString(callbackID),
|
||||
c.app.string2CString(string(dialogOptions.Type)),
|
||||
c.app.string2CString(dialogOptions.Title),
|
||||
c.app.string2CString(dialogOptions.Message),
|
||||
c.app.string2CString(dialogOptions.Icon),
|
||||
c.app.string2CString(buttons[0]),
|
||||
c.app.string2CString(buttons[1]),
|
||||
c.app.string2CString(buttons[2]),
|
||||
c.app.string2CString(buttons[3]),
|
||||
c.app.string2CString(dialogOptions.DefaultButton),
|
||||
c.app.string2CString(dialogOptions.CancelButton))
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *Client) DarkModeEnabled(callbackID string) {
|
||||
C.DarkModeEnabled(c.app.app, c.app.string2CString(callbackID))
|
||||
}
|
||||
|
||||
func (c *Client) SetApplicationMenu(applicationMenuJSON string) {
|
||||
C.SetApplicationMenu(c.app.app, c.app.string2CString(applicationMenuJSON))
|
||||
}
|
||||
|
||||
func (c *Client) SetTrayMenu(trayMenuJSON string) {
|
||||
C.SetTrayMenu(c.app.app, c.app.string2CString(trayMenuJSON))
|
||||
}
|
||||
|
||||
func (c *Client) UpdateTrayMenuLabel(JSON string) {
|
||||
C.UpdateTrayMenuLabel(c.app.app, c.app.string2CString(JSON))
|
||||
}
|
||||
|
||||
func (c *Client) UpdateContextMenu(contextMenuJSON string) {
|
||||
C.UpdateContextMenu(c.app.app, c.app.string2CString(contextMenuJSON))
|
||||
}
|
||||
|
||||
func (c *Client) DeleteTrayMenuByID(id string) {
|
||||
C.DeleteTrayMenuByID(c.app.app, c.app.string2CString(id))
|
||||
// OpenDirectoryDialog will open a directory dialog with the given title
|
||||
func (c *Client) OpenDirectoryDialog(title string) string {
|
||||
cstring := C.OpenDirectoryDialogOnMainThread(c.app.app, c.app.string2CString(title))
|
||||
var result string
|
||||
if cstring != nil {
|
||||
result = C.GoString(cstring)
|
||||
// Free the C string that was allocated by the dialog
|
||||
C.free(unsafe.Pointer(cstring))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ struct hashmap_s dialogIconCache;
|
||||
// Dispatch Method
|
||||
typedef void (^dispatchMethod)(void);
|
||||
|
||||
TrayMenuStore *TrayMenuStoreSingleton;
|
||||
|
||||
// dispatch will execute the given `func` pointer
|
||||
void dispatch(dispatchMethod func) {
|
||||
dispatch_async(dispatch_get_main_queue(), func);
|
||||
@@ -55,6 +57,9 @@ void filelog(const char *message) {
|
||||
}
|
||||
}
|
||||
|
||||
// The delegate class for tray menus
|
||||
Class trayMenuDelegateClass;
|
||||
|
||||
// Utility function to visualise a hashmap
|
||||
void dumpHashmap(const char *name, struct hashmap_s *hashmap) {
|
||||
printf("%s = { ", name);
|
||||
@@ -123,13 +128,11 @@ struct Application {
|
||||
int hideToolbarSeparator;
|
||||
int windowBackgroundIsTranslucent;
|
||||
int hasURLHandlers;
|
||||
const char *startupURL;
|
||||
|
||||
// Menu
|
||||
Menu *applicationMenu;
|
||||
|
||||
// Tray
|
||||
TrayMenuStore* trayMenuStore;
|
||||
|
||||
// Context Menus
|
||||
ContextMenuStore *contextMenuStore;
|
||||
|
||||
@@ -142,6 +145,9 @@ struct Application {
|
||||
// shutting down flag
|
||||
bool shuttingDown;
|
||||
|
||||
// Running flag
|
||||
bool running;
|
||||
|
||||
};
|
||||
|
||||
// Debug works like sprintf but mutes if the global debug flag is true
|
||||
@@ -264,7 +270,7 @@ void Hide(struct Application *app) {
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
msg(app->application, s("hide:"));
|
||||
msg(app->mainWindow, s("orderOut:"));
|
||||
);
|
||||
}
|
||||
|
||||
@@ -302,8 +308,19 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
||||
// TODO: Check this actually does reduce flicker
|
||||
msg(app->config, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("suppressesIncrementalRendering"));
|
||||
|
||||
// We are now running!
|
||||
app->running = true;
|
||||
|
||||
|
||||
// Notify backend we are ready (system startup)
|
||||
app->sendMessageToBackend("SS");
|
||||
const char *readyMessage = "SS";
|
||||
if( app->startupURL == NULL ) {
|
||||
app->sendMessageToBackend("SS");
|
||||
return;
|
||||
}
|
||||
readyMessage = concat("SS", app->startupURL);
|
||||
app->sendMessageToBackend(readyMessage);
|
||||
MEMFREE(readyMessage);
|
||||
|
||||
} else if( strcmp(name, "windowDrag") == 0 ) {
|
||||
// Guard against null events
|
||||
@@ -401,6 +418,11 @@ void ExecJS(struct Application *app, const char *js) {
|
||||
|
||||
void willFinishLaunching(id self, SEL cmd, id sender) {
|
||||
struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application");
|
||||
// If there are URL Handlers, register a listener for them
|
||||
if( app->hasURLHandlers ) {
|
||||
id eventManager = msg(c("NSAppleEventManager"), s("sharedAppleEventManager"));
|
||||
msg(eventManager, s("setEventHandler:andSelector:forEventClass:andEventID:"), self, s("getUrl:withReplyEvent:"), kInternetEventClass, kAEGetURL);
|
||||
}
|
||||
messageFromWindowCallback("Ej{\"name\":\"wails:launched\",\"data\":[]}");
|
||||
}
|
||||
|
||||
@@ -425,12 +447,6 @@ void themeChanged(id self, SEL cmd, id sender) {
|
||||
}
|
||||
}
|
||||
|
||||
// void willFinishLaunching(id self) {
|
||||
// struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application");
|
||||
// Debug(app, "willFinishLaunching called!");
|
||||
// }
|
||||
|
||||
|
||||
int releaseNSObject(void *const context, struct hashmap_element_s *const e) {
|
||||
msg(e->data, s("release"));
|
||||
return -1;
|
||||
@@ -463,6 +479,10 @@ void DestroyApplication(struct Application *app) {
|
||||
Debug(app, "Almost a double free for app->bindings");
|
||||
}
|
||||
|
||||
if( app->startupURL != NULL ) {
|
||||
MEMFREE(app->startupURL);
|
||||
}
|
||||
|
||||
// Remove mouse monitors
|
||||
if( app->mouseDownMonitor != NULL ) {
|
||||
msg( c("NSEvent"), s("removeMonitor:"), app->mouseDownMonitor);
|
||||
@@ -477,7 +497,7 @@ void DestroyApplication(struct Application *app) {
|
||||
}
|
||||
|
||||
// Delete the tray menu store
|
||||
DeleteTrayMenuStore(app->trayMenuStore);
|
||||
DeleteTrayMenuStore(TrayMenuStoreSingleton);
|
||||
|
||||
// Delete the context menu store
|
||||
DeleteContextMenuStore(app->contextMenuStore);
|
||||
@@ -1044,7 +1064,7 @@ void AddTrayMenu(struct Application *app, const char *trayMenuJSON) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
AddTrayMenuToStore(app->trayMenuStore, trayMenuJSON);
|
||||
AddTrayMenuToStore(TrayMenuStoreSingleton, trayMenuJSON);
|
||||
}
|
||||
|
||||
void SetTrayMenu(struct Application *app, const char* trayMenuJSON) {
|
||||
@@ -1053,13 +1073,13 @@ void SetTrayMenu(struct Application *app, const char* trayMenuJSON) {
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
UpdateTrayMenuInStore(app->trayMenuStore, trayMenuJSON);
|
||||
UpdateTrayMenuInStore(TrayMenuStoreSingleton, trayMenuJSON);
|
||||
);
|
||||
}
|
||||
|
||||
void DeleteTrayMenuByID(struct Application *app, const char *id) {
|
||||
ON_MAIN_THREAD(
|
||||
DeleteTrayMenuInStore(app->trayMenuStore, id);
|
||||
DeleteTrayMenuInStore(TrayMenuStoreSingleton, id);
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1068,7 +1088,7 @@ void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
UpdateTrayMenuLabelInStore(app->trayMenuStore, JSON);
|
||||
UpdateTrayMenuLabelInStore(TrayMenuStoreSingleton, JSON);
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1154,46 +1174,58 @@ void DarkModeEnabled(struct Application *app, const char *callbackID) {
|
||||
}
|
||||
|
||||
void getURL(id self, SEL selector, id event, id replyEvent) {
|
||||
id desc = msg(event, s("paramDescriptorForKeyword:"), keyDirectObject);
|
||||
id url = msg(desc, s("stringValue"));
|
||||
const char* curl = cstr(url);
|
||||
const char* message = concat("UC", curl);
|
||||
messageFromWindowCallback(message);
|
||||
MEMFREE(message);
|
||||
struct Application *app = (struct Application *)objc_getAssociatedObject(self, "application");
|
||||
id desc = msg(event, s("paramDescriptorForKeyword:"), keyDirectObject);
|
||||
id url = msg(desc, s("stringValue"));
|
||||
const char* curl = cstr(url);
|
||||
if( curl == NULL ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If this was an incoming URL, but we aren't running yet
|
||||
// save it to return when we complete
|
||||
if( app->running != true ) {
|
||||
app->startupURL = STRCOPY(curl);
|
||||
return;
|
||||
}
|
||||
|
||||
const char* message = concat("UC", curl);
|
||||
messageFromWindowCallback(message);
|
||||
MEMFREE(message);
|
||||
}
|
||||
|
||||
void openURLs(id self, SEL selector, id event) {
|
||||
filelog("\n\nI AM HERE!!!!!\n\n");
|
||||
}
|
||||
|
||||
|
||||
void createDelegate(struct Application *app) {
|
||||
// Define delegate
|
||||
Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "AppDelegate", 0);
|
||||
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSApplicationDelegate"));
|
||||
class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) no, "c@:@");
|
||||
class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
|
||||
|
||||
// Define delegate
|
||||
Class appDelegate = objc_allocateClassPair((Class) c("NSResponder"), "AppDelegate", 0);
|
||||
class_addProtocol(appDelegate, objc_getProtocol("NSTouchBarProvider"));
|
||||
|
||||
class_addMethod(appDelegate, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) no, "c@:@");
|
||||
class_addMethod(appDelegate, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
|
||||
|
||||
// All Menu Items use a common callback
|
||||
class_addMethod(delegateClass, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@");
|
||||
class_addMethod(appDelegate, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@");
|
||||
|
||||
// If there are URL Handlers, register the callback method
|
||||
if( app->hasURLHandlers ) {
|
||||
class_addMethod(delegateClass, s("getUrl:withReplyEvent:"), (IMP) getURL, "i@:@@");
|
||||
class_addMethod(appDelegate, s("getUrl:withReplyEvent:"), (IMP) getURL, "i@:@@");
|
||||
}
|
||||
|
||||
// Script handler
|
||||
class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
|
||||
objc_registerClassPair(delegateClass);
|
||||
class_addMethod(appDelegate, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
|
||||
objc_registerClassPair(appDelegate);
|
||||
|
||||
// Create delegate
|
||||
id delegate = msg((id)delegateClass, s("new"));
|
||||
id delegate = msg((id)appDelegate, s("new"));
|
||||
objc_setAssociatedObject(delegate, "application", (id)app, OBJC_ASSOCIATION_ASSIGN);
|
||||
|
||||
// If there are URL Handlers, register a listener for them
|
||||
if( app->hasURLHandlers ) {
|
||||
id eventManager = msg(c("NSAppleEventManager"), s("sharedAppleEventManager"));
|
||||
msg(eventManager, s("setEventHandler:andSelector:forEventClass:andEventID:"), delegate, s("getUrl:withReplyEvent:"), kInternetEventClass, kAEGetURL);
|
||||
}
|
||||
|
||||
// Theme change listener
|
||||
class_addMethod(delegateClass, s("themeChanged:"), (IMP) themeChanged, "v@:@@");
|
||||
class_addMethod(appDelegate, s("themeChanged:"), (IMP) themeChanged, "v@:@@");
|
||||
|
||||
// Get defaultCenter
|
||||
id defaultCenter = msg(c("NSDistributedNotificationCenter"), s("defaultCenter"));
|
||||
@@ -1205,12 +1237,12 @@ void createDelegate(struct Application *app) {
|
||||
}
|
||||
|
||||
bool windowShouldClose(id self, SEL cmd, id sender) {
|
||||
msg(sender, s("orderBack:"));
|
||||
msg(sender, s("orderOut:"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool windowShouldExit(id self, SEL cmd, id sender) {
|
||||
msg(sender, s("orderBack:"));
|
||||
msg(sender, s("orderOut:"));
|
||||
messageFromWindowCallback("WC");
|
||||
return false;
|
||||
}
|
||||
@@ -1661,6 +1693,35 @@ void processUserDialogIcons(struct Application *app) {
|
||||
|
||||
}
|
||||
|
||||
void TrayMenuWillOpen(id self, SEL selector, id menu) {
|
||||
// Extract tray menu id from menu
|
||||
id trayMenuIDStr = objc_getAssociatedObject(menu, "trayMenuID");
|
||||
const char* trayMenuID = cstr(trayMenuIDStr);
|
||||
const char *message = concat("Mo", trayMenuID);
|
||||
messageFromWindowCallback(message);
|
||||
MEMFREE(message);
|
||||
}
|
||||
|
||||
void TrayMenuDidClose(id self, SEL selector, id menu) {
|
||||
// Extract tray menu id from menu
|
||||
id trayMenuIDStr = objc_getAssociatedObject(menu, "trayMenuID");
|
||||
const char* trayMenuID = cstr(trayMenuIDStr);
|
||||
const char *message = concat("Mc", trayMenuID);
|
||||
messageFromWindowCallback(message);
|
||||
MEMFREE(message);
|
||||
}
|
||||
|
||||
void createTrayMenuDelegate() {
|
||||
// Define delegate
|
||||
trayMenuDelegateClass = objc_allocateClassPair((Class) c("NSObject"), "MenuDelegate", 0);
|
||||
class_addProtocol(trayMenuDelegateClass, objc_getProtocol("NSMenuDelegate"));
|
||||
class_addMethod(trayMenuDelegateClass, s("menuWillOpen:"), (IMP) TrayMenuWillOpen, "v@:@");
|
||||
class_addMethod(trayMenuDelegateClass, s("menuDidClose:"), (IMP) TrayMenuDidClose, "v@:@");
|
||||
|
||||
// Script handler
|
||||
objc_registerClassPair(trayMenuDelegateClass);
|
||||
}
|
||||
|
||||
|
||||
void Run(struct Application *app, int argc, char **argv) {
|
||||
|
||||
@@ -1673,6 +1734,9 @@ void Run(struct Application *app, int argc, char **argv) {
|
||||
// Define delegate
|
||||
createDelegate(app);
|
||||
|
||||
// Define tray delegate
|
||||
createTrayMenuDelegate();
|
||||
|
||||
// Create the main window
|
||||
createMainWindow(app);
|
||||
|
||||
@@ -1843,7 +1907,7 @@ void Run(struct Application *app, int argc, char **argv) {
|
||||
}
|
||||
|
||||
// Setup initial trays
|
||||
ShowTrayMenusInStore(app->trayMenuStore);
|
||||
ShowTrayMenusInStore(TrayMenuStoreSingleton);
|
||||
|
||||
// Process dialog icons
|
||||
processUserDialogIcons(app);
|
||||
@@ -1920,7 +1984,7 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
|
||||
result->applicationMenu = NULL;
|
||||
|
||||
// Tray
|
||||
result->trayMenuStore = NewTrayMenuStore();
|
||||
TrayMenuStoreSingleton = NewTrayMenuStore();
|
||||
|
||||
// Context Menus
|
||||
result->contextMenuStore = NewContextMenuStore();
|
||||
@@ -1940,6 +2004,10 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
|
||||
|
||||
result->hasURLHandlers = 0;
|
||||
|
||||
result->startupURL = NULL;
|
||||
|
||||
result->running = false;
|
||||
|
||||
return (void*) result;
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,6 @@ struct Application
|
||||
int height;
|
||||
int resizable;
|
||||
int devtools;
|
||||
int startHidden;
|
||||
int fullscreen;
|
||||
int minWidth;
|
||||
int minHeight;
|
||||
@@ -101,7 +100,7 @@ struct Application
|
||||
int lock;
|
||||
};
|
||||
|
||||
void *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden)
|
||||
void *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen)
|
||||
{
|
||||
// Setup main application struct
|
||||
struct Application *result = malloc(sizeof(struct Application));
|
||||
@@ -116,11 +115,15 @@ void *NewApplication(const char *title, int width, int height, int resizable, in
|
||||
result->maxWidth = 0;
|
||||
result->maxHeight = 0;
|
||||
result->frame = 1;
|
||||
result->startHidden = startHidden;
|
||||
|
||||
// Default drag button is PRIMARY
|
||||
result->dragButton = PRIMARY_MOUSE_BUTTON;
|
||||
|
||||
// printf("\n\nWidth: %d\n", result->width);
|
||||
// printf("Height: %d\n\n", result->height);
|
||||
|
||||
// Features
|
||||
|
||||
result->sendMessageToBackend = (ffenestriCallback)messageFromWindowCallback;
|
||||
|
||||
// Create a unique ID based on the current unix timestamp
|
||||
@@ -136,9 +139,10 @@ void *NewApplication(const char *title, int width, int height, int resizable, in
|
||||
return (void *)result;
|
||||
}
|
||||
|
||||
void DestroyApplication(struct Application *app)
|
||||
void DestroyApplication(void *appPointer)
|
||||
{
|
||||
Debug("Destroying Application");
|
||||
struct Application *app = (struct Application *)appPointer;
|
||||
|
||||
g_application_quit(G_APPLICATION(app->application));
|
||||
|
||||
@@ -167,11 +171,9 @@ void DestroyApplication(struct Application *app)
|
||||
// Disconnect signal handlers
|
||||
WebKitUserContentManager *manager = webkit_web_view_get_user_content_manager((WebKitWebView *)app->webView);
|
||||
g_signal_handler_disconnect(manager, app->signalInvoke);
|
||||
if( app->frame == 0) {
|
||||
g_signal_handler_disconnect(manager, app->signalWindowDrag);
|
||||
g_signal_handler_disconnect(app->webView, app->signalButtonPressed);
|
||||
g_signal_handler_disconnect(app->webView, app->signalButtonReleased);
|
||||
}
|
||||
g_signal_handler_disconnect(manager, app->signalWindowDrag);
|
||||
g_signal_handler_disconnect(app->webView, app->signalButtonPressed);
|
||||
g_signal_handler_disconnect(app->webView, app->signalButtonReleased);
|
||||
g_signal_handler_disconnect(app->webView, app->signalLoadChanged);
|
||||
|
||||
// Release the main GTK Application
|
||||
@@ -189,34 +191,45 @@ void DestroyApplication(struct Application *app)
|
||||
|
||||
// Quit will stop the gtk application and free up all the memory
|
||||
// used by the application
|
||||
void Quit(struct Application *app)
|
||||
void Quit(void *appPointer)
|
||||
{
|
||||
Debug("Quit Called");
|
||||
struct Application *app = (struct Application *)appPointer;
|
||||
gtk_window_close((GtkWindow *)app->mainWindow);
|
||||
g_application_quit((GApplication *)app->application);
|
||||
DestroyApplication(app);
|
||||
DestroyApplication(appPointer);
|
||||
}
|
||||
|
||||
// SetTitle sets the main window title to the given string
|
||||
void SetTitle(struct Application *app, const char *title)
|
||||
void SetTitle(void *appPointer, const char *title)
|
||||
{
|
||||
struct Application *app = (struct Application *)appPointer;
|
||||
gtk_window_set_title(app->mainWindow, title);
|
||||
}
|
||||
|
||||
// Fullscreen sets the main window to be fullscreen
|
||||
void Fullscreen(struct Application *app)
|
||||
void Fullscreen(void *appPointer)
|
||||
{
|
||||
struct Application *app = (struct Application *)appPointer;
|
||||
gtk_window_fullscreen(app->mainWindow);
|
||||
}
|
||||
|
||||
// UnFullscreen resets the main window after a fullscreen
|
||||
void UnFullscreen(struct Application *app)
|
||||
void UnFullscreen(void *appPointer)
|
||||
{
|
||||
struct Application *app = (struct Application *)appPointer;
|
||||
gtk_window_unfullscreen(app->mainWindow);
|
||||
}
|
||||
|
||||
void setMinMaxSize(struct Application *app)
|
||||
void Center(void *appPointer)
|
||||
{
|
||||
struct Application *app = (struct Application *)appPointer;
|
||||
gtk_window_set_position(app->mainWindow, GTK_WIN_POS_CENTER);
|
||||
}
|
||||
|
||||
void setMinMaxSize(void *appPointer)
|
||||
{
|
||||
struct Application *app = (struct Application *)appPointer;
|
||||
GdkGeometry size;
|
||||
size.min_width = size.min_height = size.max_width = size.max_height = 0;
|
||||
int flags = 0;
|
||||
@@ -232,43 +245,32 @@ void setMinMaxSize(struct Application *app)
|
||||
size.min_width = app->minWidth;
|
||||
flags |= GDK_HINT_MIN_SIZE;
|
||||
}
|
||||
Debug("size: %dx%d", app->width, app->height);
|
||||
Debug("min: %dx%d", size.min_width, size.min_height);
|
||||
Debug("max: %dx%d", size.max_width, size.max_height);
|
||||
gtk_window_set_geometry_hints(app->mainWindow, NULL, &size, flags);
|
||||
}
|
||||
|
||||
char *fileDialogInternal(struct Application *app, GtkFileChooserAction chooserAction, char **args) {
|
||||
// OpenFileDialog opens a dialog to select a file
|
||||
// NOTE: The result is a string that will need to be freed!
|
||||
char *OpenFileDialog(void *appPointer, char *title)
|
||||
{
|
||||
struct Application *app = (struct Application *)appPointer;
|
||||
GtkFileChooserNative *native;
|
||||
GtkFileChooserAction action = chooserAction;
|
||||
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
|
||||
gint res;
|
||||
char *filename;
|
||||
|
||||
char *title = args[0];
|
||||
char *filter = args[1];
|
||||
|
||||
native = gtk_file_chooser_native_new(title,
|
||||
app->mainWindow,
|
||||
action,
|
||||
"_Open",
|
||||
"_Cancel");
|
||||
|
||||
GtkFileChooser *chooser = GTK_FILE_CHOOSER(native);
|
||||
|
||||
// If we have filters, process them
|
||||
if (filter[0] != '\0') {
|
||||
GtkFileFilter *file_filter = gtk_file_filter_new();
|
||||
gchar **filters = g_strsplit(filter, ",", -1);
|
||||
gint i;
|
||||
for(i = 0; filters && filters[i]; i++) {
|
||||
gtk_file_filter_add_pattern(file_filter, filters[i]);
|
||||
// Debug("Adding filter pattern: %s\n", filters[i]);
|
||||
}
|
||||
gtk_file_filter_set_name(file_filter, filter);
|
||||
gtk_file_chooser_add_filter(chooser, file_filter);
|
||||
g_strfreev(filters);
|
||||
}
|
||||
|
||||
res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
|
||||
if (res == GTK_RESPONSE_ACCEPT)
|
||||
{
|
||||
GtkFileChooser *chooser = GTK_FILE_CHOOSER(native);
|
||||
filename = gtk_file_chooser_get_filename(chooser);
|
||||
}
|
||||
|
||||
@@ -277,65 +279,104 @@ char *fileDialogInternal(struct Application *app, GtkFileChooserAction chooserAc
|
||||
return filename;
|
||||
}
|
||||
|
||||
// openFileDialogInternal opens a dialog to select a file
|
||||
// SaveFileDialog opens a dialog to select a file
|
||||
// NOTE: The result is a string that will need to be freed!
|
||||
char *openFileDialogInternal(struct Application *app, char **args)
|
||||
char *SaveFileDialog(void *appPointer, char *title)
|
||||
{
|
||||
return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_OPEN, args);
|
||||
struct Application *app = (struct Application *)appPointer;
|
||||
GtkFileChooserNative *native;
|
||||
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE;
|
||||
gint res;
|
||||
char *filename;
|
||||
|
||||
native = gtk_file_chooser_native_new(title,
|
||||
app->mainWindow,
|
||||
action,
|
||||
"_Save",
|
||||
"_Cancel");
|
||||
|
||||
res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
|
||||
if (res == GTK_RESPONSE_ACCEPT)
|
||||
{
|
||||
GtkFileChooser *chooser = GTK_FILE_CHOOSER(native);
|
||||
filename = gtk_file_chooser_get_filename(chooser);
|
||||
}
|
||||
|
||||
g_object_unref(native);
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
// saveFileDialogInternal opens a dialog to select a file
|
||||
// OpenDirectoryDialog opens a dialog to select a directory
|
||||
// NOTE: The result is a string that will need to be freed!
|
||||
char *saveFileDialogInternal(struct Application *app, char **args)
|
||||
char *OpenDirectoryDialog(void *appPointer, char *title)
|
||||
{
|
||||
return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_SAVE, args);
|
||||
struct Application *app = (struct Application *)appPointer;
|
||||
GtkFileChooserNative *native;
|
||||
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
|
||||
gint res;
|
||||
char *foldername;
|
||||
|
||||
native = gtk_file_chooser_native_new(title,
|
||||
app->mainWindow,
|
||||
action,
|
||||
"_Open",
|
||||
"_Cancel");
|
||||
|
||||
res = gtk_native_dialog_run(GTK_NATIVE_DIALOG(native));
|
||||
if (res == GTK_RESPONSE_ACCEPT)
|
||||
{
|
||||
GtkFileChooser *chooser = GTK_FILE_CHOOSER(native);
|
||||
foldername = gtk_file_chooser_get_filename(chooser);
|
||||
}
|
||||
|
||||
g_object_unref(native);
|
||||
|
||||
return foldername;
|
||||
}
|
||||
|
||||
|
||||
// openDirectoryDialogInternal opens a dialog to select a directory
|
||||
// NOTE: The result is a string that will need to be freed!
|
||||
char *openDirectoryDialogInternal(struct Application *app, char **args)
|
||||
{
|
||||
return fileDialogInternal(app, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, args);
|
||||
}
|
||||
|
||||
void SetMinWindowSize(struct Application *app, int minWidth, int minHeight)
|
||||
void SetMinWindowSize(void *appPointer, int minWidth, int minHeight)
|
||||
{
|
||||
struct Application *app = (struct Application *)appPointer;
|
||||
app->minWidth = minWidth;
|
||||
app->minHeight = minHeight;
|
||||
}
|
||||
|
||||
void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight)
|
||||
void SetMaxWindowSize(void *appPointer, int maxWidth, int maxHeight)
|
||||
{
|
||||
struct Application *app = (struct Application *)appPointer;
|
||||
app->maxWidth = maxWidth;
|
||||
app->maxHeight = maxHeight;
|
||||
}
|
||||
|
||||
// SetColour sets the colour of the webview to the given colour string
|
||||
int SetColour(struct Application *app, const char *colourString)
|
||||
int SetColour(void *appPointer, const char *colourString)
|
||||
{
|
||||
struct Application *app = (struct Application *)appPointer;
|
||||
GdkRGBA rgba;
|
||||
gboolean result = gdk_rgba_parse(&rgba, colourString);
|
||||
if (result == FALSE)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// Debug("Setting webview colour to: %s", colourString);
|
||||
Debug("Setting webview colour to: %s", colourString);
|
||||
webkit_web_view_get_background_color((WebKitWebView *)(app->webView), &rgba);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// DisableFrame disables the window frame
|
||||
void DisableFrame(struct Application *app)
|
||||
void DisableFrame(void *appPointer)
|
||||
{
|
||||
struct Application *app = (struct Application *)appPointer;
|
||||
app->frame = 0;
|
||||
}
|
||||
|
||||
void syncCallback(GObject *source_object,
|
||||
GAsyncResult *res,
|
||||
void *data)
|
||||
gpointer user_data)
|
||||
{
|
||||
struct Application *app = (struct Application *)data;
|
||||
|
||||
struct Application *app = (struct Application *)user_data;
|
||||
app->lock = 0;
|
||||
}
|
||||
|
||||
@@ -343,6 +384,8 @@ void syncEval(struct Application *app, const gchar *script)
|
||||
{
|
||||
|
||||
WebKitWebView *webView = (WebKitWebView *)(app->webView);
|
||||
// Debug("[%p] webview\n", webView);
|
||||
// Debug("[%p] Running sync\n", script);
|
||||
|
||||
// wait for lock to free
|
||||
while (app->lock == 1)
|
||||
@@ -352,15 +395,18 @@ void syncEval(struct Application *app, const gchar *script)
|
||||
// Set lock
|
||||
app->lock = 1;
|
||||
|
||||
//
|
||||
webkit_web_view_run_javascript(
|
||||
webView,
|
||||
script,
|
||||
NULL, syncCallback, (void*)app);
|
||||
NULL, syncCallback, app);
|
||||
|
||||
while (app->lock == 1)
|
||||
{
|
||||
// Debug("[%p] Waiting for callback\n", script);
|
||||
g_main_context_iteration(0, true);
|
||||
}
|
||||
// Debug("[%p] Finished\n", script);
|
||||
}
|
||||
|
||||
void asyncEval(WebKitWebView *webView, const gchar *script)
|
||||
@@ -371,7 +417,7 @@ void asyncEval(WebKitWebView *webView, const gchar *script)
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
typedef void (*dispatchMethod)(struct Application *app, void *);
|
||||
typedef void (*dispatchMethod)(void *app, void *);
|
||||
|
||||
struct dispatchData
|
||||
{
|
||||
@@ -383,14 +429,20 @@ struct dispatchData
|
||||
gboolean executeMethod(gpointer data)
|
||||
{
|
||||
struct dispatchData *d = (struct dispatchData *)data;
|
||||
(d->method)(d->app, d->args);
|
||||
struct Application *app = (struct Application *)(d->app);
|
||||
// Debug("Webview %p\n", app->webView);
|
||||
// Debug("Args %s\n", d->args);
|
||||
// Debug("Method %p\n", (d->method));
|
||||
(d->method)(app, d->args);
|
||||
// Debug("Method Execute Complete. Freeing memory");
|
||||
g_free(d);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void ExecJS(struct Application *app, char *js)
|
||||
void ExecJS(void *app, char *js)
|
||||
{
|
||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
||||
struct dispatchData *data =
|
||||
(struct dispatchData *)g_new(struct dispatchData, 1);
|
||||
data->method = (dispatchMethod)syncEval;
|
||||
data->args = js;
|
||||
data->app = app;
|
||||
@@ -398,14 +450,13 @@ void ExecJS(struct Application *app, char *js)
|
||||
gdk_threads_add_idle(executeMethod, data);
|
||||
}
|
||||
|
||||
typedef char *(*dialogMethod)(struct Application *app, void *);
|
||||
typedef char *(*dialogMethod)(void *app, void *);
|
||||
|
||||
struct dialogCall
|
||||
{
|
||||
struct Application *app;
|
||||
dialogMethod method;
|
||||
void *args;
|
||||
void *filter;
|
||||
char *result;
|
||||
int done;
|
||||
};
|
||||
@@ -413,70 +464,79 @@ struct dialogCall
|
||||
gboolean executeMethodWithReturn(gpointer data)
|
||||
{
|
||||
struct dialogCall *d = (struct dialogCall *)data;
|
||||
|
||||
d->result = (d->method)(d->app, d->args);
|
||||
struct Application *app = (struct Application *)(d->app);
|
||||
Debug("Webview %p\n", app->webView);
|
||||
Debug("Args %s\n", d->args);
|
||||
Debug("Method %p\n", (d->method));
|
||||
d->result = (d->method)(app, d->args);
|
||||
d->done = 1;
|
||||
// Debug("Method Execute Complete. Freeing memory");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
char *OpenFileDialog(struct Application *app, char *title, char *filter)
|
||||
char *OpenFileDialogOnMainThread(void *app, char *title)
|
||||
{
|
||||
struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
|
||||
struct dialogCall *data =
|
||||
(struct dialogCall *)g_new(struct dialogCall, 1);
|
||||
data->result = NULL;
|
||||
data->done = 0;
|
||||
data->method = (dialogMethod)openFileDialogInternal;
|
||||
const char* dialogArgs[]={ title, filter };
|
||||
data->args = dialogArgs;
|
||||
data->app = app;
|
||||
|
||||
gdk_threads_add_idle(executeMethodWithReturn, data);
|
||||
|
||||
while (data->done == 0)
|
||||
{
|
||||
usleep(100000);
|
||||
}
|
||||
g_free(data);
|
||||
return data->result;
|
||||
}
|
||||
|
||||
char *SaveFileDialog(struct Application *app, char *title, char *filter)
|
||||
{
|
||||
struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
|
||||
data->result = NULL;
|
||||
data->done = 0;
|
||||
data->method = (dialogMethod)saveFileDialogInternal;
|
||||
const char* dialogArgs[]={ title, filter };
|
||||
data->args = dialogArgs;
|
||||
data->method = (dialogMethod)OpenFileDialog;
|
||||
data->args = title;
|
||||
data->app = app;
|
||||
|
||||
gdk_threads_add_idle(executeMethodWithReturn, data);
|
||||
|
||||
while (data->done == 0)
|
||||
{
|
||||
// Debug("Waiting for dialog");
|
||||
usleep(100000);
|
||||
}
|
||||
Debug("Dialog done");
|
||||
Debug("Result = %s\n", data->result);
|
||||
|
||||
g_free(data);
|
||||
// Fingers crossed this wasn't freed by g_free above
|
||||
return data->result;
|
||||
}
|
||||
|
||||
char *OpenDirectoryDialog(struct Application *app, char *title, char *filter)
|
||||
char *SaveFileDialogOnMainThread(void *app, char *title)
|
||||
{
|
||||
struct dialogCall *data = (struct dialogCall *)g_new(struct dialogCall, 1);
|
||||
struct dialogCall *data =
|
||||
(struct dialogCall *)g_new(struct dialogCall, 1);
|
||||
data->result = NULL;
|
||||
data->done = 0;
|
||||
data->method = (dialogMethod)openDirectoryDialogInternal;
|
||||
const char* dialogArgs[]={ title, filter };
|
||||
data->args = dialogArgs;
|
||||
data->method = (dialogMethod)SaveFileDialog;
|
||||
data->args = title;
|
||||
data->app = app;
|
||||
|
||||
gdk_threads_add_idle(executeMethodWithReturn, data);
|
||||
|
||||
while (data->done == 0)
|
||||
{
|
||||
// Debug("Waiting for dialog");
|
||||
usleep(100000);
|
||||
}
|
||||
Debug("Dialog done");
|
||||
Debug("Result = %s\n", data->result);
|
||||
g_free(data);
|
||||
// Fingers crossed this wasn't freed by g_free above
|
||||
return data->result;
|
||||
}
|
||||
|
||||
char *OpenDirectoryDialogOnMainThread(void *app, char *title)
|
||||
{
|
||||
struct dialogCall *data =
|
||||
(struct dialogCall *)g_new(struct dialogCall, 1);
|
||||
data->result = NULL;
|
||||
data->done = 0;
|
||||
data->method = (dialogMethod)OpenDirectoryDialog;
|
||||
data->args = title;
|
||||
data->app = app;
|
||||
|
||||
gdk_threads_add_idle(executeMethodWithReturn, data);
|
||||
|
||||
while (data->done == 0)
|
||||
{
|
||||
// Debug("Waiting for dialog");
|
||||
usleep(100000);
|
||||
}
|
||||
Debug("Directory Dialog done");
|
||||
@@ -495,8 +555,10 @@ void setIcon(struct Application *app)
|
||||
|
||||
static void load_finished_cb(WebKitWebView *webView,
|
||||
WebKitLoadEvent load_event,
|
||||
struct Application *app)
|
||||
gpointer userData)
|
||||
{
|
||||
struct Application *app;
|
||||
|
||||
switch (load_event)
|
||||
{
|
||||
// case WEBKIT_LOAD_STARTED:
|
||||
@@ -518,6 +580,7 @@ static void load_finished_cb(WebKitWebView *webView,
|
||||
case WEBKIT_LOAD_FINISHED:
|
||||
/* Load finished, we can now stop the spinner */
|
||||
// printf("Finished loading: %s\n", webkit_web_view_get_uri(web_view));
|
||||
app = (struct Application *)userData;
|
||||
|
||||
// Bindings
|
||||
Debug("Binding Methods");
|
||||
@@ -574,23 +637,21 @@ static void load_finished_cb(WebKitWebView *webView,
|
||||
setMinMaxSize(app);
|
||||
|
||||
// Centre by default
|
||||
gtk_window_set_position(app->mainWindow, GTK_WIN_POS_CENTER);
|
||||
Center(app);
|
||||
|
||||
// Show window and focus
|
||||
if( app->startHidden == 0) {
|
||||
gtk_widget_show_all(GTK_WIDGET(app->mainWindow));
|
||||
gtk_widget_grab_focus(app->webView);
|
||||
}
|
||||
gtk_widget_show_all(GTK_WIDGET(app->mainWindow));
|
||||
gtk_widget_grab_focus(app->webView);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean disable_context_menu_cb(
|
||||
WebKitWebView *web_view,
|
||||
WebKitContextMenu *context_menu,
|
||||
GdkEvent *event,
|
||||
WebKitHitTestResult *hit_test_result,
|
||||
gpointer user_data)
|
||||
static gboolean
|
||||
disable_context_menu_cb(WebKitWebView *web_view,
|
||||
WebKitContextMenu *context_menu,
|
||||
GdkEvent *event,
|
||||
WebKitHitTestResult *hit_test_result,
|
||||
gpointer user_data)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
@@ -605,11 +666,12 @@ static void printEvent(const char *message, GdkEventButton *event)
|
||||
event->time);
|
||||
}
|
||||
|
||||
|
||||
static void dragWindow(WebKitUserContentManager *contentManager,
|
||||
WebKitJavascriptResult *result,
|
||||
struct Application *app)
|
||||
gpointer arg)
|
||||
{
|
||||
struct Application *app = (struct Application *)arg;
|
||||
|
||||
// If we get this message erroneously, ignore
|
||||
if (app->dragButtonEvent == NULL)
|
||||
{
|
||||
@@ -634,22 +696,31 @@ static void dragWindow(WebKitUserContentManager *contentManager,
|
||||
// Clear the event
|
||||
app->dragButtonEvent = NULL;
|
||||
|
||||
return;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gboolean buttonPress(GtkWidget *widget, GdkEventButton *event, struct Application *app)
|
||||
gboolean buttonPress(GtkWidget *widget, GdkEventButton *event, gpointer arg)
|
||||
{
|
||||
struct Application *app = (struct Application *)arg;
|
||||
Debug("I am pressing a button");
|
||||
|
||||
if (event->type == GDK_BUTTON_PRESS && event->button == app->dragButton)
|
||||
{
|
||||
printEvent("Drag button event was saved", event);
|
||||
app->dragButtonEvent = event;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gboolean buttonRelease(GtkWidget *widget, GdkEventButton *event, struct Application *app)
|
||||
gboolean buttonRelease(GtkWidget *widget, GdkEventButton *event, gpointer arg)
|
||||
{
|
||||
struct Application *app = (struct Application *)arg;
|
||||
Debug("I am releasing a button");
|
||||
|
||||
if (event->type == GDK_BUTTON_RELEASE && event->button == app->dragButton)
|
||||
{
|
||||
printEvent("Drag button event was reset", event);
|
||||
|
||||
app->dragButtonEvent = NULL;
|
||||
}
|
||||
return FALSE;
|
||||
@@ -657,8 +728,9 @@ gboolean buttonRelease(GtkWidget *widget, GdkEventButton *event, struct Applicat
|
||||
|
||||
static void sendMessageToBackend(WebKitUserContentManager *contentManager,
|
||||
WebKitJavascriptResult *result,
|
||||
struct Application *app)
|
||||
gpointer arg)
|
||||
{
|
||||
struct Application *app = (struct Application *)arg;
|
||||
#if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22
|
||||
JSCValue *value = webkit_javascript_result_get_js_value(result);
|
||||
char *message = jsc_value_to_string(value);
|
||||
@@ -675,225 +747,16 @@ static void sendMessageToBackend(WebKitUserContentManager *contentManager,
|
||||
g_free(message);
|
||||
}
|
||||
|
||||
void SetDebug(struct Application *app, int flag)
|
||||
void SetDebug(void *applicationPointer, int flag)
|
||||
{
|
||||
struct Application *app = (struct Application *)applicationPointer;
|
||||
debug = flag;
|
||||
}
|
||||
|
||||
// getCurrentMonitorGeometry gets the geometry of the monitor
|
||||
// that the window is mostly on.
|
||||
GdkRectangle getCurrentMonitorGeometry(GtkWindow *window) {
|
||||
// Get the monitor that the window is currently on
|
||||
GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(window));
|
||||
GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
|
||||
GdkMonitor *monitor = gdk_display_get_monitor_at_window (display, gdk_window);
|
||||
|
||||
// Get the geometry of the monitor
|
||||
GdkRectangle result;
|
||||
gdk_monitor_get_geometry (monitor,&result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*******************
|
||||
* Window Position *
|
||||
*******************/
|
||||
|
||||
// Position holds an x/y corrdinate
|
||||
struct Position {
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
// Internal call for setting the position of the window.
|
||||
void setPositionInternal(struct Application *app, struct Position *pos) {
|
||||
|
||||
// Get the monitor geometry
|
||||
GdkRectangle m = getCurrentMonitorGeometry(app->mainWindow);
|
||||
|
||||
// Move the window relative to the monitor
|
||||
gtk_window_move(app->mainWindow, m.x + pos->x, m.y + pos->y);
|
||||
|
||||
// Free memory
|
||||
free(pos);
|
||||
}
|
||||
|
||||
// SetPosition sets the position of the window to the given x/y
|
||||
// coordinates. The x/y values are relative to the monitor
|
||||
// the window is mostly on.
|
||||
void SetPosition(struct Application *app, int x, int y) {
|
||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
||||
data->method = (dispatchMethod)setPositionInternal;
|
||||
struct Position *pos = malloc(sizeof(struct Position));
|
||||
pos->x = x;
|
||||
pos->y = y;
|
||||
data->args = pos;
|
||||
data->app = app;
|
||||
|
||||
gdk_threads_add_idle(executeMethod, data);
|
||||
}
|
||||
|
||||
/***************
|
||||
* Window Size *
|
||||
***************/
|
||||
|
||||
// Size holds a width/height
|
||||
struct Size {
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
|
||||
// Internal call for setting the size of the window.
|
||||
void setSizeInternal(struct Application *app, struct Size *size) {
|
||||
gtk_window_resize(app->mainWindow, size->width, size->height);
|
||||
free(size);
|
||||
}
|
||||
|
||||
// SetSize sets the size of the window to the given width/height
|
||||
void SetSize(struct Application *app, int width, int height) {
|
||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
||||
data->method = (dispatchMethod)setSizeInternal;
|
||||
struct Size *size = malloc(sizeof(struct Size));
|
||||
size->width = width;
|
||||
size->height = height;
|
||||
data->args = size;
|
||||
data->app = app;
|
||||
|
||||
gdk_threads_add_idle(executeMethod, data);
|
||||
}
|
||||
|
||||
|
||||
// centerInternal will center the main window on the monitor it is mostly in
|
||||
void centerInternal(struct Application *app)
|
||||
void SetBindings(void *applicationPointer, const char *bindings)
|
||||
{
|
||||
// Get the geometry of the monitor
|
||||
GdkRectangle m = getCurrentMonitorGeometry(app->mainWindow);
|
||||
struct Application *app = (struct Application *)applicationPointer;
|
||||
|
||||
// Get the window width/height
|
||||
int windowWidth, windowHeight;
|
||||
gtk_window_get_size(app->mainWindow, &windowWidth, &windowHeight);
|
||||
|
||||
// Place the window at the center of the monitor
|
||||
gtk_window_move(app->mainWindow, ((m.width - windowWidth) / 2) + m.x, ((m.height - windowHeight) / 2) + m.y);
|
||||
}
|
||||
|
||||
// Center the window
|
||||
void Center(struct Application *app) {
|
||||
|
||||
// Setup a call to centerInternal on the main thread
|
||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
||||
data->method = (dispatchMethod)centerInternal;
|
||||
data->app = app;
|
||||
|
||||
// Add call to main thread
|
||||
gdk_threads_add_idle(executeMethod, data);
|
||||
}
|
||||
|
||||
// hideInternal hides the main window
|
||||
void hideInternal(struct Application *app) {
|
||||
gtk_widget_hide (GTK_WIDGET(app->mainWindow));
|
||||
}
|
||||
|
||||
// Hide places the hideInternal method onto the main thread for execution
|
||||
void Hide(struct Application *app) {
|
||||
|
||||
// Setup a call to hideInternal on the main thread
|
||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
||||
data->method = (dispatchMethod)hideInternal;
|
||||
data->app = app;
|
||||
|
||||
// Add call to main thread
|
||||
gdk_threads_add_idle(executeMethod, data);
|
||||
}
|
||||
|
||||
// showInternal shows the main window
|
||||
void showInternal(struct Application *app) {
|
||||
gtk_widget_show_all(GTK_WIDGET(app->mainWindow));
|
||||
gtk_widget_grab_focus(app->webView);
|
||||
}
|
||||
|
||||
// Show places the showInternal method onto the main thread for execution
|
||||
void Show(struct Application *app) {
|
||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
||||
data->method = (dispatchMethod)showInternal;
|
||||
data->app = app;
|
||||
|
||||
gdk_threads_add_idle(executeMethod, data);
|
||||
}
|
||||
|
||||
|
||||
// maximiseInternal maximises the main window
|
||||
void maximiseInternal(struct Application *app) {
|
||||
gtk_window_maximize(GTK_WIDGET(app->mainWindow));
|
||||
}
|
||||
|
||||
// Maximise places the maximiseInternal method onto the main thread for execution
|
||||
void Maximise(struct Application *app) {
|
||||
|
||||
// Setup a call to maximiseInternal on the main thread
|
||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
||||
data->method = (dispatchMethod)maximiseInternal;
|
||||
data->app = app;
|
||||
|
||||
// Add call to main thread
|
||||
gdk_threads_add_idle(executeMethod, data);
|
||||
}
|
||||
|
||||
// unmaximiseInternal unmaximises the main window
|
||||
void unmaximiseInternal(struct Application *app) {
|
||||
gtk_window_unmaximize(GTK_WIDGET(app->mainWindow));
|
||||
}
|
||||
|
||||
// Unmaximise places the unmaximiseInternal method onto the main thread for execution
|
||||
void Unmaximise(struct Application *app) {
|
||||
|
||||
// Setup a call to unmaximiseInternal on the main thread
|
||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
||||
data->method = (dispatchMethod)unmaximiseInternal;
|
||||
data->app = app;
|
||||
|
||||
// Add call to main thread
|
||||
gdk_threads_add_idle(executeMethod, data);
|
||||
}
|
||||
|
||||
|
||||
// minimiseInternal minimises the main window
|
||||
void minimiseInternal(struct Application *app) {
|
||||
gtk_window_iconify(app->mainWindow);
|
||||
}
|
||||
|
||||
// Minimise places the minimiseInternal method onto the main thread for execution
|
||||
void Minimise(struct Application *app) {
|
||||
|
||||
// Setup a call to minimiseInternal on the main thread
|
||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
||||
data->method = (dispatchMethod)minimiseInternal;
|
||||
data->app = app;
|
||||
|
||||
// Add call to main thread
|
||||
gdk_threads_add_idle(executeMethod, data);
|
||||
}
|
||||
|
||||
// unminimiseInternal unminimises the main window
|
||||
void unminimiseInternal(struct Application *app) {
|
||||
gtk_window_present(app->mainWindow);
|
||||
}
|
||||
|
||||
// Unminimise places the unminimiseInternal method onto the main thread for execution
|
||||
void Unminimise(struct Application *app) {
|
||||
|
||||
// Setup a call to unminimiseInternal on the main thread
|
||||
struct dispatchData *data = (struct dispatchData *)g_new(struct dispatchData, 1);
|
||||
data->method = (dispatchMethod)unminimiseInternal;
|
||||
data->app = app;
|
||||
|
||||
// Add call to main thread
|
||||
gdk_threads_add_idle(executeMethod, data);
|
||||
}
|
||||
|
||||
|
||||
void SetBindings(struct Application *app, const char *bindings)
|
||||
{
|
||||
const char *temp = concat("window.wailsbindings = \"", bindings);
|
||||
const char *jscall = concat(temp, "\";");
|
||||
free((void *)temp);
|
||||
@@ -903,15 +766,18 @@ void SetBindings(struct Application *app, const char *bindings)
|
||||
// This is called when the close button on the window is pressed
|
||||
gboolean close_button_pressed(GtkWidget *widget,
|
||||
GdkEvent *event,
|
||||
struct Application *app)
|
||||
gpointer user_data)
|
||||
{
|
||||
struct Application *app = (struct Application *)user_data;
|
||||
app->sendMessageToBackend("WC"); // Window Close
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void setupWindow(struct Application *app)
|
||||
static void setupWindow(void *applicationPointer)
|
||||
{
|
||||
|
||||
struct Application *app = (struct Application *)applicationPointer;
|
||||
|
||||
// Create the window
|
||||
GtkWidget *mainWindow = gtk_application_window_new(app->application);
|
||||
// Save reference
|
||||
@@ -930,14 +796,10 @@ static void setupWindow(struct Application *app)
|
||||
webkit_user_content_manager_register_script_message_handler(contentManager, "external");
|
||||
app->signalInvoke = g_signal_connect(contentManager, "script-message-received::external", G_CALLBACK(sendMessageToBackend), app);
|
||||
|
||||
// Setup the window drag handler if this is a frameless app
|
||||
if ( app->frame == 0 ) {
|
||||
webkit_user_content_manager_register_script_message_handler(contentManager, "windowDrag");
|
||||
app->signalWindowDrag = g_signal_connect(contentManager, "script-message-received::windowDrag", G_CALLBACK(dragWindow), app);
|
||||
// Setup the mouse handlers
|
||||
app->signalButtonPressed = g_signal_connect(app->webView, "button-press-event", G_CALLBACK(buttonPress), app);
|
||||
app->signalButtonReleased = g_signal_connect(app->webView, "button-release-event", G_CALLBACK(buttonRelease), app);
|
||||
}
|
||||
// Setup the window drag handler
|
||||
webkit_user_content_manager_register_script_message_handler(contentManager, "windowDrag");
|
||||
app->signalWindowDrag = g_signal_connect(contentManager, "script-message-received::windowDrag", G_CALLBACK(dragWindow), app);
|
||||
|
||||
GtkWidget *webView = webkit_web_view_new_with_user_content_manager(contentManager);
|
||||
|
||||
// Save reference
|
||||
@@ -946,6 +808,9 @@ static void setupWindow(struct Application *app)
|
||||
// Add the webview to the window
|
||||
gtk_container_add(GTK_CONTAINER(mainWindow), webView);
|
||||
|
||||
// Setup the mouse handlers
|
||||
app->signalButtonPressed = g_signal_connect(app->webView, "button-press-event", G_CALLBACK(buttonPress), app);
|
||||
app->signalButtonReleased = g_signal_connect(app->webView, "button-release-event", G_CALLBACK(buttonRelease), app);
|
||||
|
||||
// Load default HTML
|
||||
app->signalLoadChanged = g_signal_connect(G_OBJECT(webView), "load-changed", G_CALLBACK(load_finished_cb), app);
|
||||
@@ -970,13 +835,20 @@ static void setupWindow(struct Application *app)
|
||||
g_signal_connect(GTK_WIDGET(mainWindow), "delete-event", G_CALLBACK(close_button_pressed), app);
|
||||
}
|
||||
|
||||
static void activate(GtkApplication* _, struct Application *app)
|
||||
static void
|
||||
activate(GtkApplication *app,
|
||||
gpointer user_data)
|
||||
{
|
||||
setupWindow(app);
|
||||
struct Application *mainApp = (struct Application *)user_data;
|
||||
|
||||
// Main Window
|
||||
setupWindow(mainApp);
|
||||
}
|
||||
|
||||
void Run(struct Application *app, int argc, char **argv)
|
||||
void Run(void *applicationPointer, int argc, char **argv)
|
||||
{
|
||||
struct Application *app = (struct Application *)applicationPointer;
|
||||
|
||||
g_signal_connect(app->application, "activate", G_CALLBACK(activate), app);
|
||||
g_application_run(G_APPLICATION(app->application), argc, argv);
|
||||
}
|
||||
|
||||
@@ -576,34 +576,7 @@ id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const c
|
||||
return item;
|
||||
}
|
||||
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
|
||||
// Create a MenuItemCallbackData
|
||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
||||
s("menuItemCallback:"), key);
|
||||
|
||||
if( tooltip != NULL ) {
|
||||
msg(item, s("setToolTip:"), str(tooltip));
|
||||
}
|
||||
|
||||
// Process image
|
||||
if( image != NULL && strlen(image) > 0) {
|
||||
id data = ALLOC("NSData");
|
||||
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(image), 0);
|
||||
id nsimage = ALLOC("NSImage");
|
||||
msg(nsimage, s("initWithData:"), imageData);
|
||||
if( templateImage ) {
|
||||
msg(nsimage, s("template"), YES);
|
||||
}
|
||||
msg(item, s("setImage:"), nsimage);
|
||||
}
|
||||
id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA) {
|
||||
|
||||
// Process Menu Item attributes
|
||||
id dictionary = ALLOC_INIT("NSMutableDictionary");
|
||||
@@ -654,19 +627,61 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
|
||||
|
||||
id attributedString = ALLOC("NSMutableAttributedString");
|
||||
msg(attributedString, s("initWithString:attributes:"), str(title), dictionary);
|
||||
msg(dictionary, s("release"));
|
||||
|
||||
msg(item, s("setAttributedTitle:"), attributedString);
|
||||
msg(attributedString, s("autorelease"));
|
||||
msg(dictionary, s("release"));
|
||||
return attributedString;
|
||||
}
|
||||
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
|
||||
// Create a MenuItemCallbackData
|
||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
|
||||
if( !alternate ) {
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
||||
s("menuItemCallback:"), key);
|
||||
} else {
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(""));
|
||||
}
|
||||
|
||||
if( tooltip != NULL ) {
|
||||
msg(item, s("setToolTip:"), str(tooltip));
|
||||
}
|
||||
|
||||
// Process image
|
||||
if( image != NULL && strlen(image) > 0) {
|
||||
id data = ALLOC("NSData");
|
||||
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(image), 0);
|
||||
id nsimage = ALLOC("NSImage");
|
||||
msg(nsimage, s("initWithData:"), imageData);
|
||||
if( templateImage ) {
|
||||
msg(nsimage, s("setTemplate:"), YES);
|
||||
}
|
||||
msg(item, s("setImage:"), nsimage);
|
||||
}
|
||||
|
||||
id attributedString = createAttributedString(title, fontName, fontSize, RGBA);
|
||||
msg(item, s("setAttributedTitle:"), attributedString);
|
||||
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
|
||||
// Process modifiers
|
||||
if( modifiers != NULL ) {
|
||||
if( modifiers != NULL && !alternate) {
|
||||
unsigned long modifierFlags = parseModifiers(modifiers);
|
||||
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
|
||||
}
|
||||
|
||||
// alternate
|
||||
if( alternate ) {
|
||||
msg(item, s("setAlternate:"), true);
|
||||
msg(item, s("setKeyEquivalentModifierMask:"), NSEventModifierFlagOption);
|
||||
}
|
||||
msg(parentMenu, s("addItem:"), item);
|
||||
|
||||
return item;
|
||||
@@ -727,6 +742,11 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
label = "(empty)";
|
||||
}
|
||||
|
||||
|
||||
// Is this an alternate menu item?
|
||||
bool alternate = false;
|
||||
getJSONBool(item, "MacAlternate", &alternate);
|
||||
|
||||
const char *menuid = getJSONString(item, "ID");
|
||||
if ( menuid == NULL) {
|
||||
menuid = "";
|
||||
@@ -747,7 +767,7 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
bool templateImage = false;
|
||||
getJSONBool(item, "MacTemplateImage", &templateImage);
|
||||
|
||||
int fontSize = 12;
|
||||
int fontSize = 0;
|
||||
getJSONInt(item, "FontSize", &fontSize);
|
||||
|
||||
// If we have an accelerator
|
||||
@@ -782,7 +802,7 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
if( type != NULL ) {
|
||||
|
||||
if( STREQ(type->string_, "Text")) {
|
||||
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage);
|
||||
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage, alternate);
|
||||
}
|
||||
else if ( STREQ(type->string_, "Separator")) {
|
||||
addSeparator(parentMenu);
|
||||
|
||||
@@ -105,10 +105,12 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char
|
||||
|
||||
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key);
|
||||
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage);
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate);
|
||||
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
|
||||
void processMenuData(Menu *menu, JsonNode *menuData);
|
||||
|
||||
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) ;
|
||||
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup);
|
||||
id GetMenu(Menu *menu);
|
||||
id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA);
|
||||
|
||||
#endif //ASSETS_C_MENU_DARWIN_H
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "traymenu_darwin.h"
|
||||
#include "trayicons.h"
|
||||
|
||||
extern Class trayMenuDelegateClass;
|
||||
|
||||
// A cache for all our tray menu icons
|
||||
// Global because it's a singleton
|
||||
struct hashmap_s trayIconCache;
|
||||
@@ -29,12 +31,23 @@ TrayMenu* NewTrayMenu(const char* menuJSON) {
|
||||
|
||||
result->ID = mustJSONString(processedJSON, "ID");
|
||||
result->label = mustJSONString(processedJSON, "Label");
|
||||
result->icon = mustJSONString(processedJSON, "Icon");
|
||||
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
|
||||
result->icon = mustJSONString(processedJSON, "Image");
|
||||
result->fontName = getJSONString(processedJSON, "FontName");
|
||||
result->RGBA = getJSONString(processedJSON, "RGBA");
|
||||
getJSONBool(processedJSON, "MacTemplateImage", &result->templateImage);
|
||||
result->fontSize = 0;
|
||||
getJSONInt(processedJSON, "FontSize", &result->fontSize);
|
||||
result->tooltip = NULL;
|
||||
result->tooltip = getJSONString(processedJSON, "Tooltip");
|
||||
result->disabled = false;
|
||||
getJSONBool(processedJSON, "Disabled", &result->disabled);
|
||||
|
||||
// Create the menu
|
||||
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
|
||||
result->menu = NewMenu(processedMenu);
|
||||
|
||||
result->delegate = NULL;
|
||||
|
||||
// Init tray status bar item
|
||||
result->statusbaritem = NULL;
|
||||
|
||||
@@ -50,7 +63,7 @@ void DumpTrayMenu(TrayMenu* trayMenu) {
|
||||
}
|
||||
|
||||
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) {
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled) {
|
||||
|
||||
// Exit early if NULL
|
||||
if( trayMenu->label == NULL ) {
|
||||
@@ -58,7 +71,15 @@ void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) {
|
||||
}
|
||||
// Update button label
|
||||
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
|
||||
msg(statusBarButton, s("setTitle:"), str(label));
|
||||
id attributedString = createAttributedString(label, fontName, fontSize, RGBA);
|
||||
|
||||
if( tooltip != NULL ) {
|
||||
msg(statusBarButton, s("setToolTip:"), str(tooltip));
|
||||
}
|
||||
|
||||
msg(statusBarButton, s("setEnabled:"), !disabled);
|
||||
|
||||
msg(statusBarButton, s("setAttributedTitle:"), attributedString);
|
||||
}
|
||||
|
||||
void UpdateTrayIcon(TrayMenu *trayMenu) {
|
||||
@@ -78,12 +99,24 @@ void UpdateTrayIcon(TrayMenu *trayMenu) {
|
||||
}
|
||||
|
||||
id trayImage = hashmap_get(&trayIconCache, trayMenu->icon, strlen(trayMenu->icon));
|
||||
|
||||
// If we don't have the image in the icon cache then assume it's base64 encoded image data
|
||||
if (trayImage == NULL) {
|
||||
id data = ALLOC("NSData");
|
||||
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(trayMenu->icon), 0);
|
||||
trayImage = ALLOC("NSImage");
|
||||
msg(trayImage, s("initWithData:"), imageData);
|
||||
|
||||
if( trayMenu->templateImage ) {
|
||||
msg(trayImage, s("setTemplate:"), YES);
|
||||
}
|
||||
}
|
||||
|
||||
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
|
||||
msg(statusBarButton, s("setImage:"), trayImage);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ShowTrayMenu(TrayMenu* trayMenu) {
|
||||
|
||||
// Create a status bar item if we don't have one
|
||||
@@ -91,7 +124,6 @@ void ShowTrayMenu(TrayMenu* trayMenu) {
|
||||
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
|
||||
trayMenu->statusbaritem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
|
||||
msg(trayMenu->statusbaritem, s("retain"));
|
||||
|
||||
}
|
||||
|
||||
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
|
||||
@@ -101,10 +133,20 @@ void ShowTrayMenu(TrayMenu* trayMenu) {
|
||||
UpdateTrayIcon(trayMenu);
|
||||
|
||||
// Update the label if needed
|
||||
UpdateTrayLabel(trayMenu, trayMenu->label);
|
||||
UpdateTrayLabel(trayMenu, trayMenu->label, trayMenu->fontName, trayMenu->fontSize, trayMenu->RGBA, trayMenu->tooltip, trayMenu->disabled);
|
||||
|
||||
// Update the menu
|
||||
id menu = GetMenu(trayMenu->menu);
|
||||
objc_setAssociatedObject(menu, "trayMenuID", str(trayMenu->ID), OBJC_ASSOCIATION_ASSIGN);
|
||||
|
||||
// Create delegate
|
||||
id trayMenuDelegate = msg((id)trayMenuDelegateClass, s("new"));
|
||||
msg(menu, s("setDelegate:"), trayMenuDelegate);
|
||||
objc_setAssociatedObject(trayMenuDelegate, "menu", menu, OBJC_ASSOCIATION_ASSIGN);
|
||||
|
||||
// Create menu delegate
|
||||
trayMenu->delegate = trayMenuDelegate;
|
||||
|
||||
msg(trayMenu->statusbaritem, s("setMenu:"), menu);
|
||||
}
|
||||
|
||||
@@ -153,6 +195,10 @@ void DeleteTrayMenu(TrayMenu* trayMenu) {
|
||||
trayMenu->statusbaritem = NULL;
|
||||
}
|
||||
|
||||
if ( trayMenu->delegate != NULL ) {
|
||||
msg(trayMenu->delegate, s("release"));
|
||||
}
|
||||
|
||||
// Free the tray menu memory
|
||||
MEMFREE(trayMenu);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,14 @@ typedef struct {
|
||||
const char *label;
|
||||
const char *icon;
|
||||
const char *ID;
|
||||
const char *tooltip;
|
||||
|
||||
bool templateImage;
|
||||
const char *fontName;
|
||||
int fontSize;
|
||||
const char *RGBA;
|
||||
|
||||
bool disabled;
|
||||
|
||||
Menu* menu;
|
||||
|
||||
@@ -21,6 +29,8 @@ typedef struct {
|
||||
|
||||
JsonNode* processedJSON;
|
||||
|
||||
id delegate;
|
||||
|
||||
} TrayMenu;
|
||||
|
||||
TrayMenu* NewTrayMenu(const char *trayJSON);
|
||||
@@ -28,7 +38,7 @@ void DumpTrayMenu(TrayMenu* trayMenu);
|
||||
void ShowTrayMenu(TrayMenu* trayMenu);
|
||||
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);
|
||||
void UpdateTrayIcon(TrayMenu *trayMenu);
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char*);
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled);
|
||||
|
||||
void LoadTrayIcons();
|
||||
void UnloadTrayIcons();
|
||||
|
||||
@@ -118,7 +118,17 @@ void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
|
||||
|
||||
// Check we have this menu
|
||||
TrayMenu *menu = MustGetTrayMenuFromStore(store, ID);
|
||||
UpdateTrayLabel(menu, Label);
|
||||
|
||||
const char *fontName = getJSONString(parsedUpdate, "FontName");
|
||||
const char *RGBA = getJSONString(parsedUpdate, "RGBA");
|
||||
int fontSize = 0;
|
||||
getJSONInt(parsedUpdate, "FontSize", &fontSize);
|
||||
const char *tooltip = getJSONString(parsedUpdate, "Tooltip");
|
||||
bool disabled = false;
|
||||
getJSONBool(parsedUpdate, "Disabled", &disabled);
|
||||
|
||||
UpdateTrayLabel(menu, Label, fontName, fontSize, RGBA, tooltip, disabled);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#ifndef TRAYMENUSTORE_DARWIN_H
|
||||
#define TRAYMENUSTORE_DARWIN_H
|
||||
|
||||
#include "traymenu_darwin.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
typedef struct {
|
||||
@@ -26,6 +28,8 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON);
|
||||
void ShowTrayMenusInStore(TrayMenuStore* store);
|
||||
void DeleteTrayMenuStore(TrayMenuStore* store);
|
||||
|
||||
TrayMenu* GetTrayMenuByID(TrayMenuStore* store, const char* menuID);
|
||||
|
||||
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON);
|
||||
void DeleteTrayMenuInStore(TrayMenuStore* store, const char* id);
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
)
|
||||
|
||||
// LocalDirectory gets the caller's file directory
|
||||
@@ -20,45 +18,11 @@ func LocalDirectory() string {
|
||||
return filepath.Dir(thisFile)
|
||||
}
|
||||
|
||||
// RelativeToCwd returns an absolute path based on the cwd
|
||||
// and the given relative path
|
||||
func RelativeToCwd(relativePath string) (string, error) {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Join(cwd, relativePath), nil
|
||||
}
|
||||
|
||||
// Mkdir will create the given directory
|
||||
func Mkdir(dirname string) error {
|
||||
return os.Mkdir(dirname, 0755)
|
||||
}
|
||||
|
||||
// MkDirs creates the given nested directories.
|
||||
// Returns error on failure
|
||||
func MkDirs(fullPath string, mode ...os.FileMode) error {
|
||||
var perms os.FileMode
|
||||
perms = 0755
|
||||
if len(mode) == 1 {
|
||||
perms = mode[0]
|
||||
}
|
||||
return os.MkdirAll(fullPath, perms)
|
||||
}
|
||||
|
||||
// MoveFile attempts to move the source file to the target
|
||||
// Target is a fully qualified path to a file *name*, not a
|
||||
// directory
|
||||
func MoveFile(source string, target string) error {
|
||||
return os.Rename(source, target)
|
||||
}
|
||||
|
||||
// DeleteFile will delete the given file
|
||||
func DeleteFile(filename string) error {
|
||||
return os.Remove(filename)
|
||||
}
|
||||
|
||||
// CopyFile from source to target
|
||||
func CopyFile(source string, target string) error {
|
||||
s, err := os.Open(source)
|
||||
@@ -180,100 +144,3 @@ func fatal(message ...string) {
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// GetSubdirectories returns a list of subdirectories for the given root directory
|
||||
func GetSubdirectories(rootDir string) (*slicer.StringSlicer, error) {
|
||||
var result slicer.StringSlicer
|
||||
|
||||
// Iterate root dir
|
||||
err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// If we have a directory, save it
|
||||
if info.IsDir() {
|
||||
result.Add(path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return &result, err
|
||||
}
|
||||
|
||||
func DirIsEmpty(dir string) (bool, error) {
|
||||
|
||||
if !DirExists(dir) {
|
||||
return false, fmt.Errorf("DirIsEmpty called with a non-existant directory: %s", dir)
|
||||
}
|
||||
|
||||
// CREDIT: https://stackoverflow.com/a/30708914/8325411
|
||||
f, err := os.Open(dir)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.Readdirnames(1) // Or f.Readdir(1)
|
||||
if err == io.EOF {
|
||||
return true, nil
|
||||
}
|
||||
return false, err // Either not empty or error, suits both cases
|
||||
}
|
||||
|
||||
// Credit: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04
|
||||
// CopyDir recursively copies a directory tree, attempting to preserve permissions.
|
||||
// Source directory must exist, destination directory must *not* exist.
|
||||
// Symlinks are ignored and skipped.
|
||||
func CopyDir(src string, dst string) (err error) {
|
||||
src = filepath.Clean(src)
|
||||
dst = filepath.Clean(dst)
|
||||
|
||||
si, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !si.IsDir() {
|
||||
return fmt.Errorf("source is not a directory")
|
||||
}
|
||||
|
||||
_, err = os.Stat(dst)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
return fmt.Errorf("destination already exists")
|
||||
}
|
||||
|
||||
err = MkDirs(dst)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
entries, err := ioutil.ReadDir(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
srcPath := filepath.Join(src, entry.Name())
|
||||
dstPath := filepath.Join(dst, entry.Name())
|
||||
|
||||
if entry.IsDir() {
|
||||
err = CopyDir(srcPath, dstPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Skip symlinks.
|
||||
if entry.Mode()&os.ModeSymlink != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
err = CopyFile(srcPath, dstPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,15 +3,11 @@ package html
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tdewolff/minify"
|
||||
"github.com/tdewolff/minify/js"
|
||||
)
|
||||
|
||||
type assetTypes struct {
|
||||
@@ -67,10 +63,6 @@ func (a *Asset) AsCHexData() string {
|
||||
result = strings.ReplaceAll(result, "\n", "")
|
||||
result = strings.ReplaceAll(result, "\r\n", "")
|
||||
result = strings.ReplaceAll(result, "\n", "")
|
||||
|
||||
// Inject wailsloader code
|
||||
result = strings.Replace(result, `</body>`, `<script id='wailsloader'>window.wailsloader = { html: true, runtime: false, userjs: false, usercss: false };var self=document.querySelector('#wailsloader');self.parentNode.removeChild(self);</script></body>`, 1)
|
||||
|
||||
url := url.URL{Path: result}
|
||||
urlString := strings.ReplaceAll(url.String(), "/", "%2f")
|
||||
|
||||
@@ -92,16 +84,6 @@ func (a *Asset) AsCHexData() string {
|
||||
result = strings.ReplaceAll(result, ` {`, `{`)
|
||||
result = strings.ReplaceAll(result, `: `, `:`)
|
||||
dataString = fmt.Sprintf("window.wails._.InjectCSS(\"%s\");", result)
|
||||
|
||||
case AssetTypes.JS:
|
||||
m := minify.New()
|
||||
m.AddFunc("application/javascript", js.Minify)
|
||||
var err error
|
||||
dataString, err = m.String("application/javascript", a.Data+";")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
a.Data = dataString
|
||||
}
|
||||
|
||||
// Get byte data of the string
|
||||
@@ -121,7 +103,6 @@ func (a *Asset) AsCHexData() string {
|
||||
return cdata.String()
|
||||
}
|
||||
|
||||
// Dump will output the asset to the terminal
|
||||
func (a *Asset) Dump() {
|
||||
fmt.Printf("{ Type: %s, Path: %s, Data: %+v }\n", a.Type, a.Path, a.Data[:10])
|
||||
}
|
||||
|
||||
@@ -61,8 +61,6 @@ func (a *AssetBundle) processHTML(htmldata string) error {
|
||||
buf := bytes.NewBufferString(htmldata)
|
||||
tokenizer := html.NewTokenizer(buf)
|
||||
|
||||
paths := slicer.String()
|
||||
|
||||
for {
|
||||
//get the next token type
|
||||
tokenType := tokenizer.Next()
|
||||
@@ -102,29 +100,19 @@ func (a *AssetBundle) processHTML(htmldata string) error {
|
||||
if attr.Key == "href" {
|
||||
asset.Path = attr.Val
|
||||
}
|
||||
// standard stylesheet
|
||||
// stylesheet
|
||||
if attr.Key == "rel" && attr.Val == "stylesheet" {
|
||||
asset.Type = AssetTypes.CSS
|
||||
}
|
||||
if attr.Key == "as" && attr.Val == "style" {
|
||||
asset.Type = AssetTypes.CSS
|
||||
}
|
||||
if attr.Key == "as" && attr.Val == "script" {
|
||||
asset.Type = AssetTypes.JS
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we don't include duplicates
|
||||
if !paths.Contains(asset.Path) {
|
||||
err := asset.Load(a.basedirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.assets = append(a.assets, asset)
|
||||
paths.Add(asset.Path)
|
||||
err := asset.Load(a.basedirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.assets = append(a.assets, asset)
|
||||
}
|
||||
if "script" == token.Data {
|
||||
|
||||
tokenType = tokenizer.Next()
|
||||
//just make sure it's actually a text token
|
||||
asset := &Asset{Type: AssetTypes.JS}
|
||||
@@ -134,14 +122,11 @@ func (a *AssetBundle) processHTML(htmldata string) error {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !paths.Contains(asset.Path) {
|
||||
err := asset.Load(a.basedirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.assets = append(a.assets, asset)
|
||||
paths.Add(asset.Path)
|
||||
err := asset.Load(a.basedirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.assets = append(a.assets, asset)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,7 +141,7 @@ func (a *AssetBundle) WriteToCFile(targetDir string) (string, error) {
|
||||
var cdata strings.Builder
|
||||
|
||||
// Write header
|
||||
header := `// assets.h
|
||||
header := `// assets.c
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL.
|
||||
// This file was auto-generated. DO NOT MODIFY.
|
||||
|
||||
@@ -168,10 +153,6 @@ func (a *AssetBundle) WriteToCFile(targetDir string) (string, error) {
|
||||
assetVariables := slicer.String()
|
||||
var variableName string
|
||||
for index, asset := range a.assets {
|
||||
// For desktop we ignore the favicon
|
||||
if asset.Type == AssetTypes.FAVICON {
|
||||
continue
|
||||
}
|
||||
variableName = fmt.Sprintf("%s%d", asset.Type, index)
|
||||
assetCdata := fmt.Sprintf("const unsigned char %s[]={ %s0x00 };\n", variableName, asset.AsCHexData())
|
||||
cdata.WriteString(assetCdata)
|
||||
@@ -179,13 +160,13 @@ func (a *AssetBundle) WriteToCFile(targetDir string) (string, error) {
|
||||
}
|
||||
|
||||
if assetVariables.Length() > 0 {
|
||||
cdata.WriteString(fmt.Sprintf("\nconst unsigned char *assets[] = { %s, 0x00 };", assetVariables.Join(", ")))
|
||||
cdata.WriteString(fmt.Sprintf("\nconst char *assets[] = { %s, 0x00 };", assetVariables.Join(", ")))
|
||||
} else {
|
||||
cdata.WriteString("\nconst unsigned char *assets[] = { 0x00 };")
|
||||
cdata.WriteString("\nconst char *assets[] = { 0x00 };")
|
||||
}
|
||||
|
||||
// Save file
|
||||
assetsFile := filepath.Join(targetDir, "assets.h")
|
||||
assetsFile := filepath.Join(targetDir, "assets.c")
|
||||
err = ioutil.WriteFile(assetsFile, []byte(cdata.String()), 0600)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -196,17 +177,16 @@ func (a *AssetBundle) WriteToCFile(targetDir string) (string, error) {
|
||||
// ConvertToAssetDB returns an assetdb.AssetDB initialized with
|
||||
// the items in the AssetBundle
|
||||
func (a *AssetBundle) ConvertToAssetDB() (*assetdb.AssetDB, error) {
|
||||
theassetdb := assetdb.NewAssetDB()
|
||||
assetdb := assetdb.NewAssetDB()
|
||||
|
||||
// Loop over the Assets
|
||||
for _, asset := range a.assets {
|
||||
theassetdb.AddAsset(asset.Path, []byte(asset.Data))
|
||||
assetdb.AddAsset(asset.Path, []byte(asset.Data))
|
||||
}
|
||||
|
||||
return theassetdb, nil
|
||||
return assetdb, nil
|
||||
}
|
||||
|
||||
// Dump will output the assets to the terminal
|
||||
func (a *AssetBundle) Dump() {
|
||||
println("Assets:")
|
||||
for _, asset := range a.assets {
|
||||
|
||||
@@ -2,30 +2,31 @@ package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// CustomLogger defines what a user can do with a logger
|
||||
type CustomLogger interface {
|
||||
// Writeln writes directly to the output with no log level plus line ending
|
||||
Writeln(message string)
|
||||
Writeln(message string) error
|
||||
|
||||
// Write writes directly to the output with no log level
|
||||
Write(message string)
|
||||
Write(message string) error
|
||||
|
||||
// Trace level logging. Works like Sprintf.
|
||||
Trace(format string, args ...interface{})
|
||||
Trace(format string, args ...interface{}) error
|
||||
|
||||
// Debug level logging. Works like Sprintf.
|
||||
Debug(format string, args ...interface{})
|
||||
Debug(format string, args ...interface{}) error
|
||||
|
||||
// Info level logging. Works like Sprintf.
|
||||
Info(format string, args ...interface{})
|
||||
Info(format string, args ...interface{}) error
|
||||
|
||||
// Warning level logging. Works like Sprintf.
|
||||
Warning(format string, args ...interface{})
|
||||
Warning(format string, args ...interface{}) error
|
||||
|
||||
// Error level logging. Works like Sprintf.
|
||||
Error(format string, args ...interface{})
|
||||
Error(format string, args ...interface{}) error
|
||||
|
||||
// Fatal level logging. Works like Sprintf.
|
||||
Fatal(format string, args ...interface{})
|
||||
@@ -49,48 +50,49 @@ func newcustomLogger(logger *Logger, name string) *customLogger {
|
||||
|
||||
// Writeln writes directly to the output with no log level
|
||||
// Appends a carriage return to the message
|
||||
func (l *customLogger) Writeln(message string) {
|
||||
l.logger.Writeln(message)
|
||||
func (l *customLogger) Writeln(message string) error {
|
||||
return l.logger.Writeln(message)
|
||||
}
|
||||
|
||||
// Write writes directly to the output with no log level
|
||||
func (l *customLogger) Write(message string) {
|
||||
l.logger.Write(message)
|
||||
func (l *customLogger) Write(message string) error {
|
||||
return l.logger.Write(message)
|
||||
}
|
||||
|
||||
// Trace level logging. Works like Sprintf.
|
||||
func (l *customLogger) Trace(format string, args ...interface{}) {
|
||||
func (l *customLogger) Trace(format string, args ...interface{}) error {
|
||||
format = fmt.Sprintf("%s | %s", l.name, format)
|
||||
l.logger.Trace(format, args...)
|
||||
return l.logger.processLogMessage(TRACE, format, args...)
|
||||
}
|
||||
|
||||
// Debug level logging. Works like Sprintf.
|
||||
func (l *customLogger) Debug(format string, args ...interface{}) {
|
||||
func (l *customLogger) Debug(format string, args ...interface{}) error {
|
||||
format = fmt.Sprintf("%s | %s", l.name, format)
|
||||
l.logger.Debug(format, args...)
|
||||
return l.logger.processLogMessage(DEBUG, format, args...)
|
||||
}
|
||||
|
||||
// Info level logging. Works like Sprintf.
|
||||
func (l *customLogger) Info(format string, args ...interface{}) {
|
||||
func (l *customLogger) Info(format string, args ...interface{}) error {
|
||||
format = fmt.Sprintf("%s | %s", l.name, format)
|
||||
l.logger.Info(format, args...)
|
||||
return l.logger.processLogMessage(INFO, format, args...)
|
||||
}
|
||||
|
||||
// Warning level logging. Works like Sprintf.
|
||||
func (l *customLogger) Warning(format string, args ...interface{}) {
|
||||
func (l *customLogger) Warning(format string, args ...interface{}) error {
|
||||
format = fmt.Sprintf("%s | %s", l.name, format)
|
||||
l.logger.Warning(format, args...)
|
||||
return l.logger.processLogMessage(WARNING, format, args...)
|
||||
}
|
||||
|
||||
// Error level logging. Works like Sprintf.
|
||||
func (l *customLogger) Error(format string, args ...interface{}) {
|
||||
func (l *customLogger) Error(format string, args ...interface{}) error {
|
||||
format = fmt.Sprintf("%s | %s", l.name, format)
|
||||
l.logger.Error(format, args...)
|
||||
return l.logger.processLogMessage(ERROR, format, args...)
|
||||
|
||||
}
|
||||
|
||||
// Fatal level logging. Works like Sprintf.
|
||||
func (l *customLogger) Fatal(format string, args ...interface{}) {
|
||||
format = fmt.Sprintf("%s | %s", l.name, format)
|
||||
l.logger.Fatal(format, args...)
|
||||
l.logger.processLogMessage(FATAL, format, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -2,32 +2,37 @@ package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// LogLevel is an alias for the public LogLevel
|
||||
type LogLevel = logger.LogLevel
|
||||
|
||||
// Logger is a utlility to log messages to a number of destinations
|
||||
type Logger struct {
|
||||
output logger.Logger
|
||||
logLevel LogLevel
|
||||
writers []io.Writer
|
||||
logLevel uint8
|
||||
showLevelInLog bool
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// New creates a new Logger. You may pass in a number of `io.Writer`s that
|
||||
// are the targets for the logs
|
||||
func New(output logger.Logger) *Logger {
|
||||
func New(writers ...io.Writer) *Logger {
|
||||
result := &Logger{
|
||||
logLevel: logger.INFO,
|
||||
logLevel: INFO,
|
||||
showLevelInLog: true,
|
||||
output: output,
|
||||
}
|
||||
for _, writer := range writers {
|
||||
result.AddOutput(writer)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Writers gets the log writers
|
||||
func (l *Logger) Writers() []io.Writer {
|
||||
return l.writers
|
||||
}
|
||||
|
||||
// CustomLogger creates a new custom logger that prints out a name/id
|
||||
// before the messages
|
||||
func (l *Logger) CustomLogger(name string) CustomLogger {
|
||||
@@ -40,66 +45,99 @@ func (l *Logger) HideLogLevel() {
|
||||
}
|
||||
|
||||
// SetLogLevel sets the minimum level of logs that will be output
|
||||
func (l *Logger) SetLogLevel(level LogLevel) {
|
||||
func (l *Logger) SetLogLevel(level uint8) {
|
||||
l.logLevel = level
|
||||
}
|
||||
|
||||
// AddOutput adds the given `io.Writer` to the list of destinations
|
||||
// that get logged to
|
||||
func (l *Logger) AddOutput(writer io.Writer) {
|
||||
l.writers = append(l.writers, writer)
|
||||
}
|
||||
|
||||
func (l *Logger) write(loglevel uint8, message string) error {
|
||||
|
||||
// Don't print logs lower than the current log level
|
||||
if loglevel < l.logLevel {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Show log level text if enabled
|
||||
if l.showLevelInLog {
|
||||
message = mapLogLevel[loglevel] + message
|
||||
}
|
||||
|
||||
// write out the logs
|
||||
l.lock.Lock()
|
||||
for _, writer := range l.writers {
|
||||
_, err := writer.Write([]byte(message))
|
||||
if err != nil {
|
||||
l.lock.Unlock() // Because defer is slow
|
||||
return err
|
||||
}
|
||||
}
|
||||
l.lock.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeln appends a newline character to the message before writing
|
||||
func (l *Logger) writeln(loglevel uint8, message string) error {
|
||||
return l.write(loglevel, message+"\n")
|
||||
}
|
||||
|
||||
// Writeln writes directly to the output with no log level
|
||||
// Appends a carriage return to the message
|
||||
func (l *Logger) Writeln(message string) {
|
||||
l.output.Print(message)
|
||||
func (l *Logger) Writeln(message string) error {
|
||||
return l.write(BYPASS, message+"\n")
|
||||
}
|
||||
|
||||
// Write writes directly to the output with no log level
|
||||
func (l *Logger) Write(message string) {
|
||||
l.output.Print(message)
|
||||
func (l *Logger) Write(message string) error {
|
||||
return l.write(BYPASS, message)
|
||||
}
|
||||
|
||||
// Print writes directly to the output with no log level
|
||||
// Appends a carriage return to the message
|
||||
func (l *Logger) Print(message string) {
|
||||
l.Write(message)
|
||||
// processLogMessage formats the given message before writing it out
|
||||
func (l *Logger) processLogMessage(loglevel uint8, format string, args ...interface{}) error {
|
||||
message := fmt.Sprintf(format, args...)
|
||||
return l.writeln(loglevel, message)
|
||||
}
|
||||
|
||||
// Trace level logging. Works like Sprintf.
|
||||
func (l *Logger) Trace(format string, args ...interface{}) {
|
||||
if l.logLevel <= logger.TRACE {
|
||||
l.output.Trace(fmt.Sprintf(format, args...))
|
||||
func (l *Logger) Trace(format string, args ...interface{}) error {
|
||||
return l.processLogMessage(TRACE, format, args...)
|
||||
}
|
||||
|
||||
// CustomTrace returns a custom Logging function that will insert the given name before the message
|
||||
func (l *Logger) CustomTrace(name string) func(format string, args ...interface{}) {
|
||||
return func(format string, args ...interface{}) {
|
||||
format = name + " | " + format
|
||||
l.processLogMessage(TRACE, format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// Debug level logging. Works like Sprintf.
|
||||
func (l *Logger) Debug(format string, args ...interface{}) {
|
||||
if l.logLevel <= logger.DEBUG {
|
||||
l.output.Debug(fmt.Sprintf(format, args...))
|
||||
}
|
||||
func (l *Logger) Debug(format string, args ...interface{}) error {
|
||||
return l.processLogMessage(DEBUG, format, args...)
|
||||
}
|
||||
|
||||
// Info level logging. Works like Sprintf.
|
||||
func (l *Logger) Info(format string, args ...interface{}) {
|
||||
if l.logLevel <= logger.INFO {
|
||||
l.output.Info(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (l *Logger) Info(format string, args ...interface{}) error {
|
||||
return l.processLogMessage(INFO, format, args...)
|
||||
}
|
||||
|
||||
// Warning level logging. Works like Sprintf.
|
||||
func (l *Logger) Warning(format string, args ...interface{}) {
|
||||
if l.logLevel <= logger.WARNING {
|
||||
l.output.Warning(fmt.Sprintf(format, args...))
|
||||
}
|
||||
func (l *Logger) Warning(format string, args ...interface{}) error {
|
||||
return l.processLogMessage(WARNING, format, args...)
|
||||
}
|
||||
|
||||
// Error level logging. Works like Sprintf.
|
||||
func (l *Logger) Error(format string, args ...interface{}) {
|
||||
if l.logLevel <= logger.ERROR {
|
||||
l.output.Error(fmt.Sprintf(format, args...))
|
||||
}
|
||||
func (l *Logger) Error(format string, args ...interface{}) error {
|
||||
return l.processLogMessage(ERROR, format, args...)
|
||||
|
||||
}
|
||||
|
||||
// Fatal level logging. Works like Sprintf.
|
||||
func (l *Logger) Fatal(format string, args ...interface{}) {
|
||||
l.output.Fatal(fmt.Sprintf(format, args...))
|
||||
l.processLogMessage(FATAL, format, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
34
v2/internal/logger/logger.go
Normal file
34
v2/internal/logger/logger.go
Normal 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: "",
|
||||
}
|
||||
202
v2/internal/logger/logger_test.go
Normal file
202
v2/internal/logger/logger_test.go
Normal 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")
|
||||
|
||||
}
|
||||
@@ -37,6 +37,7 @@ type ProcessedMenuItem struct {
|
||||
// Image - base64 image data
|
||||
Image string `json:",omitempty"`
|
||||
MacTemplateImage bool `json:", omitempty"`
|
||||
MacAlternate bool `json:", omitempty"`
|
||||
|
||||
// Tooltip
|
||||
Tooltip string `json:",omitempty"`
|
||||
@@ -60,6 +61,7 @@ func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *Pr
|
||||
FontName: menuItem.FontName,
|
||||
Image: menuItem.Image,
|
||||
MacTemplateImage: menuItem.MacTemplateImage,
|
||||
MacAlternate: menuItem.MacAlternate,
|
||||
Tooltip: menuItem.Tooltip,
|
||||
}
|
||||
|
||||
|
||||
@@ -23,12 +23,19 @@ func generateTrayID() string {
|
||||
}
|
||||
|
||||
type TrayMenu struct {
|
||||
ID string
|
||||
Label string
|
||||
Icon string
|
||||
menuItemMap *MenuItemMap
|
||||
menu *menu.Menu
|
||||
ProcessedMenu *WailsMenu
|
||||
ID string
|
||||
Label string
|
||||
FontSize int
|
||||
FontName string
|
||||
Disabled bool
|
||||
Tooltip string `json:",omitempty"`
|
||||
Image string
|
||||
MacTemplateImage bool
|
||||
RGBA string
|
||||
menuItemMap *MenuItemMap
|
||||
menu *menu.Menu
|
||||
ProcessedMenu *WailsMenu
|
||||
trayMenu *menu.TrayMenu
|
||||
}
|
||||
|
||||
func (t *TrayMenu) AsJSON() (string, error) {
|
||||
@@ -42,10 +49,17 @@ func (t *TrayMenu) AsJSON() (string, error) {
|
||||
func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||
|
||||
result := &TrayMenu{
|
||||
Label: trayMenu.Label,
|
||||
Icon: trayMenu.Icon,
|
||||
menu: trayMenu.Menu,
|
||||
menuItemMap: NewMenuItemMap(),
|
||||
Label: trayMenu.Label,
|
||||
FontName: trayMenu.FontName,
|
||||
FontSize: trayMenu.FontSize,
|
||||
Disabled: trayMenu.Disabled,
|
||||
Tooltip: trayMenu.Tooltip,
|
||||
Image: trayMenu.Image,
|
||||
MacTemplateImage: trayMenu.MacTemplateImage,
|
||||
menu: trayMenu.Menu,
|
||||
RGBA: trayMenu.RGBA,
|
||||
menuItemMap: NewMenuItemMap(),
|
||||
trayMenu: trayMenu,
|
||||
}
|
||||
|
||||
result.menuItemMap.AddMenu(trayMenu.Menu)
|
||||
@@ -54,6 +68,28 @@ func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *Manager) OnTrayMenuOpen(id string) {
|
||||
trayMenu, ok := m.trayMenus[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if trayMenu.trayMenu.OnOpen == nil {
|
||||
return
|
||||
}
|
||||
go trayMenu.trayMenu.OnOpen()
|
||||
}
|
||||
|
||||
func (m *Manager) OnTrayMenuClose(id string) {
|
||||
trayMenu, ok := m.trayMenus[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if trayMenu.trayMenu.OnClose == nil {
|
||||
return
|
||||
}
|
||||
go trayMenu.trayMenu.OnClose()
|
||||
}
|
||||
|
||||
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||
newTrayMenu := NewTrayMenu(trayMenu)
|
||||
|
||||
@@ -113,13 +149,27 @@ func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) {
|
||||
}
|
||||
|
||||
type LabelUpdate struct {
|
||||
ID string
|
||||
Label string
|
||||
ID string
|
||||
Label string
|
||||
FontName string
|
||||
FontSize int
|
||||
RGBA string
|
||||
Disabled bool
|
||||
Tooltip string
|
||||
Image string
|
||||
MacTemplateImage bool
|
||||
}
|
||||
|
||||
update := &LabelUpdate{
|
||||
ID: trayID,
|
||||
Label: trayMenu.Label,
|
||||
ID: trayID,
|
||||
Label: trayMenu.Label,
|
||||
FontName: trayMenu.FontName,
|
||||
FontSize: trayMenu.FontSize,
|
||||
Disabled: trayMenu.Disabled,
|
||||
Tooltip: trayMenu.Tooltip,
|
||||
Image: trayMenu.Image,
|
||||
MacTemplateImage: trayMenu.MacTemplateImage,
|
||||
RGBA: trayMenu.RGBA,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(update)
|
||||
|
||||
@@ -6,38 +6,19 @@ import (
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
)
|
||||
|
||||
// Client defines what a frontend client can do
|
||||
type Client interface {
|
||||
Quit()
|
||||
NotifyEvent(message string)
|
||||
CallResult(message string)
|
||||
OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string)
|
||||
SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string)
|
||||
MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string)
|
||||
SaveFileDialog(title string) string
|
||||
OpenFileDialog(title string) string
|
||||
OpenDirectoryDialog(title string) string
|
||||
WindowSetTitle(title string)
|
||||
WindowShow()
|
||||
WindowHide()
|
||||
WindowCenter()
|
||||
WindowMaximise()
|
||||
WindowUnmaximise()
|
||||
WindowMinimise()
|
||||
WindowUnminimise()
|
||||
WindowPosition(x int, y int)
|
||||
WindowSize(width int, height int)
|
||||
WindowSetMinSize(width int, height int)
|
||||
WindowSetMaxSize(width int, height int)
|
||||
WindowFullscreen()
|
||||
WindowUnFullscreen()
|
||||
WindowSetColour(colour int)
|
||||
DarkModeEnabled(callbackID string)
|
||||
SetApplicationMenu(menuJSON string)
|
||||
SetTrayMenu(trayMenuJSON string)
|
||||
UpdateTrayMenuLabel(JSON string)
|
||||
UpdateContextMenu(contextMenuJSON string)
|
||||
DeleteTrayMenuByID(id string)
|
||||
WindowSetColour(colour string) bool
|
||||
}
|
||||
|
||||
// DispatchClient is what the frontends use to interface with the
|
||||
@@ -72,7 +53,7 @@ func (d *DispatchClient) DispatchMessage(incomingMessage string) {
|
||||
d.logger.Trace(fmt.Sprintf("Received message: %+v", incomingMessage))
|
||||
parsedMessage, err := message.Parse(incomingMessage)
|
||||
if err != nil {
|
||||
d.logger.Error(err.Error())
|
||||
d.logger.Trace("Error: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -81,6 +62,14 @@ func (d *DispatchClient) DispatchMessage(incomingMessage string) {
|
||||
|
||||
d.logger.Trace("I got a parsedMessage: %+v", parsedMessage)
|
||||
|
||||
// Check error
|
||||
if err != nil {
|
||||
d.logger.Trace("Error: " + err.Error())
|
||||
// Hrm... what do we do with this?
|
||||
d.bus.PublishForTarget("generic:message", incomingMessage, d.id)
|
||||
return
|
||||
}
|
||||
|
||||
// Publish the parsed message
|
||||
d.bus.PublishForTarget(parsedMessage.Topic, parsedMessage.Data, d.id)
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
)
|
||||
|
||||
type CallMessage struct {
|
||||
Name string `json:"name"`
|
||||
Args []json.RawMessage `json:"args"`
|
||||
CallbackID string `json:"callbackID,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Args []interface{} `json:"args"`
|
||||
CallbackID string `json:"callbackID,omitempty"`
|
||||
}
|
||||
|
||||
// callMessageParser does what it says on the tin!
|
||||
@@ -22,7 +22,6 @@ func callMessageParser(message string) (*parsedMessage, error) {
|
||||
callMessage := new(CallMessage)
|
||||
|
||||
m := message[1:]
|
||||
|
||||
err := json.Unmarshal([]byte(m), callMessage)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package message
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
import "fmt"
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type EventMessage struct {
|
||||
Name string `json:"name"`
|
||||
@@ -13,7 +12,6 @@ type EventMessage struct {
|
||||
type OnEventMessage struct {
|
||||
Name string
|
||||
Callback func(optionalData ...interface{})
|
||||
Counter int
|
||||
}
|
||||
|
||||
// eventMessageParser does what it says on the tin!
|
||||
@@ -29,6 +27,8 @@ func eventMessageParser(message string) (*parsedMessage, error) {
|
||||
|
||||
// Switch the event type (with or without data)
|
||||
switch message[0] {
|
||||
case 'e':
|
||||
eventMessage.Name = message[2:]
|
||||
case 'E':
|
||||
m := message[2:]
|
||||
err := json.Unmarshal([]byte(m), eventMessage)
|
||||
|
||||
@@ -3,19 +3,16 @@ package message
|
||||
import "fmt"
|
||||
|
||||
var logMessageMap = map[byte]string{
|
||||
'P': "log:print",
|
||||
'T': "log:trace",
|
||||
'D': "log:debug",
|
||||
'I': "log:info",
|
||||
'W': "log:warning",
|
||||
'E': "log:error",
|
||||
'F': "log:fatal",
|
||||
'S': "log:setlevel",
|
||||
}
|
||||
|
||||
// logMessageParser does what it says on the tin!
|
||||
func logMessageParser(message string) (*parsedMessage, error) {
|
||||
|
||||
|
||||
// Sanity check: Log messages must be at least 2 bytes
|
||||
if len(message) < 2 {
|
||||
return nil, fmt.Errorf("log message was an invalid length")
|
||||
@@ -26,7 +23,7 @@ func logMessageParser(message string) (*parsedMessage, error) {
|
||||
|
||||
// If the type is invalid, raise error
|
||||
if messageTopic == "" {
|
||||
return nil, fmt.Errorf("log message type '%c' invalid", message[1])
|
||||
return nil, fmt.Errorf("log message type '%b' invalid", message[1])
|
||||
}
|
||||
|
||||
// Create a new parsed message struct
|
||||
|
||||
@@ -32,6 +32,14 @@ func menuMessageParser(message string) (*parsedMessage, error) {
|
||||
callbackid := message[2:]
|
||||
topic = "menu:clicked"
|
||||
data = callbackid
|
||||
case 'o':
|
||||
callbackid := message[2:]
|
||||
topic = "menu:ontrayopen"
|
||||
data = callbackid
|
||||
case 'c':
|
||||
callbackid := message[2:]
|
||||
topic = "menu:ontrayclose"
|
||||
data = callbackid
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid menu message: %s", message)
|
||||
}
|
||||
|
||||
@@ -14,26 +14,17 @@ var messageParsers = map[byte]func(string) (*parsedMessage, error){
|
||||
'L': logMessageParser,
|
||||
'R': runtimeMessageParser,
|
||||
'E': eventMessageParser,
|
||||
'e': eventMessageParser,
|
||||
'C': callMessageParser,
|
||||
'W': windowMessageParser,
|
||||
'D': dialogMessageParser,
|
||||
'S': systemMessageParser,
|
||||
'M': menuMessageParser,
|
||||
'T': trayMessageParser,
|
||||
'X': contextMenusMessageParser,
|
||||
'U': urlMessageParser,
|
||||
}
|
||||
|
||||
// Parse will attempt to parse the given message
|
||||
func Parse(message string) (*parsedMessage, error) {
|
||||
|
||||
if len(message) == 0 {
|
||||
return nil, fmt.Errorf("MessageParser received blank message")
|
||||
}
|
||||
|
||||
parseMethod := messageParsers[message[0]]
|
||||
if parseMethod == nil {
|
||||
return nil, fmt.Errorf("message type '%c' invalid", message[0])
|
||||
return nil, fmt.Errorf("message type '%b' invalid", message[0])
|
||||
}
|
||||
|
||||
return parseMethod(message)
|
||||
|
||||
@@ -22,14 +22,13 @@ func runtimeMessageParser(message string) (*parsedMessage, error) {
|
||||
|
||||
// processBrowserMessage expects messages of the following format:
|
||||
// RB<METHOD><DATA>
|
||||
// O = Open
|
||||
func processBrowserMessage(message string) (*parsedMessage, error) {
|
||||
method := message[2]
|
||||
switch method {
|
||||
case 'O':
|
||||
case 'U':
|
||||
// Open URL
|
||||
target := message[3:]
|
||||
return &parsedMessage{Topic: "runtime:browser:open", Data: target}, nil
|
||||
url := message[3:]
|
||||
return &parsedMessage{Topic: "runtime:browser:openurl", Data: url}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown browser message: %s", message)
|
||||
|
||||
@@ -40,7 +40,8 @@ func systemMessageParser(message string) (*parsedMessage, error) {
|
||||
// This is our startup hook - the frontend is now ready
|
||||
case 'S':
|
||||
topic := "hooks:startup"
|
||||
responseMessage = &parsedMessage{Topic: topic, Data: nil}
|
||||
startupURL := message[1:]
|
||||
responseMessage = &parsedMessage{Topic: topic, Data: startupURL}
|
||||
default:
|
||||
return nil, fmt.Errorf("Invalid message to systemMessageParser()")
|
||||
}
|
||||
|
||||
@@ -22,66 +22,6 @@ func windowMessageParser(message string) (*parsedMessage, error) {
|
||||
parsedMessage.Topic = "quit"
|
||||
parsedMessage.Data = "Window Closed"
|
||||
|
||||
// Center window
|
||||
case 'c':
|
||||
parsedMessage.Topic = "window:center"
|
||||
parsedMessage.Data = ""
|
||||
|
||||
// Hide window
|
||||
case 'H':
|
||||
parsedMessage.Topic = "window:hide"
|
||||
parsedMessage.Data = ""
|
||||
|
||||
// Show window
|
||||
case 'S':
|
||||
parsedMessage.Topic = "window:show"
|
||||
parsedMessage.Data = ""
|
||||
|
||||
// Position window
|
||||
case 'p':
|
||||
parsedMessage.Topic = "window:position:" + message[3:]
|
||||
parsedMessage.Data = ""
|
||||
|
||||
// Set window size
|
||||
case 's':
|
||||
parsedMessage.Topic = "window:size:" + message[3:]
|
||||
parsedMessage.Data = ""
|
||||
|
||||
// Maximise window
|
||||
case 'M':
|
||||
parsedMessage.Topic = "window:maximise"
|
||||
parsedMessage.Data = ""
|
||||
|
||||
// Unmaximise window
|
||||
case 'U':
|
||||
parsedMessage.Topic = "window:unmaximise"
|
||||
parsedMessage.Data = ""
|
||||
|
||||
// Minimise window
|
||||
case 'm':
|
||||
parsedMessage.Topic = "window:minimise"
|
||||
parsedMessage.Data = ""
|
||||
|
||||
// Unminimise window
|
||||
case 'u':
|
||||
parsedMessage.Topic = "window:unminimise"
|
||||
parsedMessage.Data = ""
|
||||
|
||||
// Fullscreen window
|
||||
case 'F':
|
||||
parsedMessage.Topic = "window:fullscreen"
|
||||
parsedMessage.Data = ""
|
||||
|
||||
// UnFullscreen window
|
||||
case 'f':
|
||||
parsedMessage.Topic = "window:unfullscreen"
|
||||
parsedMessage.Data = ""
|
||||
|
||||
// Set Title
|
||||
case 'T':
|
||||
parsedMessage.Topic = "window:settitle"
|
||||
parsedMessage.Data = message[2:]
|
||||
|
||||
// Unknown event type
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown message: %s", message)
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
package messagedispatcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/crypto"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
||||
@@ -23,8 +19,7 @@ type Dispatcher struct {
|
||||
eventChannel <-chan *servicebus.Message
|
||||
windowChannel <-chan *servicebus.Message
|
||||
dialogChannel <-chan *servicebus.Message
|
||||
systemChannel <-chan *servicebus.Message
|
||||
menuChannel <-chan *servicebus.Message
|
||||
running bool
|
||||
|
||||
servicebus *servicebus.ServiceBus
|
||||
logger logger.CustomLogger
|
||||
@@ -32,13 +27,6 @@ type Dispatcher struct {
|
||||
// Clients
|
||||
clients map[string]*DispatchClient
|
||||
lock sync.RWMutex
|
||||
|
||||
// Context for cancellation
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
// internal wait group
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// New dispatcher. Needs a service bus to send to.
|
||||
@@ -73,19 +61,6 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
systemChannel, err := servicebus.Subscribe("system:")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
menuChannel, err := servicebus.Subscribe("menufrontend:")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create context
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
result := &Dispatcher{
|
||||
servicebus: servicebus,
|
||||
eventChannel: eventChannel,
|
||||
@@ -95,10 +70,6 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher,
|
||||
quitChannel: quitChannel,
|
||||
windowChannel: windowChannel,
|
||||
dialogChannel: dialogChannel,
|
||||
systemChannel: systemChannel,
|
||||
menuChannel: menuChannel,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
@@ -109,18 +80,15 @@ func (d *Dispatcher) Start() error {
|
||||
|
||||
d.logger.Trace("Starting")
|
||||
|
||||
d.wg.Add(1)
|
||||
d.running = true
|
||||
|
||||
// Spin off a go routine
|
||||
go func() {
|
||||
defer d.logger.Trace("Shutdown")
|
||||
for {
|
||||
for d.running {
|
||||
select {
|
||||
case <-d.ctx.Done():
|
||||
d.wg.Done()
|
||||
return
|
||||
case <-d.quitChannel:
|
||||
d.processQuit()
|
||||
d.running = false
|
||||
case resultMessage := <-d.resultChannel:
|
||||
d.processCallResult(resultMessage)
|
||||
case eventMessage := <-d.eventChannel:
|
||||
@@ -129,12 +97,11 @@ func (d *Dispatcher) Start() error {
|
||||
d.processWindowMessage(windowMessage)
|
||||
case dialogMessage := <-d.dialogChannel:
|
||||
d.processDialogMessage(dialogMessage)
|
||||
case systemMessage := <-d.systemChannel:
|
||||
d.processSystemMessage(systemMessage)
|
||||
case menuMessage := <-d.menuChannel:
|
||||
d.processMenuMessage(menuMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// Call shutdown
|
||||
d.shutdown()
|
||||
}()
|
||||
|
||||
return nil
|
||||
@@ -148,6 +115,10 @@ func (d *Dispatcher) processQuit() {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Dispatcher) shutdown() {
|
||||
d.logger.Trace("Shutdown")
|
||||
}
|
||||
|
||||
// RegisterClient will register the given callback with the dispatcher
|
||||
// and return a DispatchClient that the caller can use to send messages
|
||||
func (d *Dispatcher) RegisterClient(client Client) *DispatchClient {
|
||||
@@ -195,35 +166,12 @@ func (d *Dispatcher) processCallResult(result *servicebus.Message) {
|
||||
if client == nil {
|
||||
// This is fatal - unknown target!
|
||||
d.logger.Fatal("Unknown target for call result: %+v", result)
|
||||
return
|
||||
}
|
||||
|
||||
d.logger.Trace("Sending message to client %s: R%s", target, result.Data().(string))
|
||||
client.frontend.CallResult(result.Data().(string))
|
||||
}
|
||||
|
||||
// processSystem
|
||||
func (d *Dispatcher) processSystemMessage(result *servicebus.Message) {
|
||||
|
||||
d.logger.Trace("Got system in message dispatcher: %+v", result)
|
||||
|
||||
splitTopic := strings.Split(result.Topic(), ":")
|
||||
command := splitTopic[1]
|
||||
callbackID := splitTopic[2]
|
||||
switch command {
|
||||
case "isdarkmode":
|
||||
d.lock.RLock()
|
||||
for _, client := range d.clients {
|
||||
client.frontend.DarkModeEnabled(callbackID)
|
||||
break
|
||||
}
|
||||
d.lock.RUnlock()
|
||||
|
||||
default:
|
||||
d.logger.Error("Unknown system command: %s", command)
|
||||
}
|
||||
}
|
||||
|
||||
// processEvent will
|
||||
func (d *Dispatcher) processEvent(result *servicebus.Message) {
|
||||
|
||||
@@ -282,7 +230,7 @@ func (d *Dispatcher) processWindowMessage(result *servicebus.Message) {
|
||||
client.frontend.WindowUnFullscreen()
|
||||
}
|
||||
case "setcolour":
|
||||
colour, ok := result.Data().(int)
|
||||
colour, ok := result.Data().(string)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid colour for 'window:setcolour' : %#v", result.Data())
|
||||
return
|
||||
@@ -291,105 +239,6 @@ func (d *Dispatcher) processWindowMessage(result *servicebus.Message) {
|
||||
for _, client := range d.clients {
|
||||
client.frontend.WindowSetColour(colour)
|
||||
}
|
||||
case "show":
|
||||
// Notify clients
|
||||
for _, client := range d.clients {
|
||||
client.frontend.WindowShow()
|
||||
}
|
||||
case "hide":
|
||||
// Notify clients
|
||||
for _, client := range d.clients {
|
||||
client.frontend.WindowHide()
|
||||
}
|
||||
case "center":
|
||||
// Notify clients
|
||||
for _, client := range d.clients {
|
||||
client.frontend.WindowCenter()
|
||||
}
|
||||
case "maximise":
|
||||
// Notify clients
|
||||
for _, client := range d.clients {
|
||||
client.frontend.WindowMaximise()
|
||||
}
|
||||
case "unmaximise":
|
||||
// Notify clients
|
||||
for _, client := range d.clients {
|
||||
client.frontend.WindowUnmaximise()
|
||||
}
|
||||
case "minimise":
|
||||
// Notify clients
|
||||
for _, client := range d.clients {
|
||||
client.frontend.WindowMinimise()
|
||||
}
|
||||
case "unminimise":
|
||||
// Notify clients
|
||||
for _, client := range d.clients {
|
||||
client.frontend.WindowUnminimise()
|
||||
}
|
||||
case "position":
|
||||
// We need 2 arguments
|
||||
if len(splitTopic) != 4 {
|
||||
d.logger.Error("Invalid number of parameters for 'window:position' : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
x, err1 := strconv.Atoi(splitTopic[2])
|
||||
y, err2 := strconv.Atoi(splitTopic[3])
|
||||
if err1 != nil || err2 != nil {
|
||||
d.logger.Error("Invalid integer parameters for 'window:position' : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
// Notify clients
|
||||
for _, client := range d.clients {
|
||||
client.frontend.WindowPosition(x, y)
|
||||
}
|
||||
case "size":
|
||||
// We need 2 arguments
|
||||
if len(splitTopic) != 4 {
|
||||
d.logger.Error("Invalid number of parameters for 'window:size' : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
w, err1 := strconv.Atoi(splitTopic[2])
|
||||
h, err2 := strconv.Atoi(splitTopic[3])
|
||||
if err1 != nil || err2 != nil {
|
||||
d.logger.Error("Invalid integer parameters for 'window:size' : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
// Notifh clients
|
||||
for _, client := range d.clients {
|
||||
client.frontend.WindowSize(w, h)
|
||||
}
|
||||
case "minsize":
|
||||
// We need 2 arguments
|
||||
if len(splitTopic) != 4 {
|
||||
d.logger.Error("Invalid number of parameters for 'window:minsize' : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
w, err1 := strconv.Atoi(splitTopic[2])
|
||||
h, err2 := strconv.Atoi(splitTopic[3])
|
||||
if err1 != nil || err2 != nil {
|
||||
d.logger.Error("Invalid integer parameters for 'window:minsize' : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
// Notifh clients
|
||||
for _, client := range d.clients {
|
||||
client.frontend.WindowSetMinSize(w, h)
|
||||
}
|
||||
case "maxsize":
|
||||
// We need 2 arguments
|
||||
if len(splitTopic) != 4 {
|
||||
d.logger.Error("Invalid number of parameters for 'window:maxsize' : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
w, err1 := strconv.Atoi(splitTopic[2])
|
||||
h, err2 := strconv.Atoi(splitTopic[3])
|
||||
if err1 != nil || err2 != nil {
|
||||
d.logger.Error("Invalid integer parameters for 'window:maxsize' : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
// Notifh clients
|
||||
for _, client := range d.clients {
|
||||
client.frontend.WindowSetMaxSize(w, h)
|
||||
}
|
||||
default:
|
||||
d.logger.Error("Unknown window command: %s", command)
|
||||
}
|
||||
@@ -400,6 +249,7 @@ func (d *Dispatcher) processWindowMessage(result *servicebus.Message) {
|
||||
// processDialogMessage processes dialog messages
|
||||
func (d *Dispatcher) processDialogMessage(result *servicebus.Message) {
|
||||
splitTopic := strings.Split(result.Topic(), ":")
|
||||
|
||||
if len(splitTopic) < 4 {
|
||||
d.logger.Error("Invalid dialog message : %#v", result.Data())
|
||||
return
|
||||
@@ -409,142 +259,61 @@ func (d *Dispatcher) processDialogMessage(result *servicebus.Message) {
|
||||
switch command {
|
||||
case "select":
|
||||
dialogType := splitTopic[2]
|
||||
title := splitTopic[3]
|
||||
switch dialogType {
|
||||
case "open":
|
||||
dialogOptions, ok := result.Data().(*dialog.OpenDialog)
|
||||
case "file":
|
||||
responseTopic, ok := result.Data().(string)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid data for 'dialog:select:open' : %#v", result.Data())
|
||||
d.logger.Error("Invalid responseTopic for 'dialog:select:file' : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
// This is hardcoded in the sender too
|
||||
callbackID := splitTopic[3]
|
||||
d.logger.Info("Opening File dialog! responseTopic = %s", responseTopic)
|
||||
|
||||
// TODO: Work out what we mean in a multi window environment...
|
||||
// For now we will just pick the first one
|
||||
var result string
|
||||
for _, client := range d.clients {
|
||||
client.frontend.OpenDialog(dialogOptions, callbackID)
|
||||
result = client.frontend.OpenFileDialog(title)
|
||||
}
|
||||
case "save":
|
||||
dialogOptions, ok := result.Data().(*dialog.SaveDialog)
|
||||
|
||||
// Send dummy response
|
||||
d.servicebus.Publish(responseTopic, result)
|
||||
case "filesave":
|
||||
responseTopic, ok := result.Data().(string)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid data for 'dialog:select:save' : %#v", result.Data())
|
||||
d.logger.Error("Invalid responseTopic for 'dialog:select:filesave' : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
// This is hardcoded in the sender too
|
||||
callbackID := splitTopic[3]
|
||||
d.logger.Info("Opening Save File dialog! responseTopic = %s", responseTopic)
|
||||
|
||||
// TODO: Work out what we mean in a multi window environment...
|
||||
// For now we will just pick the first one
|
||||
var result string
|
||||
for _, client := range d.clients {
|
||||
client.frontend.SaveDialog(dialogOptions, callbackID)
|
||||
result = client.frontend.SaveFileDialog(title)
|
||||
}
|
||||
case "message":
|
||||
dialogOptions, ok := result.Data().(*dialog.MessageDialog)
|
||||
|
||||
// Send dummy response
|
||||
d.servicebus.Publish(responseTopic, result)
|
||||
case "directory":
|
||||
responseTopic, ok := result.Data().(string)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid data for 'dialog:select:message' : %#v", result.Data())
|
||||
d.logger.Error("Invalid responseTopic for 'dialog:select:directory' : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
// This is hardcoded in the sender too
|
||||
callbackID := splitTopic[3]
|
||||
d.logger.Info("Opening Directory dialog! responseTopic = %s", responseTopic)
|
||||
|
||||
// TODO: Work out what we mean in a multi window environment...
|
||||
// For now we will just pick the first one
|
||||
var result string
|
||||
for _, client := range d.clients {
|
||||
client.frontend.MessageDialog(dialogOptions, callbackID)
|
||||
result = client.frontend.OpenDirectoryDialog(title)
|
||||
}
|
||||
// Send dummy response
|
||||
d.servicebus.Publish(responseTopic, result)
|
||||
|
||||
default:
|
||||
d.logger.Error("Unknown dialog type: %s", dialogType)
|
||||
d.logger.Error("Unknown dialog command: %s", command)
|
||||
}
|
||||
|
||||
default:
|
||||
d.logger.Error("Unknown dialog command: %s", command)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
|
||||
splitTopic := strings.Split(result.Topic(), ":")
|
||||
if len(splitTopic) < 2 {
|
||||
d.logger.Error("Invalid menu message : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
|
||||
command := splitTopic[1]
|
||||
switch command {
|
||||
case "updateappmenu":
|
||||
|
||||
updatedMenu, ok := result.Data().(string)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid data for 'menufrontend:updateappmenu' : %#v",
|
||||
result.Data())
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Work out what we mean in a multi window environment...
|
||||
// For now we will just pick the first one
|
||||
for _, client := range d.clients {
|
||||
client.frontend.SetApplicationMenu(updatedMenu)
|
||||
}
|
||||
|
||||
case "settraymenu":
|
||||
trayMenuJSON, ok := result.Data().(string)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid data for 'menufrontend:settraymenu' : %#v",
|
||||
result.Data())
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Work out what we mean in a multi window environment...
|
||||
// For now we will just pick the first one
|
||||
for _, client := range d.clients {
|
||||
client.frontend.SetTrayMenu(trayMenuJSON)
|
||||
}
|
||||
|
||||
case "updatecontextmenu":
|
||||
updatedContextMenu, ok := result.Data().(string)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid data for 'menufrontend:updatecontextmenu' : %#v",
|
||||
result.Data())
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Work out what we mean in a multi window environment...
|
||||
// For now we will just pick the first one
|
||||
for _, client := range d.clients {
|
||||
client.frontend.UpdateContextMenu(updatedContextMenu)
|
||||
}
|
||||
|
||||
case "updatetraymenulabel":
|
||||
updatedTrayMenuLabel, ok := result.Data().(string)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid data for 'menufrontend:updatetraymenulabel' : %#v",
|
||||
result.Data())
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Work out what we mean in a multi window environment...
|
||||
// For now we will just pick the first one
|
||||
for _, client := range d.clients {
|
||||
client.frontend.UpdateTrayMenuLabel(updatedTrayMenuLabel)
|
||||
}
|
||||
case "deletetraymenu":
|
||||
traymenuid, ok := result.Data().(string)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid data for 'menufrontend:updatetraymenulabel' : %#v",
|
||||
result.Data())
|
||||
return
|
||||
}
|
||||
|
||||
for _, client := range d.clients {
|
||||
client.frontend.DeleteTrayMenuByID(traymenuid)
|
||||
}
|
||||
|
||||
default:
|
||||
d.logger.Error("Unknown menufrontend command: %s", command)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Dispatcher) Close() {
|
||||
d.cancel()
|
||||
d.wg.Wait()
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
var internalMethods = slicer.String([]string{"WailsInit", "Wails Shutdown"})
|
||||
|
||||
var structCache = make(map[string]*ParsedStruct)
|
||||
var boundStructs = make(map[string]*ParsedStruct)
|
||||
var boundMethods = []string{}
|
||||
@@ -47,7 +49,7 @@ func ParseProject(projectPath string) (BoundStructs, error) {
|
||||
cfg := &packages.Config{Mode: packages.NeedFiles | packages.NeedSyntax | packages.NeedTypesInfo}
|
||||
pkgs, err := packages.Load(cfg, projectPath)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "load: %v\n", err)
|
||||
fmt.Fprintf(os.Stderr, "load: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if packages.PrintErrors(pkgs) > 0 {
|
||||
@@ -63,6 +65,7 @@ func ParseProject(projectPath string) (BoundStructs, error) {
|
||||
var wailsPkgVar = ""
|
||||
|
||||
ast.Inspect(file, func(n ast.Node) bool {
|
||||
var s string
|
||||
switch x := n.(type) {
|
||||
// Parse import declarations
|
||||
case *ast.ImportSpec:
|
||||
@@ -201,6 +204,10 @@ func ParseProject(projectPath string) (BoundStructs, error) {
|
||||
// This is a struct pointer method
|
||||
i, ok := se.X.(*ast.Ident)
|
||||
if ok {
|
||||
// We want to ignore Internal functions
|
||||
if internalMethods.Contains(x.Name.Name) {
|
||||
continue
|
||||
}
|
||||
// If we haven't already found this struct,
|
||||
// Create a placeholder in the cache
|
||||
parsedStruct := structCache[i.Name]
|
||||
@@ -431,6 +438,4 @@ func ParseProject(projectPath string) (BoundStructs, error) {
|
||||
println()
|
||||
println("}")
|
||||
println()
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
)
|
||||
|
||||
// Process defines a process that can be executed
|
||||
type Process struct {
|
||||
logger *clilogger.CLILogger
|
||||
logger *logger.Logger
|
||||
cmd *exec.Cmd
|
||||
exitChannel chan bool
|
||||
Running bool
|
||||
}
|
||||
|
||||
// NewProcess creates a new process struct
|
||||
func NewProcess(logger *clilogger.CLILogger, cmd string, args ...string) *Process {
|
||||
result := &Process{
|
||||
func NewProcess(logger *logger.Logger, cmd string, args ...string) *Process {
|
||||
return &Process{
|
||||
logger: logger,
|
||||
cmd: exec.Command(cmd, args...),
|
||||
exitChannel: make(chan bool, 1),
|
||||
}
|
||||
result.cmd.Stdout = os.Stdout
|
||||
result.cmd.Stderr = os.Stderr
|
||||
return result
|
||||
}
|
||||
|
||||
// Start the process
|
||||
@@ -37,15 +33,10 @@ func (p *Process) Start() error {
|
||||
|
||||
p.Running = true
|
||||
|
||||
go func(cmd *exec.Cmd, running *bool, logger *clilogger.CLILogger, exitChannel chan bool) {
|
||||
logger.Println("Starting process (PID: %d)", cmd.Process.Pid)
|
||||
err := cmd.Wait()
|
||||
if err != nil {
|
||||
if err.Error() != "signal: killed" {
|
||||
logger.Fatal("Fatal error from app: " + err.Error())
|
||||
}
|
||||
}
|
||||
logger.Println("Exiting process (PID: %d)", cmd.Process.Pid)
|
||||
go func(cmd *exec.Cmd, running *bool, logger *logger.Logger, exitChannel chan bool) {
|
||||
logger.Info("Starting process (PID: %d)", cmd.Process.Pid)
|
||||
cmd.Wait()
|
||||
logger.Info("Exiting process (PID: %d)", cmd.Process.Pid)
|
||||
*running = false
|
||||
exitChannel <- true
|
||||
}(p.cmd, &p.Running, p.logger, p.exitChannel)
|
||||
|
||||
@@ -25,9 +25,6 @@ type Project struct {
|
||||
// The path to the project directory
|
||||
Path string
|
||||
|
||||
// Assets directory
|
||||
AssetsDir string `json:"assetsdir"`
|
||||
|
||||
// The output filename
|
||||
OutputFilename string `json:"outputfilename"`
|
||||
|
||||
@@ -36,15 +33,6 @@ type Project struct {
|
||||
|
||||
// The platform to target
|
||||
Platform string
|
||||
|
||||
// The application author
|
||||
Author Author
|
||||
}
|
||||
|
||||
// Author stores details about the application author
|
||||
type Author struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// Load the project from the current working directory
|
||||
@@ -75,11 +63,6 @@ func Load(projectPath string) (*Project, error) {
|
||||
result.Name = "wailsapp"
|
||||
}
|
||||
|
||||
// Set default assets directory if none given
|
||||
if result.AssetsDir == "" {
|
||||
result.AssetsDir = filepath.Join(result.Path, "assets")
|
||||
}
|
||||
|
||||
// Fix up OutputFilename
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
|
||||
36
v2/internal/runtime/goruntime/browser.go
Normal file
36
v2/internal/runtime/goruntime/browser.go
Normal 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{}
|
||||
}
|
||||
16
v2/internal/runtime/goruntime/browser_test.go
Normal file
16
v2/internal/runtime/goruntime/browser_test.go
Normal 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")
|
||||
}
|
||||
105
v2/internal/runtime/goruntime/dialog.go
Normal file
105
v2/internal/runtime/goruntime/dialog.go
Normal 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)
|
||||
}
|
||||
43
v2/internal/runtime/goruntime/events.go
Normal file
43
v2/internal/runtime/goruntime/events.go
Normal 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)
|
||||
}
|
||||
28
v2/internal/runtime/goruntime/runtime.go
Normal file
28
v2/internal/runtime/goruntime/runtime.go
Normal 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()")
|
||||
}
|
||||
54
v2/internal/runtime/goruntime/window.go
Normal file
54
v2/internal/runtime/goruntime/window.go
Normal 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)
|
||||
}
|
||||
@@ -65,7 +65,7 @@ export function SetBindings(bindingsMap) {
|
||||
window.backend[packageName][structName][methodName] = function () {
|
||||
|
||||
// No timeout by default
|
||||
let timeout = 0;
|
||||
var timeout = 0;
|
||||
|
||||
// Actual function
|
||||
function dynamic() {
|
||||
@@ -89,3 +89,19 @@ export function SetBindings(bindingsMap) {
|
||||
});
|
||||
});
|
||||
}
|
||||
// /**
|
||||
// * Determines if the given identifier is valid Javascript
|
||||
// *
|
||||
// * @param {boolean} name
|
||||
// * @returns
|
||||
// */
|
||||
// function isValidIdentifier(name) {
|
||||
// // Don't xss yourself :-)
|
||||
// try {
|
||||
// new Function('var ' + name);
|
||||
// return true;
|
||||
// } catch (e) {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
@@ -12,13 +12,23 @@ The lightweight framework for web-like apps
|
||||
import { SendMessage } from 'ipc';
|
||||
|
||||
/**
|
||||
* Opens the given URL / filename in the system browser
|
||||
* Opens the given URL in the system browser
|
||||
*
|
||||
* @export
|
||||
* @param {string} target
|
||||
* @param {string} url
|
||||
* @returns
|
||||
*/
|
||||
export function Open(target) {
|
||||
return SendMessage('RBO' + target);
|
||||
export function OpenURL(url) {
|
||||
return SendMessage('RBU' + url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the given filename using the system's default file handler
|
||||
*
|
||||
* @export
|
||||
* @param {sting} filename
|
||||
* @returns
|
||||
*/
|
||||
export function OpenFile(filename) {
|
||||
return SendMessage('runtime:browser:openfile', filename);
|
||||
}
|
||||
|
||||
@@ -151,7 +151,6 @@ export function Callback(incomingMessage) {
|
||||
* @param {any[]=} data
|
||||
* @returns
|
||||
*/
|
||||
export function SystemCall(method) {
|
||||
var data = [].slice.apply(arguments).slice(1);
|
||||
export function SystemCall(method, data) {
|
||||
return Call('.wails.' + method, data);
|
||||
}
|
||||
|
||||
@@ -10,19 +10,16 @@ The lightweight framework for web-like apps
|
||||
/* jshint esversion: 6 */
|
||||
import { SetBindings } from './bindings';
|
||||
import { Init } from './main';
|
||||
import {RaiseError} from '../desktop/darwin';
|
||||
|
||||
// Setup global error handler
|
||||
window.onerror = function (msg, url, lineNo, columnNo, error) {
|
||||
const errorMessage = {
|
||||
message: msg,
|
||||
url: url,
|
||||
line: lineNo,
|
||||
column: columnNo,
|
||||
error: JSON.stringify(error),
|
||||
stack: function() { return JSON.stringify(new Error().stack); }(),
|
||||
};
|
||||
RaiseError(errorMessage);
|
||||
window.onerror = function (/*msg, url, lineNo, columnNo, error*/) {
|
||||
// window.wails.Log.Error('**** Caught Unhandled Error ****');
|
||||
// window.wails.Log.Error('Message: ' + msg);
|
||||
// window.wails.Log.Error('URL: ' + url);
|
||||
// window.wails.Log.Error('Line No: ' + lineNo);
|
||||
// window.wails.Log.Error('Column No: ' + columnNo);
|
||||
// window.wails.Log.Error('error: ' + error);
|
||||
(function () { window.wails.Log.Error(new Error().stack); })();
|
||||
};
|
||||
|
||||
// Initialise the Runtime
|
||||
|
||||
@@ -13,7 +13,6 @@ import { Error } from './log';
|
||||
import { SendMessage } from 'ipc';
|
||||
|
||||
// Defines a single listener with a maximum number of times to callback
|
||||
|
||||
/**
|
||||
* The Listener class defines a listener! :-)
|
||||
*
|
||||
@@ -44,7 +43,7 @@ class Listener {
|
||||
}
|
||||
}
|
||||
|
||||
let eventListeners = {};
|
||||
var eventListeners = {};
|
||||
|
||||
/**
|
||||
* Registers an event listener that will be invoked `maxCallbacks` times before being destroyed
|
||||
@@ -72,17 +71,6 @@ export function On(eventName, callback) {
|
||||
OnMultiple(eventName, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers listeners for when the system theme changes from light/dark. A bool is
|
||||
* sent to the listener, true if it is dark mode.
|
||||
*
|
||||
* @export
|
||||
* @param {function} callback
|
||||
*/
|
||||
export function OnThemeChange(callback) {
|
||||
On('wails:system:themechange', callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an event listener that will be invoked once then destroyed
|
||||
*
|
||||
@@ -94,43 +82,11 @@ export function Once(eventName, callback) {
|
||||
OnMultiple(eventName, callback, 1);
|
||||
}
|
||||
|
||||
function notifyListeners(eventData) {
|
||||
|
||||
// Get the event name
|
||||
let eventName = eventData.name;
|
||||
|
||||
// Check if we have any listeners for this event
|
||||
if (eventListeners[eventName]) {
|
||||
|
||||
// Keep a list of listener indexes to destroy
|
||||
const newEventListenerList = eventListeners[eventName].slice();
|
||||
|
||||
// Iterate listeners
|
||||
for (let count = 0; count < eventListeners[eventName].length; count += 1) {
|
||||
|
||||
// Get next listener
|
||||
const listener = eventListeners[eventName][count];
|
||||
|
||||
let data = eventData.data;
|
||||
|
||||
// Do the callback
|
||||
const destroy = listener.Callback(data);
|
||||
if (destroy) {
|
||||
// if the listener indicated to destroy itself, add it to the destroy list
|
||||
newEventListenerList.splice(count, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Update callbacks with new list of listeners
|
||||
eventListeners[eventName] = newEventListenerList;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify informs frontend listeners that an event was emitted with the given data
|
||||
*
|
||||
* @export
|
||||
* @param {string} notifyMessage - encoded notification message
|
||||
* @param {string} encoded notification message
|
||||
|
||||
*/
|
||||
export function Notify(notifyMessage) {
|
||||
@@ -144,7 +100,33 @@ export function Notify(notifyMessage) {
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
notifyListeners(message);
|
||||
var eventName = message.name;
|
||||
|
||||
// Check if we have any listeners for this event
|
||||
if (eventListeners[eventName]) {
|
||||
|
||||
// Keep a list of listener indexes to destroy
|
||||
const newEventListenerList = eventListeners[eventName].slice();
|
||||
|
||||
// Iterate listeners
|
||||
for (let count = 0; count < eventListeners[eventName].length; count += 1) {
|
||||
|
||||
// Get next listener
|
||||
const listener = eventListeners[eventName][count];
|
||||
|
||||
var data = message.data;
|
||||
|
||||
// Do the callback
|
||||
const destroy = listener.Callback(data);
|
||||
if (destroy) {
|
||||
// if the listener indicated to destroy itself, add it to the destroy list
|
||||
newEventListenerList.splice(count, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Update callbacks with new list of listners
|
||||
eventListeners[eventName] = newEventListenerList;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,15 +137,66 @@ export function Notify(notifyMessage) {
|
||||
*/
|
||||
export function Emit(eventName) {
|
||||
|
||||
const payload = {
|
||||
name: eventName,
|
||||
data: [].slice.apply(arguments).slice(1),
|
||||
};
|
||||
// Calculate the data
|
||||
if (arguments.length > 1) {
|
||||
// Notify backend
|
||||
const payload = {
|
||||
name: eventName,
|
||||
data: [].slice.apply(arguments).slice(1),
|
||||
};
|
||||
SendMessage('Ej' + JSON.stringify(payload));
|
||||
} else {
|
||||
SendMessage('ej' + eventName);
|
||||
}
|
||||
|
||||
// Notify JS listeners
|
||||
notifyListeners(payload);
|
||||
}
|
||||
|
||||
// Notify Go listeners
|
||||
SendMessage('Ej' + JSON.stringify(payload));
|
||||
// Callbacks for the heartbeat calls
|
||||
const heartbeatCallbacks = {};
|
||||
|
||||
}
|
||||
/**
|
||||
* Heartbeat emits the event `eventName`, every `timeInMilliseconds` milliseconds until
|
||||
* the event is acknowledged via `Event.Acknowledge`. Once this happens, `callback` is invoked ONCE
|
||||
*
|
||||
* @export
|
||||
* @param {string} eventName
|
||||
* @param {number} timeInMilliseconds
|
||||
* @param {function} callback
|
||||
*/
|
||||
export function Heartbeat(eventName, timeInMilliseconds, callback) {
|
||||
|
||||
// Declare interval variable
|
||||
let interval = null;
|
||||
|
||||
// Setup callback
|
||||
function dynamicCallback() {
|
||||
// Kill interval
|
||||
clearInterval(interval);
|
||||
// Callback
|
||||
callback();
|
||||
}
|
||||
|
||||
// Register callback
|
||||
heartbeatCallbacks[eventName] = dynamicCallback;
|
||||
|
||||
// Start emitting the event
|
||||
interval = setInterval(function () {
|
||||
Emit(eventName);
|
||||
}, timeInMilliseconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acknowledges a heartbeat event by name
|
||||
*
|
||||
* @export
|
||||
* @param {string} eventName
|
||||
*/
|
||||
export function Acknowledge(eventName) {
|
||||
// If we are waiting for acknowledgement for this event type
|
||||
if (heartbeatCallbacks[eventName]) {
|
||||
// Acknowledge!
|
||||
heartbeatCallbacks[eventName]();
|
||||
} else {
|
||||
throw new Error(`Cannot acknowledge unknown heartbeat '${eventName}'`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,26 +25,6 @@ function sendLogMessage(level, message) {
|
||||
SendMessage('L' + level + message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the given trace message with the backend
|
||||
*
|
||||
* @export
|
||||
* @param {string} message
|
||||
*/
|
||||
export function Trace(message) {
|
||||
sendLogMessage('T', message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the given message with the backend
|
||||
*
|
||||
* @export
|
||||
* @param {string} message
|
||||
*/
|
||||
export function Print(message) {
|
||||
sendLogMessage('P', message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the given debug message with the backend
|
||||
*
|
||||
@@ -94,22 +74,3 @@ export function Error(message) {
|
||||
export function Fatal(message) {
|
||||
sendLogMessage('F', message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Log level to the given log level
|
||||
*
|
||||
* @export
|
||||
* @param {number} loglevel
|
||||
*/
|
||||
export function SetLogLevel(loglevel) {
|
||||
sendLogMessage('S', loglevel);
|
||||
}
|
||||
|
||||
// Log levels
|
||||
export const Level = {
|
||||
TRACE: 1,
|
||||
DEBUG: 2,
|
||||
INFO: 3,
|
||||
WARNING: 4,
|
||||
ERROR: 5,
|
||||
};
|
||||
|
||||
@@ -10,15 +10,11 @@ The lightweight framework for web-like apps
|
||||
/* jshint esversion: 6 */
|
||||
import * as Log from './log';
|
||||
import * as Browser from './browser';
|
||||
import * as Window from './window';
|
||||
import * as Dialog from './dialog';
|
||||
import { On, Once, OnMultiple, Emit, Notify } from './events';
|
||||
import { Callback, SystemCall } from './calls';
|
||||
import { AddScript, InjectCSS, DisableDefaultContextMenu } from './utils';
|
||||
import { On, OnMultiple, Emit, Notify, Heartbeat, Acknowledge } from './events';
|
||||
import { Callback } from './calls';
|
||||
import { AddScript, InjectCSS } from './utils';
|
||||
import { AddIPCListener } from 'ipc';
|
||||
import * as Platform from 'platform';
|
||||
import * as Store from './store';
|
||||
import * as Tray from './tray';
|
||||
|
||||
export function Init() {
|
||||
// Backend is where the Go struct wrappers get bound to
|
||||
@@ -29,37 +25,23 @@ export function Init() {
|
||||
System: Platform.System,
|
||||
Log,
|
||||
Browser,
|
||||
Window,
|
||||
Tray,
|
||||
Dialog,
|
||||
Events: {
|
||||
On,
|
||||
Once,
|
||||
OnMultiple,
|
||||
Emit,
|
||||
Heartbeat,
|
||||
Acknowledge,
|
||||
},
|
||||
_: {
|
||||
Callback,
|
||||
Notify,
|
||||
AddScript,
|
||||
InjectCSS,
|
||||
DisableDefaultContextMenu,
|
||||
// Init,
|
||||
AddIPCListener,
|
||||
SystemCall,
|
||||
},
|
||||
Store,
|
||||
Init,
|
||||
AddIPCListener
|
||||
}
|
||||
};
|
||||
|
||||
// Setup system. Store uses window.wails so needs to be setup after that
|
||||
window.wails.System = {
|
||||
IsDarkMode: Store.New('wails:isdarkmode'),
|
||||
LogLevel: Store.New('wails:loglevel'),
|
||||
AppConfig: Store.New('wails:appconfig'),
|
||||
};
|
||||
// Copy platform specific information into it
|
||||
Object.assign(window.wails.System, Platform.System);
|
||||
|
||||
// Do platform specific Init
|
||||
Platform.Init();
|
||||
}
|
||||
@@ -36,7 +36,3 @@ export function InjectCSS(css) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
export function DisableDefaultContextMenu() {
|
||||
window.disableWailsDefaultContextMenu = true;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ The lightweight framework for web-like apps
|
||||
*/
|
||||
|
||||
export const System = {
|
||||
...common,
|
||||
Platform: () => "linux",
|
||||
Platform: "linux",
|
||||
AppType: "desktop"
|
||||
}
|
||||
|
||||
export function SendMessage(message) {
|
||||
@@ -27,7 +27,7 @@ export function Init() {
|
||||
// Setup drag handler
|
||||
// Based on code from: https://github.com/patr0nus/DeskGap
|
||||
window.addEventListener('mousedown', function (e) {
|
||||
let currentElement = e.target;
|
||||
var currentElement = e.target;
|
||||
while (currentElement != null) {
|
||||
if (currentElement.hasAttribute('data-wails-no-drag')) {
|
||||
break;
|
||||
|
||||
6
v2/internal/runtime/js/package-lock.json
generated
6
v2/internal/runtime/js/package-lock.json
generated
@@ -1923,9 +1923,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001171",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001171.tgz",
|
||||
"integrity": "sha512-5Alrh8TTYPG9IH4UkRqEBZoEToWRLvPbSQokvzSz0lii8/FOWKG4keO1HoYfPWs8IF/NH/dyNPg1cmJGvV3Zlg==",
|
||||
"version": "1.0.30001040",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001040.tgz",
|
||||
"integrity": "sha512-Ep0tEPeI5wCvmJNrXjE3etgfI+lkl1fTDU6Y3ZH1mhrjkPlVI9W4pcKbMo+BQLpEWKVYYp2EmYaRsqpPC3k7lQ==",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
|
||||
@@ -39,6 +39,5 @@
|
||||
"eslint": "^6.8.0",
|
||||
"webpack": "^4.41.5",
|
||||
"webpack-cli": "^3.3.10"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
7a2c438e79cf603ba763055e515650be
|
||||
77a32d4461a2cad598edfd551fe64dcd
|
||||
@@ -1 +1 @@
|
||||
src
|
||||
bridge.js
|
||||
@@ -1,3 +1,3 @@
|
||||
# Wails Runtime
|
||||
|
||||
This module is the Javascript runtime library for the [Wails](https://wails.app) framework. It is intended to be installed as part of a [Wails](https://wails.app) V2 project, not a standalone module.
|
||||
This module is the Javascript runtime library for the [Wails](https://wails.app) framework. It is intended to be installed as part of a [Wails](https://wails.app) project, not a standalone module.
|
||||
|
||||
@@ -10,12 +10,28 @@ The lightweight framework for web-like apps
|
||||
/* jshint esversion: 6 */
|
||||
|
||||
/**
|
||||
* Opens the given URL or Filename in the system browser
|
||||
* Opens the given URL in the system browser
|
||||
*
|
||||
* @export
|
||||
* @param {string} target
|
||||
* @param {string} url
|
||||
* @returns
|
||||
*/
|
||||
export function Open(target) {
|
||||
return window.wails.Browser.Open(target);
|
||||
function OpenURL(url) {
|
||||
return window.wails.Browser.OpenURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the given filename using the system's default file handler
|
||||
*
|
||||
* @export
|
||||
* @param {sting} filename
|
||||
* @returns
|
||||
*/
|
||||
function OpenFile(filename) {
|
||||
return window.wails.Browser.OpenFile(filename);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
OpenURL: OpenURL,
|
||||
OpenFile: OpenFile
|
||||
};
|
||||
@@ -56,14 +56,28 @@ function Emit(eventName) {
|
||||
return window.wails.Events.Emit.apply(null, args);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Registers listeners for when the system theme changes
|
||||
* Heartbeat emits the event `eventName`, every `timeInMilliseconds` milliseconds until
|
||||
* the event is acknowledged via `Event.Acknowledge`. Once this happens, `callback` is invoked ONCE
|
||||
*
|
||||
* @export
|
||||
* @param {string} eventName
|
||||
* @param {number} timeInMilliseconds
|
||||
* @param {function} callback
|
||||
*/
|
||||
function OnThemeChange(callback) {
|
||||
On('wails:system:themechange', callback);
|
||||
function Heartbeat(eventName, timeInMilliseconds, callback) {
|
||||
window.wails.Events.Heartbeat(eventName, timeInMilliseconds, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Acknowledges a heartbeat event by name
|
||||
*
|
||||
* @export
|
||||
* @param {string} eventName
|
||||
*/
|
||||
function Acknowledge(eventName) {
|
||||
return window.wails.Events.Acknowledge(eventName);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@@ -71,5 +85,6 @@ module.exports = {
|
||||
On: On,
|
||||
Once: Once,
|
||||
Emit: Emit,
|
||||
OnThemeChange: OnThemeChange,
|
||||
Heartbeat: Heartbeat,
|
||||
Acknowledge: Acknowledge
|
||||
};
|
||||
@@ -9,25 +9,13 @@ The lightweight framework for web-like apps
|
||||
*/
|
||||
/* jshint esversion: 6 */
|
||||
|
||||
import bridge from './bridge';
|
||||
|
||||
/**
|
||||
* ready will execute the callback when Wails has loaded
|
||||
* and initialised.
|
||||
* Initialises the Wails runtime
|
||||
*
|
||||
* @param {function} callback
|
||||
*/
|
||||
function ready(callback) {
|
||||
|
||||
// If window.wails exists, we are ready
|
||||
if( window.wails ) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
// If not we need to setup the bridge
|
||||
bridge.InitBridge(callback);
|
||||
function Init(callback) {
|
||||
window.wails._.Init(callback);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ready: ready,
|
||||
};
|
||||
module.exports = Init;
|
||||
@@ -11,26 +11,6 @@ The lightweight framework for web-like apps
|
||||
/* jshint esversion: 6 */
|
||||
|
||||
|
||||
/**
|
||||
* Log the given message with the backend
|
||||
*
|
||||
* @export
|
||||
* @param {string} message
|
||||
*/
|
||||
function Print(message) {
|
||||
window.wails.Log.Print(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the given trace message with the backend
|
||||
*
|
||||
* @export
|
||||
* @param {string} message
|
||||
*/
|
||||
function Trace(message) {
|
||||
window.wails.Log.Trace(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the given debug message with the backend
|
||||
*
|
||||
@@ -74,40 +54,17 @@ function Error(message) {
|
||||
/**
|
||||
* Log the given fatal message with the backend
|
||||
*
|
||||
* @export
|
||||
* @param {string} message
|
||||
*/
|
||||
function Fatal(message) {
|
||||
window.wails.Log.Fatal(message);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the Log level to the given log level
|
||||
*
|
||||
* @param {number} loglevel
|
||||
*/
|
||||
function SetLogLevel(loglevel) {
|
||||
window.wails.Log.SetLogLevel(loglevel);
|
||||
}
|
||||
|
||||
// Log levels
|
||||
const Level = {
|
||||
TRACE: 1,
|
||||
DEBUG: 2,
|
||||
INFO: 3,
|
||||
WARNING: 4,
|
||||
ERROR: 5,
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
Print: Print,
|
||||
Trace: Trace,
|
||||
Debug: Debug,
|
||||
Info: Info,
|
||||
Warning: Warning,
|
||||
Error: Error,
|
||||
Fatal: Fatal,
|
||||
SetLogLevel: SetLogLevel,
|
||||
Level: Level,
|
||||
Fatal: Fatal
|
||||
};
|
||||
|
||||
@@ -11,22 +11,12 @@ The lightweight framework for web-like apps
|
||||
|
||||
const Log = require('./log');
|
||||
const Browser = require('./browser');
|
||||
const Dialog = require('./dialog');
|
||||
const Events = require('./events');
|
||||
const Init = require('./init');
|
||||
const System = require('./system');
|
||||
const Store = require('./store');
|
||||
const Window = require('./window');
|
||||
const Tray = require('./tray');
|
||||
|
||||
module.exports = {
|
||||
Browser: Browser,
|
||||
Dialog: Dialog,
|
||||
Events: Events,
|
||||
ready: Init.ready,
|
||||
Log: Log,
|
||||
System: System,
|
||||
Store: Store,
|
||||
Window: Window,
|
||||
Tray: Tray,
|
||||
Browser: Browser,
|
||||
Events: Events,
|
||||
Init: Init
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@wails/runtime",
|
||||
"version": "1.3.12",
|
||||
"description": "Wails V2 Javascript runtime library",
|
||||
"name": "@wailsapp/runtime",
|
||||
"version": "1.0.10",
|
||||
"description": "Wails Javascript runtime library",
|
||||
"main": "main.js",
|
||||
"types": "runtime.d.ts",
|
||||
"scripts": {
|
||||
|
||||
129
v2/internal/runtime/js/runtime/runtime.d.ts
vendored
129
v2/internal/runtime/js/runtime/runtime.d.ts
vendored
@@ -1,137 +1,26 @@
|
||||
export = wailsapp__runtime;
|
||||
|
||||
interface Store {
|
||||
get(): any;
|
||||
set(value: any): void;
|
||||
subscribe(callback: (newvalue: any) => void): void;
|
||||
update(callback: (currentvalue: any) => any): void;
|
||||
}
|
||||
|
||||
interface MacTitleBar {
|
||||
TitleBarAppearsTransparent: boolean; // NSWindow.titleBarAppearsTransparent
|
||||
HideTitle: boolean; // NSWindow.hideTitle
|
||||
HideTitleBar: boolean; // NSWindow.hideTitleBar
|
||||
FullSizeContent: boolean; // Makes the webview portion of the window the full size of the window, even over the titlebar
|
||||
UseToolbar: boolean; // Set true to add a blank toolbar to the window (makes the title bar larger)
|
||||
HideToolbarSeparator: boolean; // Set true to remove the separator between the toolbar and the main content area
|
||||
}
|
||||
|
||||
interface MacAppConfig {
|
||||
TitleBar: MacTitleBar;
|
||||
}
|
||||
interface LinuxAppConfig {
|
||||
}
|
||||
interface WindowsAppConfig {
|
||||
}
|
||||
|
||||
interface AppConfig {
|
||||
Title: string; // Application Title
|
||||
Width: number; // Window Width
|
||||
Height: number; // Window Height
|
||||
DisableResize: boolean; // True if resize is disabled
|
||||
Fullscreen: boolean; // App started in fullscreen
|
||||
MinWidth: number; // Window Minimum Width
|
||||
MinHeight: number; // Window Minimum Height
|
||||
MaxWidth: number; // Window Maximum Width
|
||||
MaxHeight: number; // Window Maximum Height
|
||||
StartHidden: boolean; // Start with window hidden
|
||||
DevTools: boolean; // Enables the window devtools
|
||||
RBGA: number; // The initial window colour. Convert to hex then it'll mean 0xRRGGBBAA
|
||||
Mac?: MacAppConfig; // - Configuration when running on Mac
|
||||
Linux?: LinuxAppConfig; // - Configuration when running on Linux
|
||||
Windows?: WindowsAppConfig; // - Configuration when running on Windows
|
||||
Appearance: string; // The default application appearance. Use the values listed here: https://developer.apple.com/documentation/appkit/nsappearance?language=objc
|
||||
WebviewIsTransparent: number; // Makes the background of the webview content transparent. Use this with the Alpha part of the window colour to make parts of your application transparent.
|
||||
WindowBackgroundIsTranslucent: number; // Makes the transparent parts of the application window translucent. Example: https://en.wikipedia.org/wiki/MacOS_Big_Sur#/media/File:MacOS_Big_Sur_-_Safari_Extensions_category_in_App_Store.jpg
|
||||
LogLevel: number; // The initial log level (lower is more verbose)
|
||||
}
|
||||
interface Level {
|
||||
TRACE: 1,
|
||||
DEBUG: 2,
|
||||
INFO: 3,
|
||||
WARNING: 4,
|
||||
ERROR: 5,
|
||||
}
|
||||
|
||||
interface OpenDialogOptions {
|
||||
DefaultDirectory: string;
|
||||
DefaultFilename: string;
|
||||
Title: string;
|
||||
Filters: string;
|
||||
AllowFiles: boolean;
|
||||
AllowDirectories: boolean;
|
||||
AllowMultiple: boolean;
|
||||
ShowHiddenFiles: boolean;
|
||||
CanCreateDirectories: boolean;
|
||||
ResolvesAliases: boolean;
|
||||
TreatPackagesAsDirectories: boolean;
|
||||
}
|
||||
|
||||
interface SaveDialogOptions {
|
||||
DefaultDirectory: string;
|
||||
DefaultFilename: string;
|
||||
Title: string;
|
||||
Filters: string;
|
||||
ShowHiddenFiles: boolean;
|
||||
CanCreateDirectories: boolean;
|
||||
TreatPackagesAsDirectories: boolean;
|
||||
}
|
||||
|
||||
interface DialogType {
|
||||
InfoDialog: 'info',
|
||||
WarningDialog: 'warning',
|
||||
ErrorDialog: 'error',
|
||||
QuestionDialog: 'question',
|
||||
}
|
||||
|
||||
interface MessageDialogOptions {
|
||||
Type: DialogType;
|
||||
Title: string;
|
||||
Message: string;
|
||||
Buttons: string[];
|
||||
DefaultButton: string;
|
||||
CancelButton: string;
|
||||
Icon: string;
|
||||
}
|
||||
|
||||
declare const wailsapp__runtime: {
|
||||
Browser: {
|
||||
Open(target: string): Promise<any>;
|
||||
OpenFile(filename: string): Promise<any>;
|
||||
OpenURL(url: string): Promise<any>;
|
||||
};
|
||||
Events: {
|
||||
Emit(eventName: string, data?: any): void;
|
||||
On(eventName: string, callback: (data?: any) => void): void;
|
||||
OnMultiple(eventName: string, callback: (data?: any) => void, maxCallbacks: number): void;
|
||||
Once(eventName: string, callback: (data?: any) => void): void;
|
||||
Acknowledge(eventName: string): void;
|
||||
Emit(eventName: string): void;
|
||||
Heartbeat(eventName: string, timeInMilliseconds: number, callback: () => void): void;
|
||||
On(eventName: string, callback: () => void): void;
|
||||
OnMultiple(eventName: string, callback: () => void, maxCallbacks: number): void;
|
||||
Once(eventName: string, callback: () => void): void;
|
||||
};
|
||||
// Init(callback: () => void): void;
|
||||
Init(callback: () => void): void;
|
||||
Log: {
|
||||
Debug(message: string): void;
|
||||
Error(message: string): void;
|
||||
Fatal(message: string): void;
|
||||
Info(message: string): void;
|
||||
Warning(message: string): void;
|
||||
Level: Level;
|
||||
};
|
||||
System: {
|
||||
DarkModeEnabled(): Promise<boolean>;
|
||||
OnThemeChange(callback: (darkModeEnabled: boolean) => void): void;
|
||||
LogLevel(): Store;
|
||||
Platform(): string;
|
||||
AppType(): string;
|
||||
AppConfig(): AppConfig;
|
||||
};
|
||||
Store: {
|
||||
New(name: string, defaultValue?: any): Store;
|
||||
};
|
||||
Dialog: {
|
||||
Open(options: OpenDialogOptions): Promise<Array<string>>;
|
||||
Save(options: SaveDialogOptions): Promise<string>;
|
||||
Message(options: MessageDialogOptions): Promise<string>;
|
||||
};
|
||||
Tray: {
|
||||
SetIcon(trayIconID: string): void;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ module.exports = {
|
||||
mode: 'production',
|
||||
output: {
|
||||
path: path.resolve(__dirname, '..', 'assets'),
|
||||
filename: 'desktop_'+platform+'.js',
|
||||
filename: 'desktop.js',
|
||||
library: 'Wails'
|
||||
},
|
||||
resolve: {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package servicebus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -13,26 +12,23 @@ import (
|
||||
type ServiceBus struct {
|
||||
listeners map[string][]chan *Message
|
||||
messageQueue chan *Message
|
||||
quitChannel chan struct{}
|
||||
wg sync.WaitGroup
|
||||
lock sync.RWMutex
|
||||
closed bool
|
||||
debug bool
|
||||
logger logger.CustomLogger
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// New creates a new ServiceBus
|
||||
// The internal message queue is set to 100 messages
|
||||
// Listener queues are set to 10
|
||||
func New(logger *logger.Logger) *ServiceBus {
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
return &ServiceBus{
|
||||
listeners: make(map[string][]chan *Message),
|
||||
messageQueue: make(chan *Message, 100),
|
||||
quitChannel: make(chan struct{}, 1),
|
||||
logger: logger.CustomLogger("Service Bus"),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,22 +63,23 @@ func (s *ServiceBus) Debug() {
|
||||
// Start the service bus
|
||||
func (s *ServiceBus) Start() error {
|
||||
|
||||
s.logger.Trace("Starting")
|
||||
|
||||
// Prevent starting when closed
|
||||
if s.closed {
|
||||
return fmt.Errorf("cannot call start on closed servicebus")
|
||||
}
|
||||
|
||||
s.logger.Trace("Starting")
|
||||
|
||||
// We run in a different thread
|
||||
go func() {
|
||||
defer s.logger.Trace("Stopped")
|
||||
|
||||
quit := false
|
||||
s.wg.Add(1)
|
||||
|
||||
// Loop until we get a quit message
|
||||
for {
|
||||
for !quit {
|
||||
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
return
|
||||
|
||||
// Listen for messages
|
||||
case message := <-s.messageQueue:
|
||||
@@ -93,9 +90,16 @@ func (s *ServiceBus) Start() error {
|
||||
}
|
||||
// Dispatch message
|
||||
s.dispatchMessage(message)
|
||||
|
||||
// Listen for quit messages
|
||||
case <-s.quitChannel:
|
||||
quit = true
|
||||
}
|
||||
}
|
||||
|
||||
// Indicate we have shut down
|
||||
s.wg.Done()
|
||||
|
||||
}()
|
||||
|
||||
return nil
|
||||
@@ -112,7 +116,10 @@ func (s *ServiceBus) Stop() error {
|
||||
s.closed = true
|
||||
|
||||
// Send quit message
|
||||
s.cancel()
|
||||
s.quitChannel <- struct{}{}
|
||||
|
||||
// Wait for dispatcher to stop
|
||||
s.wg.Wait()
|
||||
|
||||
// Close down subscriber channels
|
||||
s.lock.Lock()
|
||||
@@ -127,6 +134,7 @@ func (s *ServiceBus) Stop() error {
|
||||
// Close message queue
|
||||
close(s.messageQueue)
|
||||
|
||||
s.logger.Trace("Stopped")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -160,22 +168,25 @@ func (s *ServiceBus) Subscribe(topic string) (<-chan *Message, error) {
|
||||
}
|
||||
|
||||
// Publish sends the given message on the service bus
|
||||
func (s *ServiceBus) Publish(topic string, data interface{}) {
|
||||
func (s *ServiceBus) Publish(topic string, data interface{}) error {
|
||||
// Prevent publish when closed
|
||||
if s.closed {
|
||||
return
|
||||
return fmt.Errorf("cannot call publish on closed servicebus")
|
||||
}
|
||||
|
||||
message := NewMessage(topic, data)
|
||||
s.messageQueue <- message
|
||||
return nil
|
||||
}
|
||||
|
||||
// PublishForTarget sends the given message on the service bus for the given target
|
||||
func (s *ServiceBus) PublishForTarget(topic string, data interface{}, target string) {
|
||||
func (s *ServiceBus) PublishForTarget(topic string, data interface{}, target string) error {
|
||||
// Prevent publish when closed
|
||||
if s.closed {
|
||||
return
|
||||
return fmt.Errorf("cannot call publish on closed servicebus")
|
||||
}
|
||||
|
||||
message := NewMessageForTarget(topic, data, target)
|
||||
s.messageQueue <- message
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package signal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
gosignal "os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
@@ -22,24 +20,24 @@ type Manager struct {
|
||||
// signalChannel
|
||||
signalchannel chan os.Signal
|
||||
|
||||
// ctx
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
// Parent waitgroup
|
||||
wg *sync.WaitGroup
|
||||
// Quit channel
|
||||
quitChannel <-chan *servicebus.Message
|
||||
}
|
||||
|
||||
// NewManager creates a new signal manager
|
||||
func NewManager(ctx context.Context, cancel context.CancelFunc, bus *servicebus.ServiceBus, logger *logger.Logger) (*Manager, error) {
|
||||
func NewManager(bus *servicebus.ServiceBus, logger *logger.Logger) (*Manager, error) {
|
||||
|
||||
// Register quit channel
|
||||
quitChannel, err := bus.Subscribe("quit")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := &Manager{
|
||||
bus: bus,
|
||||
logger: logger.CustomLogger("Event Manager"),
|
||||
signalchannel: make(chan os.Signal, 2),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
|
||||
quitChannel: quitChannel,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
@@ -51,23 +49,20 @@ func (m *Manager) Start() {
|
||||
// Hook into interrupts
|
||||
gosignal.Notify(m.signalchannel, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
m.wg.Add(1)
|
||||
|
||||
// Spin off signal listener and wait for either a cancellation
|
||||
// or signal
|
||||
// Spin off signal listener
|
||||
go func() {
|
||||
select {
|
||||
case <-m.signalchannel:
|
||||
println()
|
||||
m.logger.Trace("Ctrl+C detected. Shutting down...")
|
||||
m.bus.Publish("quit", "ctrl-c pressed")
|
||||
|
||||
// Start shutdown of Wails
|
||||
m.cancel()
|
||||
|
||||
case <-m.ctx.Done():
|
||||
running := true
|
||||
for running {
|
||||
select {
|
||||
case <-m.signalchannel:
|
||||
println()
|
||||
m.logger.Trace("Ctrl+C detected. Shutting down...")
|
||||
m.bus.Publish("quit", "ctrl-c pressed")
|
||||
case <-m.quitChannel:
|
||||
running = false
|
||||
break
|
||||
}
|
||||
}
|
||||
m.logger.Trace("Shutdown")
|
||||
m.wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -3,16 +3,16 @@ package subsystem
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/runtime/goruntime"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
)
|
||||
|
||||
// Binding is the Binding subsystem. It manages all service bus messages
|
||||
// starting with "binding".
|
||||
type Binding struct {
|
||||
quitChannel <-chan *servicebus.Message
|
||||
bindingChannel <-chan *servicebus.Message
|
||||
|
||||
running bool
|
||||
running bool
|
||||
|
||||
// Binding db
|
||||
bindings *binding.Bindings
|
||||
@@ -21,11 +21,17 @@ type Binding struct {
|
||||
logger logger.CustomLogger
|
||||
|
||||
// runtime
|
||||
runtime *runtime.Runtime
|
||||
runtime *goruntime.Runtime
|
||||
}
|
||||
|
||||
// NewBinding creates a new binding subsystem. Uses the given bindings db for reference.
|
||||
func NewBinding(bus *servicebus.ServiceBus, logger *logger.Logger, bindings *binding.Bindings, runtime *runtime.Runtime) (*Binding, error) {
|
||||
func NewBinding(bus *servicebus.ServiceBus, logger *logger.Logger, bindings *binding.Bindings, runtime *goruntime.Runtime) (*Binding, error) {
|
||||
|
||||
// Register quit channel
|
||||
quitChannel, err := bus.Subscribe("quit")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Subscribe to event messages
|
||||
bindingChannel, err := bus.Subscribe("binding")
|
||||
@@ -34,12 +40,21 @@ func NewBinding(bus *servicebus.ServiceBus, logger *logger.Logger, bindings *bin
|
||||
}
|
||||
|
||||
result := &Binding{
|
||||
quitChannel: quitChannel,
|
||||
bindingChannel: bindingChannel,
|
||||
logger: logger.CustomLogger("Binding Subsystem"),
|
||||
bindings: bindings,
|
||||
runtime: runtime,
|
||||
}
|
||||
|
||||
// Call WailsInit methods once the frontend is loaded
|
||||
// TODO: Double check that this is actually being emitted
|
||||
// when we want it to be
|
||||
runtime.Events.On("wails:loaded", func(...interface{}) {
|
||||
result.logger.Trace("Calling WailsInit() methods")
|
||||
result.CallWailsInit()
|
||||
})
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -54,16 +69,45 @@ func (b *Binding) Start() error {
|
||||
go func() {
|
||||
for b.running {
|
||||
select {
|
||||
case <-b.quitChannel:
|
||||
b.running = false
|
||||
case bindingMessage := <-b.bindingChannel:
|
||||
b.logger.Trace("Got binding message: %+v", bindingMessage)
|
||||
}
|
||||
}
|
||||
b.logger.Trace("Shutdown")
|
||||
|
||||
// Call shutdown
|
||||
b.shutdown()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Binding) Close() {
|
||||
b.running = false
|
||||
// CallWailsInit will callback to the registered WailsInit
|
||||
// methods with the runtime object
|
||||
func (b *Binding) CallWailsInit() error {
|
||||
for _, wailsinit := range b.bindings.DB().WailsInitMethods() {
|
||||
_, err := wailsinit.Call([]interface{}{b.runtime})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CallWailsShutdown will callback to the registered WailsShutdown
|
||||
// methods with the runtime object
|
||||
func (b *Binding) CallWailsShutdown() error {
|
||||
for _, wailsshutdown := range b.bindings.DB().WailsShutdownMethods() {
|
||||
_, err := wailsshutdown.Call([]interface{}{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Binding) shutdown() {
|
||||
b.CallWailsShutdown()
|
||||
b.logger.Trace("Shutdown")
|
||||
}
|
||||
|
||||
@@ -1,28 +1,21 @@
|
||||
package subsystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
||||
"github.com/wailsapp/wails/v2/internal/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
)
|
||||
|
||||
// Call is the Call subsystem. It manages all service bus messages
|
||||
// starting with "call".
|
||||
type Call struct {
|
||||
quitChannel <-chan *servicebus.Message
|
||||
callChannel <-chan *servicebus.Message
|
||||
|
||||
// quit flag
|
||||
shouldQuit bool
|
||||
running bool
|
||||
|
||||
// bindings DB
|
||||
DB *binding.DB
|
||||
@@ -32,19 +25,16 @@ type Call struct {
|
||||
|
||||
// logger
|
||||
logger logger.CustomLogger
|
||||
|
||||
// runtime
|
||||
runtime *runtime.Runtime
|
||||
|
||||
// context
|
||||
ctx context.Context
|
||||
|
||||
// parent waitgroup
|
||||
wg *sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewCall creates a new call subsystem
|
||||
func NewCall(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger, DB *binding.DB, runtime *runtime.Runtime) (*Call, error) {
|
||||
// NewCall creates a new log subsystem
|
||||
func NewCall(bus *servicebus.ServiceBus, logger *logger.Logger, DB *binding.DB) (*Call, error) {
|
||||
|
||||
// Register quit channel
|
||||
quitChannel, err := bus.Subscribe("quit")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Subscribe to event messages
|
||||
callChannel, err := bus.Subscribe("call:invoke")
|
||||
@@ -53,13 +43,11 @@ func NewCall(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Log
|
||||
}
|
||||
|
||||
result := &Call{
|
||||
quitChannel: quitChannel,
|
||||
callChannel: callChannel,
|
||||
logger: logger.CustomLogger("Call Subsystem"),
|
||||
DB: DB,
|
||||
bus: bus,
|
||||
runtime: runtime,
|
||||
ctx: ctx,
|
||||
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
|
||||
}
|
||||
|
||||
return result, nil
|
||||
@@ -68,21 +56,22 @@ func NewCall(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Log
|
||||
// Start the subsystem
|
||||
func (c *Call) Start() error {
|
||||
|
||||
c.wg.Add(1)
|
||||
c.running = true
|
||||
|
||||
// Spin off a go routine
|
||||
go func() {
|
||||
defer c.logger.Trace("Shutdown")
|
||||
for {
|
||||
for c.running {
|
||||
select {
|
||||
case <-c.ctx.Done():
|
||||
c.wg.Done()
|
||||
return
|
||||
case <-c.quitChannel:
|
||||
c.running = false
|
||||
case callMessage := <-c.callChannel:
|
||||
|
||||
c.processCall(callMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// Call shutdown
|
||||
c.shutdown()
|
||||
}()
|
||||
|
||||
return nil
|
||||
@@ -98,12 +87,6 @@ func (c *Call) processCall(callMessage *servicebus.Message) {
|
||||
// Lookup method
|
||||
registeredMethod := c.DB.GetMethod(payload.Name)
|
||||
|
||||
// Check if it's a system call
|
||||
if strings.HasPrefix(payload.Name, ".wails.") {
|
||||
c.processSystemCall(payload, callMessage.Target())
|
||||
return
|
||||
}
|
||||
|
||||
// Check we have it
|
||||
if registeredMethod == nil {
|
||||
c.sendError(fmt.Errorf("Method not registered"), payload, callMessage.Target())
|
||||
@@ -111,12 +94,7 @@ func (c *Call) processCall(callMessage *servicebus.Message) {
|
||||
}
|
||||
c.logger.Trace("Got registered method: %+v", registeredMethod)
|
||||
|
||||
args, err := registeredMethod.ParseArgs(payload.Args)
|
||||
if err != nil {
|
||||
c.sendError(fmt.Errorf("Error parsing arguments: %s", err.Error()), payload, callMessage.Target())
|
||||
}
|
||||
|
||||
result, err := registeredMethod.Call(args)
|
||||
result, err := registeredMethod.Call(payload.Args)
|
||||
if err != nil {
|
||||
c.sendError(err, payload, callMessage.Target())
|
||||
return
|
||||
@@ -127,50 +105,14 @@ func (c *Call) processCall(callMessage *servicebus.Message) {
|
||||
|
||||
}
|
||||
|
||||
func (c *Call) processSystemCall(payload *message.CallMessage, clientID string) {
|
||||
c.logger.Trace("Got internal System call: %+v", payload)
|
||||
callName := strings.TrimPrefix(payload.Name, ".wails.")
|
||||
switch callName {
|
||||
case "IsDarkMode":
|
||||
darkModeEnabled := c.runtime.System.IsDarkMode()
|
||||
c.sendResult(darkModeEnabled, payload, clientID)
|
||||
case "Dialog.Open":
|
||||
dialogOptions := new(dialog.OpenDialog)
|
||||
err := json.Unmarshal(payload.Args[0], dialogOptions)
|
||||
if err != nil {
|
||||
c.logger.Error("Error decoding: %s", err)
|
||||
}
|
||||
result := c.runtime.Dialog.Open(dialogOptions)
|
||||
c.sendResult(result, payload, clientID)
|
||||
case "Dialog.Save":
|
||||
dialogOptions := new(dialog.SaveDialog)
|
||||
err := json.Unmarshal(payload.Args[0], dialogOptions)
|
||||
if err != nil {
|
||||
c.logger.Error("Error decoding: %s", err)
|
||||
}
|
||||
result := c.runtime.Dialog.Save(dialogOptions)
|
||||
c.sendResult(result, payload, clientID)
|
||||
case "Dialog.Message":
|
||||
dialogOptions := new(dialog.MessageDialog)
|
||||
err := json.Unmarshal(payload.Args[0], dialogOptions)
|
||||
if err != nil {
|
||||
c.logger.Error("Error decoding: %s", err)
|
||||
}
|
||||
result := c.runtime.Dialog.Message(dialogOptions)
|
||||
c.sendResult(result, payload, clientID)
|
||||
default:
|
||||
c.logger.Error("Unknown system call: %+v", callName)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Call) sendResult(result interface{}, payload *message.CallMessage, clientID string) {
|
||||
c.logger.Trace("Sending success result with CallbackID '%s' : %+v\n", payload.CallbackID, result)
|
||||
incomingMessage := &CallbackMessage{
|
||||
message := &CallbackMessage{
|
||||
Result: result,
|
||||
CallbackID: payload.CallbackID,
|
||||
}
|
||||
messageData, err := json.Marshal(incomingMessage)
|
||||
c.logger.Trace("json incomingMessage data: %+v\n", string(messageData))
|
||||
messageData, err := json.Marshal(message)
|
||||
c.logger.Trace("json message data: %+v\n", string(messageData))
|
||||
if err != nil {
|
||||
// what now?
|
||||
c.logger.Fatal(err.Error())
|
||||
@@ -180,13 +122,13 @@ func (c *Call) sendResult(result interface{}, payload *message.CallMessage, clie
|
||||
|
||||
func (c *Call) sendError(err error, payload *message.CallMessage, clientID string) {
|
||||
c.logger.Trace("Sending error result with CallbackID '%s' : %+v\n", payload.CallbackID, err.Error())
|
||||
incomingMessage := &CallbackMessage{
|
||||
message := &CallbackMessage{
|
||||
Err: err.Error(),
|
||||
CallbackID: payload.CallbackID,
|
||||
}
|
||||
|
||||
messageData, err := json.Marshal(incomingMessage)
|
||||
c.logger.Trace("json incomingMessage data: %+v\n", string(messageData))
|
||||
messageData, err := json.Marshal(message)
|
||||
c.logger.Trace("json message data: %+v\n", string(messageData))
|
||||
if err != nil {
|
||||
// what now?
|
||||
c.logger.Fatal(err.Error())
|
||||
@@ -194,6 +136,10 @@ func (c *Call) sendError(err error, payload *message.CallMessage, clientID strin
|
||||
c.bus.PublishForTarget("call:result", string(messageData), clientID)
|
||||
}
|
||||
|
||||
func (c *Call) shutdown() {
|
||||
c.logger.Trace("Shutdown")
|
||||
}
|
||||
|
||||
// CallbackMessage defines a message that contains the result of a call
|
||||
type CallbackMessage struct {
|
||||
Result interface{} `json:"result"`
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package subsystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -16,14 +15,16 @@ import (
|
||||
// means it does not expire (default).
|
||||
type eventListener struct {
|
||||
callback func(...interface{}) // Function to call with emitted event data
|
||||
counter int // The number of times this callback may be called. -1 = infinite
|
||||
counter int64 // The number of times this callback may be called. -1 = infinite
|
||||
delete bool // Flag to indicate that this listener should be deleted
|
||||
}
|
||||
|
||||
// Event is the Eventing subsystem. It manages all service bus messages
|
||||
// starting with "event".
|
||||
type Event struct {
|
||||
quitChannel <-chan *servicebus.Message
|
||||
eventChannel <-chan *servicebus.Message
|
||||
running bool
|
||||
|
||||
// Event listeners
|
||||
listeners map[string][]*eventListener
|
||||
@@ -31,16 +32,16 @@ type Event struct {
|
||||
|
||||
// logger
|
||||
logger logger.CustomLogger
|
||||
|
||||
// ctx
|
||||
ctx context.Context
|
||||
|
||||
// parent waitgroup
|
||||
wg *sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewEvent creates a new log subsystem
|
||||
func NewEvent(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger) (*Event, error) {
|
||||
func NewEvent(bus *servicebus.ServiceBus, logger *logger.Logger) (*Event, error) {
|
||||
|
||||
// Register quit channel
|
||||
quitChannel, err := bus.Subscribe("quit")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Subscribe to event messages
|
||||
eventChannel, err := bus.Subscribe("event")
|
||||
@@ -49,23 +50,22 @@ func NewEvent(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Lo
|
||||
}
|
||||
|
||||
result := &Event{
|
||||
quitChannel: quitChannel,
|
||||
eventChannel: eventChannel,
|
||||
logger: logger.CustomLogger("Event Subsystem"),
|
||||
listeners: make(map[string][]*eventListener),
|
||||
ctx: ctx,
|
||||
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// RegisterListener provides a means of subscribing to events of type "eventName"
|
||||
func (e *Event) RegisterListener(eventName string, callback func(...interface{}), counter int) {
|
||||
func (e *Event) RegisterListener(eventName string, callback func(...interface{})) {
|
||||
|
||||
// Create new eventListener
|
||||
thisListener := &eventListener{
|
||||
callback: callback,
|
||||
counter: counter,
|
||||
counter: 0,
|
||||
delete: false,
|
||||
}
|
||||
|
||||
@@ -80,16 +80,15 @@ func (e *Event) Start() error {
|
||||
|
||||
e.logger.Trace("Starting")
|
||||
|
||||
e.wg.Add(1)
|
||||
e.running = true
|
||||
|
||||
// Spin off a go routine
|
||||
go func() {
|
||||
defer e.logger.Trace("Shutdown")
|
||||
for {
|
||||
for e.running {
|
||||
select {
|
||||
case <-e.ctx.Done():
|
||||
e.wg.Done()
|
||||
return
|
||||
case <-e.quitChannel:
|
||||
e.running = false
|
||||
break
|
||||
case eventMessage := <-e.eventChannel:
|
||||
splitTopic := strings.Split(eventMessage.Topic(), ":")
|
||||
eventType := splitTopic[1]
|
||||
@@ -121,7 +120,7 @@ func (e *Event) Start() error {
|
||||
var message *message.OnEventMessage = eventMessage.Data().(*message.OnEventMessage)
|
||||
eventName := message.Name
|
||||
callback := message.Callback
|
||||
e.RegisterListener(eventName, callback, message.Counter)
|
||||
e.RegisterListener(eventName, callback)
|
||||
e.logger.Trace("Registered listener for event '%s' with callback %p", eventName, callback)
|
||||
default:
|
||||
e.logger.Error("unknown event message: %+v", eventMessage)
|
||||
@@ -129,6 +128,8 @@ func (e *Event) Start() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Call shutdown
|
||||
e.shutdown()
|
||||
}()
|
||||
|
||||
return nil
|
||||
@@ -140,7 +141,7 @@ func (e *Event) notifyListeners(eventName string, message *message.EventMessage)
|
||||
// Get list of event listeners
|
||||
listeners := e.listeners[eventName]
|
||||
if listeners == nil {
|
||||
e.logger.Trace("No listeners for event '%s'", eventName)
|
||||
println("no listeners for", eventName)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -178,14 +179,15 @@ func (e *Event) notifyListeners(eventName string, message *message.EventMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// Save new listeners or remove entry
|
||||
if len(newListeners) > 0 {
|
||||
e.listeners[eventName] = newListeners
|
||||
} else {
|
||||
delete(e.listeners, eventName)
|
||||
}
|
||||
// Save new listeners
|
||||
e.listeners[eventName] = newListeners
|
||||
}
|
||||
|
||||
// Unlock
|
||||
e.notifyLock.Unlock()
|
||||
|
||||
}
|
||||
|
||||
func (e *Event) shutdown() {
|
||||
e.logger.Trace("Shutdown")
|
||||
}
|
||||
|
||||
@@ -1,40 +1,25 @@
|
||||
package subsystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
)
|
||||
|
||||
// Log is the Logging subsystem. It handles messages with topics starting
|
||||
// with "log:"
|
||||
type Log struct {
|
||||
logChannel <-chan *servicebus.Message
|
||||
|
||||
// quit flag
|
||||
shouldQuit bool
|
||||
logChannel <-chan *servicebus.Message
|
||||
quitChannel <-chan *servicebus.Message
|
||||
running bool
|
||||
|
||||
// Logger!
|
||||
logger *logger.Logger
|
||||
|
||||
// Loglevel store
|
||||
logLevelStore *runtime.Store
|
||||
|
||||
// Context for shutdown
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
// internal waitgroup
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewLog creates a new log subsystem
|
||||
func NewLog(bus *servicebus.ServiceBus, logger *logger.Logger, logLevelStore *runtime.Store) (*Log, error) {
|
||||
func NewLog(bus *servicebus.ServiceBus, logger *logger.Logger) (*Log, error) {
|
||||
|
||||
// Subscribe to log messages
|
||||
logChannel, err := bus.Subscribe("log")
|
||||
@@ -42,14 +27,16 @@ func NewLog(bus *servicebus.ServiceBus, logger *logger.Logger, logLevelStore *ru
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
// Subscribe to quit messages
|
||||
quitChannel, err := bus.Subscribe("quit")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := &Log{
|
||||
logChannel: logChannel,
|
||||
logger: logger,
|
||||
logLevelStore: logLevelStore,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
logChannel: logChannel,
|
||||
quitChannel: quitChannel,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
@@ -58,24 +45,18 @@ func NewLog(bus *servicebus.ServiceBus, logger *logger.Logger, logLevelStore *ru
|
||||
// Start the subsystem
|
||||
func (l *Log) Start() error {
|
||||
|
||||
l.wg.Add(1)
|
||||
l.running = true
|
||||
|
||||
// Spin off a go routine
|
||||
go func() {
|
||||
defer l.logger.Trace("Logger Shutdown")
|
||||
|
||||
for l.shouldQuit == false {
|
||||
for l.running {
|
||||
select {
|
||||
case <-l.ctx.Done():
|
||||
l.wg.Done()
|
||||
return
|
||||
case <-l.quitChannel:
|
||||
l.running = false
|
||||
break
|
||||
case logMessage := <-l.logChannel:
|
||||
logType := strings.TrimPrefix(logMessage.Topic(), "log:")
|
||||
switch logType {
|
||||
case "print":
|
||||
l.logger.Print(logMessage.Data().(string))
|
||||
case "trace":
|
||||
l.logger.Trace(logMessage.Data().(string))
|
||||
case "debug":
|
||||
l.logger.Debug(logMessage.Data().(string))
|
||||
case "info":
|
||||
@@ -86,33 +67,13 @@ func (l *Log) Start() error {
|
||||
l.logger.Error(logMessage.Data().(string))
|
||||
case "fatal":
|
||||
l.logger.Fatal(logMessage.Data().(string))
|
||||
case "setlevel":
|
||||
switch inLevel := logMessage.Data().(type) {
|
||||
case logger.LogLevel:
|
||||
l.logger.SetLogLevel(inLevel)
|
||||
l.logLevelStore.Set(inLevel)
|
||||
case string:
|
||||
uint64level, err := strconv.ParseUint(inLevel, 10, 8)
|
||||
if err != nil {
|
||||
l.logger.Error("Error parsing log level: %+v", inLevel)
|
||||
continue
|
||||
}
|
||||
level := logger.LogLevel(uint64level)
|
||||
l.logLevelStore.Set(level)
|
||||
l.logger.SetLogLevel(level)
|
||||
}
|
||||
|
||||
default:
|
||||
l.logger.Error("unknown log message: %+v", logMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
l.logger.Trace("Logger Shutdown")
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Log) Close() {
|
||||
l.cancel()
|
||||
l.wg.Wait()
|
||||
}
|
||||
|
||||
@@ -77,6 +77,12 @@ func (m *Menu) Start() error {
|
||||
splitTopic := strings.Split(menuMessage.Topic(), ":")
|
||||
menuMessageType := splitTopic[1]
|
||||
switch menuMessageType {
|
||||
case "ontrayopen":
|
||||
trayID := menuMessage.Data().(string)
|
||||
m.menuManager.OnTrayMenuOpen(trayID)
|
||||
case "ontrayclose":
|
||||
trayID := menuMessage.Data().(string)
|
||||
m.menuManager.OnTrayMenuClose(trayID)
|
||||
case "clicked":
|
||||
if len(splitTopic) != 2 {
|
||||
m.logger.Error("Received clicked message with invalid topic format. Expected 2 sections in topic, got %s", splitTopic)
|
||||
|
||||
@@ -1,63 +1,47 @@
|
||||
package subsystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/runtime/goruntime"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
)
|
||||
|
||||
// Runtime is the Runtime subsystem. It handles messages with topics starting
|
||||
// with "runtime:"
|
||||
type Runtime struct {
|
||||
quitChannel <-chan *servicebus.Message
|
||||
runtimeChannel <-chan *servicebus.Message
|
||||
|
||||
// The hooks channel allows us to hook into frontend startup
|
||||
hooksChannel <-chan *servicebus.Message
|
||||
startupCallback func(*runtime.Runtime)
|
||||
shutdownCallback func()
|
||||
|
||||
// quit flag
|
||||
shouldQuit bool
|
||||
running bool
|
||||
|
||||
logger logger.CustomLogger
|
||||
|
||||
// Runtime library
|
||||
runtime *runtime.Runtime
|
||||
|
||||
//ctx
|
||||
ctx context.Context
|
||||
|
||||
// Startup Hook
|
||||
startupOnce sync.Once
|
||||
runtime *goruntime.Runtime
|
||||
}
|
||||
|
||||
// NewRuntime creates a new runtime subsystem
|
||||
func NewRuntime(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger, startupCallback func(*runtime.Runtime)) (*Runtime, error) {
|
||||
func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger) (*Runtime, error) {
|
||||
|
||||
// Subscribe to log messages
|
||||
runtimeChannel, err := bus.Subscribe("runtime:")
|
||||
// Register quit channel
|
||||
quitChannel, err := bus.Subscribe("quit")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Subscribe to log messages
|
||||
hooksChannel, err := bus.Subscribe("hooks:")
|
||||
runtimeChannel, err := bus.Subscribe("runtime")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := &Runtime{
|
||||
runtimeChannel: runtimeChannel,
|
||||
hooksChannel: hooksChannel,
|
||||
logger: logger.CustomLogger("Runtime Subsystem"),
|
||||
runtime: runtime.New(bus),
|
||||
startupCallback: startupCallback,
|
||||
ctx: ctx,
|
||||
quitChannel: quitChannel,
|
||||
runtimeChannel: runtimeChannel,
|
||||
logger: logger.CustomLogger("Runtime Subsystem"),
|
||||
runtime: goruntime.New(bus),
|
||||
}
|
||||
|
||||
return result, nil
|
||||
@@ -66,28 +50,15 @@ func NewRuntime(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.
|
||||
// Start the subsystem
|
||||
func (r *Runtime) Start() error {
|
||||
|
||||
r.running = true
|
||||
|
||||
// Spin off a go routine
|
||||
go func() {
|
||||
defer r.logger.Trace("Shutdown")
|
||||
for {
|
||||
for r.running {
|
||||
select {
|
||||
case hooksMessage := <-r.hooksChannel:
|
||||
r.logger.Trace(fmt.Sprintf("Received hooksmessage: %+v", hooksMessage))
|
||||
messageSlice := strings.Split(hooksMessage.Topic(), ":")
|
||||
hook := messageSlice[1]
|
||||
switch hook {
|
||||
case "startup":
|
||||
if r.startupCallback != nil {
|
||||
r.startupOnce.Do(func() {
|
||||
go r.startupCallback(r.runtime)
|
||||
})
|
||||
} else {
|
||||
r.logger.Warning("no startup callback registered!")
|
||||
}
|
||||
default:
|
||||
r.logger.Error("unknown hook message: %+v", hooksMessage)
|
||||
continue
|
||||
}
|
||||
case <-r.quitChannel:
|
||||
r.running = false
|
||||
break
|
||||
case runtimeMessage := <-r.runtimeChannel:
|
||||
r.logger.Trace(fmt.Sprintf("Received message: %+v", runtimeMessage))
|
||||
// Topics have the format: "runtime:category:call"
|
||||
@@ -104,41 +75,40 @@ func (r *Runtime) Start() error {
|
||||
case "browser":
|
||||
err = r.processBrowserMessage(method, runtimeMessage.Data())
|
||||
default:
|
||||
err = fmt.Errorf("unknown runtime message: %+v",
|
||||
runtimeMessage)
|
||||
fmt.Errorf("unknown log message: %+v", runtimeMessage)
|
||||
}
|
||||
|
||||
// If we had an error, log it
|
||||
if err != nil {
|
||||
r.logger.Error(err.Error())
|
||||
}
|
||||
case <-r.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Call shutdown
|
||||
r.shutdown()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GoRuntime returns the Go Runtime object
|
||||
func (r *Runtime) GoRuntime() *runtime.Runtime {
|
||||
func (r *Runtime) GoRuntime() *goruntime.Runtime {
|
||||
return r.runtime
|
||||
}
|
||||
|
||||
func (r *Runtime) shutdown() {
|
||||
r.logger.Trace("Shutdown")
|
||||
}
|
||||
|
||||
func (r *Runtime) processBrowserMessage(method string, data interface{}) error {
|
||||
switch method {
|
||||
case "open":
|
||||
target, ok := data.(string)
|
||||
case "openurl":
|
||||
url, ok := data.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected 1 string parameter for runtime:browser:open")
|
||||
return fmt.Errorf("expected 1 string parameter for runtime:browser:openurl")
|
||||
}
|
||||
go func() {
|
||||
err := r.runtime.Browser.Open(target)
|
||||
if err != nil {
|
||||
r.logger.Error(err.Error())
|
||||
}
|
||||
}()
|
||||
go r.runtime.Browser.Open(url)
|
||||
default:
|
||||
return fmt.Errorf("unknown method runtime:browser:%s", method)
|
||||
}
|
||||
|
||||
@@ -68,6 +68,10 @@ func (u *URL) Start() error {
|
||||
u.wg.Done()
|
||||
return
|
||||
case urlMessage := <-u.urlChannel:
|
||||
// Guard against nil messages
|
||||
if urlMessage == nil {
|
||||
continue
|
||||
}
|
||||
messageType := strings.TrimPrefix(urlMessage.Topic(), "url:")
|
||||
switch messageType {
|
||||
case "handler":
|
||||
|
||||
@@ -9,6 +9,24 @@ import (
|
||||
"github.com/wailsapp/wails/v2/internal/shell"
|
||||
)
|
||||
|
||||
// PackageManager is a common interface across all package managers
|
||||
type PackageManager interface {
|
||||
Name() string
|
||||
Packages() packagemap
|
||||
PackageInstalled(*Package) (bool, error)
|
||||
PackageAvailable(*Package) (bool, error)
|
||||
InstallCommand(*Package) string
|
||||
}
|
||||
|
||||
// Package contains information about a system package
|
||||
type Package struct {
|
||||
Name string
|
||||
Version string
|
||||
InstallCommand map[string]string
|
||||
SystemPackage bool
|
||||
Library bool
|
||||
Optional bool
|
||||
}
|
||||
|
||||
// A list of package manager commands
|
||||
var pmcommands = []string{
|
||||
@@ -20,6 +38,8 @@ var pmcommands = []string{
|
||||
"zypper",
|
||||
}
|
||||
|
||||
type packagemap = map[string][]*Package
|
||||
|
||||
// Find will attempt to find the system package manager
|
||||
func Find(osid string) PackageManager {
|
||||
|
||||
@@ -50,6 +70,58 @@ func newPackageManager(pmname string, osid string) PackageManager {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dependancy represents a system package that we require
|
||||
type Dependancy struct {
|
||||
Name string
|
||||
PackageName string
|
||||
Installed bool
|
||||
InstallCommand string
|
||||
Version string
|
||||
Optional bool
|
||||
External bool
|
||||
}
|
||||
|
||||
// DependencyList is a list of Dependency instances
|
||||
type DependencyList []*Dependancy
|
||||
|
||||
// InstallAllRequiredCommand returns the command you need to use to install all required dependencies
|
||||
func (d DependencyList) InstallAllRequiredCommand() string {
|
||||
|
||||
result := ""
|
||||
for _, dependency := range d {
|
||||
if dependency.PackageName != "" {
|
||||
if !dependency.Installed && !dependency.Optional {
|
||||
if result == "" {
|
||||
result = dependency.InstallCommand
|
||||
} else {
|
||||
result += " " + dependency.PackageName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// InstallAllOptionalCommand returns the command you need to use to install all optional dependencies
|
||||
func (d DependencyList) InstallAllOptionalCommand() string {
|
||||
|
||||
result := ""
|
||||
for _, dependency := range d {
|
||||
if dependency.PackageName != "" {
|
||||
if !dependency.Installed && dependency.Optional {
|
||||
if result == "" {
|
||||
result = dependency.InstallCommand
|
||||
} else {
|
||||
result += " " + dependency.PackageName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Dependancies scans the system for required dependancies
|
||||
// Returns a list of dependancies search for, whether they were found
|
||||
// and whether they were installed
|
||||
|
||||
@@ -2,20 +2,14 @@
|
||||
|
||||
package system
|
||||
|
||||
import "github.com/wailsapp/wails/v2/internal/system/operatingsystem"
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func (i *Info) discover() error {
|
||||
|
||||
var err error
|
||||
osinfo, err := operatingsystem.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.OS = osinfo
|
||||
|
||||
// dll := syscall.MustLoadDLL("kernel32.dll")
|
||||
// p := dll.MustFindProc("GetVersion")
|
||||
// v, _, _ := p.Call()
|
||||
// fmt.Printf("Windows version %d.%d (Build %d)\n", byte(v), uint8(v>>8), uint16(v>>16))
|
||||
return nil
|
||||
func (i *Info) discover() {
|
||||
dll := syscall.MustLoadDLL("kernel32.dll")
|
||||
p := dll.MustFindProc("GetVersion")
|
||||
v, _, _ := p.Call()
|
||||
fmt.Printf("Windows version %d.%d (Build %d)\n", byte(v), uint8(v>>8), uint16(v>>16))
|
||||
}
|
||||
|
||||
@@ -6,14 +6,13 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/leaanthony/gosod"
|
||||
"github.com/leaanthony/slicer"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
)
|
||||
|
||||
// Cahce for the templates
|
||||
@@ -22,29 +21,21 @@ var templateCache []Template = nil
|
||||
|
||||
// Data contains the data we wish to embed during template installation
|
||||
type Data struct {
|
||||
ProjectName string
|
||||
BinaryName string
|
||||
WailsVersion string
|
||||
NPMProjectName string
|
||||
AuthorName string
|
||||
AuthorEmail string
|
||||
AuthorNameAndEmail string
|
||||
WailsDirectory string
|
||||
ProjectName string
|
||||
BinaryName string
|
||||
WailsVersion string
|
||||
NPMProjectName string
|
||||
Author string
|
||||
WailsDirectory string
|
||||
}
|
||||
|
||||
// Options for installing a template
|
||||
type Options struct {
|
||||
ProjectName string
|
||||
TemplateName string
|
||||
BinaryName string
|
||||
TargetDir string
|
||||
Logger *clilogger.CLILogger
|
||||
GenerateVSCode bool
|
||||
PathToDesktopBinary string
|
||||
PathToServerBinary string
|
||||
InitGit bool
|
||||
AuthorName string
|
||||
AuthorEmail string
|
||||
ProjectName string
|
||||
TemplateName string
|
||||
BinaryName string
|
||||
TargetDir string
|
||||
Logger *logger.Logger
|
||||
}
|
||||
|
||||
// Template holds data relating to a template
|
||||
@@ -171,24 +162,9 @@ func Install(options *Options) error {
|
||||
}
|
||||
|
||||
// Did the user want to install in current directory?
|
||||
if options.TargetDir == "" {
|
||||
|
||||
// If the current directory is empty, use it
|
||||
isEmpty, err := fs.DirIsEmpty(cwd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isEmpty {
|
||||
// Yes - use cwd
|
||||
options.TargetDir = cwd
|
||||
} else {
|
||||
options.TargetDir = filepath.Join(cwd, options.ProjectName)
|
||||
if fs.DirExists(options.TargetDir) {
|
||||
return fmt.Errorf("cannot create project directory. Dir exists: %s", options.TargetDir)
|
||||
}
|
||||
}
|
||||
|
||||
if options.TargetDir == "." {
|
||||
// Yes - use cwd
|
||||
options.TargetDir = cwd
|
||||
} else {
|
||||
// Get the absolute path of the given directory
|
||||
targetDir, err := filepath.Abs(filepath.Join(cwd, options.TargetDir))
|
||||
@@ -229,93 +205,43 @@ func Install(options *Options) error {
|
||||
BinaryName: filepath.Base(options.TargetDir),
|
||||
NPMProjectName: NPMProjectName,
|
||||
WailsDirectory: localWailsDirectory,
|
||||
AuthorEmail: options.AuthorEmail,
|
||||
AuthorName: options.AuthorName,
|
||||
}
|
||||
|
||||
// Create a formatted name and email combo.
|
||||
if options.AuthorName != "" {
|
||||
templateData.AuthorNameAndEmail = options.AuthorName + " "
|
||||
}
|
||||
if options.AuthorEmail != "" {
|
||||
templateData.AuthorNameAndEmail += "<" + options.AuthorEmail + ">"
|
||||
}
|
||||
templateData.AuthorNameAndEmail = strings.TrimSpace(templateData.AuthorNameAndEmail)
|
||||
|
||||
// Extract the template
|
||||
err = installer.Extract(options.TargetDir, templateData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = generateIDEFiles(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Calculate the directory name
|
||||
return nil
|
||||
}
|
||||
|
||||
// OutputList prints the list of available tempaltes to the given logger
|
||||
func OutputList(logger *clilogger.CLILogger) error {
|
||||
func OutputList(logger *logger.Logger) error {
|
||||
templates, err := List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(logger.Writer)
|
||||
table.SetHeader([]string{"Template", "Short Name", "Description"})
|
||||
table.SetAutoWrapText(false)
|
||||
table.SetAutoFormatHeaders(true)
|
||||
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
||||
table.SetCenterSeparator("")
|
||||
table.SetColumnSeparator("")
|
||||
table.SetRowSeparator("")
|
||||
table.SetHeaderLine(false)
|
||||
table.SetBorder(false)
|
||||
table.SetTablePadding("\t") // pad with tabs
|
||||
table.SetNoWhiteSpace(true)
|
||||
for _, template := range templates {
|
||||
table.Append([]string{template.Name, template.ShortName, template.Description})
|
||||
for _, writer := range logger.Writers() {
|
||||
table := tablewriter.NewWriter(writer)
|
||||
table.SetHeader([]string{"Template", "Short Name", "Description"})
|
||||
table.SetAutoWrapText(false)
|
||||
table.SetAutoFormatHeaders(true)
|
||||
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
||||
table.SetCenterSeparator("")
|
||||
table.SetColumnSeparator("")
|
||||
table.SetRowSeparator("")
|
||||
table.SetHeaderLine(false)
|
||||
table.SetBorder(false)
|
||||
table.SetTablePadding("\t") // pad with tabs
|
||||
table.SetNoWhiteSpace(true)
|
||||
for _, template := range templates {
|
||||
table.Append([]string{template.Name, template.ShortName, template.Description})
|
||||
}
|
||||
table.Render()
|
||||
}
|
||||
table.Render()
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateIDEFiles(options *Options) error {
|
||||
|
||||
if options.GenerateVSCode {
|
||||
return generateVSCodeFiles(options)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateVSCodeFiles(options *Options) error {
|
||||
|
||||
targetDir := filepath.Join(options.TargetDir, ".vscode")
|
||||
sourceDir := fs.RelativePath(filepath.Join("./ides/vscode"))
|
||||
|
||||
// Use Gosod to install the template
|
||||
installer, err := gosod.TemplateDir(sourceDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
binaryName := filepath.Base(options.TargetDir)
|
||||
if runtime.GOOS == "windows" {
|
||||
// yay windows
|
||||
binaryName += ".exe"
|
||||
}
|
||||
|
||||
options.PathToDesktopBinary = filepath.Join("build", runtime.GOOS, "desktop", binaryName)
|
||||
options.PathToServerBinary = filepath.Join("build", runtime.GOOS, "server", binaryName)
|
||||
|
||||
err = installer.Extract(targetDir, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wailsapp/wails/v2"
|
||||
wails "github.com/wailsapp/wails/v2"
|
||||
)
|
||||
|
||||
// Basic application struct
|
||||
|
||||
@@ -2,21 +2,21 @@
|
||||
"name": "svelte-app",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"build": "npx rollup -c",
|
||||
"dev": "npx rollup -c -w",
|
||||
"start": "npx sirv public",
|
||||
"start:dev": "npx sirv public --single --host 0.0.0.0 --dev"
|
||||
"build": "rollup -c",
|
||||
"dev": "rollup -c -w",
|
||||
"start": "sirv public",
|
||||
"start:dev": "sirv public --single --host 0.0.0.0 --dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^17.0.0",
|
||||
"@rollup/plugin-node-resolve": "^11.0.1",
|
||||
"focus-visible": "^5.2.0",
|
||||
"rollup": "^2.35.1",
|
||||
"rollup-plugin-livereload": "^2.0.0",
|
||||
"rollup-plugin-svelte": "^7.0.0",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"svelte": "^3.31.1",
|
||||
"svelte-mui": "^0.3.3-5"
|
||||
"@rollup/plugin-commonjs": "^11.0.0",
|
||||
"@rollup/plugin-node-resolve": "^7.0.0",
|
||||
"focus-visible": "^5.0.2",
|
||||
"rollup": "^1.20.0",
|
||||
"rollup-plugin-livereload": "^1.0.0",
|
||||
"rollup-plugin-svelte": "^5.0.3",
|
||||
"rollup-plugin-terser": "^5.1.2",
|
||||
"svelte": "^3.0.0",
|
||||
"svelte-mui": "^0.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"sirv-cli": "^0.4.4"
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"log"
|
||||
wails "github.com/wailsapp/wails/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// Create application with options
|
||||
app, err := wails.CreateApp("{{.ProjectName}}", 1024, 768)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
app := wails.CreateApp("{{.ProjectName}}", 1024, 768)
|
||||
|
||||
app.Bind(newBasic())
|
||||
|
||||
err = app.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
app.Run()
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wailsapp/wails/v2"
|
||||
wails "github.com/wailsapp/wails/v2"
|
||||
)
|
||||
|
||||
// Basic application struct
|
||||
@@ -12,19 +12,20 @@ type Basic struct {
|
||||
}
|
||||
|
||||
// newBasic creates a new Basic application struct
|
||||
func NewBasic() *Basic {
|
||||
func newBasic() *Basic {
|
||||
return &Basic{}
|
||||
}
|
||||
|
||||
// startup is called at application startup
|
||||
func (b *Basic) startup(runtime *wails.Runtime) {
|
||||
// WailsInit is called at application startup
|
||||
func (b *Basic) WailsInit(runtime *wails.Runtime) error {
|
||||
// Perform your setup here
|
||||
b.runtime = runtime
|
||||
runtime.Window.SetTitle("{{.ProjectName}}")
|
||||
return nil
|
||||
}
|
||||
|
||||
// shutdown is called at application termination
|
||||
func (b *Basic) shutdown() {
|
||||
// WailsShutdown is called at application termination
|
||||
func (b *Basic) WailsShutdown() {
|
||||
// Perform your teardown here
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<link rel="stylesheet" href="/main.css">
|
||||
</head>
|
||||
|
||||
<body data-wails-drag>
|
||||
<body>
|
||||
<div id="logo"></div>
|
||||
<div id="input">
|
||||
<input id="name" type="text"></input>
|
||||
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user