Compare commits

...

1 Commits

Author SHA1 Message Date
Lea Anthony
71bfd29376 Feature/v2 mac (#555)
* Get it compiling

* Stubs in place to compile

* Semi runs

* add darwin platform for server

* Evaluation working correctly. Still WIP

* Ignore favicon for desktop

* lint

* Remove feature flag code

* More feature flag removal

* Support sending messages to the backend

* Callbacks working

* Add Center + refactor prefs

* Fix logger

* Callback hooks for MOAE

* Update packages

* ignore test builds

* Support Un/Fullscreen

* vscode stuff

* Only show window when rendered

* Get it compiling again!

* Support tons of stuff!

* Tidy up

* More refactoring

* WIP [bugged]

* Got get frame working

* fix setsize and setposition

* Add Mac Application Options

* Add HideTitleBar

* Support more mac window options

* Add Toolbar support for Mac

* Support colour

* Support runtime colour change

* Moved options to own package

* Refactored mac titlebar options

* Support hidden titlebar

* Support HiddenInset Titlebar

* Support TitleBar Default

Fixed merging defaults

* Sample titlebars

* Fix minmax app

* Min/Max size supported

* WIP: Support multiple value return

* Support OpenDialog

* Remove old dialog code

* change service bus topics for dialogs

* Revert changes to v1

* Use options struct for dialogs

* Initial support for OpenDialog

* Support selecting files+dirs

* Support multiple files in dialog

* Support all dialog properties

* Add comments

* Filter support

* Support default directory

* Support SaveDialog

* Tidy Up

* WIP: Basics of window drag

* Support window dragging

* Update tests

* Frameless is calculated for Mac

* Tidy up

* Support vibrancy and transparency for webview

Options Colour -> RGBA

* Rename vibrancy to appearance

* Add default appearance

* Refactor part 1

* Refactor Part 2

* Support Translucent Window Background

* Update runtime test

* Add IsDarkMode

Updated runtime test

* Support theme mode change event

* Misc fixes for events

* Support OnMultiple

* Small fixes to frontend events

* Add System calls to runtime

* Add system calls to js runtime

* Support System calls in Go Runtime

* Port Sync Store

* Refactor store. Add get().

* Refactor system. Add IsDarkMode state store

* Use IsDarkMode state store

* Remove generated files

* Support setting app state at startup

* Add Store to go runtime

* Update runtime to v1.0.3

* Remove unused event messages

* Debugging

* initial kitchen sink

* Fix right click crash

* Better drag support

* WIP

* Remove log package

* Add Log to Go runtime

* Add logging to kitchen sink

* Improved CodeBlock. Dark mode to store.

* Start Events. List styling moved to global scope.

* Make logger a public package

* Revert logger package

* Major logging refactor

* Make Ffenestri use logging subsystem.

* Debug refactor

* Add trace to JS runtime

* Migrate runtime to @wails

* Support Trace in kitchensink

* Support trace in go runtime

* Support log level

* Support Print in JS runtime

* Runtime v1.0.1

* Move Info message to Trace

* Support Print logging

* Updated Logger interface

* Fix number of methods in Log

* Support SetLogLevel() at runtime

Refactor of loglevel

* Made go runtime package public.

Using loglevel store to keep loglevel in sync

* Support dynamic loglevel

* Runtime refactor

* Fully refactored logging

* Better looking scrollbar

* Terminal output component

* Link component

* SetLogLevel fully supported

* Runtime defs update.

Slight System refactor

* More Logging updates

* Move preview for SetLogLevel

* Fix log level reactivity.

Misc fixes and tweaks

* logging: slight refactor

* Update logger constants to fix default values

* @wails/runtime v1.0.4

* Fix change in logging levels

* hook in windowWillClose

* refactor clilogger

* WIP Events.On

* Add Events.On

* Improved error handling?

* Disable annoying smart quotes

* update runtime definitions

* Support Emit & Once. Improved On.

* Remove old event methods

* Remove old Event methods

* Update runtime in kitchensink

* Revert Fatal on JS Error

* Tidy up events runtime

* Finish events page

* Update Browser runtime API

* Unify Browser runtime

* JS Runtime v1.0.8

* Fix browser runtime export

* Remove debug line

* Add Browser examples

* Update title

* Improved runtime.System

* Update runtime.System to make all methods

* Expose System methods in Go runtime

* Add System to kitchensink

* Huge improvement to calls: Now handles objects

* Add JS runtime Dialog

* Dialog WIP

* Js package generation (#554)

* WIP

* Generation of index.js

* Add RelativeToCwd

* Add JSDoc comments

* Convert to ES6 syntax

* Fix typo

* Initial generation of typescript declarations

* Typescript improvements

* Improved @returns jsdoc

* Improved declaration files

* Simplified output

* Rename file

* Tidy up

* Revert "Simplified output"

This reverts commit 15cdf7382b.

* Now parsing actual code

* Support Array types

* Reimagined parser

* Wrap parsing in Parser

* Rewritten module generator (TS Only)

* Final touches

* Slight refactor to improve output

* Struct comments. External struct literal binding

* Reworked project parser *working*

* remove debug info

* Refactor of parser

* remove the spew

* Better Ts support

* Better project generation logic

* Support local functions in bind()

* JS Object generation. Linting.

* Support json tags in module generation

* Updated mod files

* Support vscode file generation

* Better global.d.ts

* add ts-check to templates

* Support TS declaration files

* improved 'generate' command for module

Co-authored-by: Travis McLane <tmclane@gmail.com>
2020-11-15 09:27:23 +11:00
219 changed files with 14067 additions and 2058 deletions

11
.gitignore vendored
View File

@@ -16,4 +16,13 @@ examples/**/example*
cmd/wails/wails
.DS_Store
tmp
node_modules/
node_modules/
package.json.md5
v2/test/**/frontend/dist
v2/test/**/build/
v2/test/frameless/icon.png
v2/test/hidden/icon.png
v2/internal/ffenestri/runtime.c
v2/internal/runtime/assets/desktop.js
v2/test/kitchensink/frontend/public/bundle.*
v2/pkg/parser/testproject/frontend/wails

2
go.mod
View File

@@ -22,7 +22,7 @@ require (
github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba
golang.org/x/image v0.0.0-20200430140353-33d19683fad8
golang.org/x/net v0.0.0-20200625001655-4c5254603344 // indirect
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
golang.org/x/text v0.3.0
gopkg.in/AlecAivazis/survey.v1 v1.8.4
gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22

2
go.sum
View File

@@ -81,6 +81,8 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-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 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/AlecAivazis/survey.v1 v1.8.4 h1:10xXXN3wgIhPheb5NI58zFgZv32Ana7P3Tl4shW+0Qc=

13
v2/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,13 @@
{
"files.associations": {
"ios": "c",
"typeinfo": "c",
"sstream": "c",
"__functional_03": "c",
"functional": "c",
"__locale": "c",
"locale": "c",
"chrono": "c",
"system_error": "c"
}
}

View File

@@ -2,19 +2,19 @@ package build
import (
"fmt"
"os"
"io"
"runtime"
"strings"
"time"
"github.com/leaanthony/clir"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/build"
)
// AddBuildSubcommand adds the `build` command for the Wails application
func AddBuildSubcommand(app *clir.Cli) {
func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
outputType := "desktop"
@@ -56,11 +56,8 @@ func AddBuildSubcommand(app *clir.Cli) {
command.Action(func() error {
// Create logger
logger := logger.New()
if !quiet {
logger.AddOutput(os.Stdout)
}
logger := clilogger.New(w)
logger.Mute(quiet)
// Validate output type
if !validTargetTypes.Contains(outputType) {
@@ -72,8 +69,8 @@ func AddBuildSubcommand(app *clir.Cli) {
}
task := fmt.Sprintf("Building %s Application", strings.Title(outputType))
logger.Writeln(task)
logger.Writeln(strings.Repeat("-", len(task)))
logger.Println(task)
logger.Println(strings.Repeat("-", len(task)))
// Setup mode
mode := build.Debug
@@ -108,9 +105,9 @@ func doBuild(buildOptions *build.Options) error {
}
// Output stats
elapsed := time.Since(start)
buildOptions.Logger.Writeln("")
buildOptions.Logger.Writeln(fmt.Sprintf("Built '%s' in %s.", outputFilename, elapsed.Round(time.Millisecond).String()))
buildOptions.Logger.Writeln("")
buildOptions.Logger.Println("")
buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.", outputFilename, elapsed.Round(time.Millisecond).String()))
buildOptions.Logger.Println("")
return nil
}

View File

@@ -2,6 +2,7 @@ package dev
import (
"fmt"
"io"
"os"
"os/signal"
"runtime"
@@ -13,13 +14,13 @@ import (
"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"
)
// AddSubcommand adds the `dev` command for the Wails application
func AddSubcommand(app *clir.Cli) error {
func AddSubcommand(app *clir.Cli, w io.Writer) error {
command := app.NewSubCommand("dev", "Development mode")
@@ -51,8 +52,7 @@ func AddSubcommand(app *clir.Cli) error {
}
// Create logger
logger := logger.New()
logger.AddOutput(os.Stdout)
logger := clilogger.New(w)
app.PrintBanner()
// TODO: Check you are in a project directory
@@ -74,11 +74,11 @@ func AddSubcommand(app *clir.Cli) error {
debounceQuit := make(chan bool, 1)
// Do initial build
logger.Info("Building application for development...")
logger.Println("Building application for development...")
debugBinaryProcess = restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess)
go debounce(100*time.Millisecond, watcher.Events, debounceQuit, func(event fsnotify.Event) {
// logger.Info("event: %+v", event)
// logger.Println("event: %+v", event)
// Check for new directories
if event.Op&fsnotify.Create == fsnotify.Create {
@@ -86,7 +86,7 @@ func AddSubcommand(app *clir.Cli) error {
if fs.DirExists(event.Name) {
if !strings.Contains(event.Name, "node_modules") {
watcher.Add(event.Name)
logger.Info("Watching directory: %s", event.Name)
logger.Println("Watching directory: %s", event.Name)
}
}
return
@@ -95,7 +95,7 @@ func AddSubcommand(app *clir.Cli) error {
// Check for file writes
if event.Op&fsnotify.Write == fsnotify.Write {
// logger.Info("modified file: %s", event.Name)
// logger.Println("modified file: %s", event.Name)
var rebuild bool = false
// Iterate all file patterns
@@ -112,14 +112,14 @@ func AddSubcommand(app *clir.Cli) error {
}
if !rebuild {
logger.Info("Filename change: %s did not match extension list %s", event.Name, extensions)
logger.Println("Filename change: %s did not match extension list %s", event.Name, extensions)
return
}
if buildFrontend {
logger.Info("Full rebuild triggered: %s updated", event.Name)
logger.Println("Full rebuild triggered: %s updated", event.Name)
} else {
logger.Info("Partial build triggered: %s updated", event.Name)
logger.Println("Partial build triggered: %s updated", event.Name)
}
// Do a rebuild
@@ -152,7 +152,7 @@ func AddSubcommand(app *clir.Cli) error {
if strings.Contains(dir, "node_modules") {
return
}
logger.Info("Watching directory: %s", dir)
logger.Println("Watching directory: %s", dir)
err = watcher.Add(dir)
if err != nil {
logger.Fatal(err.Error())
@@ -176,7 +176,7 @@ func AddSubcommand(app *clir.Cli) error {
debugBinaryProcess.Kill()
}
logger.Info("Development mode exited")
logger.Println("Development mode exited")
return nil
})
@@ -203,15 +203,15 @@ exit:
}
}
func restartApp(logger *logger.Logger, outputType string, ldflags string, compilerCommand string, buildFrontend bool, debugBinaryProcess *process.Process) *process.Process {
func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, buildFrontend bool, debugBinaryProcess *process.Process) *process.Process {
appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand, buildFrontend)
println()
if err != nil {
logger.Error("Build Failed: %s", err.Error())
logger.Println("[ERROR] Build Failed: %s", err.Error())
return nil
}
logger.Info("Build new binary: %s", appBinary)
logger.Println("Build new binary: %s", appBinary)
// Kill existing binary if need be
if debugBinaryProcess != nil {
@@ -238,7 +238,7 @@ func restartApp(logger *logger.Logger, outputType string, ldflags string, compil
return newProcess
}
func buildApp(logger *logger.Logger, outputType string, ldflags string, compilerCommand string, buildFrontend bool) (string, error) {
func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, buildFrontend bool) (string, error) {
// Create random output file
outputFile := fmt.Sprintf("debug-%d", time.Now().Unix())

View File

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

View File

@@ -0,0 +1,91 @@
package generate
import (
"io"
"time"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/parser"
)
// AddSubcommand adds the `dev` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error {
command := app.NewSubCommand("generate", "Code Generation Tools")
// Backend API
backendAPI := command.NewSubCommand("module", "Generates a JS module for the frontend to interface with the backend")
// Quiet Init
quiet := false
backendAPI.BoolFlag("q", "Supress output to console", &quiet)
backendAPI.Action(func() error {
// Create logger
logger := clilogger.New(w)
logger.Mute(quiet)
app.PrintBanner()
logger.Print("Generating Javascript module for Go code...")
// Start Time
start := time.Now()
p, err := parser.GenerateWailsFrontendPackage()
if err != nil {
return err
}
logger.Println("done.")
logger.Println("")
elapsed := time.Since(start)
packages := p.Packages
// Print report
for _, pkg := range p.Packages {
if pkg.ShouldGenerate() {
logPackage(pkg, logger)
}
}
logger.Println("%d packages parsed in %s.", len(packages), elapsed)
return nil
})
return nil
}
func logPackage(pkg *parser.Package, logger *clilogger.CLILogger) {
logger.Println("Processed Go package '" + pkg.Gopackage.Name + "' as '" + pkg.Name + "'")
for _, strct := range pkg.Structs() {
logger.Println("")
logger.Println(" Processed struct '" + strct.Name + "'")
if strct.IsBound {
for _, method := range strct.Methods {
logger.Println(" Bound method '" + method.Name + "'")
}
}
if strct.IsUsedAsData {
for _, field := range strct.Fields {
if !field.Ignored {
logger.Print(" Processed ")
if field.IsOptional {
logger.Print("optional ")
}
logger.Println("field '" + field.Name + "' as '" + field.JSName() + "'")
}
}
}
}
logger.Println("")
// logger.Println(" Original Go Package Path:", pkg.Gopackage.PkgPath)
// logger.Println(" Original Go Package Path:", pkg.Gopackage.PkgPath)
}

View File

@@ -2,17 +2,17 @@ package initialise
import (
"fmt"
"os"
"io"
"strings"
"time"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/templates"
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
// AddSubcommand adds the `init` command for the Wails application
func AddSubcommand(app *clir.Cli) error {
func AddSubcommand(app *clir.Cli, w io.Writer) error {
// Load the template shortnames
validShortNames, err := templates.TemplateShortNames()
@@ -32,13 +32,17 @@ func AddSubcommand(app *clir.Cli) 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)
// VSCode project files
vscode := false
command.BoolFlag("vscode", "Generate VSCode project files", &vscode)
// List templates
list := false
command.BoolFlag("l", "List templates", &list)
@@ -46,32 +50,29 @@ func AddSubcommand(app *clir.Cli) error {
command.Action(func() error {
// Create logger
logger := logger.New()
if !quiet {
logger.AddOutput(os.Stdout)
}
logger := clilogger.New(w)
logger.Mute(quiet)
// Are we listing templates?
if list {
app.PrintBanner()
err := templates.OutputList(logger)
logger.Writeln("")
logger.Println("")
return err
}
// Validate output type
if !validShortNames.Contains(templateName) {
logger.Write(fmt.Sprintf("ERROR: Template '%s' is not valid", templateName))
logger.Writeln("")
logger.Print(fmt.Sprintf("[ERROR] Template '%s' is not valid", templateName))
logger.Println("")
command.PrintHelp()
return nil
}
// Validate name
if len(projectName) == 0 {
logger.Writeln("ERROR: Project name required")
logger.Writeln("")
logger.Println("ERROR: Project name required")
logger.Println("")
command.PrintHelp()
return nil
}
@@ -81,15 +82,16 @@ func AddSubcommand(app *clir.Cli) error {
}
task := fmt.Sprintf("Initialising Project %s", strings.Title(projectName))
logger.Writeln(task)
logger.Writeln(strings.Repeat("-", len(task)))
logger.Println(task)
logger.Println(strings.Repeat("-", len(task)))
// Create Template Options
options := &templates.Options{
ProjectName: projectName,
TargetDir: projectDirectory,
TemplateName: templateName,
Logger: logger,
ProjectName: projectName,
TargetDir: projectDirectory,
TemplateName: templateName,
Logger: logger,
GenerateVSCode: vscode,
}
return initProject(options)
@@ -112,9 +114,17 @@ func initProject(options *templates.Options) error {
// Output stats
elapsed := time.Since(start)
options.Logger.Writeln("")
options.Logger.Writeln(fmt.Sprintf("Initialised project '%s' in %s.", options.ProjectName, elapsed.Round(time.Millisecond).String()))
options.Logger.Writeln("")
options.Logger.Println("")
options.Logger.Println("Project Name: " + options.ProjectName)
options.Logger.Println("Project Directory: " + options.TargetDir)
options.Logger.Println("Project Template: " + options.TemplateName)
options.Logger.Println("")
if options.GenerateVSCode {
options.Logger.Println("VSCode config files generated.")
}
options.Logger.Println("")
options.Logger.Println(fmt.Sprintf("Initialised project '%s' in %s.", options.ProjectName, elapsed.Round(time.Millisecond).String()))
options.Logger.Println("")
return nil
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/dev"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/doctor"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/generate"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/initialise"
)
@@ -22,17 +23,22 @@ func main() {
app := clir.NewCli("Wails", "Go/HTML Application Framework", version)
build.AddBuildSubcommand(app)
err = initialise.AddSubcommand(app)
build.AddBuildSubcommand(app, os.Stdout)
err = initialise.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = doctor.AddSubcommand(app)
err = doctor.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = dev.AddSubcommand(app)
err = dev.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = generate.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}

View File

@@ -3,15 +3,23 @@ module github.com/wailsapp/wails/v2
go 1.13
require (
github.com/davecgh/go-spew v1.1.1
github.com/fatih/structtag v1.2.0
github.com/fsnotify/fsnotify v1.4.9
github.com/imdario/mergo v0.3.11
github.com/leaanthony/clir v1.0.4
github.com/leaanthony/gosod v0.0.4
github.com/leaanthony/slicer v1.4.1
github.com/leaanthony/slicer v1.5.0
github.com/matryer/is v1.4.0
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/olekukonko/tablewriter v0.0.4
github.com/pkg/errors v0.9.1
github.com/tdewolff/minify v2.3.6+incompatible
github.com/tdewolff/parse v2.3.4+incompatible // indirect
github.com/tdewolff/test v1.0.6 // indirect
github.com/xyproto/xpm v1.2.1
golang.org/x/net v0.0.0-20200822124328-c89045814202
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
nhooyr.io/websocket v1.8.6

View File

@@ -1,6 +1,8 @@
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=
@@ -29,6 +31,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
@@ -40,8 +44,8 @@ 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.4.1 h1:X/SmRIDhkUAolP79mSTO0jTcVX1k504PJBqvV6TwP0w=
github.com/leaanthony/slicer v1.4.1/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
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=
@@ -58,12 +62,20 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo=
github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs=
github.com/tdewolff/parse v2.3.4+incompatible h1:x05/cnGwIMf4ceLuDMBOdQ1qGniMoxpP46ghf0Qzh38=
github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ=
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
@@ -89,6 +101,8 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -110,5 +124,7 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=

View File

@@ -5,6 +5,5 @@ package app
// Init initialises the application for a debug environment
func (a *App) Init() error {
a.debug = true
println("Initialising debug options")
return nil
}

View File

@@ -10,7 +10,7 @@ package app
import (
"os"
"github.com/wailsapp/wails/v2/internal/features"
"github.com/wailsapp/wails/v2/pkg/options"
)
// App defines a Wails application structure
@@ -22,12 +22,10 @@ type App struct {
// Indicates if the app is running in debug mode
debug bool
Features *features.Features
}
// CreateApp returns a null application
func CreateApp(options *Options) *App {
func CreateApp(options *options.App) *App {
return &App{}
}

View File

@@ -3,16 +3,15 @@
package app
import (
"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/messagedispatcher"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/internal/signal"
"github.com/wailsapp/wails/v2/internal/subsystem"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/internal/runtime"
)
// App defines a Wails application structure
@@ -21,6 +20,7 @@ type App struct {
servicebus *servicebus.ServiceBus
logger *logger.Logger
signal *signal.Manager
options *options.App
// Subsystems
log *subsystem.Log
@@ -36,46 +36,31 @@ type App struct {
// This is our binding DB
bindings *binding.Bindings
// Feature flags
Features *features.Features
// LogLevel Store
loglevelStore *runtime.Store
}
// Create App
func CreateApp(options *Options) *App {
func CreateApp(options *options.App) *App {
// Merge default options
options.mergeDefaults()
options.MergeDefaults()
// Set up logger
myLogger := logger.New(os.Stdout)
myLogger.SetLogLevel(logger.TRACE)
myLogger := logger.New(options.Logger)
myLogger.SetLogLevel(options.LogLevel)
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,
StartHidden: options.StartHidden,
// This should be controlled by the compile time flags...
DevTools: true,
Resizable: !options.DisableResize,
Fullscreen: options.Fullscreen,
}, myLogger)
window := ffenestri.NewApplicationWithConfig(options, myLogger)
result := &App{
window: window,
servicebus: servicebus.New(myLogger),
logger: myLogger,
bindings: binding.NewBindings(myLogger),
Features: features.New(),
}
result.options = options
// Initialise the app
result.Init()
@@ -106,6 +91,9 @@ func (a *App) Run() error {
a.runtime = runtime
a.runtime.Start()
// Application Stores
a.loglevelStore = a.runtime.GoRuntime().Store.New("loglevel", a.options.LogLevel)
// Start the binding subsystem
binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, a.runtime.GoRuntime())
if err != nil {
@@ -115,7 +103,7 @@ func (a *App) Run() error {
a.binding.Start()
// Start the logging subsystem
log, err := subsystem.NewLog(a.servicebus, a.logger)
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
if err != nil {
return err
}
@@ -139,7 +127,7 @@ func (a *App) Run() error {
a.event.Start()
// Start the call subsystem
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB())
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
if err != nil {
return err
}
@@ -152,7 +140,7 @@ func (a *App) Run() error {
return err
}
result := a.window.Run(dispatcher, bindingDump, a.Features)
result := a.window.Run(dispatcher, bindingDump)
a.logger.Trace("Ffenestri.Run() exited")
a.servicebus.Stop()

View File

@@ -8,7 +8,6 @@ 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"
@@ -44,9 +43,6 @@ type App struct {
servicebus *servicebus.ServiceBus
debug bool
// Feature flags
Features *features.Features
}
// Create App
@@ -67,7 +63,6 @@ func CreateApp(options *Options) *App {
MinHeight: options.MinHeight,
MaxWidth: options.MaxWidth,
MaxHeight: options.MaxHeight,
Frameless: options.Frameless,
StartHidden: options.StartHidden,
// This should be controlled by the compile time flags...
@@ -191,7 +186,7 @@ func (a *App) Run() error {
}
}()
result := a.window.Run(dispatcher, bindingDump, a.Features)
result := a.window.Run(dispatcher, bindingDump)
a.servicebus.Stop()
return result

View File

@@ -1,35 +0,0 @@
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
StartHidden bool
}
// mergeDefaults will set the minimum default values for an application
func (o *Options) mergeDefaults() {
// Create a default title
if len(o.Title) == 0 {
o.Title = "My Wails App"
}
// Default width
if o.Width == 0 {
o.Width = 1024
}
// Default height
if o.Height == 0 {
o.Height = 768
}
}

View File

@@ -1,6 +1,7 @@
package binding
import (
"encoding/json"
"fmt"
"reflect"
"strings"
@@ -34,7 +35,7 @@ func (b *BoundMethod) VerifyWailsInit() error {
}
// Check input type
if !b.Inputs[0].IsType("*goruntime.Runtime") {
if !b.Inputs[0].IsType("*runtime.Runtime") {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
@@ -78,6 +79,26 @@ func (b *BoundMethod) OutputCount() int {
return len(b.Outputs)
}
// ParseArgs method converts the input json into the types expected by the method
func (b *BoundMethod) ParseArgs(args []json.RawMessage) ([]interface{}, error) {
result := make([]interface{}, b.InputCount())
for index, arg := range args {
typ := b.Inputs[index].reflectType
inputValue := reflect.New(typ).Interface()
err := json.Unmarshal(arg, inputValue)
if err != nil {
return nil, err
}
if inputValue == nil {
result[index] = reflect.Zero(typ).Interface()
} else {
result[index] = reflect.ValueOf(inputValue).Elem().Interface()
}
}
return result, nil
}
// Call will attempt to call this bound method with the given args
func (b *BoundMethod) Call(args []interface{}) (interface{}, error) {
// Check inputs
@@ -94,17 +115,8 @@ 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] = value
callArgs[index] = reflect.ValueOf(arg)
}
// Do the call

View File

@@ -1,6 +1,7 @@
package binding
import (
"encoding/json"
"fmt"
"reflect"
)
@@ -67,6 +68,8 @@ func 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++ {
@@ -84,7 +87,7 @@ func getMethods(value interface{}) ([]*BoundMethod, error) {
}
// convertArgToValue
func convertArgToValue(input interface{}, target *Parameter) (result reflect.Value, err error) {
func convertArgToValue(input json.RawMessage, target *Parameter) (result reflect.Value, err error) {
// Catch type conversion panics thrown by convert
defer func() {

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -1,23 +0,0 @@
// +build linux
package ffenestri
/*
#cgo linux CFLAGS: -DFFENESTRI_LINUX=1
#cgo linux pkg-config: gtk+-3.0 webkit2gtk-4.0
#include <stdlib.h>
#include "ffenestri.h"
*/
import "C"
import "github.com/wailsapp/wails/v2/internal/features"
func (a *Application) processOSFeatureFlags(features *features.Features) {
// Process Linux features
// linux := features.Linux
}

View File

@@ -5,9 +5,9 @@ import (
"strings"
"unsafe"
"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"
)
/*
@@ -15,6 +15,9 @@ 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"
@@ -26,36 +29,9 @@ import "C"
// 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
StartHidden bool
}
var defaultConfig = &Config{
Title: "My Wails App",
Width: 800,
Height: 600,
DevTools: true,
Resizable: true,
Fullscreen: false,
Frameless: false,
StartHidden: false,
}
// Application is our main application object
type Application struct {
config *Config
config *options.App
memory []unsafe.Pointer
// This is the main app pointer
@@ -80,7 +56,7 @@ func init() {
}
// NewApplicationWithConfig creates a new application based on the given config
func NewApplicationWithConfig(config *Config, logger *logger.Logger) *Application {
func NewApplicationWithConfig(config *options.App, logger *logger.Logger) *Application {
return &Application{
config: config,
logger: logger.CustomLogger("Ffenestri"),
@@ -90,7 +66,7 @@ func NewApplicationWithConfig(config *Config, logger *logger.Logger) *Applicatio
// NewApplication creates a new Application with the default config
func NewApplication(logger *logger.Logger) *Application {
return &Application{
config: defaultConfig,
config: options.Default,
logger: logger.CustomLogger("Ffenestri"),
}
}
@@ -123,16 +99,25 @@ 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, features *features.Features) error {
func (a *Application) Run(incomingDispatcher Dispatcher, bindings string) error {
title := a.string2CString(a.config.Title)
width := C.int(a.config.Width)
height := C.int(a.config.Height)
resizable := a.bool2Cint(a.config.Resizable)
resizable := a.bool2Cint(!a.config.DisableResize)
devtools := a.bool2Cint(a.config.DevTools)
fullscreen := a.bool2Cint(a.config.Fullscreen)
startHidden := a.bool2Cint(a.config.StartHidden)
app := C.NewApplication(title, width, height, resizable, devtools, fullscreen, startHidden)
logLevel := C.int(a.config.LogLevel)
app := C.NewApplication(title, width, height, resizable, devtools, fullscreen, startHidden, logLevel)
// Save app reference
a.app = unsafe.Pointer(app)
@@ -150,9 +135,14 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, featur
// Set debug if needed
C.SetDebug(app, a.bool2Cint(DEBUG))
// Set Frameless
if a.config.Frameless {
C.DisableFrame(a.app)
// TODO: Move frameless to Linux options
// if a.config.Frameless {
// C.DisableFrame(a.app)
// }
if a.config.RGBA != 0 {
r, g, b, alpha := intToColour(a.config.RGBA)
C.SetColour(a.app, r, g, b, alpha)
}
// Escape bindings so C doesn't freak out
@@ -161,13 +151,13 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, featur
// 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
a.processPlatformSettings()
// Check we could initialise the application
if app != nil {
// Yes - Save memory reference and run app, cleaning up afterwards
@@ -189,11 +179,3 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, featur
func messageFromWindowCallback(data *C.char) {
dispatcher.DispatchMessage(C.GoString(data))
}
func (a *Application) processFeatureFlags(features *features.Features) {
// Process generic features
// Process OS Specific flags
a.processOSFeatureFlags(features)
}

View File

@@ -3,7 +3,7 @@
#include <stdio.h>
extern void *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden);
extern void *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel);
extern void SetMinWindowSize(void *app, int minWidth, int minHeight);
extern void SetMaxWindowSize(void *app, int maxWidth, int maxHeight);
extern void Run(void *app, int argc, char **argv);
@@ -16,18 +16,20 @@ extern void Show(void *app);
extern void Center(void *app);
extern void Maximise(void *app);
extern void Unmaximise(void *app);
extern void ToggleMaximise(void *app);
extern void Minimise(void *app);
extern void Unminimise(void *app);
extern void ToggleMinimise(void *app);
extern void SetColour(void *app, int red, int green, int blue, int alpha);
extern void SetSize(void *app, int width, int height);
extern void SetPosition(void *app, int x, int y);
extern void Quit(void *app);
extern void SetTitle(void *app, const char *title);
extern void Fullscreen(void *app);
extern void UnFullscreen(void *app);
extern int SetColour(void *app, const char *colourString);
extern void ToggleFullscreen(void *app);
extern void DisableFrame(void *app);
extern char *SaveFileDialog(void *appPointer, char *title, char *filter);
extern char *OpenFileDialog(void *appPointer, char *title, char *filter);
extern char *OpenDirectoryDialog(void *appPointer, char *title, char *filter);
extern void OpenDialog(void *appPointer, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolveAliases, int treatPackagesAsDirectories);
extern void SaveDialog(void *appPointer, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories);
extern void DarkModeEnabled(void *appPointer, char *callbackID);
#endif

View File

@@ -13,9 +13,9 @@ import "C"
import (
"strconv"
"unsafe"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options"
)
// Client is our implentation of messageDispatcher.Client
@@ -114,46 +114,43 @@ func (c *Client) WindowSize(width int, height int) {
}
// WindowSetColour sets the window colour
func (c *Client) WindowSetColour(colour string) bool {
result := C.SetColour(c.app.app, c.app.string2CString(colour))
return result == 1
func (c *Client) WindowSetColour(colour int) {
r, g, b, a := intToColour(colour)
C.SetColour(c.app.app, r, g, b, a)
}
// OpenFileDialog will open a file dialog with the given title
func (c *Client) OpenFileDialog(title string, filter string) string {
cstring := C.OpenFileDialog(c.app.app, c.app.string2CString(title), c.app.string2CString(filter))
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
// OpenDialog will open a dialog with the given title and filter
func (c *Client) OpenDialog(dialogOptions *options.OpenDialog, callbackID string) {
C.OpenDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(dialogOptions.Title),
c.app.string2CString(dialogOptions.Filters),
c.app.string2CString(dialogOptions.DefaultFilename),
c.app.string2CString(dialogOptions.DefaultDirectory),
c.app.bool2Cint(dialogOptions.AllowFiles),
c.app.bool2Cint(dialogOptions.AllowDirectories),
c.app.bool2Cint(dialogOptions.AllowMultiple),
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
c.app.bool2Cint(dialogOptions.ResolveAliases),
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
)
}
// SaveFileDialog will open a save file dialog with the given title
func (c *Client) SaveFileDialog(title string, filter string) string {
cstring := C.SaveFileDialog(c.app.app, c.app.string2CString(title), c.app.string2CString(filter))
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
// SaveDialog will open a dialog with the given title and filter
func (c *Client) SaveDialog(dialogOptions *options.SaveDialog, callbackID string) {
C.SaveDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(dialogOptions.Title),
c.app.string2CString(dialogOptions.Filters),
c.app.string2CString(dialogOptions.DefaultFilename),
c.app.string2CString(dialogOptions.DefaultDirectory),
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
)
}
// OpenDirectoryDialog will open a directory dialog with the given title
func (c *Client) OpenDirectoryDialog(title string, filter string) string {
cstring := C.OpenDirectoryDialog(c.app.app, c.app.string2CString(title), c.app.string2CString(filter))
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
func (c *Client) DarkModeEnabled(callbackID string) {
C.DarkModeEnabled(c.app.app, c.app.string2CString(callbackID))
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,67 @@
package ffenestri
/*
#cgo darwin CFLAGS: -DFFENESTRI_DARWIN=1
#cgo darwin LDFLAGS: -framework WebKit -lobjc
extern void TitlebarAppearsTransparent(void *);
extern void HideTitle(void *);
extern void HideTitleBar(void *);
extern void FullSizeContent(void *);
extern void UseToolbar(void *);
extern void HideToolbarSeparator(void *);
extern void DisableFrame(void *);
extern void SetAppearance(void *, const char *);
extern void WebviewIsTransparent(void *);
extern void SetWindowBackgroundIsTranslucent(void *);
*/
import "C"
func (a *Application) processPlatformSettings() {
mac := a.config.Mac
titlebar := mac.TitleBar
// HideTitle
if titlebar.HideTitle {
C.HideTitle(a.app)
}
// HideTitleBar
if titlebar.HideTitleBar {
C.HideTitleBar(a.app)
}
// Full Size Content
if titlebar.FullSizeContent {
C.FullSizeContent(a.app)
}
// Toolbar
if titlebar.UseToolbar {
C.UseToolbar(a.app)
}
if titlebar.HideToolbarSeparator {
C.HideToolbarSeparator(a.app)
}
if titlebar.TitlebarAppearsTransparent {
C.TitlebarAppearsTransparent(a.app)
}
// Process window Appearance
if mac.Appearance != "" {
C.SetAppearance(a.app, a.string2CString(string(mac.Appearance)))
}
// Check if the webview should be transparent
if mac.WebviewIsTransparent {
C.WebviewIsTransparent(a.app)
}
// Check if window should be translucent
if mac.WindowBackgroundIsTranslucent {
C.SetWindowBackgroundIsTranslucent(a.app)
}
}

1383
v2/internal/ffenestri/json.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,119 @@
/*
Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com)
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Source: http://git.ozlabs.org/?p=ccan;a=tree;f=ccan/json;hb=HEAD
*/
#ifndef CCAN_JSON_H
#define CCAN_JSON_H
#include <stdbool.h>
#include <stddef.h>
typedef enum {
JSON_NULL,
JSON_BOOL,
JSON_STRING,
JSON_NUMBER,
JSON_ARRAY,
JSON_OBJECT,
} JsonTag;
typedef struct JsonNode JsonNode;
struct JsonNode
{
/* only if parent is an object or array (NULL otherwise) */
JsonNode *parent;
JsonNode *prev, *next;
/* only if parent is an object (NULL otherwise) */
char *key; /* Must be valid UTF-8. */
JsonTag tag;
union {
/* JSON_BOOL */
bool bool_;
/* JSON_STRING */
char *string_; /* Must be valid UTF-8. */
/* JSON_NUMBER */
double number_;
/* JSON_ARRAY */
/* JSON_OBJECT */
struct {
JsonNode *head, *tail;
} children;
};
};
/*** Encoding, decoding, and validation ***/
JsonNode *json_decode (const char *json);
char *json_encode (const JsonNode *node);
char *json_encode_string (const char *str);
char *json_stringify (const JsonNode *node, const char *space);
void json_delete (JsonNode *node);
bool json_validate (const char *json);
/*** Lookup and traversal ***/
JsonNode *json_find_element (JsonNode *array, int index);
JsonNode *json_find_member (JsonNode *object, const char *key);
JsonNode *json_first_child (const JsonNode *node);
#define json_foreach(i, object_or_array) \
for ((i) = json_first_child(object_or_array); \
(i) != NULL; \
(i) = (i)->next)
/*** Construction and manipulation ***/
JsonNode *json_mknull(void);
JsonNode *json_mkbool(bool b);
JsonNode *json_mkstring(const char *s);
JsonNode *json_mknumber(double n);
JsonNode *json_mkarray(void);
JsonNode *json_mkobject(void);
void json_append_element(JsonNode *array, JsonNode *element);
void json_prepend_element(JsonNode *array, JsonNode *element);
void json_append_member(JsonNode *object, const char *key, JsonNode *value);
void json_prepend_member(JsonNode *object, const char *key, JsonNode *value);
void json_remove_from_parent(JsonNode *node);
/*** Debugging ***/
/*
* Look for structure and encoding problems in a JsonNode or its descendents.
*
* If a problem is detected, return false, writing a description of the problem
* to errmsg (unless errmsg is NULL).
*/
bool json_check(const JsonNode *node, char errmsg[256]);
#endif

View File

@@ -20,6 +20,17 @@ 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)
@@ -169,3 +180,23 @@ func GetSubdirectories(rootDir string) (*slicer.StringSlicer, error) {
})
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
}

View File

@@ -3,11 +3,15 @@ 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 {
@@ -63,6 +67,10 @@ 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")
@@ -84,6 +92,16 @@ 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
@@ -103,6 +121,7 @@ func (a *Asset) AsCHexData() string {
return cdata.String()
}
// Dump will output the asset to the terminal
func (a *Asset) Dump() {
fmt.Printf("{ Type: %s, Path: %s, Data: %+v }\n", a.Type, a.Path, a.Data[:10])
}

View File

@@ -153,6 +153,10 @@ 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)
@@ -160,9 +164,9 @@ func (a *AssetBundle) WriteToCFile(targetDir string) (string, error) {
}
if assetVariables.Length() > 0 {
cdata.WriteString(fmt.Sprintf("\nconst char *assets[] = { %s, 0x00 };", assetVariables.Join(", ")))
cdata.WriteString(fmt.Sprintf("\nconst unsigned char *assets[] = { %s, 0x00 };", assetVariables.Join(", ")))
} else {
cdata.WriteString("\nconst char *assets[] = { 0x00 };")
cdata.WriteString("\nconst unsigned char *assets[] = { 0x00 };")
}
// Save file
@@ -187,6 +191,7 @@ func (a *AssetBundle) ConvertToAssetDB() (*assetdb.AssetDB, error) {
return assetdb, nil
}
// Dump will output the assets to the terminal
func (a *AssetBundle) Dump() {
println("Assets:")
for _, asset := range a.assets {

View File

@@ -2,7 +2,6 @@ package logger
import (
"fmt"
"os"
)
// CustomLogger defines what a user can do with a logger
@@ -62,37 +61,36 @@ func (l *customLogger) Write(message string) error {
// Trace level logging. Works like Sprintf.
func (l *customLogger) Trace(format string, args ...interface{}) error {
format = fmt.Sprintf("%s | %s", l.name, format)
return l.logger.processLogMessage(TRACE, format, args...)
return l.logger.Trace(format, args...)
}
// Debug level logging. Works like Sprintf.
func (l *customLogger) Debug(format string, args ...interface{}) error {
format = fmt.Sprintf("%s | %s", l.name, format)
return l.logger.processLogMessage(DEBUG, format, args...)
return l.logger.Debug(format, args...)
}
// Info level logging. Works like Sprintf.
func (l *customLogger) Info(format string, args ...interface{}) error {
format = fmt.Sprintf("%s | %s", l.name, format)
return l.logger.processLogMessage(INFO, format, args...)
return l.logger.Info(format, args...)
}
// Warning level logging. Works like Sprintf.
func (l *customLogger) Warning(format string, args ...interface{}) error {
format = fmt.Sprintf("%s | %s", l.name, format)
return l.logger.processLogMessage(WARNING, format, args...)
return l.logger.Warning(format, args...)
}
// Error level logging. Works like Sprintf.
func (l *customLogger) Error(format string, args ...interface{}) error {
format = fmt.Sprintf("%s | %s", l.name, format)
return l.logger.processLogMessage(ERROR, format, args...)
return l.logger.Error(format, args...)
}
// Fatal level logging. Works like Sprintf.
func (l *customLogger) Fatal(format string, args ...interface{}) {
format = fmt.Sprintf("%s | %s", l.name, format)
l.logger.processLogMessage(FATAL, format, args...)
os.Exit(1)
l.logger.Fatal(format, args...)
}

View File

@@ -2,37 +2,32 @@ package logger
import (
"fmt"
"io"
"os"
"sync"
"github.com/wailsapp/wails/v2/pkg/logger"
)
// LogLevel is an alias for the public LogLevel
type LogLevel = logger.LogLevel
// Logger is a utlility to log messages to a number of destinations
type Logger struct {
writers []io.Writer
logLevel uint8
output logger.Logger
logLevel LogLevel
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(writers ...io.Writer) *Logger {
func New(output logger.Logger) *Logger {
result := &Logger{
logLevel: INFO,
logLevel: logger.INFO,
showLevelInLog: true,
}
for _, writer := range writers {
result.AddOutput(writer)
output: output,
}
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 {
@@ -45,99 +40,73 @@ func (l *Logger) HideLogLevel() {
}
// SetLogLevel sets the minimum level of logs that will be output
func (l *Logger) SetLogLevel(level uint8) {
func (l *Logger) SetLogLevel(level LogLevel) {
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) error {
return l.write(BYPASS, message+"\n")
return l.output.Print(message)
}
// Write writes directly to the output with no log level
func (l *Logger) Write(message string) error {
return l.write(BYPASS, message)
return l.output.Print(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)
// Print writes directly to the output with no log level
// Appends a carriage return to the message
func (l *Logger) Print(message string) error {
return l.Write(message)
}
// Trace level logging. Works like Sprintf.
func (l *Logger) Trace(format string, args ...interface{}) error {
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...)
if l.logLevel <= logger.TRACE {
return l.output.Trace(fmt.Sprintf(format, args...))
}
return nil
}
// Debug level logging. Works like Sprintf.
func (l *Logger) Debug(format string, args ...interface{}) error {
return l.processLogMessage(DEBUG, format, args...)
if l.logLevel <= logger.DEBUG {
return l.output.Debug(fmt.Sprintf(format, args...))
}
return nil
}
// Info level logging. Works like Sprintf.
func (l *Logger) Info(format string, args ...interface{}) error {
return l.processLogMessage(INFO, format, args...)
if l.logLevel <= logger.INFO {
return l.output.Info(fmt.Sprintf(format, args...))
}
return nil
}
// Warning level logging. Works like Sprintf.
func (l *Logger) Warning(format string, args ...interface{}) error {
return l.processLogMessage(WARNING, format, args...)
if l.logLevel <= logger.WARNING {
return l.output.Warning(fmt.Sprintf(format, args...))
}
return nil
}
// Error level logging. Works like Sprintf.
func (l *Logger) Error(format string, args ...interface{}) error {
return l.processLogMessage(ERROR, format, args...)
if l.logLevel <= logger.ERROR {
return l.output.Error(fmt.Sprintf(format, args...))
}
return nil
}
// Fatal level logging. Works like Sprintf.
func (l *Logger) Fatal(format string, args ...interface{}) {
l.processLogMessage(FATAL, format, args...)
err := l.output.Fatal(fmt.Sprintf(format, args...))
// Not much we can do but print it out before exiting
if err != nil {
println(err.Error())
}
os.Exit(1)
}

View File

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

View File

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

View File

@@ -6,6 +6,7 @@ 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"
)
// Client defines what a frontend client can do
@@ -13,9 +14,8 @@ type Client interface {
Quit()
NotifyEvent(message string)
CallResult(message string)
SaveFileDialog(title string, filter string) string
OpenFileDialog(title string, filter string) string
OpenDirectoryDialog(title string, filter string) string
OpenDialog(dialogOptions *options.OpenDialog, callbackID string)
SaveDialog(dialogOptions *options.SaveDialog, callbackID string)
WindowSetTitle(title string)
WindowShow()
WindowHide()
@@ -28,7 +28,8 @@ type Client interface {
WindowSize(width int, height int)
WindowFullscreen()
WindowUnFullscreen()
WindowSetColour(colour string) bool
WindowSetColour(colour int)
DarkModeEnabled(callbackID string)
}
// DispatchClient is what the frontends use to interface with the
@@ -63,7 +64,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.Trace("Error: " + err.Error())
d.logger.Error(err.Error())
return
}
@@ -74,7 +75,7 @@ func (d *DispatchClient) DispatchMessage(incomingMessage string) {
// Check error
if err != nil {
d.logger.Trace("Error: " + err.Error())
d.logger.Error(err.Error())
// Hrm... what do we do with this?
d.bus.PublishForTarget("generic:message", incomingMessage, d.id)
return

View File

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

View File

@@ -0,0 +1,52 @@
package message
import (
"encoding/json"
"fmt"
"strings"
)
// dialogMessageParser does what it says on the tin!
func dialogMessageParser(message string) (*parsedMessage, error) {
// Sanity check: Dialog messages must be at least 4 bytes
if len(message) < 4 {
return nil, fmt.Errorf("dialog message was an invalid length")
}
var topic = "bad topic from dialogMessageParser"
var responseMessage *parsedMessage
// Switch the event type (with or without data)
switch message[0] {
// Format of Dialog response messages: D<dialog type><callbackID>|<[]string as json encoded string>
case 'D':
dialogType := message[1]
message = message[2:]
idx := strings.IndexByte(message, '|')
if idx < 0 {
return nil, fmt.Errorf("Invalid dialog response message format")
}
callbackID := message[:idx+1]
payloadData := message[idx+1:]
switch dialogType {
case 'O':
var data []string
topic = "dialog:openselected:" + callbackID
err := json.Unmarshal([]byte(payloadData), &data)
if err != nil {
return nil, err
}
responseMessage = &parsedMessage{Topic: topic, Data: data}
case 'S':
topic = "dialog:saveselected:" + callbackID
responseMessage = &parsedMessage{Topic: topic, Data: payloadData}
}
default:
return nil, fmt.Errorf("Invalid message to dialogMessageParser()")
}
return responseMessage, nil
}

View File

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

View File

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

View File

@@ -14,9 +14,10 @@ var messageParsers = map[byte]func(string) (*parsedMessage, error){
'L': logMessageParser,
'R': runtimeMessageParser,
'E': eventMessageParser,
'e': eventMessageParser,
'C': callMessageParser,
'W': windowMessageParser,
'D': dialogMessageParser,
'S': systemMessageParser,
}
// Parse will attempt to parse the given message

View File

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

View File

@@ -0,0 +1,42 @@
package message
import (
"fmt"
"strings"
)
// systemMessageParser does what it says on the tin!
func systemMessageParser(message string) (*parsedMessage, error) {
// Sanity check: system messages must be at least 4 bytes
if len(message) < 4 {
return nil, fmt.Errorf("system message was an invalid length")
}
var responseMessage *parsedMessage
// Remove 'S'
message = message[1:]
// Switch the event type (with or without data)
switch message[0] {
// Format of system response messages: S<command><callbackID>|<payload>
// DarkModeEnabled
case 'D':
message = message[1:]
idx := strings.IndexByte(message, '|')
if idx < 0 {
return nil, fmt.Errorf("Invalid system response message format")
}
callbackID := message[:idx]
payloadData := message[idx+1:]
topic := "systemresponse:" + callbackID
responseMessage = &parsedMessage{Topic: topic, Data: payloadData == "T"}
default:
return nil, fmt.Errorf("Invalid message to systemMessageParser()")
}
return responseMessage, nil
}

View File

@@ -10,6 +10,7 @@ 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"
)
// Dispatcher translates messages received from the frontend
@@ -20,6 +21,7 @@ type Dispatcher struct {
eventChannel <-chan *servicebus.Message
windowChannel <-chan *servicebus.Message
dialogChannel <-chan *servicebus.Message
systemChannel <-chan *servicebus.Message
running bool
servicebus *servicebus.ServiceBus
@@ -62,6 +64,11 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher,
return nil, err
}
systemChannel, err := servicebus.Subscribe("system:")
if err != nil {
return nil, err
}
result := &Dispatcher{
servicebus: servicebus,
eventChannel: eventChannel,
@@ -71,6 +78,7 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher,
quitChannel: quitChannel,
windowChannel: windowChannel,
dialogChannel: dialogChannel,
systemChannel: systemChannel,
}
return result, nil
@@ -98,6 +106,8 @@ func (d *Dispatcher) Start() error {
d.processWindowMessage(windowMessage)
case dialogMessage := <-d.dialogChannel:
d.processDialogMessage(dialogMessage)
case systemMessage := <-d.systemChannel:
d.processSystemMessage(systemMessage)
}
}
@@ -173,6 +183,28 @@ func (d *Dispatcher) processCallResult(result *servicebus.Message) {
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) {
@@ -231,7 +263,7 @@ func (d *Dispatcher) processWindowMessage(result *servicebus.Message) {
client.frontend.WindowUnFullscreen()
}
case "setcolour":
colour, ok := result.Data().(string)
colour, ok := result.Data().(int)
if !ok {
d.logger.Error("Invalid colour for 'window:setcolour' : %#v", result.Data())
return
@@ -317,7 +349,6 @@ 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
@@ -327,65 +358,42 @@ func (d *Dispatcher) processDialogMessage(result *servicebus.Message) {
switch command {
case "select":
dialogType := splitTopic[2]
title := splitTopic[3]
filter := ""
if len(splitTopic) > 4 {
filter = splitTopic[4]
}
switch dialogType {
case "file":
responseTopic, ok := result.Data().(string)
case "open":
dialogOptions, ok := result.Data().(*options.OpenDialog)
if !ok {
d.logger.Error("Invalid responseTopic for 'dialog:select:file' : %#v", result.Data())
d.logger.Error("Invalid data for 'dialog:select:open' : %#v", result.Data())
return
}
d.logger.Info("Opening File dialog! responseTopic = %s", responseTopic)
// This is hardcoded in the sender too
callbackID := splitTopic[3]
// TODO: Work out what we mean in a multi window environment...
// For now we will just pick the first one
var result string
for _, client := range d.clients {
result = client.frontend.OpenFileDialog(title, filter)
client.frontend.OpenDialog(dialogOptions, callbackID)
}
// Send dummy response
d.servicebus.Publish(responseTopic, result)
case "filesave":
responseTopic, ok := result.Data().(string)
case "save":
dialogOptions, ok := result.Data().(*options.SaveDialog)
if !ok {
d.logger.Error("Invalid responseTopic for 'dialog:select:filesave' : %#v", result.Data())
d.logger.Error("Invalid data for 'dialog:select:save' : %#v", result.Data())
return
}
d.logger.Info("Opening Save File dialog! responseTopic = %s", responseTopic)
// This is hardcoded in the sender too
callbackID := splitTopic[3]
// TODO: Work out what we mean in a multi window environment...
// For now we will just pick the first one
var result string
for _, client := range d.clients {
result = client.frontend.SaveFileDialog(title, filter)
client.frontend.SaveDialog(dialogOptions, callbackID)
}
// Send dummy response
d.servicebus.Publish(responseTopic, result)
case "directory":
responseTopic, ok := result.Data().(string)
if !ok {
d.logger.Error("Invalid responseTopic for 'dialog:select:directory' : %#v", result.Data())
return
}
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 {
result = client.frontend.OpenDirectoryDialog(title, filter)
}
// Send dummy response
d.servicebus.Publish(responseTopic, result)
default:
d.logger.Error("Unknown dialog command: %s", command)
d.logger.Error("Unknown dialog type: %s", dialogType)
}
default:
d.logger.Error("Unknown dialog command: %s", command)
}
}

View File

@@ -65,7 +65,6 @@ 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:

View File

@@ -3,19 +3,19 @@ package process
import (
"os/exec"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
// Process defines a process that can be executed
type Process struct {
logger *logger.Logger
logger *clilogger.CLILogger
cmd *exec.Cmd
exitChannel chan bool
Running bool
}
// NewProcess creates a new process struct
func NewProcess(logger *logger.Logger, cmd string, args ...string) *Process {
func NewProcess(logger *clilogger.CLILogger, cmd string, args ...string) *Process {
return &Process{
logger: logger,
cmd: exec.Command(cmd, args...),
@@ -33,10 +33,10 @@ func (p *Process) Start() error {
p.Running = true
go func(cmd *exec.Cmd, running *bool, logger *logger.Logger, exitChannel chan bool) {
logger.Info("Starting process (PID: %d)", cmd.Process.Pid)
go func(cmd *exec.Cmd, running *bool, logger *clilogger.CLILogger, exitChannel chan bool) {
logger.Println("Starting process (PID: %d)", cmd.Process.Pid)
cmd.Wait()
logger.Info("Exiting process (PID: %d)", cmd.Process.Pid)
logger.Println("Exiting process (PID: %d)", cmd.Process.Pid)
*running = false
exitChannel <- true
}(p.cmd, &p.Running, p.logger, p.exitChannel)

View File

@@ -1,4 +1,4 @@
package goruntime
package runtime
import (
"fmt"
@@ -8,23 +8,23 @@ import (
// Browser defines all browser related operations
type Browser interface {
Open(url string) error
Open(target string) error
}
type browser struct{}
// Open a url / file using the system default application
// Credit: https://gist.github.com/hyg/9c4afcd91fe24316cbf0
func (b *browser) Open(url string) error {
func (b *browser) Open(target string) error {
var err error
switch runtime.GOOS {
case "linux":
err = exec.Command("xdg-open", url).Start()
err = exec.Command("xdg-open", target).Start()
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", target).Start()
case "darwin":
err = exec.Command("open", url).Start()
err = exec.Command("open", target).Start()
default:
err = fmt.Errorf("unsupported platform")
}

View File

@@ -1,4 +1,4 @@
package goruntime
package runtime
import (
"os"

View File

@@ -0,0 +1,94 @@
package runtime
import (
"fmt"
"github.com/wailsapp/wails/v2/internal/crypto"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/pkg/options"
)
// Dialog defines all Dialog related operations
type Dialog interface {
Open(dialogOptions *options.OpenDialog) []string
Save(dialogOptions *options.SaveDialog) string
}
// dialog exposes the Dialog interface
type dialog struct {
bus *servicebus.ServiceBus
}
// newDialogs creates a new Dialogs struct
func newDialog(bus *servicebus.ServiceBus) Dialog {
return &dialog{
bus: bus,
}
}
// processTitleAndFilter return the title and filter from the given params.
// title is the first string, filter is the second
func (r *dialog) processTitleAndFilter(params ...string) (string, string) {
var title, filter string
if len(params) > 0 {
title = params[0]
}
if len(params) > 1 {
filter = params[1]
}
return title, filter
}
// Open prompts the user to select a file
func (r *dialog) Open(dialogOptions *options.OpenDialog) []string {
// Create unique dialog callback
uniqueCallback := crypto.RandomID()
// Subscribe to the respose channel
responseTopic := "dialog:openselected:" + uniqueCallback
dialogResponseChannel, err := r.bus.Subscribe(responseTopic)
if err != nil {
fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error())
}
message := "dialog:select:open:" + uniqueCallback
r.bus.Publish(message, dialogOptions)
// Wait for result
var result *servicebus.Message = <-dialogResponseChannel
// Delete subscription to response topic
r.bus.UnSubscribe(responseTopic)
return result.Data().([]string)
}
// Save prompts the user to select a file
func (r *dialog) Save(dialogOptions *options.SaveDialog) string {
// Create unique dialog callback
uniqueCallback := crypto.RandomID()
// Subscribe to the respose channel
responseTopic := "dialog:saveselected:" + uniqueCallback
dialogResponseChannel, err := r.bus.Subscribe(responseTopic)
if err != nil {
fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error())
}
message := "dialog:select:save:" + uniqueCallback
r.bus.Publish(message, dialogOptions)
// Wait for result
var result *servicebus.Message = <-dialogResponseChannel
// Delete subscription to response topic
r.bus.UnSubscribe(responseTopic)
return result.Data().(string)
}

View File

@@ -0,0 +1,68 @@
package runtime
import (
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
"github.com/wailsapp/wails/v2/internal/servicebus"
)
// Events defines all events related operations
type Events interface {
On(eventName string, callback func(optionalData ...interface{}))
Once(eventName string, callback func(optionalData ...interface{}))
OnMultiple(eventName string, callback func(optionalData ...interface{}), maxCallbacks int)
Emit(eventName string, optionalData ...interface{})
}
// event exposes the events interface
type event struct {
bus *servicebus.ServiceBus
}
// newEvents creates a new Events struct
func newEvents(bus *servicebus.ServiceBus) Events {
return &event{
bus: bus,
}
}
// On registers a listener for a particular event
func (r *event) On(eventName string, callback func(optionalData ...interface{})) {
eventMessage := &message.OnEventMessage{
Name: eventName,
Callback: callback,
Counter: -1,
}
r.bus.Publish("event:on", eventMessage)
}
// Once registers a listener for a particular event. After the first callback, the
// listener is deleted.
func (r *event) Once(eventName string, callback func(optionalData ...interface{})) {
eventMessage := &message.OnEventMessage{
Name: eventName,
Callback: callback,
Counter: 1,
}
r.bus.Publish("event:on", eventMessage)
}
// OnMultiple registers a listener for a particular event, for a given maximum amount of callbacks.
// Once the callback has been run `maxCallbacks` times, the listener is deleted.
func (r *event) OnMultiple(eventName string, callback func(optionalData ...interface{}), maxCallbacks int) {
eventMessage := &message.OnEventMessage{
Name: eventName,
Callback: callback,
Counter: maxCallbacks,
}
r.bus.Publish("event:on", eventMessage)
}
// Emit pass through
func (r *event) Emit(eventName string, optionalData ...interface{}) {
eventMessage := &message.EventMessage{
Name: eventName,
Data: optionalData,
}
r.bus.Publish("event:emit:from:g", eventMessage)
}

View File

@@ -1,140 +0,0 @@
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(params ...string) string
SelectFile(params ...string) string
SelectDirectory(params ...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,
}
}
// processTitleAndFilter return the title and filter from the given params.
// title is the first string, filter is the second
func (r *dialog) processTitleAndFilter(params ...string) (string, string) {
var title, filter string
if len(params) > 0 {
title = params[0]
}
if len(params) > 1 {
filter = params[1]
}
return title, filter
}
// SelectFile prompts the user to select a file
func (r *dialog) SelectFile(params ...string) string {
// Extract title + filter
title, filter := r.processTitleAndFilter(params...)
// 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
message := "dialog:select:file:" + title
if filter != "" {
message += ":" + filter
}
r.bus.Publish(message, 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(params ...string) string {
// Extract title + filter
title, filter := r.processTitleAndFilter(params...)
// 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
message := "dialog:select:filesave:" + title
if filter != "" {
message += ":" + filter
}
r.bus.Publish(message, 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(params ...string) string {
// Extract title + filter
title, filter := r.processTitleAndFilter(params...)
// 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
message := "dialog:select:directory:" + title
if filter != "" {
message += ":" + filter
}
r.bus.Publish(message, responseTopic)
// Wait for result
var result *servicebus.Message = <-dialogResponseChannel
// Delete subscription to response topic
r.bus.UnSubscribe(responseTopic)
return result.Data().(string)
}

View File

@@ -1,43 +0,0 @@
package goruntime
import (
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
"github.com/wailsapp/wails/v2/internal/servicebus"
)
// Events defines all events related operations
type Events interface {
On(eventName string, callback func(optionalData ...interface{}))
Emit(eventName string, optionalData ...interface{})
}
// event exposes the events interface
type event struct {
bus *servicebus.ServiceBus
}
// newEvents creates a new Events struct
func newEvents(bus *servicebus.ServiceBus) Events {
return &event{
bus: bus,
}
}
// On pass through
func (r *event) On(eventName string, callback func(optionalData ...interface{})) {
eventMessage := &message.OnEventMessage{
Name: eventName,
Callback: callback,
}
r.bus.Publish("event:on", eventMessage)
}
// Emit pass through
func (r *event) Emit(eventName string, optionalData ...interface{}) {
eventMessage := &message.EventMessage{
Name: eventName,
Data: optionalData,
}
r.bus.Publish("event:emit:from:g", eventMessage)
}

View File

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

View File

@@ -12,14 +12,16 @@ import { SetBindings } from './bindings';
import { Init } from './main';
// Setup global error handler
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); })();
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); }(),
};
window.wails.Log.Error(JSON.stringify(errorMessage));
};
// Initialise the Runtime

View File

@@ -0,0 +1,23 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
import { SendMessage } from 'ipc';
/**
* Open a dialog with the given parameters
*
* @export
* @param {object} options
*/
export function Open(options) {
SendMessage('DO'+JSON.stringify(options));
}

View File

@@ -82,6 +82,38 @@ export function Once(eventName, callback) {
OnMultiple(eventName, callback, 1);
}
function notifyListeners(eventData) {
// Get the event name
var 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];
var 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 listners
eventListeners[eventName] = newEventListenerList;
}
}
/**
* Notify informs frontend listeners that an event was emitted with the given data
*
@@ -100,33 +132,7 @@ export function Notify(notifyMessage) {
throw new Error(error);
}
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;
}
notifyListeners(message);
}
/**
@@ -137,66 +143,15 @@ export function Notify(notifyMessage) {
*/
export function Emit(eventName) {
// 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);
}
const payload = {
name: eventName,
data: [].slice.apply(arguments).slice(1),
};
}
// Notify JS listeners
notifyListeners(payload);
// Callbacks for the heartbeat calls
const heartbeatCallbacks = {};
// Notify Go listeners
SendMessage('Ej' + JSON.stringify(payload));
/**
* Heartbeat emits the event `eventName`, every `timeInMilliseconds` milliseconds until
* the event is acknowledged via `Event.Acknowledge`. Once this happens, `callback` is invoked ONCE
*
* @export
* @param {string} eventName
* @param {number} timeInMilliseconds
* @param {function} callback
*/
export function Heartbeat(eventName, timeInMilliseconds, callback) {
// Declare interval variable
let interval = null;
// Setup callback
function dynamicCallback() {
// Kill interval
clearInterval(interval);
// Callback
callback();
}
// Register callback
heartbeatCallbacks[eventName] = dynamicCallback;
// Start emitting the event
interval = setInterval(function () {
Emit(eventName);
}, timeInMilliseconds);
}
/**
* Acknowledges a heartbeat event by name
*
* @export
* @param {string} eventName
*/
export function Acknowledge(eventName) {
// If we are waiting for acknowledgement for this event type
if (heartbeatCallbacks[eventName]) {
// Acknowledge!
heartbeatCallbacks[eventName]();
} else {
throw new Error(`Cannot acknowledge unknown heartbeat '${eventName}'`);
}
}
}

View File

@@ -25,6 +25,26 @@ 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
*
@@ -74,3 +94,22 @@ export function Error(message) {
export function Fatal(message) {
sendLogMessage('F', message);
}
/**
* Sets the Log level to the given log level
*
* @export
* @param {number} loglevel
*/
export function SetLogLevel(loglevel) {
sendLogMessage('S', loglevel);
}
// Log levels
export const Level = {
TRACE: 1,
DEBUG: 2,
INFO: 3,
WARNING: 4,
ERROR: 5,
};

View File

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

View File

@@ -0,0 +1,93 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
/**
* Creates a new sync store with the given name and optional default value
*
* @export
* @param {string} name
* @param {*} optionalDefault
*/
export function New(name, optionalDefault) {
// This is the store state
var state;
// Check we are initialised
if( !window.wails) {
throw Error('Wails is not initialised');
}
// Store for the callbacks
let callbacks = [];
// Subscribe to updates by providing a callback
let subscribe = function(callback) {
callbacks.push(callback);
};
// sets the store state to the provided `newstate` value
let set = function(newstate) {
state = newstate;
// Emit a notification to back end
window.wails.Events.Emit('wails:sync:store:updatedbyfrontend:'+name, JSON.stringify(state));
// Notify callbacks
callbacks.forEach( function(callback) {
callback(state);
});
};
// update mutates the value in the store by calling the
// provided method with the current value. The value returned
// by the updater function will be set as the new store value
let update = function(updater) {
var newValue = updater(state);
this.set(newValue);
};
// get returns the current value of the store
let get = function() {
return state;
};
// Setup event callback
window.wails.Events.On('wails:sync:store:updatedbybackend:'+name, function(jsonEncodedState) {
// Parse state
let newState = JSON.parse(jsonEncodedState);
// Todo: Potential preprocessing?
// Save state
state = newState;
// Notify callbacks
callbacks.forEach( function(callback) {
callback(state);
});
});
// Set to the optional default if set
if( optionalDefault ) {
this.set(optionalDefault);
}
return {
subscribe,
get,
set,
update,
};
}

View File

@@ -0,0 +1,15 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
/**
* Initialises platform specific code
*/
export const AppType = "desktop";

View File

@@ -0,0 +1,44 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
/**
* Initialises platform specific code
*/
// import * as common from './common';
const common = require('./common');
export const System = {
...common,
Platform: () => "darwin",
}
export function SendMessage(message) {
window.webkit.messageHandlers.external.postMessage(message);
}
export function Init() {
// Setup drag handler
// Based on code from: https://github.com/patr0nus/DeskGap
window.addEventListener('mousedown', function (e) {
var currentElement = e.target;
while (currentElement != null) {
if (currentElement.hasAttribute('data-wails-no-drag')) {
break;
} else if (currentElement.hasAttribute('data-wails-drag')) {
window.webkit.messageHandlers.windowDrag.postMessage(null);
break;
}
currentElement = currentElement.parentElement;
}
});
}

View File

@@ -14,8 +14,8 @@ The lightweight framework for web-like apps
*/
export const System = {
Platform: "linux",
AppType: "desktop"
...common,
Platform: () => "linux",
}
export function SendMessage(message) {

View File

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

View File

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

View File

@@ -0,0 +1,22 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
/**
* Open a dialog with the given parameters
*
* @export
* @param {object} options
*/
export function Open(options) {
window.wails.Dialog.Open(options);
}

View File

@@ -56,35 +56,9 @@ function Emit(eventName) {
return window.wails.Events.Emit.apply(null, args);
}
/**
* 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 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 = {
OnMultiple: OnMultiple,
On: On,
Once: Once,
Emit: Emit,
Heartbeat: Heartbeat,
Acknowledge: Acknowledge
};

View File

@@ -11,6 +11,26 @@ 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
*
@@ -54,17 +74,40 @@ 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
Fatal: Fatal,
SetLogLevel: SetLogLevel,
Level: Level,
};

View File

@@ -11,12 +11,18 @@ 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');
module.exports = {
Log: Log,
Browser: Browser,
Dialog: Dialog,
Events: Events,
Init: Init
Init: Init,
Log: Log,
System: System,
Store: Store,
};

View File

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

View File

@@ -1,26 +1,49 @@
export = wailsapp__runtime;
interface Store {
get(): any;
set(value: any): void;
subscribe(callback: (newvalue: any) => void): void;
update(callback: (currentvalue: any) => any): void;
}
interface Level {
TRACE: 1,
DEBUG: 2,
INFO: 3,
WARNING: 4,
ERROR: 5,
};
declare const wailsapp__runtime: {
Browser: {
OpenFile(filename: string): Promise<any>;
OpenURL(url: string): Promise<any>;
Open(target: string): Promise<any>;
};
Events: {
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;
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;
};
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
};
Store: {
New(name: string, defaultValue?: any): Store;
}
};

View File

@@ -0,0 +1,27 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
/**
* Create a new Store with the given name and optional default value
*
* @export
* @param {string} name
* @param {*} optionalDefault
*/
function New(name, optionalDefault) {
return window.wails.Store.New(name, optionalDefault);
}
module.exports = {
New: New,
};

View File

@@ -0,0 +1,42 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
const Events = require('./events');
/**
* Registers an event listener that will be invoked when the user changes the
* desktop theme (light mode / dark mode). The callback receives a boolean which
* indicates if dark mode is enabled.
*
* @export
* @param {function} callback The callback to invoke on theme change
*/
function OnThemeChange(callback) {
Events.On("wails:system:themechange", callback);
}
/**
* Checks if dark mode is curently enabled.
*
* @export
* @returns {Promise}
*/
function DarkModeEnabled() {
return window.wails.System.IsDarkMode.get();
}
module.exports = {
OnThemeChange: OnThemeChange,
DarkModeEnabled: DarkModeEnabled,
LogLevel: window.wails.System.LogLevel,
Platform: window.wails.System.Platform,
AppType: window.wails.System.AppType
};

View File

@@ -0,0 +1,21 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
/**
* Initialises platform specific code
*/
export const System = {
Platform: "darwin",
AppType: "server"
}
export function Init() { }

View File

@@ -0,0 +1,69 @@
package runtime
import (
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/pkg/logger"
)
// Log defines all Log related operations
type Log interface {
Print(message string)
Trace(message string)
Debug(message string)
Info(message string)
Warning(message string)
Error(message string)
Fatal(message string)
SetLogLevel(level logger.LogLevel)
}
type log struct {
bus *servicebus.ServiceBus
}
// newLog creates a new Log struct
func newLog(bus *servicebus.ServiceBus) Log {
return &log{
bus: bus,
}
}
// Print prints a Print level message
func (r *log) Print(message string) {
r.bus.Publish("log:print", message)
}
// Trace prints a Trace level message
func (r *log) Trace(message string) {
r.bus.Publish("log:trace", message)
}
// Debug prints a Debug level message
func (r *log) Debug(message string) {
r.bus.Publish("log:debug", message)
}
// Info prints a Info level message
func (r *log) Info(message string) {
r.bus.Publish("log:info", message)
}
// Warning prints a Warning level message
func (r *log) Warning(message string) {
r.bus.Publish("log:warning", message)
}
// Error prints a Error level message
func (r *log) Error(message string) {
r.bus.Publish("log:error", message)
}
// Fatal prints a Fatal level message
func (r *log) Fatal(message string) {
r.bus.Publish("log:fatal", message)
}
// Sets the log level
func (r *log) SetLogLevel(level logger.LogLevel) {
r.bus.Publish("log:setlevel", level)
}

View File

@@ -1,4 +1,4 @@
package goruntime
package runtime
import "github.com/wailsapp/wails/v2/internal/servicebus"
@@ -8,18 +8,25 @@ type Runtime struct {
Events Events
Window Window
Dialog Dialog
System System
Store *StoreProvider
Log Log
bus *servicebus.ServiceBus
}
// New creates a new runtime
func New(serviceBus *servicebus.ServiceBus) *Runtime {
return &Runtime{
result := &Runtime{
Browser: newBrowser(),
Events: newEvents(serviceBus),
Window: newWindow(serviceBus),
Dialog: newDialog(serviceBus),
System: newSystem(serviceBus),
Log: newLog(serviceBus),
bus: serviceBus,
}
result.Store = newStore(result)
return result
}
// Quit the application

View File

@@ -0,0 +1,295 @@
// package runtime contains all the methods and data structures related to the
// runtime library of Wails. This includes both Go and JS runtimes.
package runtime
import (
"bytes"
"encoding/json"
"fmt"
"os"
"reflect"
"sync"
)
// Options defines the optional data that may be used
// when creating a Store
type Options struct {
// The name of the store
Name string
// The runtime to attach the store to
Runtime *Runtime
// Indicates if notifying Go listeners should be notified of updates
// synchronously (on the current thread) or asynchronously using
// goroutines
NotifySynchronously bool
}
// StoreProvider is a struct that creates Stores
type StoreProvider struct {
runtime *Runtime
}
// newStore creates new stores using the provided Runtime reference.
func newStore(runtime *Runtime) *StoreProvider {
return &StoreProvider{
runtime: runtime,
}
}
// Store is where we keep named data
type Store struct {
name string
data reflect.Value
dataType reflect.Type
eventPrefix string
callbacks []reflect.Value
runtime *Runtime
notifySynchronously bool
// Lock
mux sync.Mutex
// Error handler
errorHandler func(error)
}
func fatal(err error) {
println(err.Error())
os.Exit(1)
}
// New creates a new store
func (p *StoreProvider) New(name string, defaultValue interface{}) *Store {
dataType := reflect.TypeOf(defaultValue)
result := Store{
name: name,
runtime: p.runtime,
data: reflect.ValueOf(defaultValue),
dataType: dataType,
}
// Setup the sync listener
result.setupListener()
return &result
}
// OnError takes a function that will be called
// whenever an error occurs
func (s *Store) OnError(callback func(error)) {
s.errorHandler = callback
}
// Processes the updates sent by the front end
func (s *Store) processUpdatedData(data string) error {
// Decode incoming data
var rawdata json.RawMessage
d := json.NewDecoder(bytes.NewBufferString(data))
err := d.Decode(&rawdata)
if err != nil {
return err
}
// Create a new instance of our data and unmarshal
// the received value into it
newData := reflect.New(s.dataType).Interface()
err = json.Unmarshal(rawdata, &newData)
if err != nil {
return err
}
// Lock mutex for writing
s.mux.Lock()
// Handle nulls
if newData == nil {
s.data = reflect.Zero(s.dataType)
} else {
// Store the resultant value in the data store
s.data = reflect.ValueOf(newData).Elem()
}
// Unlock mutex
s.mux.Unlock()
return nil
}
// Setup listener for front end changes
func (s *Store) setupListener() {
// Listen for updates from the front end
s.runtime.Events.On("wails:sync:store:updatedbyfrontend:"+s.name, func(data ...interface{}) {
// Process the incoming data
err := s.processUpdatedData(data[0].(string))
if err != nil {
if s.errorHandler != nil {
s.errorHandler(err)
return
}
}
// Notify listeners
s.notify()
})
}
// notify the listeners of the current data state
func (s *Store) notify() {
// Execute callbacks
for _, callback := range s.callbacks {
// Build args
args := []reflect.Value{s.data}
if s.notifySynchronously {
callback.Call(args)
} else {
go callback.Call(args)
}
}
}
// Set will update the data held by the store
// and notify listeners of the change
func (s *Store) Set(data interface{}) error {
inType := reflect.TypeOf(data)
if inType != s.dataType {
return fmt.Errorf("invalid data given in Store.Set(). Expected %s, got %s", s.dataType.String(), inType.String())
}
// Save data
s.mux.Lock()
s.data = reflect.ValueOf(data)
s.mux.Unlock()
// Stringify data
newdata, err := json.Marshal(data)
if err != nil {
if s.errorHandler != nil {
return err
}
}
// Emit event to front end
s.runtime.Events.Emit("wails:sync:store:updatedbybackend:"+s.name, string(newdata))
// Notify subscribers
s.notify()
return nil
}
// callbackCheck ensures the given function to Subscribe() is
// of the correct signature. Absolutely cannot wait for
// generics to land rather than writing this nonsense.
func (s *Store) callbackCheck(callback interface{}) error {
// Get type
callbackType := reflect.TypeOf(callback)
// Check callback is a function
if callbackType.Kind() != reflect.Func {
return fmt.Errorf("invalid value given to store.Subscribe(). Expected 'func(%s)'", s.dataType.String())
}
// Check input param
if callbackType.NumIn() != 1 {
return fmt.Errorf("invalid number of parameters given in callback function. Expected 1")
}
// Check input data type
if callbackType.In(0) != s.dataType {
return fmt.Errorf("invalid type for input parameter given in callback function. Expected %s, got %s", s.dataType.String(), callbackType.In(0))
}
// Check output param
if callbackType.NumOut() != 0 {
return fmt.Errorf("invalid number of return parameters given in callback function. Expected 0")
}
return nil
}
// Subscribe will subscribe to updates to the store by
// providing a callback. Any updates to the store are sent
// to the callback
func (s *Store) Subscribe(callback interface{}) {
err := s.callbackCheck(callback)
if err != nil {
fatal(err)
}
callbackFunc := reflect.ValueOf(callback)
s.callbacks = append(s.callbacks, callbackFunc)
}
// updaterCheck ensures the given function to Update() is
// of the correct signature. Absolutely cannot wait for
// generics to land rather than writing this nonsense.
func (s *Store) updaterCheck(updater interface{}) error {
// Get type
updaterType := reflect.TypeOf(updater)
// Check updater is a function
if updaterType.Kind() != reflect.Func {
return fmt.Errorf("invalid value given to store.Update(). Expected 'func(%s) %s'", s.dataType.String(), s.dataType.String())
}
// Check input param
if updaterType.NumIn() != 1 {
return fmt.Errorf("invalid number of parameters given in updater function. Expected 1")
}
// Check input data type
if updaterType.In(0) != s.dataType {
return fmt.Errorf("invalid type for input parameter given in updater function. Expected %s, got %s", s.dataType.String(), updaterType.In(0))
}
// Check output param
if updaterType.NumOut() != 1 {
return fmt.Errorf("invalid number of return parameters given in updater function. Expected 1")
}
// Check output data type
if updaterType.Out(0) != s.dataType {
return fmt.Errorf("invalid type for return parameter given in updater function. Expected %s, got %s", s.dataType.String(), updaterType.Out(0))
}
return nil
}
// Update takes a function that is passed the current state.
// The result of that function is then set as the new state
// of the store. This will notify listeners of the change
func (s *Store) Update(updater interface{}) {
err := s.updaterCheck(updater)
if err != nil {
fatal(err)
}
// Build args
args := []reflect.Value{s.data}
// Make call
results := reflect.ValueOf(updater).Call(args)
// We will only have 1 result. Set the store to it
s.Set(results[0].Interface())
}

View File

@@ -0,0 +1,59 @@
package runtime
import (
"fmt"
"runtime"
"github.com/wailsapp/wails/v2/internal/crypto"
"github.com/wailsapp/wails/v2/internal/servicebus"
)
// System defines all System related operations
type System interface {
IsDarkMode() bool
Platform() string
AppType() string
}
// system exposes the System interface
type system struct {
bus *servicebus.ServiceBus
}
// newSystem creates a new System struct
func newSystem(bus *servicebus.ServiceBus) System {
return &system{
bus: bus,
}
}
// Platform returns the platform name the application
// is running on
func (r *system) Platform() string {
return runtime.GOOS
}
// On pass through
func (r *system) IsDarkMode() bool {
// Create unique system callback
uniqueCallback := crypto.RandomID()
// Subscribe to the respose channel
responseTopic := "systemresponse:" + uniqueCallback
systemResponseChannel, err := r.bus.Subscribe(responseTopic)
if err != nil {
fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error())
}
message := "system:isdarkmode:" + uniqueCallback
r.bus.Publish(message, nil)
// Wait for result
var result *servicebus.Message = <-systemResponseChannel
// Delete subscription to response topic
r.bus.UnSubscribe(responseTopic)
return result.Data().(bool)
}

View File

@@ -0,0 +1,8 @@
// +build desktop,!server
package runtime
// AppType returns the application type, EG: desktop
func (r *system) AppType() string {
return "desktop"
}

View File

@@ -0,0 +1,8 @@
// +build server
package runtime
// AppType returns the application type, EG: desktop
func (r *system) AppType() string {
return "server"
}

View File

@@ -1,4 +1,4 @@
package goruntime
package runtime
import (
"github.com/wailsapp/wails/v2/internal/servicebus"
@@ -16,7 +16,7 @@ type Window interface {
SetTitle(title string)
Fullscreen()
UnFullscreen()
SetColour(colour string)
SetColour(colour int)
}
// Window exposes the Windows interface
@@ -54,8 +54,8 @@ func (w *window) UnFullscreen() {
w.bus.Publish("window:unfullscreen", "")
}
// SetColour sets the window colour to the given string
func (w *window) SetColour(colour string) {
// SetColour sets the window colour to the given int
func (w *window) SetColour(colour int) {
w.bus.Publish("window:setcolour", colour)
}

View File

@@ -3,7 +3,7 @@ package subsystem
import (
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/runtime/goruntime"
"github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/internal/servicebus"
)
@@ -21,11 +21,11 @@ type Binding struct {
logger logger.CustomLogger
// runtime
runtime *goruntime.Runtime
runtime *runtime.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 *goruntime.Runtime) (*Binding, error) {
func NewBinding(bus *servicebus.ServiceBus, logger *logger.Logger, bindings *binding.Bindings, runtime *runtime.Runtime) (*Binding, error) {
// Register quit channel
quitChannel, err := bus.Subscribe("quit")

View File

@@ -3,10 +3,12 @@ package subsystem
import (
"encoding/json"
"fmt"
"strings"
"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"
)
@@ -25,10 +27,13 @@ type Call struct {
// logger
logger logger.CustomLogger
// runtime
runtime *runtime.Runtime
}
// NewCall creates a new log subsystem
func NewCall(bus *servicebus.ServiceBus, logger *logger.Logger, DB *binding.DB) (*Call, error) {
// NewCall creates a new call subsystem
func NewCall(bus *servicebus.ServiceBus, logger *logger.Logger, DB *binding.DB, runtime *runtime.Runtime) (*Call, error) {
// Register quit channel
quitChannel, err := bus.Subscribe("quit")
@@ -48,6 +53,7 @@ func NewCall(bus *servicebus.ServiceBus, logger *logger.Logger, DB *binding.DB)
logger: logger.CustomLogger("Call Subsystem"),
DB: DB,
bus: bus,
runtime: runtime,
}
return result, nil
@@ -65,7 +71,7 @@ func (c *Call) Start() error {
case <-c.quitChannel:
c.running = false
case callMessage := <-c.callChannel:
// TODO: Check if this works ok in a goroutine
c.processCall(callMessage)
}
}
@@ -87,6 +93,12 @@ 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())
@@ -94,7 +106,12 @@ func (c *Call) processCall(callMessage *servicebus.Message) {
}
c.logger.Trace("Got registered method: %+v", registeredMethod)
result, err := registeredMethod.Call(payload.Args)
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)
if err != nil {
c.sendError(err, payload, callMessage.Target())
return
@@ -105,6 +122,16 @@ 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)
}
}
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)
message := &CallbackMessage{

View File

@@ -15,7 +15,7 @@ import (
// means it does not expire (default).
type eventListener struct {
callback func(...interface{}) // Function to call with emitted event data
counter int64 // The number of times this callback may be called. -1 = infinite
counter int // The number of times this callback may be called. -1 = infinite
delete bool // Flag to indicate that this listener should be deleted
}
@@ -60,12 +60,12 @@ func NewEvent(bus *servicebus.ServiceBus, logger *logger.Logger) (*Event, error)
}
// RegisterListener provides a means of subscribing to events of type "eventName"
func (e *Event) RegisterListener(eventName string, callback func(...interface{})) {
func (e *Event) RegisterListener(eventName string, callback func(...interface{}), counter int) {
// Create new eventListener
thisListener := &eventListener{
callback: callback,
counter: 0,
counter: counter,
delete: false,
}
@@ -120,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)
e.RegisterListener(eventName, callback, message.Counter)
e.logger.Trace("Registered listener for event '%s' with callback %p", eventName, callback)
default:
e.logger.Error("unknown event message: %+v", eventMessage)
@@ -141,7 +141,7 @@ func (e *Event) notifyListeners(eventName string, message *message.EventMessage)
// Get list of event listeners
listeners := e.listeners[eventName]
if listeners == nil {
println("no listeners for", eventName)
e.logger.Trace("No listeners for %s", eventName)
return
}
@@ -179,13 +179,16 @@ func (e *Event) notifyListeners(eventName string, message *message.EventMessage)
}
}
// Save new listeners
e.listeners[eventName] = newListeners
// Save new listeners or remove entry
if len(newListeners) > 0 {
e.listeners[eventName] = newListeners
} else {
delete(e.listeners, eventName)
}
}
// Unlock
e.notifyLock.Unlock()
}
func (e *Event) shutdown() {

View File

@@ -1,9 +1,11 @@
package subsystem
import (
"strconv"
"strings"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/internal/servicebus"
)
@@ -16,10 +18,13 @@ type Log struct {
// Logger!
logger *logger.Logger
// Loglevel store
logLevelStore *runtime.Store
}
// NewLog creates a new log subsystem
func NewLog(bus *servicebus.ServiceBus, logger *logger.Logger) (*Log, error) {
func NewLog(bus *servicebus.ServiceBus, logger *logger.Logger, logLevelStore *runtime.Store) (*Log, error) {
// Subscribe to log messages
logChannel, err := bus.Subscribe("log")
@@ -34,9 +39,10 @@ func NewLog(bus *servicebus.ServiceBus, logger *logger.Logger) (*Log, error) {
}
result := &Log{
logChannel: logChannel,
quitChannel: quitChannel,
logger: logger,
logChannel: logChannel,
quitChannel: quitChannel,
logger: logger,
logLevelStore: logLevelStore,
}
return result, nil
@@ -57,6 +63,10 @@ func (l *Log) Start() error {
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":
@@ -67,6 +77,22 @@ 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)
}

View File

@@ -5,7 +5,7 @@ import (
"strings"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/runtime/goruntime"
"github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/internal/servicebus"
)
@@ -19,7 +19,7 @@ type Runtime struct {
logger logger.CustomLogger
// Runtime library
runtime *goruntime.Runtime
runtime *runtime.Runtime
}
// NewRuntime creates a new runtime subsystem
@@ -32,7 +32,7 @@ func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger) (*Runtime, er
}
// Subscribe to log messages
runtimeChannel, err := bus.Subscribe("runtime")
runtimeChannel, err := bus.Subscribe("runtime:")
if err != nil {
return nil, err
}
@@ -41,7 +41,7 @@ func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger) (*Runtime, er
quitChannel: quitChannel,
runtimeChannel: runtimeChannel,
logger: logger.CustomLogger("Runtime Subsystem"),
runtime: goruntime.New(bus),
runtime: runtime.New(bus),
}
return result, nil
@@ -75,7 +75,7 @@ func (r *Runtime) Start() error {
case "browser":
err = r.processBrowserMessage(method, runtimeMessage.Data())
default:
fmt.Errorf("unknown log message: %+v", runtimeMessage)
fmt.Errorf("unknown runtime message: %+v", runtimeMessage)
}
// If we had an error, log it
@@ -93,7 +93,7 @@ func (r *Runtime) Start() error {
}
// GoRuntime returns the Go Runtime object
func (r *Runtime) GoRuntime() *goruntime.Runtime {
func (r *Runtime) GoRuntime() *runtime.Runtime {
return r.runtime
}
@@ -103,12 +103,12 @@ func (r *Runtime) shutdown() {
func (r *Runtime) processBrowserMessage(method string, data interface{}) error {
switch method {
case "openurl":
url, ok := data.(string)
case "open":
target, ok := data.(string)
if !ok {
return fmt.Errorf("expected 1 string parameter for runtime:browser:openurl")
return fmt.Errorf("expected 1 string parameter for runtime:browser:open")
}
go r.runtime.Browser.Open(url)
go r.runtime.Browser.Open(target)
default:
return fmt.Errorf("unknown method runtime:browser:%s", method)
}

View File

@@ -0,0 +1,30 @@
package operatingsystem
import "github.com/wailsapp/wails/v2/internal/shell"
func platformInfo() (*OS, error) {
// Default value
var result OS
result.ID = "Unknown"
result.Name = "MacOS"
result.Version = "Unknown"
stdout, stderr, err := shell.RunCommand(".", "sysctl", "kern.osrelease")
println(stdout)
println(stderr)
println(err)
// cmd := CreateCommand(directory, command, args...)
// var stdo, stde bytes.Buffer
// cmd.Stdout = &stdo
// cmd.Stderr = &stde
// err := cmd.Run()
// return stdo.String(), stde.String(), err
// }
// sysctl := shell.NewCommand("sysctl")
// kern.ostype: Darwin
// kern.osrelease: 20.1.0
// kern.osrevision: 199506
return nil, nil
}

View File

@@ -0,0 +1,29 @@
package operatingsystem
import (
"fmt"
"golang.org/x/sys/windows/registry"
)
func platformInfo() (*OS, error) {
// Default value
var result OS
result.ID = "Unknown"
result.Name = "Windows"
result.Version = "Unknown"
// Credit: https://stackoverflow.com/a/33288328
// Ignore errors as it isn't a showstopper
key, _ := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
defer key.Close()
fmt.Printf("%+v\n", key)
// Ignore errors as it isn't a showstopper
productName, _, _ := key.GetStringValue("ProductName")
fmt.Println(productName)
return nil, nil
}

View File

@@ -9,24 +9,6 @@ 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{
@@ -38,8 +20,6 @@ var pmcommands = []string{
"zypper",
}
type packagemap = map[string][]*Package
// Find will attempt to find the system package manager
func Find(osid string) PackageManager {
@@ -70,58 +50,6 @@ func newPackageManager(pmname string, osid string) PackageManager {
return nil
}
// Dependancy represents a system package that we require
type Dependancy struct {
Name string
PackageName string
Installed bool
InstallCommand string
Version string
Optional bool
External bool
}
// DependencyList is a list of Dependency instances
type DependencyList []*Dependancy
// InstallAllRequiredCommand returns the command you need to use to install all required dependencies
func (d DependencyList) InstallAllRequiredCommand() string {
result := ""
for _, dependency := range d {
if dependency.PackageName != "" {
if !dependency.Installed && !dependency.Optional {
if result == "" {
result = dependency.InstallCommand
} else {
result += " " + dependency.PackageName
}
}
}
}
return result
}
// InstallAllOptionalCommand returns the command you need to use to install all optional dependencies
func (d DependencyList) InstallAllOptionalCommand() string {
result := ""
for _, dependency := range d {
if dependency.PackageName != "" {
if !dependency.Installed && dependency.Optional {
if result == "" {
result = dependency.InstallCommand
} else {
result += " " + dependency.PackageName
}
}
}
}
return result
}
// Dependancies scans the system for required dependancies
// Returns a list of dependancies search for, whether they were found
// and whether they were installed

View File

@@ -0,0 +1,77 @@
package packagemanager
// Package contains information about a system package
type Package struct {
Name string
Version string
InstallCommand map[string]string
SystemPackage bool
Library bool
Optional bool
}
type packagemap = map[string][]*Package
// 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
}
// 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
}

View File

@@ -0,0 +1,8 @@
// +build darwin
package system
func (i *Info) discover() error {
return nil
}

View File

@@ -2,14 +2,20 @@
package system
import (
"fmt"
"syscall"
)
import "github.com/wailsapp/wails/v2/internal/system/operatingsystem"
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))
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
}

View File

@@ -0,0 +1,27 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Wails: Debug {{.ProjectName}} (Desktop)",
"type": "go",
"request": "launch",
"mode": "exec",
"program": "${workspaceFolder}/{{.PathToDesktopBinary}}",
"preLaunchTask": "build_desktop",
"cwd": "",
"env": {},
"args": []
},
{
"name": "Wails: Debug {{.ProjectName}} (Server)",
"type": "go",
"request": "launch",
"mode": "exec",
"program": "${workspaceFolder}/{{.PathToServerBinary}}",
"preLaunchTask": "build_server",
"cwd": "",
"env": {},
"args": []
},
]
}

View File

@@ -0,0 +1,23 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build_desktop",
"type": "shell",
"options": {
"cwd": "{{.TargetDir}}"
},
"command": "wails build"
},
{
"label": "build_server",
"type": "shell",
"options": {
"cwd": "{{.TargetDir}}"
},
"command": "wails build -t server"
},
]
}

View File

@@ -6,13 +6,14 @@ 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/internal/logger"
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
// Cahce for the templates
@@ -31,11 +32,14 @@ type Data struct {
// Options for installing a template
type Options struct {
ProjectName string
TemplateName string
BinaryName string
TargetDir string
Logger *logger.Logger
ProjectName string
TemplateName string
BinaryName string
TargetDir string
Logger *clilogger.CLILogger
GenerateVSCode bool
PathToDesktopBinary string
PathToServerBinary string
}
// Template holds data relating to a template
@@ -162,9 +166,24 @@ func Install(options *Options) error {
}
// Did the user want to install in current directory?
if options.TargetDir == "." {
// Yes - use cwd
options.TargetDir = cwd
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)
}
}
} else {
// Get the absolute path of the given directory
targetDir, err := filepath.Abs(filepath.Join(cwd, options.TargetDir))
@@ -213,35 +232,74 @@ func Install(options *Options) error {
return err
}
// Calculate the directory name
err = generateIDEFiles(options)
if err != nil {
return err
}
return nil
}
// OutputList prints the list of available tempaltes to the given logger
func OutputList(logger *logger.Logger) error {
func OutputList(logger *clilogger.CLILogger) error {
templates, err := List()
if err != nil {
return err
}
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 := 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})
}
table.Render()
return nil
}
func generateIDEFiles(options *Options) error {
if options.GenerateVSCode {
return generateVSCodeFiles(options)
}
return nil
}
func generateVSCodeFiles(options *Options) error {
targetDir := filepath.Join(options.TargetDir, ".vscode")
sourceDir := fs.RelativePath(filepath.Join("./ides/vscode"))
// Use Gosod to install the template
installer, err := gosod.TemplateDir(sourceDir)
if err != nil {
return err
}
binaryName := filepath.Base(options.TargetDir)
if runtime.GOOS == "windows" {
// yay windows
binaryName += ".exe"
}
options.PathToDesktopBinary = filepath.Join("build", runtime.GOOS, "desktop", binaryName)
options.PathToServerBinary = filepath.Join("build", runtime.GOOS, "server", binaryName)
err = installer.Extract(targetDir, options)
if err != nil {
return err
}
return nil
}

View File

@@ -34,18 +34,8 @@ func (wc *WebClient) CallResult(message string) {
wc.SendMessage("R" + message)
}
// SaveFileDialog is a noop in the webclient
func (wc *WebClient) SaveFileDialog(title string) string {
return ""
}
// OpenFileDialog is a noop in the webclient
func (wc *WebClient) OpenFileDialog(title string) string {
return ""
}
// OpenDirectoryDialog is a noop in the webclient
func (wc *WebClient) OpenDirectoryDialog(title string) string {
// OpenDialog is a noop in the webclient
func (wc *WebClient) OpenDialog(title string) string {
return ""
}
@@ -59,8 +49,7 @@ func (wc *WebClient) WindowFullscreen() {}
func (wc *WebClient) WindowUnFullscreen() {}
// WindowSetColour is a noop in the webclient
func (wc *WebClient) WindowSetColour(colour string) bool {
return false
func (wc *WebClient) WindowSetColour(colour int) {
}
// Run processes messages from the remote webclient

View File

@@ -0,0 +1,59 @@
package clilogger
import (
"fmt"
"io"
"os"
)
// CLILogger is used by the cli
type CLILogger struct {
Writer io.Writer
mute bool
}
// New cli logger
func New(writer io.Writer) *CLILogger {
return &CLILogger{
Writer: writer,
}
}
// Mute sets whether the logger should be muted
func (c *CLILogger) Mute(value bool) {
c.mute = value
}
// Print works like Printf
func (c *CLILogger) Print(message string, args ...interface{}) {
if c.mute {
return
}
_, err := fmt.Fprintf(c.Writer, message, args...)
if err != nil {
c.Fatal("Fatal: ", err)
}
}
// Println works like Printf but with a line ending
func (c *CLILogger) Println(message string, args ...interface{}) {
if c.mute {
return
}
temp := fmt.Sprintf(message, args...)
_, err := fmt.Fprintln(c.Writer, temp)
if err != nil {
c.Fatal("Fatal: ", err)
}
}
// Fatal prints the given message then aborts
func (c *CLILogger) Fatal(message string, args ...interface{}) {
temp := fmt.Sprintf(message, args...)
_, err := fmt.Fprintln(c.Writer, "FATAL: "+temp)
if err != nil {
println("FATAL: ", err)
}
os.Exit(1)
}

View File

@@ -14,9 +14,9 @@ import (
"github.com/wailsapp/wails/v2/internal/assetdb"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/html"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/internal/shell"
"github.com/wailsapp/wails/v2/pkg/clilogger"
)
// BaseBuilder is the common builder struct
@@ -305,7 +305,7 @@ func (b *BaseBuilder) NpmRunWithEnvironment(projectDir, buildTarget string, verb
}
// BuildFrontend executes the `npm build` command for the frontend directory
func (b *BaseBuilder) BuildFrontend(outputLogger *logger.Logger) error {
func (b *BaseBuilder) BuildFrontend(outputLogger *clilogger.CLILogger) error {
verbose := false
frontendDir := filepath.Join(b.projectData.Path, "frontend")
@@ -313,10 +313,10 @@ func (b *BaseBuilder) BuildFrontend(outputLogger *logger.Logger) error {
// Check there is an 'InstallCommand' provided in wails.json
if b.projectData.InstallCommand == "" {
// No - don't install
outputLogger.Writeln(" - No Install command. Skipping.")
outputLogger.Println(" - No Install command. Skipping.")
} else {
// Do install if needed
outputLogger.Writeln(" - Installing dependencies...")
outputLogger.Println(" - Installing dependencies...")
if err := b.NpmInstallUsingCommand(frontendDir, b.projectData.InstallCommand); err != nil {
return err
}
@@ -324,12 +324,12 @@ func (b *BaseBuilder) BuildFrontend(outputLogger *logger.Logger) error {
// Check if there is a build command
if b.projectData.BuildCommand == "" {
outputLogger.Writeln(" - No Build command. Skipping.")
outputLogger.Println(" - No Build command. Skipping.")
// No - ignore
return nil
}
outputLogger.Writeln(" - Compiling Frontend Project")
outputLogger.Println(" - Compiling Frontend Project")
cmd := strings.Split(b.projectData.BuildCommand, " ")
stdout, stderr, err := shell.RunCommand(frontendDir, cmd[0], cmd[1:]...)
if verbose || err != nil {

View File

@@ -6,8 +6,9 @@ import (
"runtime"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/project"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/parser"
)
// Mode is the type used to indicate the build modes
@@ -24,16 +25,16 @@ var modeMap = []string{"Debug", "Production"}
// Options contains all the build options as well as the project data
type Options struct {
LDFlags string // Optional flags to pass to linker
Logger *logger.Logger // All output to the logger
OutputType string // EG: desktop, server....
Mode Mode // release or debug
ProjectData *project.Project // The project data
Pack bool // Create a package for the app after building
Platform string // The platform to build for
Compiler string // The compiler command to use
IgnoreFrontend bool // Indicates if the frontend does not need building
OutputFile string // Override the output filename
LDFlags string // Optional flags to pass to linker
Logger *clilogger.CLILogger // All output to the logger
OutputType string // EG: desktop, server....
Mode Mode // release or debug
ProjectData *project.Project // The project data
Pack bool // Create a package for the app after building
Platform string // The platform to build for
Compiler string // The compiler command to use
IgnoreFrontend bool // Indicates if the frontend does not need building
OutputFile string // Override the output filename
}
// GetModeAsString returns the current mode as a string
@@ -47,11 +48,6 @@ func Build(options *Options) (string, error) {
// Extract logger
outputLogger := options.Logger
// Create a default logger if it doesn't exist
if outputLogger == nil {
outputLogger = logger.New()
}
// Get working directory
cwd, err := os.Getwd()
if err != nil {
@@ -59,7 +55,7 @@ func Build(options *Options) (string, error) {
}
// Check platform
validPlatforms := slicer.String([]string{"linux"})
validPlatforms := slicer.String([]string{"linux", "darwin"})
if !validPlatforms.Contains(options.Platform) {
return "", fmt.Errorf("platform %s not supported", options.Platform)
}
@@ -94,8 +90,15 @@ func Build(options *Options) (string, error) {
// Initialise Builder
builder.SetProjectData(projectData)
// Generate Frontend JS Package
outputLogger.Println(" - Generating Backend JS Package")
// Ignore the parser report coming back
_, err = parser.GenerateWailsFrontendPackage()
if err != nil {
return "", err
}
if !options.IgnoreFrontend {
outputLogger.Writeln(" - Building Wails Frontend")
outputLogger.Println(" - Building Wails Frontend")
err = builder.BuildFrontend(outputLogger)
if err != nil {
return "", err
@@ -103,7 +106,7 @@ func Build(options *Options) (string, error) {
}
// Build the base assets
outputLogger.Writeln(" - Compiling Assets")
outputLogger.Println(" - Compiling Assets")
err = builder.BuildRuntime(options)
if err != nil {
return "", err
@@ -114,16 +117,17 @@ func Build(options *Options) (string, error) {
}
// Compile the application
outputLogger.Write(" - Compiling Application in " + GetModeAsString(options.Mode) + " mode...")
outputLogger.Print(" - Compiling Application in " + GetModeAsString(options.Mode) + " mode...")
err = builder.CompileProject(options)
if err != nil {
return "", err
}
outputLogger.Writeln("done.")
outputLogger.Println("done.")
// Do we need to pack the app?
if options.Pack {
outputLogger.Writeln(" - Packaging Application")
outputLogger.Println(" - Packaging Application")
// TODO: Allow cross platform build
err = packageProject(options, runtime.GOOS)

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