Compare commits

...

83 Commits

Author SHA1 Message Date
Lea Anthony
febd867fa7 Merge branch 'feature/v2-mac' into v2-alpha
# Conflicts:
#	v2/cmd/wails/internal/commands/build/build.go
#	v2/cmd/wails/internal/commands/dev/dev.go
#	v2/cmd/wails/internal/commands/doctor/doctor.go
#	v2/cmd/wails/internal/commands/initialise/initialise.go
#	v2/cmd/wails/main.go
#	v2/go.mod
#	v2/go.sum
#	v2/internal/app/default.go
#	v2/internal/app/desktop.go
#	v2/internal/app/hybrid.go
#	v2/internal/app/server.go
#	v2/internal/binding/binding.go
#	v2/internal/binding/boundMethod.go
#	v2/internal/binding/db.go
#	v2/internal/binding/reflect.go
#	v2/internal/ffenestri/ffenestri.go
#	v2/internal/ffenestri/ffenestri.h
#	v2/internal/ffenestri/ffenestri_client.go
#	v2/internal/ffenestri/ffenestri_linux.c
#	v2/internal/fs/fs.go
#	v2/internal/html/asset.go
#	v2/internal/html/assetbundle.go
#	v2/internal/logger/custom_logger.go
#	v2/internal/logger/default_logger.go
#	v2/internal/messagedispatcher/dispatchclient.go
#	v2/internal/messagedispatcher/message/call.go
#	v2/internal/messagedispatcher/message/event.go
#	v2/internal/messagedispatcher/message/log.go
#	v2/internal/messagedispatcher/message/messageparser.go
#	v2/internal/messagedispatcher/message/runtime.go
#	v2/internal/messagedispatcher/message/window.go
#	v2/internal/messagedispatcher/messagedispatcher.go
#	v2/internal/parse/parse.go
#	v2/internal/process/process.go
#	v2/internal/project/project.go
#	v2/internal/runtime/js/core/bindings.js
#	v2/internal/runtime/js/core/browser.js
#	v2/internal/runtime/js/core/calls.js
#	v2/internal/runtime/js/core/desktop.js
#	v2/internal/runtime/js/core/events.js
#	v2/internal/runtime/js/core/log.js
#	v2/internal/runtime/js/core/main.js
#	v2/internal/runtime/js/core/utils.js
#	v2/internal/runtime/js/desktop/linux.js
#	v2/internal/runtime/js/package-lock.json
#	v2/internal/runtime/js/package.json
#	v2/internal/runtime/js/package.json.md5
#	v2/internal/runtime/js/runtime/.npmignore
#	v2/internal/runtime/js/runtime/README.md
#	v2/internal/runtime/js/runtime/browser.js
#	v2/internal/runtime/js/runtime/events.js
#	v2/internal/runtime/js/runtime/init.js
#	v2/internal/runtime/js/runtime/log.js
#	v2/internal/runtime/js/runtime/main.js
#	v2/internal/runtime/js/runtime/package.json
#	v2/internal/runtime/js/runtime/runtime.d.ts
#	v2/internal/runtime/js/webpack.desktop.js
#	v2/internal/servicebus/servicebus.go
#	v2/internal/signal/signal.go
#	v2/internal/subsystem/binding.go
#	v2/internal/subsystem/call.go
#	v2/internal/subsystem/event.go
#	v2/internal/subsystem/log.go
#	v2/internal/subsystem/runtime.go
#	v2/internal/system/packagemanager/packagemanager.go
#	v2/internal/system/system_windows.go
#	v2/internal/templates/templates.go
#	v2/internal/templates/templates/svelte-mui/basic.tmpl.go
#	v2/internal/templates/templates/svelte-mui/frontend/package.json
#	v2/internal/templates/templates/svelte-mui/main.tmpl.go
#	v2/internal/templates/templates/vanilla/basic.tmpl.go
#	v2/internal/templates/templates/vanilla/frontend/index.html
#	v2/internal/templates/templates/vanilla/frontend/main.css
#	v2/internal/templates/templates/vanilla/main.tmpl.go
#	v2/internal/templates/templates/vanilla/wails.tmpl.json
#	v2/internal/templates/templates/vuetify2-basic/basic.tmpl.go
#	v2/internal/templates/templates/vuetify2-basic/main.tmpl.go
#	v2/internal/templates/templates/vuetify2-basic/wails.tmpl.json
#	v2/internal/webserver/websockets.go
#	v2/pkg/commands/build/base.go
#	v2/pkg/commands/build/build.go
#	v2/pkg/commands/build/builder.go
#	v2/pkg/commands/build/desktop.go
#	v2/pkg/commands/build/hybrid.go
#	v2/pkg/commands/build/packager.go
#	v2/pkg/commands/build/packager_linux.go
#	v2/pkg/commands/build/server.go
#	v2/test/disable-resize/basic.go
#	v2/test/disable-resize/main.go
#	v2/test/frameless/basic.go
#	v2/test/frameless/main.go
#	v2/test/hidden/basic.go
#	v2/test/hidden/main.go
#	v2/test/minmax/basic.go
#	v2/test/minmax/main.go
#	v2/test/runtime/calc.go
#	v2/test/runtime/frontend/package-lock.json
#	v2/test/runtime/frontend/src/assets/css/main.css
#	v2/test/runtime/frontend/src/index.html
#	v2/test/runtime/frontend/src/main.js
#	v2/test/runtime/main.go
#	v2/test/runtime/runtime.go
#	v2/wails.go
2021-03-20 15:06:17 +11:00
Travis McLane
cbd98b5a1a update scripts/build.sh to run test only on v1 2021-03-20 15:01:09 +11:00
Lea Anthony
c8e0aea69c v2.0.0-alpha.54 2021-03-19 09:17:18 +11:00
Lea Anthony
7c0b236eb0 Fix for hiding window on close 2021-03-19 09:14:49 +11:00
Lea Anthony
16debbd109 v2.0.0-alpha.53 2021-03-18 20:55:26 +11:00
Lea Anthony
39bfa5d910 Support disabling tray menu. Fix font sizing. Tooltip in tray menu support. 2021-03-18 20:54:53 +11:00
Lea Anthony
6424579a9e v2.0.0-alpha.52 2021-03-17 23:30:42 +11:00
Lea Anthony
a962ae6f63 Support rich text in Tray labels 2021-03-17 23:30:08 +11:00
Lea Anthony
c7dee158ba tray menu Icon->Image. Support template images. 2021-03-17 22:24:09 +11:00
Lea Anthony
237d25089d Update CLI banner 2021-03-17 21:59:31 +11:00
Lea Anthony
265328d648 v2.0.0-alpha.51 2021-03-15 06:13:51 +11:00
Lea Anthony
6f40e85a6e Support startup urls when app not running 2021-03-15 06:13:00 +11:00
Lea Anthony
96d8509da3 v2.0.0-alpha.50 2021-03-13 15:17:35 +11:00
Lea Anthony
598445ab0f v2.0.0-alpha.49 2021-03-13 15:00:19 +11:00
Lea Anthony
24788e2fd3 Fix template images 2021-03-13 14:59:12 +11:00
Lea Anthony
bbf4dde43f Support upserting environment variables 2021-03-12 23:41:13 +11:00
Lea Anthony
0afd27ab45 Add FileLogger option 2021-03-12 23:40:35 +11:00
Lea Anthony
d498423ec2 v2.0.0-alpha.48 2021-03-09 22:28:07 +11:00
Mat Ryer
66ce84973c fixes for machines running TouchBar addresses https://github.com/matryer/xbar/issues/610 2021-03-09 11:24:46 +00:00
Lea Anthony
55e6a0f312 v2.0.0-alpha.47 2021-03-07 16:24:20 +11:00
Lea Anthony
81e83fdf18 Ensure modifiers are lowercase when parsing 2021-03-07 16:21:30 +11:00
Lea Anthony
f9b79d24f8 Guard against nil url messages 2021-03-06 15:51:06 +11:00
Lea Anthony
0599a47bfe v2.0.0-alpha.46 2021-03-06 15:43:44 +11:00
Lea Anthony
817c55d318 Support base64 images in tray 2021-03-06 15:43:11 +11:00
Lea Anthony
14146c8c0c v2.0.0-alpha.45 2021-03-06 00:29:00 +11:00
Lea Anthony
18adac20d4 Tray menu open/close events 2021-03-06 00:25:34 +11:00
Lea Anthony
eb4bff89da v2.0.0-alpha.44 2021-03-04 06:18:31 +11:00
Lea Anthony
c66dc777f3 Remove debug logging 2021-03-04 06:18:11 +11:00
Lea Anthony
9003462457 v2.0.0-alpha.43 2021-03-04 06:09:17 +11:00
Lea Anthony
e124f0a220 Support Alternative menu items 2021-03-04 06:07:45 +11:00
Lea Anthony
c6d3f57712 v2.0.0-alpha.42 2021-03-01 08:49:31 +11:00
Lea Anthony
b4c669ff86 Support custom protocols 2021-02-28 22:08:23 +11:00
Lea Anthony
2d1b2c0947 Guard app signing 2021-02-28 15:29:15 +11:00
Lea Anthony
4a0c5aa785 v2.0.0-alpha.41 2021-02-27 20:33:42 +11:00
Lea Anthony
f48d7f8f60 Add support for -sign 2021-02-27 20:32:29 +11:00
Lea Anthony
651f24f641 update vanilla template 2021-02-27 20:06:49 +11:00
Lea Anthony
8fd77148ca update vanilla template 2021-02-27 16:59:16 +11:00
Lea Anthony
0dc0762fdf update vanilla template 2021-02-27 16:45:30 +11:00
Lea Anthony
1a92550709 update vanilla template 2021-02-27 16:08:53 +11:00
Lea Anthony
bffc15bc14 fix: terminate app on window close 2021-02-27 14:49:44 +11:00
Lea Anthony
198d206c46 Update basic template to new API 2021-02-27 14:07:27 +11:00
Lea Anthony
bb8e848ef6 Run go mod tidy before compilation 2021-02-27 14:03:54 +11:00
Lea Anthony
bac3e9e5c1 Fix asset dir perms 2021-02-27 13:54:39 +11:00
Lea Anthony
bc5eddeb66 v2.0.0-alpha.40 2021-02-26 15:31:37 +11:00
Lea Anthony
8e7258d812 Add locking for tray operations 2021-02-26 15:23:39 +11:00
Lea Anthony
7118762cec v2.0.0-alpha.39 2021-02-25 22:05:38 +11:00
Lea Anthony
6af92cf0a4 Delete Tray for bridge 2021-02-25 19:57:36 +11:00
matryer
1bb91634f7 avoid fmt in happy path 2021-02-25 07:39:42 +00:00
Lea Anthony
f71ce7913f v2.0.0-alpha.38 2021-02-24 21:50:39 +11:00
Lea Anthony
53db687a26 Support DeleteTrayMenuByID 2021-02-24 21:49:55 +11:00
Lea Anthony
13939d3d6b v2.0.0-alpha.37 2021-02-23 20:15:01 +11:00
Lea Anthony
552c6b8711 fix: modifiers 2021-02-23 18:57:59 +11:00
Lea Anthony
feee2b3db2 v2.0.0-alpha.36 2021-02-23 08:53:26 +11:00
Lea Anthony
9889c2bdbb Support Activation Policy 2021-02-23 08:52:56 +11:00
Lea Anthony
2432fccf71 v2.0.0-alpha.35 2021-02-22 20:17:22 +11:00
Lea Anthony
70510fd180 @wails/runtime v1.3.10 2021-02-22 20:16:14 +11:00
Lea Anthony
17c6201469 Menu off by default in dev. Toggle with backtick. 2021-02-22 20:14:44 +11:00
Lea Anthony
0f209c8900 v2.0.0-alpha.34 2021-02-22 19:39:07 +11:00
Lea Anthony
cbf043585c v2.0.0-alpha.33 2021-02-22 19:33:44 +11:00
Lea Anthony
5ae621ceaa Start signal handler a little later 2021-02-22 19:33:18 +11:00
Lea Anthony
1231b59443 better signal handling for shutdown 2021-02-22 17:39:33 +11:00
Lea Anthony
b18d4fbf41 v2.0.0-alpha.32 2021-02-22 09:00:47 +11:00
Lea Anthony
9ec5605e63 fix: loglevel duplication 2021-02-22 09:00:09 +11:00
Lea Anthony
98a4de8878 v2.0.0-alpha.31 2021-02-21 20:38:14 +11:00
Lea Anthony
5fe709f558 Trigger clean shutdown on Quit 2021-02-21 20:37:42 +11:00
Lea Anthony
5231a6893b v2.0.0-alpha.30 2021-02-21 19:28:07 +11:00
Lea Anthony
74f3ce990f Support loglevel flag in dev mode 2021-02-21 19:26:20 +11:00
Lea Anthony
998a913853 Hide dev warnings by default 2021-02-21 16:36:56 +11:00
Lea Anthony
964844835c Better Wails update messaging 2021-02-21 06:12:19 +11:00
Lea Anthony
4e152bb849 v2.0.0-alpha.29 2021-02-21 05:56:11 +11:00
Lea Anthony
51133d098c Slight refactor of backend module generation 2021-02-21 05:52:42 +11:00
Lea Anthony
d4191e7d1b Support parsing keyboard shortcuts 2021-02-20 21:32:32 +11:00
Lea Anthony
9c273bc745 v2.0.0-alpha.28 2021-02-20 16:04:42 +11:00
Lea Anthony
c6f6ad6beb Improved dev reload. Early abort for bad app. Don't reload if bad build. 2021-02-20 16:04:03 +11:00
Lea Anthony
4362a14459 Dev colours 2021-02-20 15:25:40 +11:00
Lea Anthony
0080e9e311 Gimme some colour 2021-02-20 15:14:20 +11:00
Lea Anthony
83d9297cac v2.0.0-alpha.27 2021-02-20 14:51:38 +11:00
Travis McLane
9806b9c651 update scripts/build.sh to run test only on v1 2020-09-04 13:38:17 -05:00
Lea Anthony
2a20867d00 Add README 2020-09-02 10:36:03 -05:00
Lea Anthony
48ff661150 package doc 2020-09-02 10:36:03 -05:00
Lea Anthony
19d59bef51 Update module path 2020-09-02 10:36:03 -05:00
Travis McLane
bdcb2fe810 Merge commit '25a157e6614739fbd4df1da3d11d46afe72ae9a8' as 'v2' 2020-09-01 19:34:51 -05:00
Travis McLane
25a157e661 Squashed 'v2/' content from commit 7d8960e
git-subtree-dir: v2
git-subtree-split: 7d8960e87431924f5705df4c777758a0eb32e145
2020-09-01 19:34:51 -05:00
155 changed files with 4375 additions and 4321 deletions

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
echo "**** Checking if Wails passes unit tests ****" echo "**** Checking if Wails passes unit tests ****"
if ! go test ./... if ! go test ./lib/... ./runtime/... ./cmd/...
then then
echo "" echo ""
echo "ERROR: Unit tests failed!" echo "ERROR: Unit tests failed!"

View File

@@ -2,19 +2,19 @@ package build
import ( import (
"fmt" "fmt"
"io" "os"
"runtime" "runtime"
"strings" "strings"
"time" "time"
"github.com/leaanthony/clir" "github.com/leaanthony/clir"
"github.com/leaanthony/slicer" "github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/pkg/clilogger" "github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/commands/build" "github.com/wailsapp/wails/v2/pkg/commands/build"
) )
// AddBuildSubcommand adds the `build` command for the Wails application // AddBuildSubcommand adds the `build` command for the Wails application
func AddBuildSubcommand(app *clir.Cli, w io.Writer) { func AddBuildSubcommand(app *clir.Cli) {
outputType := "desktop" outputType := "desktop"
@@ -32,7 +32,7 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
// Setup pack flag // Setup pack flag
pack := false pack := false
command.BoolFlag("package", "Create a platform specific package", &pack) command.BoolFlag("pack", "Create a platform specific package", &pack)
compilerCommand := "go" compilerCommand := "go"
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand) command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
@@ -43,7 +43,7 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
// Quiet Build // Quiet Build
quiet := false quiet := false
command.BoolFlag("q", "Suppress output to console", &quiet) command.BoolFlag("q", "Supress output to console", &quiet)
// ldflags to pass to `go` // ldflags to pass to `go`
ldflags := "" ldflags := ""
@@ -53,19 +53,19 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
logFile := "" logFile := ""
command.StringFlag("l", "Log to file", &logFile) command.StringFlag("l", "Log to file", &logFile)
// Retain assets
keepAssets := false
command.BoolFlag("k", "Keep generated assets", &keepAssets)
command.Action(func() error { command.Action(func() error {
// Create logger // Create logger
logger := clilogger.New(w) logger := logger.New()
logger.Mute(quiet)
if !quiet {
logger.AddOutput(os.Stdout)
}
// Validate output type // Validate output type
if !validTargetTypes.Contains(outputType) { if !validTargetTypes.Contains(outputType) {
return fmt.Errorf("output type '%s' is not valid", outputType) logger.Fatal(fmt.Sprintf("Output type '%s' is not valid.", outputType))
os.Exit(1)
} }
if !quiet { if !quiet {
@@ -73,8 +73,8 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
} }
task := fmt.Sprintf("Building %s Application", strings.Title(outputType)) task := fmt.Sprintf("Building %s Application", strings.Title(outputType))
logger.Println(task) logger.Writeln(task)
logger.Println(strings.Repeat("-", len(task))) logger.Writeln(strings.Repeat("-", len(task)))
// Setup mode // Setup mode
mode := build.Debug mode := build.Debug
@@ -91,7 +91,6 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
Platform: platform, Platform: platform,
LDFlags: ldflags, LDFlags: ldflags,
Compiler: compilerCommand, Compiler: compilerCommand,
KeepAssets: keepAssets,
} }
return doBuild(buildOptions) return doBuild(buildOptions)
@@ -108,12 +107,11 @@ func doBuild(buildOptions *build.Options) error {
if err != nil { if err != nil {
return err return err
} }
// Output stats // Output stats
elapsed := time.Since(start) elapsed := time.Since(start)
buildOptions.Logger.Println("") buildOptions.Logger.Writeln("")
buildOptions.Logger.Println(fmt.Sprintf("Built '%s' in %s.", outputFilename, elapsed.Round(time.Millisecond).String())) buildOptions.Logger.Writeln(fmt.Sprintf("Built '%s' in %s.", outputFilename, elapsed.Round(time.Millisecond).String()))
buildOptions.Logger.Println("") buildOptions.Logger.Writeln("")
return nil return nil
} }

View File

@@ -2,7 +2,6 @@ package dev
import ( import (
"fmt" "fmt"
"io"
"os" "os"
"os/signal" "os/signal"
"runtime" "runtime"
@@ -10,21 +9,28 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/pkg/errors"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/leaanthony/clir" "github.com/leaanthony/clir"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/fs" "github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/process" "github.com/wailsapp/wails/v2/internal/process"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"github.com/wailsapp/wails/v2/pkg/commands/build" "github.com/wailsapp/wails/v2/pkg/commands/build"
) )
// AddSubcommand adds the `dev` command for the Wails application // AddSubcommand adds the `dev` command for the Wails application
func AddSubcommand(app *clir.Cli, w io.Writer) error { func AddSubcommand(app *clir.Cli) error {
command := app.NewSubCommand("dev", "Development mode") command := app.NewSubCommand("dev", "Development mode")
outputType := "desktop"
validTargetTypes := slicer.String([]string{"desktop", "hybrid", "server"})
// Setup target type flag
description := "Type of application to develop. Valid types: " + validTargetTypes.Join(",")
command.StringFlag("t", description, &outputType)
// Passthrough ldflags // Passthrough ldflags
ldflags := "" ldflags := ""
command.StringFlag("ldflags", "optional ldflags", &ldflags) command.StringFlag("ldflags", "optional ldflags", &ldflags)
@@ -35,12 +41,18 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
// extensions to trigger rebuilds // extensions to trigger rebuilds
extensions := "go" extensions := "go"
command.StringFlag("e", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions) command.StringFlag("m", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions)
command.Action(func() error { command.Action(func() error {
// Validate inputs
if !validTargetTypes.Contains(outputType) {
return fmt.Errorf("output type '%s' is not valid", outputType)
}
// Create logger // Create logger
logger := clilogger.New(w) logger := logger.New()
logger.AddOutput(os.Stdout)
app.PrintBanner() app.PrintBanner()
// TODO: Check you are in a project directory // TODO: Check you are in a project directory
@@ -52,7 +64,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
defer watcher.Close() defer watcher.Close()
var debugBinaryProcess *process.Process = nil var debugBinaryProcess *process.Process = nil
var buildFrontend bool = false var buildFrontend bool = true
var extensionsThatTriggerARebuild = strings.Split(extensions, ",") var extensionsThatTriggerARebuild = strings.Split(extensions, ",")
// Setup signal handler // Setup signal handler
@@ -62,24 +74,19 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
debounceQuit := make(chan bool, 1) debounceQuit := make(chan bool, 1)
// Do initial build // Do initial build
logger.Println("Building application for development...") logger.Info("Building application for development...")
debugBinaryProcess, err = restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess) debugBinaryProcess = restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess)
if err != nil {
return err
}
go debounce(100*time.Millisecond, watcher.Events, debounceQuit, func(event fsnotify.Event) { go debounce(100*time.Millisecond, watcher.Events, debounceQuit, func(event fsnotify.Event) {
// logger.Println("event: %+v", event) // logger.Info("event: %+v", event)
// Check for new directories // Check for new directories
if event.Op&fsnotify.Create == fsnotify.Create { if event.Op&fsnotify.Create == fsnotify.Create {
// If this is a folder, add it to our watch list // If this is a folder, add it to our watch list
if fs.DirExists(event.Name) { if fs.DirExists(event.Name) {
if !strings.Contains(event.Name, "node_modules") { if !strings.Contains(event.Name, "node_modules") {
err := watcher.Add(event.Name) watcher.Add(event.Name)
if err != nil { logger.Info("Watching directory: %s", event.Name)
logger.Fatal("%s", err.Error())
}
logger.Println("Watching directory: %s", event.Name)
} }
} }
return return
@@ -88,7 +95,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
// Check for file writes // Check for file writes
if event.Op&fsnotify.Write == fsnotify.Write { if event.Op&fsnotify.Write == fsnotify.Write {
logger.Println("modified file: %s", event.Name) // logger.Info("modified file: %s", event.Name)
var rebuild bool = false var rebuild bool = false
// Iterate all file patterns // Iterate all file patterns
@@ -105,20 +112,21 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
} }
if !rebuild { if !rebuild {
logger.Println("Filename change: %s did not match extension list (%s)", event.Name, extensions) logger.Info("Filename change: %s did not match extension list %s", event.Name, extensions)
return return
} }
logger.Println("Partial build triggered: %s updated", event.Name) if buildFrontend {
logger.Info("Full rebuild triggered: %s updated", event.Name)
} else {
logger.Info("Partial build triggered: %s updated", event.Name)
}
// Do a rebuild // Do a rebuild
// Try and build the app // Try and build the app
newBinaryProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess) newBinaryProcess := restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess)
if err != nil {
fmt.Printf("Error during build: %s", err.Error())
return
}
// If we have a new process, save it // If we have a new process, save it
if newBinaryProcess != nil { if newBinaryProcess != nil {
debugBinaryProcess = newBinaryProcess debugBinaryProcess = newBinaryProcess
@@ -144,7 +152,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
if strings.Contains(dir, "node_modules") { if strings.Contains(dir, "node_modules") {
return return
} }
logger.Println("Watching directory: %s", dir) logger.Info("Watching directory: %s", dir)
err = watcher.Add(dir) err = watcher.Add(dir)
if err != nil { if err != nil {
logger.Fatal(err.Error()) logger.Fatal(err.Error())
@@ -156,7 +164,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
for quit == false { for quit == false {
select { select {
case <-quitChannel: case <-quitChannel:
println("Caught quit") println()
// Notify debouncer to quit // Notify debouncer to quit
debounceQuit <- true debounceQuit <- true
quit = true quit = true
@@ -165,13 +173,10 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
// Kill the current program if running // Kill the current program if running
if debugBinaryProcess != nil { if debugBinaryProcess != nil {
err := debugBinaryProcess.Kill() debugBinaryProcess.Kill()
if err != nil {
return err
}
} }
logger.Println("Development mode exited") logger.Info("Development mode exited")
return nil return nil
}) })
@@ -198,15 +203,15 @@ exit:
} }
} }
func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, debugBinaryProcess *process.Process) (*process.Process, error) { func restartApp(logger *logger.Logger, outputType string, ldflags string, compilerCommand string, buildFrontend bool, debugBinaryProcess *process.Process) *process.Process {
appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand) appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand, buildFrontend)
println() println()
if err != nil { if err != nil {
logger.Fatal(err.Error()) logger.Error("Build Failed: %s", err.Error())
return nil, errors.Wrap(err, "Build Failed:") return nil
} }
logger.Println("Build new binary: %s", appBinary) logger.Info("Build new binary: %s", appBinary)
// Kill existing binary if need be // Kill existing binary if need be
if debugBinaryProcess != nil { if debugBinaryProcess != nil {
@@ -226,17 +231,14 @@ func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string,
err = newProcess.Start() err = newProcess.Start()
if err != nil { if err != nil {
// Remove binary // Remove binary
deleteError := fs.DeleteFile(appBinary) fs.DeleteFile(appBinary)
if deleteError != nil {
logger.Fatal("Unable to delete app binary: " + appBinary)
}
logger.Fatal("Unable to start application: %s", err.Error()) logger.Fatal("Unable to start application: %s", err.Error())
} }
return newProcess, nil return newProcess
} }
func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string) (string, error) { func buildApp(logger *logger.Logger, outputType string, ldflags string, compilerCommand string, buildFrontend bool) (string, error) {
// Create random output file // Create random output file
outputFile := fmt.Sprintf("debug-%d", time.Now().Unix()) outputFile := fmt.Sprintf("debug-%d", time.Now().Unix())
@@ -251,7 +253,7 @@ func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, co
LDFlags: ldflags, LDFlags: ldflags,
Compiler: compilerCommand, Compiler: compilerCommand,
OutputFile: outputFile, OutputFile: outputFile,
IgnoreFrontend: true, IgnoreFrontend: !buildFrontend,
} }
return build.Build(buildOptions) return build.Build(buildOptions)

View File

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

View File

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

View File

@@ -18,14 +18,14 @@ import (
func AddSubcommand(app *clir.Cli, w io.Writer, currentVersion string) error { func AddSubcommand(app *clir.Cli, w io.Writer, currentVersion string) error {
command := app.NewSubCommand("update", "Update the Wails CLI") command := app.NewSubCommand("update", "Update the Wails CLI")
command.LongDescription(`This command allows you to update your version of Wails.`) command.LongDescription(`This command allows you to update your version of the Wails CLI.`)
// Setup flags // Setup flags
var prereleaseRequired bool var prereleaseRequired bool
command.BoolFlag("pre", "Update to latest Prerelease", &prereleaseRequired) command.BoolFlag("pre", "Update CLI to latest Prerelease", &prereleaseRequired)
var specificVersion string var specificVersion string
command.StringFlag("version", "Install a specific version (Overrides other flags)", &specificVersion) command.StringFlag("version", "Install a specific version (Overrides other flags) of the CLI", &specificVersion)
command.Action(func() error { command.Action(func() error {
@@ -143,7 +143,7 @@ func updateToVersion(logger *clilogger.CLILogger, targetVersion *github.Semantic
} }
fmt.Println() fmt.Println()
logger.Print("Installing Wails " + desiredVersion + "...") logger.Print("Installing Wails CLI " + desiredVersion + "...")
// Run command in non module directory // Run command in non module directory
homeDir, err := os.UserHomeDir() homeDir, err := os.UserHomeDir()
@@ -158,7 +158,7 @@ func updateToVersion(logger *clilogger.CLILogger, targetVersion *github.Semantic
return err return err
} }
fmt.Println() fmt.Println()
logger.Println("Wails updated to " + desiredVersion) logger.Println("Wails CLI updated to " + desiredVersion)
return nil return nil
} }

View File

@@ -3,14 +3,9 @@ package main
import ( import (
"os" "os"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/update"
"github.com/leaanthony/clir" "github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build" "github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/debug"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/dev"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/doctor" "github.com/wailsapp/wails/v2/cmd/wails/internal/commands/doctor"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/generate"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/initialise" "github.com/wailsapp/wails/v2/cmd/wails/internal/commands/initialise"
) )
@@ -22,35 +17,16 @@ func fatal(message string) {
func main() { func main() {
var err error var err error
version := "v2.0.0-alpha"
app := clir.NewCli("Wails", "Go/HTML Application Framework", version) app := clir.NewCli("Wails", "Go/HTML Application Framework", version)
build.AddBuildSubcommand(app, os.Stdout) build.AddBuildSubcommand(app)
err = initialise.AddSubcommand(app, os.Stdout) err = initialise.AddSubcommand(app)
if err != nil { if err != nil {
fatal(err.Error()) fatal(err.Error())
} }
err = doctor.AddSubcommand(app)
err = debug.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = doctor.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = dev.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = generate.AddSubcommand(app, os.Stdout)
if err != nil {
fatal(err.Error())
}
err = update.AddSubcommand(app, os.Stdout, version)
if err != nil { if err != nil {
fatal(err.Error()) fatal(err.Error())
} }

View File

@@ -1,3 +1,3 @@
package main package main
var version = "v2.0.0-alpha.26" var version = "v2.0.0-alpha.54"

View File

@@ -1,29 +1,14 @@
module github.com/wailsapp/wails/v2 module github.com/wailsapp/wails/v2
go 1.16 go 1.13
require ( require (
github.com/Masterminds/semver v1.5.0 github.com/leaanthony/clir v1.0.2
github.com/davecgh/go-spew v1.1.1 github.com/leaanthony/gosod v0.0.3
github.com/fatih/structtag v1.2.0 github.com/leaanthony/slicer v1.4.1
github.com/fsnotify/fsnotify v1.4.9 github.com/matryer/is v1.3.0
github.com/gorilla/websocket v1.4.1
github.com/imdario/mergo v0.3.11
github.com/jackmordaunt/icns v1.0.0
github.com/leaanthony/clir v1.0.4
github.com/leaanthony/gosod v0.0.4
github.com/leaanthony/slicer v1.5.0
github.com/matryer/is v1.4.0
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/olekukonko/tablewriter v0.0.4 github.com/olekukonko/tablewriter v0.0.4
github.com/pkg/errors v0.9.1
github.com/tdewolff/minify v2.3.6+incompatible
github.com/tdewolff/parse v2.3.4+incompatible // indirect
github.com/tdewolff/test v1.0.6 // indirect
github.com/xyproto/xpm v1.2.1 github.com/xyproto/xpm v1.2.1
golang.org/x/net v0.0.0-20200822124328-c89045814202 golang.org/x/net v0.0.0-20200301022130-244492dfa37a
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a nhooyr.io/websocket v1.7.4
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82
nhooyr.io/websocket v1.8.6
) )

129
v2/go.sum
View File

@@ -1,122 +1,57 @@
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmAMQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/jackmordaunt/icns v1.0.0/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/leaanthony/clir v1.0.2 h1:Ham9Ep7NH95il0x/ci5s2ExJa/K4B2dE8L2uaSj2cxo=
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8= github.com/leaanthony/clir v1.0.2/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/leaanthony/gosod v0.0.3 h1:VlPilye0zoH4I0WihShyoiTHyiAN1v6dcBW4mMvGJao=
github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU= github.com/leaanthony/gosod v0.0.3/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0= github.com/leaanthony/slicer v1.4.1 h1:X/SmRIDhkUAolP79mSTO0jTcVX1k504PJBqvV6TwP0w=
github.com/leaanthony/gosod v0.0.4 h1:v4hepo4IyL8E8c9qzDsvYcA0KGh7Npf8As74K5ibQpI= github.com/leaanthony/slicer v1.4.1/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leaanthony/gosod v0.0.4/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU= github.com/matryer/is v1.3.0 h1:9qiso3jaJrOe6qBRJRBt2Ldht05qDiFP9le0JOIhRSI=
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY= github.com/matryer/is v1.3.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo=
github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs=
github.com/tdewolff/parse v2.3.4+incompatible h1:x05/cnGwIMf4ceLuDMBOdQ1qGniMoxpP46ghf0Qzh38=
github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ=
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/xyproto/xpm v1.2.1 h1:trdvGjjWBsOOKzBBUPT6JvaIQM3acJEEYfbxN7M96wg= github.com/xyproto/xpm v1.2.1 h1:trdvGjjWBsOOKzBBUPT6JvaIQM3acJEEYfbxN7M96wg=
github.com/xyproto/xpm v1.2.1/go.mod h1:cMnesLsD0PBXLgjDfTDEaKr8XyTFsnP1QycSqRw7BiY= github.com/xyproto/xpm v1.2.1/go.mod h1:cMnesLsD0PBXLgjDfTDEaKr8XyTFsnP1QycSqRw7BiY=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82 h1:shxDsb9Dz27xzk3A0DxP0JuJnZMpKrdg8+E14eiUAX4=
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= nhooyr.io/websocket v1.7.4 h1:w/LGB2sZT0RV8lZYR7nfyaYz4PUbYZ5oF7NBon2M0NY=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= nhooyr.io/websocket v1.7.4/go.mod h1:PxYxCwFdFYQ0yRvtQz3s/dC+VEm7CSuC/4b9t8MQQxw=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=

View File

@@ -4,8 +4,9 @@ package app
import ( import (
"flag" "flag"
"github.com/wailsapp/wails/v2/pkg/logger"
"strings" "strings"
"github.com/wailsapp/wails/v2/pkg/logger"
) )
// Init initialises the application for a debug environment // Init initialises the application for a debug environment
@@ -19,10 +20,10 @@ func (a *App) Init() error {
} }
// Set log levels // Set log levels
greeting := flag.String("loglevel", "debug", "Loglevel to use - Trace, Debug, Info, Warning, Error") loglevel := flag.String("loglevel", "debug", "Loglevel to use - Trace, Debug, Info, Warning, Error")
flag.Parse() flag.Parse()
if len(*greeting) > 0 { if len(*loglevel) > 0 {
switch strings.ToLower(*greeting) { switch strings.ToLower(*loglevel) {
case "trace": case "trace":
a.logger.SetLogLevel(logger.TRACE) a.logger.SetLogLevel(logger.TRACE)
case "info": case "info":

View File

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

View File

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

View File

@@ -66,7 +66,6 @@ func CreateApp(appoptions *options.App) (*App, error) {
// Set up logger // Set up logger
myLogger := logger.New(appoptions.Logger) myLogger := logger.New(appoptions.Logger)
myLogger.SetLogLevel(appoptions.LogLevel)
// Create the menu manager // Create the menu manager
menuManager := menumanager.NewManager() menuManager := menumanager.NewManager()
@@ -119,14 +118,6 @@ func (a *App) Run() error {
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup) parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
ctx, cancel := context.WithCancel(parentContext) ctx, cancel := context.WithCancel(parentContext)
// Setup signal handler
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger, a.shutdownCallback)
if err != nil {
return err
}
a.signal = signalsubsystem
a.signal.Start()
// Start the service bus // Start the service bus
a.servicebus.Debug() a.servicebus.Debug()
err = a.servicebus.Start() err = a.servicebus.Start()
@@ -134,7 +125,7 @@ func (a *App) Run() error {
return err return err
} }
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback, a.shutdownCallback) runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback)
if err != nil { if err != nil {
return err return err
} }
@@ -212,6 +203,14 @@ func (a *App) Run() error {
// Generate backend.js // Generate backend.js
a.bindings.GenerateBackendJS() a.bindings.GenerateBackendJS()
// Setup signal handler
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger)
if err != nil {
return err
}
a.signal = signalsubsystem
a.signal.Start()
err = a.bridge.Run(dispatcher, a.menuManager, bindingDump, a.debug) err = a.bridge.Run(dispatcher, a.menuManager, bindingDump, a.debug)
a.logger.Trace("Bridge.Run() exited") a.logger.Trace("Bridge.Run() exited")
if err != nil { if err != nil {
@@ -236,6 +235,10 @@ func (a *App) Run() error {
return err return err
} }
// Shutdown callback
if a.shutdownCallback != nil {
a.shutdownCallback()
}
return nil return nil
} }

View File

@@ -8,6 +8,7 @@ import (
"github.com/leaanthony/clir" "github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/binding" "github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/features"
"github.com/wailsapp/wails/v2/internal/ffenestri" "github.com/wailsapp/wails/v2/internal/ffenestri"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher" "github.com/wailsapp/wails/v2/internal/messagedispatcher"
@@ -42,7 +43,8 @@ type App struct {
dispatcher *messagedispatcher.Dispatcher dispatcher *messagedispatcher.Dispatcher
servicebus *servicebus.ServiceBus servicebus *servicebus.ServiceBus
debug bool // Feature flags
Features *features.Features
} }
// Create App // Create App
@@ -56,15 +58,17 @@ func CreateApp(options *Options) *App {
myLogger.SetLogLevel(logger.INFO) myLogger.SetLogLevel(logger.INFO)
window := ffenestri.NewApplicationWithConfig(&ffenestri.Config{ window := ffenestri.NewApplicationWithConfig(&ffenestri.Config{
Title: options.Title, Title: options.Title,
Width: options.Width, Width: options.Width,
Height: options.Height, Height: options.Height,
MinWidth: options.MinWidth, MinWidth: options.MinWidth,
MinHeight: options.MinHeight, MinHeight: options.MinHeight,
MaxWidth: options.MaxWidth, MaxWidth: options.MaxWidth,
MaxHeight: options.MaxHeight, MaxHeight: options.MaxHeight,
StartHidden: options.StartHidden, Frameless: options.Frameless,
DevTools: options.DevTools,
// This should be controlled by the compile time flags...
DevTools: true,
Resizable: !options.DisableResize, Resizable: !options.DisableResize,
Fullscreen: options.Fullscreen, Fullscreen: options.Fullscreen,
@@ -75,12 +79,9 @@ func CreateApp(options *Options) *App {
webserver: webserver.NewWebServer(myLogger), webserver: webserver.NewWebServer(myLogger),
servicebus: servicebus.New(myLogger), servicebus: servicebus.New(myLogger),
logger: myLogger, logger: myLogger,
bindings: binding.NewBindings(myLogger, options.Bind), bindings: binding.NewBindings(myLogger),
} }
// Initialise the app
app.Init()
return app return app
} }
@@ -184,7 +185,7 @@ func (a *App) Run() error {
} }
}() }()
result := a.window.Run(dispatcher, bindingDump) result := a.window.Run(dispatcher, bindingDump, a.Features)
a.servicebus.Stop() a.servicebus.Stop()
return result return result
@@ -192,3 +193,14 @@ func (a *App) Run() error {
return cli.Run() return cli.Run()
} }
// Bind a struct to the application by passing in
// a pointer to it
func (a *App) Bind(structPtr interface{}) {
// Add the struct to the bindings
err := a.bindings.Add(structPtr)
if err != nil {
a.logger.Fatal("Error during binding: " + err.Error())
}
}

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
package binding package binding
import ( import (
"encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"strings"
) )
// BoundMethod defines all the data related to a Go method that is // BoundMethod defines all the data related to a Go method that is
@@ -16,6 +16,58 @@ type BoundMethod struct {
Method reflect.Value `json:"-"` Method reflect.Value `json:"-"`
} }
// IsWailsInit returns true if the method name is "WailsInit"
func (b *BoundMethod) IsWailsInit() bool {
return strings.HasSuffix(b.Name, "WailsInit")
}
// IsWailsShutdown returns true if the method name is "WailsShutdown"
func (b *BoundMethod) IsWailsShutdown() bool {
return strings.HasSuffix(b.Name, "WailsShutdown")
}
// VerifyWailsInit checks if the WailsInit signature is correct
func (b *BoundMethod) VerifyWailsInit() error {
// Must only have 1 input
if b.InputCount() != 1 {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Check input type
if !b.Inputs[0].IsType("*goruntime.Runtime") {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Must only have 1 output
if b.OutputCount() != 1 {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Check output type
if !b.Outputs[0].IsError() {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Input must be of type Runtime
return nil
}
// VerifyWailsShutdown checks if the WailsShutdown signature is correct
func (b *BoundMethod) VerifyWailsShutdown() error {
// Must have no inputs
if b.InputCount() != 0 {
return fmt.Errorf("invalid method signature for WailsShutdown: expected `WailsShutdown()`")
}
// Must have no outputs
if b.OutputCount() != 0 {
return fmt.Errorf("invalid method signature for WailsShutdown: expected `WailsShutdown()`")
}
// Input must be of type Runtime
return nil
}
// InputCount returns the number of inputs this bound method has // InputCount returns the number of inputs this bound method has
func (b *BoundMethod) InputCount() int { func (b *BoundMethod) InputCount() int {
return len(b.Inputs) return len(b.Inputs)
@@ -26,29 +78,6 @@ func (b *BoundMethod) OutputCount() int {
return len(b.Outputs) return len(b.Outputs)
} }
// ParseArgs method converts the input json into the types expected by the method
func (b *BoundMethod) ParseArgs(args []json.RawMessage) ([]interface{}, error) {
result := make([]interface{}, b.InputCount())
if len(args) != b.InputCount() {
return nil, fmt.Errorf("received %d arguments to method '%s', expected %d", len(args), b.Name, b.InputCount())
}
for index, arg := range args {
typ := b.Inputs[index].reflectType
inputValue := reflect.New(typ).Interface()
err := json.Unmarshal(arg, inputValue)
if err != nil {
return nil, err
}
if inputValue == nil {
result[index] = reflect.Zero(typ).Interface()
} else {
result[index] = reflect.ValueOf(inputValue).Elem().Interface()
}
}
return result, nil
}
// Call will attempt to call this bound method with the given args // Call will attempt to call this bound method with the given args
func (b *BoundMethod) Call(args []interface{}) (interface{}, error) { func (b *BoundMethod) Call(args []interface{}) (interface{}, error) {
// Check inputs // Check inputs
@@ -65,8 +94,17 @@ func (b *BoundMethod) Call(args []interface{}) (interface{}, error) {
// Iterate over given arguments // Iterate over given arguments
for index, arg := range args { for index, arg := range args {
// Attempt to convert the argument to the type expected by the method
value, err := convertArgToValue(arg, b.Inputs[index])
// If it fails, return a suitable error
if err != nil {
return nil, fmt.Errorf("%s (parameter %d): %s", b.Name, index+1, err.Error())
}
// Save the converted argument // Save the converted argument
callArgs[index] = reflect.ValueOf(arg) callArgs[index] = value
} }
// Do the call // Do the call

View File

@@ -15,6 +15,10 @@ type DB struct {
// It used for performance gains at runtime // It used for performance gains at runtime
methodMap map[string]*BoundMethod methodMap map[string]*BoundMethod
// These are slices of methods registered using WailsInit and WailsShutdown
wailsInitMethods []*BoundMethod
wailsShutdownMethods []*BoundMethod
// Lock to ensure sync access to the data // Lock to ensure sync access to the data
lock sync.RWMutex lock sync.RWMutex
} }
@@ -90,6 +94,38 @@ func (d *DB) AddMethod(packageName string, structName string, methodName string,
} }
// AddWailsInit checks the given method is a WailsInit method and if it
// is, adds it to the list of WailsInit methods
func (d *DB) AddWailsInit(method *BoundMethod) error {
err := method.VerifyWailsInit()
if err != nil {
return err
}
// Lock the db whilst processing and unlock on return
d.lock.Lock()
defer d.lock.Unlock()
d.wailsInitMethods = append(d.wailsInitMethods, method)
return nil
}
// AddWailsShutdown checks the given method is a WailsInit method and if it
// is, adds it to the list of WailsShutdown methods
func (d *DB) AddWailsShutdown(method *BoundMethod) error {
err := method.VerifyWailsShutdown()
if err != nil {
return err
}
// Lock the db whilst processing and unlock on return
d.lock.Lock()
defer d.lock.Unlock()
d.wailsShutdownMethods = append(d.wailsShutdownMethods, method)
return nil
}
// ToJSON converts the method map to JSON // ToJSON converts the method map to JSON
func (d *DB) ToJSON() (string, error) { func (d *DB) ToJSON() (string, error) {
@@ -102,3 +138,13 @@ func (d *DB) ToJSON() (string, error) {
// Return zero copy string as this string will be read only // Return zero copy string as this string will be read only
return *(*string)(unsafe.Pointer(&bytes)), err return *(*string)(unsafe.Pointer(&bytes)), err
} }
// WailsInitMethods returns the list of registered WailsInit methods
func (d *DB) WailsInitMethods() []*BoundMethod {
return d.wailsInitMethods
}
// WailsShutdownMethods returns the list of registered WailsInit methods
func (d *DB) WailsShutdownMethods() []*BoundMethod {
return d.wailsShutdownMethods
}

View File

@@ -58,7 +58,7 @@ const backend = {`)
sortedPackageNames.Add(packageName) sortedPackageNames.Add(packageName)
} }
sortedPackageNames.Sort() sortedPackageNames.Sort()
for _, packageName := range sortedPackageNames.AsSlice() { sortedPackageNames.Each(func(packageName string) {
packages := store[packageName] packages := store[packageName]
output.WriteString(fmt.Sprintf(" \"%s\": {", packageName)) output.WriteString(fmt.Sprintf(" \"%s\": {", packageName))
output.WriteString("\n") output.WriteString("\n")
@@ -67,7 +67,8 @@ const backend = {`)
sortedStructNames.Add(structName) sortedStructNames.Add(structName)
} }
sortedStructNames.Sort() sortedStructNames.Sort()
for _, structName := range sortedStructNames.AsSlice() {
sortedStructNames.Each(func(structName string) {
structs := packages[structName] structs := packages[structName]
output.WriteString(fmt.Sprintf(" \"%s\": {", structName)) output.WriteString(fmt.Sprintf(" \"%s\": {", structName))
output.WriteString("\n") output.WriteString("\n")
@@ -78,7 +79,7 @@ const backend = {`)
} }
sortedMethodNames.Sort() sortedMethodNames.Sort()
for _, methodName := range sortedMethodNames.AsSlice() { sortedMethodNames.Each(func(methodName string) {
methodDetails := structs[methodName] methodDetails := structs[methodName]
output.WriteString(" /**\n") output.WriteString(" /**\n")
output.WriteString(" * " + methodName + "\n") output.WriteString(" * " + methodName + "\n")
@@ -109,13 +110,16 @@ const backend = {`)
output.WriteString("\n") output.WriteString("\n")
output.WriteString(fmt.Sprintf(" },")) output.WriteString(fmt.Sprintf(" },"))
output.WriteString("\n") output.WriteString("\n")
}
})
output.WriteString(fmt.Sprintf(" }")) output.WriteString(fmt.Sprintf(" }"))
output.WriteString("\n") output.WriteString("\n")
} })
output.WriteString(fmt.Sprintf(" }\n")) output.WriteString(fmt.Sprintf(" }\n"))
output.WriteString("\n") output.WriteString("\n")
} })
output.WriteString(`}; output.WriteString(`};
export default backend;`) export default backend;`)

View File

@@ -3,7 +3,6 @@ package binding
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"runtime"
) )
// isStructPtr returns true if the value given is a // isStructPtr returns true if the value given is a
@@ -13,35 +12,14 @@ func isStructPtr(value interface{}) bool {
reflect.ValueOf(value).Elem().Kind() == reflect.Struct reflect.ValueOf(value).Elem().Kind() == reflect.Struct
} }
// isFunction returns true if the given value is a function func getMethods(value interface{}) ([]*BoundMethod, error) {
func isFunction(value interface{}) bool {
return reflect.ValueOf(value).Kind() == reflect.Func
}
// isStructPtr returns true if the value given is a struct
func isStruct(value interface{}) bool {
return reflect.ValueOf(value).Kind() == reflect.Struct
}
func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
// Create result placeholder // Create result placeholder
var result []*BoundMethod var result []*BoundMethod
// Check type // Check type
if !isStructPtr(value) { if !isStructPtr(value) {
return nil, fmt.Errorf("not a pointer to an interface")
if isStruct(value) {
name := reflect.ValueOf(value).Type().Name()
return nil, fmt.Errorf("%s is a struct, not a pointer to a struct", name)
}
if isFunction(value) {
name := runtime.FuncForPC(reflect.ValueOf(value).Pointer()).Name()
return nil, fmt.Errorf("%s is a function, not a pointer to a struct. Wails v2 has deprecated the binding of functions. Please wrap your functions up in a struct and bind a pointer to that struct.", name)
}
return nil, fmt.Errorf("not a pointer to a struct.")
} }
// Process Struct // Process Struct
@@ -56,11 +34,6 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
fullMethodName := baseName + "." + methodName fullMethodName := baseName + "." + methodName
method := structValue.MethodByName(methodName) method := structValue.MethodByName(methodName)
methodReflectName := runtime.FuncForPC(methodDef.Func.Pointer()).Name()
if b.exemptions.Contains(methodReflectName) {
continue
}
// Create new method // Create new method
boundMethod := &BoundMethod{ boundMethod := &BoundMethod{
Name: fullMethodName, Name: fullMethodName,
@@ -83,8 +56,6 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
boundMethod.Inputs = inputs boundMethod.Inputs = inputs
// Iterate outputs // Iterate outputs
// TODO: Determine what to do about limiting return types
// especially around errors.
outputParamCount := methodType.NumOut() outputParamCount := methodType.NumOut()
var outputs []*Parameter var outputs []*Parameter
for outputIndex := 0; outputIndex < outputParamCount; outputIndex++ { for outputIndex := 0; outputIndex < outputParamCount; outputIndex++ {
@@ -100,3 +71,39 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
} }
return result, nil return result, nil
} }
// convertArgToValue
func convertArgToValue(input interface{}, target *Parameter) (result reflect.Value, err error) {
// Catch type conversion panics thrown by convert
defer func() {
if r := recover(); r != nil {
// Modify error
err = fmt.Errorf("%s", r.(string)[23:])
}
}()
// Do the conversion
// Handle nil values
if input == nil {
switch target.reflectType.Kind() {
case reflect.Chan,
reflect.Func,
reflect.Interface,
reflect.Map,
reflect.Ptr,
reflect.Slice:
result = reflect.ValueOf(input).Convert(target.reflectType)
default:
return reflect.Zero(target.reflectType), fmt.Errorf("Unable to use null value")
}
} else {
result = reflect.ValueOf(input).Convert(target.reflectType)
}
// We don't like doing this but it's the only way to
// handle recover() correctly
return
}

View File

@@ -11,6 +11,10 @@ type BridgeClient struct {
messageCache chan string messageCache chan string
} }
func (b BridgeClient) DeleteTrayMenuByID(id string) {
b.session.sendMessage("TD" + id)
}
func NewBridgeClient() *BridgeClient { func NewBridgeClient() *BridgeClient {
return &BridgeClient{ return &BridgeClient{
messageCache: make(chan string, 100), messageCache: make(chan string, 100),

View File

@@ -19,6 +19,9 @@ type DialogClient struct {
log *logger.Logger log *logger.Logger
} }
func (d *DialogClient) DeleteTrayMenuByID(id string) {
}
func NewDialogClient(log *logger.Logger) *DialogClient { func NewDialogClient(log *logger.Logger) *DialogClient {
return &DialogClient{ return &DialogClient{
log: log, log: log,

View File

@@ -0,0 +1,89 @@
package colour
import (
"fmt"
"strings"
"github.com/wzshiming/ctc"
)
func Col(col ctc.Color, text string) string {
return fmt.Sprintf("%s%s%s", col, text, ctc.Reset)
}
func Yellow(text string) string {
return Col(ctc.ForegroundBrightYellow, text)
}
func Red(text string) string {
return Col(ctc.ForegroundBrightRed, text)
}
func Blue(text string) string {
return Col(ctc.ForegroundBrightBlue, text)
}
func Green(text string) string {
return Col(ctc.ForegroundBrightGreen, text)
}
func Cyan(text string) string {
return Col(ctc.ForegroundBrightCyan, text)
}
func Magenta(text string) string {
return Col(ctc.ForegroundBrightMagenta, text)
}
func White(text string) string {
return Col(ctc.ForegroundBrightWhite, text)
}
func Black(text string) string {
return Col(ctc.ForegroundBrightBlack, text)
}
func DarkYellow(text string) string {
return Col(ctc.ForegroundYellow, text)
}
func DarkRed(text string) string {
return Col(ctc.ForegroundRed, text)
}
func DarkBlue(text string) string {
return Col(ctc.ForegroundBlue, text)
}
func DarkGreen(text string) string {
return Col(ctc.ForegroundGreen, text)
}
func DarkCyan(text string) string {
return Col(ctc.ForegroundCyan, text)
}
func DarkMagenta(text string) string {
return Col(ctc.ForegroundMagenta, text)
}
func DarkWhite(text string) string {
return Col(ctc.ForegroundWhite, text)
}
func DarkBlack(text string) string {
return Col(ctc.ForegroundBlack, text)
}
var rainbowCols = []func(string) string{Red, Yellow, Green, Cyan, Blue, Magenta}
func Rainbow(text string) string {
var builder strings.Builder
for i := 0; i < len(text); i++ {
fn := rainbowCols[i%len(rainbowCols)]
builder.WriteString(fn(text[i : i+1]))
}
return builder.String()
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,8 +13,7 @@ import "C"
import ( import (
"strconv" "strconv"
"unsafe"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
) )
@@ -67,144 +66,44 @@ func (c *Client) WindowUnFullscreen() {
C.UnFullscreen(c.app.app) C.UnFullscreen(c.app.app)
} }
// WindowShow will show the window
func (c *Client) WindowShow() {
C.Show(c.app.app)
}
// WindowHide will hide the window
func (c *Client) WindowHide() {
C.Hide(c.app.app)
}
// WindowCenter will hide the window
func (c *Client) WindowCenter() {
C.Center(c.app.app)
}
// WindowMaximise will maximise the window
func (c *Client) WindowMaximise() {
C.Maximise(c.app.app)
}
// WindowMinimise will minimise the window
func (c *Client) WindowMinimise() {
C.Minimise(c.app.app)
}
// WindowUnmaximise will unmaximise the window
func (c *Client) WindowUnmaximise() {
C.Unmaximise(c.app.app)
}
// WindowUnminimise will unminimise the window
func (c *Client) WindowUnminimise() {
C.Unminimise(c.app.app)
}
// WindowPosition will position the window to x,y on the
// monitor that the window is mostly on
func (c *Client) WindowPosition(x int, y int) {
C.SetPosition(c.app.app, C.int(x), C.int(y))
}
// WindowSize will resize the window to the given
// width and height
func (c *Client) WindowSize(width int, height int) {
C.SetSize(c.app.app, C.int(width), C.int(height))
}
func (c *Client) WindowSetMinSize(width int, height int) {
C.SetMinWindowSize(c.app.app, C.int(width), C.int(height))
}
func (c *Client) WindowSetMaxSize(width int, height int) {
C.SetMaxWindowSize(c.app.app, C.int(width), C.int(height))
}
// WindowSetColour sets the window colour // WindowSetColour sets the window colour
func (c *Client) WindowSetColour(colour int) { func (c *Client) WindowSetColour(colour string) bool {
r, g, b, a := intToColour(colour) result := C.SetColour(c.app.app, c.app.string2CString(colour))
C.SetColour(c.app.app, r, g, b, a) return result == 1
} }
// OpenDialog will open a dialog with the given title and filter // OpenFileDialog will open a file dialog with the given title
func (c *Client) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) { func (c *Client) OpenFileDialog(title string) string {
C.OpenDialog(c.app.app, cstring := C.OpenFileDialogOnMainThread(c.app.app, c.app.string2CString(title))
c.app.string2CString(callbackID), var result string
c.app.string2CString(dialogOptions.Title), if cstring != nil {
c.app.string2CString(dialogOptions.Filters), result = C.GoString(cstring)
c.app.string2CString(dialogOptions.DefaultFilename), // Free the C string that was allocated by the dialog
c.app.string2CString(dialogOptions.DefaultDirectory), C.free(unsafe.Pointer(cstring))
c.app.bool2Cint(dialogOptions.AllowFiles),
c.app.bool2Cint(dialogOptions.AllowDirectories),
c.app.bool2Cint(dialogOptions.AllowMultiple),
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
c.app.bool2Cint(dialogOptions.ResolvesAliases),
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
)
}
// SaveDialog will open a dialog with the given title and filter
func (c *Client) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
C.SaveDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(dialogOptions.Title),
c.app.string2CString(dialogOptions.Filters),
c.app.string2CString(dialogOptions.DefaultFilename),
c.app.string2CString(dialogOptions.DefaultDirectory),
c.app.bool2Cint(dialogOptions.ShowHiddenFiles),
c.app.bool2Cint(dialogOptions.CanCreateDirectories),
c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories),
)
}
// MessageDialog will open a message dialog with the given options
func (c *Client) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
// Sanity check button length
if len(dialogOptions.Buttons) > 4 {
c.app.logger.Error("Given %d message dialog buttons. Maximum is 4", len(dialogOptions.Buttons))
return
} }
return result
}
// Process buttons // SaveFileDialog will open a save file dialog with the given title
buttons := []string{"", "", "", ""} func (c *Client) SaveFileDialog(title string) string {
for i, button := range dialogOptions.Buttons { cstring := C.SaveFileDialogOnMainThread(c.app.app, c.app.string2CString(title))
buttons[i] = button var result string
if cstring != nil {
result = C.GoString(cstring)
// Free the C string that was allocated by the dialog
C.free(unsafe.Pointer(cstring))
} }
return result
C.MessageDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(string(dialogOptions.Type)),
c.app.string2CString(dialogOptions.Title),
c.app.string2CString(dialogOptions.Message),
c.app.string2CString(dialogOptions.Icon),
c.app.string2CString(buttons[0]),
c.app.string2CString(buttons[1]),
c.app.string2CString(buttons[2]),
c.app.string2CString(buttons[3]),
c.app.string2CString(dialogOptions.DefaultButton),
c.app.string2CString(dialogOptions.CancelButton))
} }
func (c *Client) DarkModeEnabled(callbackID string) { // OpenDirectoryDialog will open a directory dialog with the given title
C.DarkModeEnabled(c.app.app, c.app.string2CString(callbackID)) func (c *Client) OpenDirectoryDialog(title string) string {
} cstring := C.OpenDirectoryDialogOnMainThread(c.app.app, c.app.string2CString(title))
var result string
func (c *Client) SetApplicationMenu(applicationMenuJSON string) { if cstring != nil {
C.SetApplicationMenu(c.app.app, c.app.string2CString(applicationMenuJSON)) result = C.GoString(cstring)
} // Free the C string that was allocated by the dialog
C.free(unsafe.Pointer(cstring))
func (c *Client) SetTrayMenu(trayMenuJSON string) { }
C.SetTrayMenu(c.app.app, c.app.string2CString(trayMenuJSON)) return result
}
func (c *Client) UpdateTrayMenuLabel(JSON string) {
C.UpdateTrayMenuLabel(c.app.app, c.app.string2CString(JSON))
}
func (c *Client) UpdateContextMenu(contextMenuJSON string) {
C.UpdateContextMenu(c.app.app, c.app.string2CString(contextMenuJSON))
} }

View File

@@ -24,6 +24,8 @@ struct hashmap_s dialogIconCache;
// Dispatch Method // Dispatch Method
typedef void (^dispatchMethod)(void); typedef void (^dispatchMethod)(void);
TrayMenuStore *TrayMenuStoreSingleton;
// dispatch will execute the given `func` pointer // dispatch will execute the given `func` pointer
void dispatch(dispatchMethod func) { void dispatch(dispatchMethod func) {
dispatch_async(dispatch_get_main_queue(), func); dispatch_async(dispatch_get_main_queue(), func);
@@ -46,6 +48,18 @@ int hashmap_log(void *const context, struct hashmap_element_s *const e) {
return 0; return 0;
} }
void filelog(const char *message) {
FILE *fp = fopen("/tmp/wailslog.txt", "ab");
if (fp != NULL)
{
fputs(message, fp);
fclose(fp);
}
}
// The delegate class for tray menus
Class trayMenuDelegateClass;
// Utility function to visualise a hashmap // Utility function to visualise a hashmap
void dumpHashmap(const char *name, struct hashmap_s *hashmap) { void dumpHashmap(const char *name, struct hashmap_s *hashmap) {
printf("%s = { ", name); printf("%s = { ", name);
@@ -79,6 +93,7 @@ struct Application {
id mouseEvent; id mouseEvent;
id mouseDownMonitor; id mouseDownMonitor;
id mouseUpMonitor; id mouseUpMonitor;
int activationPolicy;
// Window Data // Window Data
const char *title; const char *title;
@@ -112,13 +127,12 @@ struct Application {
int useToolBar; int useToolBar;
int hideToolbarSeparator; int hideToolbarSeparator;
int windowBackgroundIsTranslucent; int windowBackgroundIsTranslucent;
int hasURLHandlers;
const char *startupURL;
// Menu // Menu
Menu *applicationMenu; Menu *applicationMenu;
// Tray
TrayMenuStore* trayMenuStore;
// Context Menus // Context Menus
ContextMenuStore *contextMenuStore; ContextMenuStore *contextMenuStore;
@@ -131,6 +145,9 @@ struct Application {
// shutting down flag // shutting down flag
bool shuttingDown; bool shuttingDown;
// Running flag
bool running;
}; };
// Debug works like sprintf but mutes if the global debug flag is true // Debug works like sprintf but mutes if the global debug flag is true
@@ -253,7 +270,7 @@ void Hide(struct Application *app) {
if( app->shuttingDown ) return; if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
msg(app->application, s("hide:")) msg(app->mainWindow, s("orderOut:"));
); );
} }
@@ -291,8 +308,19 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
// TODO: Check this actually does reduce flicker // TODO: Check this actually does reduce flicker
msg(app->config, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("suppressesIncrementalRendering")); msg(app->config, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("suppressesIncrementalRendering"));
// We are now running!
app->running = true;
// Notify backend we are ready (system startup) // Notify backend we are ready (system startup)
app->sendMessageToBackend("SS"); const char *readyMessage = "SS";
if( app->startupURL == NULL ) {
app->sendMessageToBackend("SS");
return;
}
readyMessage = concat("SS", app->startupURL);
app->sendMessageToBackend(readyMessage);
MEMFREE(readyMessage);
} else if( strcmp(name, "windowDrag") == 0 ) { } else if( strcmp(name, "windowDrag") == 0 ) {
// Guard against null events // Guard against null events
@@ -390,6 +418,11 @@ void ExecJS(struct Application *app, const char *js) {
void willFinishLaunching(id self, SEL cmd, id sender) { void willFinishLaunching(id self, SEL cmd, id sender) {
struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application"); struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application");
// If there are URL Handlers, register a listener for them
if( app->hasURLHandlers ) {
id eventManager = msg(c("NSAppleEventManager"), s("sharedAppleEventManager"));
msg(eventManager, s("setEventHandler:andSelector:forEventClass:andEventID:"), self, s("getUrl:withReplyEvent:"), kInternetEventClass, kAEGetURL);
}
messageFromWindowCallback("Ej{\"name\":\"wails:launched\",\"data\":[]}"); messageFromWindowCallback("Ej{\"name\":\"wails:launched\",\"data\":[]}");
} }
@@ -414,12 +447,6 @@ void themeChanged(id self, SEL cmd, id sender) {
} }
} }
// void willFinishLaunching(id self) {
// struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application");
// Debug(app, "willFinishLaunching called!");
// }
int releaseNSObject(void *const context, struct hashmap_element_s *const e) { int releaseNSObject(void *const context, struct hashmap_element_s *const e) {
msg(e->data, s("release")); msg(e->data, s("release"));
return -1; return -1;
@@ -452,6 +479,10 @@ void DestroyApplication(struct Application *app) {
Debug(app, "Almost a double free for app->bindings"); Debug(app, "Almost a double free for app->bindings");
} }
if( app->startupURL != NULL ) {
MEMFREE(app->startupURL);
}
// Remove mouse monitors // Remove mouse monitors
if( app->mouseDownMonitor != NULL ) { if( app->mouseDownMonitor != NULL ) {
msg( c("NSEvent"), s("removeMonitor:"), app->mouseDownMonitor); msg( c("NSEvent"), s("removeMonitor:"), app->mouseDownMonitor);
@@ -466,7 +497,7 @@ void DestroyApplication(struct Application *app) {
} }
// Delete the tray menu store // Delete the tray menu store
DeleteTrayMenuStore(app->trayMenuStore); DeleteTrayMenuStore(TrayMenuStoreSingleton);
// Delete the context menu store // Delete the context menu store
DeleteContextMenuStore(app->contextMenuStore); DeleteContextMenuStore(app->contextMenuStore);
@@ -498,31 +529,6 @@ void DestroyApplication(struct Application *app) {
Debug(app, "Finished Destroying Application"); Debug(app, "Finished Destroying Application");
} }
// Quit will stop the cocoa application and free up all the memory
// used by the application
void Quit(struct Application *app) {
Debug(app, "Quit Called");
ON_MAIN_THREAD (
// Terminate app
msg(app->application, s("stop:"), NULL);
id fakeevent = msg(c("NSEvent"),
s("otherEventWithType:location:modifierFlags:timestamp:windowNumber:context:subtype:data1:data2:"),
15, // Type
msg(c("CGPoint"), s("init:x:y:"), 0, 0), // location
0, // flags
0, // timestamp
0, // window
NULL, // context
0, // subtype
0, // data1
0 // data2
);
msg(c("NSApp"), s("postEvent:atStart:"), fakeevent, true);
// msg(c(app->mainWindow), s("performClose:"))
);
}
// SetTitle sets the main window title to the given string // SetTitle sets the main window title to the given string
void SetTitle(struct Application *app, const char *title) { void SetTitle(struct Application *app, const char *title) {
// Guard against calling during shutdown // Guard against calling during shutdown
@@ -1058,7 +1064,7 @@ void AddTrayMenu(struct Application *app, const char *trayMenuJSON) {
// Guard against calling during shutdown // Guard against calling during shutdown
if( app->shuttingDown ) return; if( app->shuttingDown ) return;
AddTrayMenuToStore(app->trayMenuStore, trayMenuJSON); AddTrayMenuToStore(TrayMenuStoreSingleton, trayMenuJSON);
} }
void SetTrayMenu(struct Application *app, const char* trayMenuJSON) { void SetTrayMenu(struct Application *app, const char* trayMenuJSON) {
@@ -1067,7 +1073,13 @@ void SetTrayMenu(struct Application *app, const char* trayMenuJSON) {
if( app->shuttingDown ) return; if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
UpdateTrayMenuInStore(app->trayMenuStore, trayMenuJSON); UpdateTrayMenuInStore(TrayMenuStoreSingleton, trayMenuJSON);
);
}
void DeleteTrayMenuByID(struct Application *app, const char *id) {
ON_MAIN_THREAD(
DeleteTrayMenuInStore(TrayMenuStoreSingleton, id);
); );
} }
@@ -1076,7 +1088,7 @@ void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
if( app->shuttingDown ) return; if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
UpdateTrayMenuLabelInStore(app->trayMenuStore, JSON); UpdateTrayMenuLabelInStore(TrayMenuStoreSingleton, JSON);
); );
} }
@@ -1137,7 +1149,7 @@ void processDecorations(struct Application *app) {
void createApplication(struct Application *app) { void createApplication(struct Application *app) {
id application = msg(c("NSApplication"), s("sharedApplication")); id application = msg(c("NSApplication"), s("sharedApplication"));
app->application = application; app->application = application;
msg(application, s("setActivationPolicy:"), 0); msg(application, s("setActivationPolicy:"), app->activationPolicy);
} }
void DarkModeEnabled(struct Application *app, const char *callbackID) { void DarkModeEnabled(struct Application *app, const char *callbackID) {
@@ -1161,27 +1173,59 @@ void DarkModeEnabled(struct Application *app, const char *callbackID) {
); );
} }
void getURL(id self, SEL selector, id event, id replyEvent) {
struct Application *app = (struct Application *)objc_getAssociatedObject(self, "application");
id desc = msg(event, s("paramDescriptorForKeyword:"), keyDirectObject);
id url = msg(desc, s("stringValue"));
const char* curl = cstr(url);
if( curl == NULL ) {
return;
}
// If this was an incoming URL, but we aren't running yet
// save it to return when we complete
if( app->running != true ) {
app->startupURL = STRCOPY(curl);
return;
}
const char* message = concat("UC", curl);
messageFromWindowCallback(message);
MEMFREE(message);
}
void openURLs(id self, SEL selector, id event) {
filelog("\n\nI AM HERE!!!!!\n\n");
}
void createDelegate(struct Application *app) { void createDelegate(struct Application *app) {
// Define delegate
Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "AppDelegate", 0); // Define delegate
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSApplicationDelegate")); Class appDelegate = objc_allocateClassPair((Class) c("NSResponder"), "AppDelegate", 0);
class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) no, "c@:@"); class_addProtocol(appDelegate, objc_getProtocol("NSTouchBarProvider"));
// class_addMethod(delegateClass, s("applicationWillTerminate:"), (IMP) closeWindow, "v@:@");
class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@"); class_addMethod(appDelegate, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) no, "c@:@");
class_addMethod(appDelegate, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
// All Menu Items use a common callback // All Menu Items use a common callback
class_addMethod(delegateClass, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@"); class_addMethod(appDelegate, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@");
// If there are URL Handlers, register the callback method
if( app->hasURLHandlers ) {
class_addMethod(appDelegate, s("getUrl:withReplyEvent:"), (IMP) getURL, "i@:@@");
}
// Script handler // Script handler
class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@"); class_addMethod(appDelegate, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
objc_registerClassPair(delegateClass); objc_registerClassPair(appDelegate);
// Create delegate // Create delegate
id delegate = msg((id)delegateClass, s("new")); id delegate = msg((id)appDelegate, s("new"));
objc_setAssociatedObject(delegate, "application", (id)app, OBJC_ASSOCIATION_ASSIGN); objc_setAssociatedObject(delegate, "application", (id)app, OBJC_ASSOCIATION_ASSIGN);
// Theme change listener // Theme change listener
class_addMethod(delegateClass, s("themeChanged:"), (IMP) themeChanged, "v@:@@"); class_addMethod(appDelegate, s("themeChanged:"), (IMP) themeChanged, "v@:@@");
// Get defaultCenter // Get defaultCenter
id defaultCenter = msg(c("NSDistributedNotificationCenter"), s("defaultCenter")); id defaultCenter = msg(c("NSDistributedNotificationCenter"), s("defaultCenter"));
@@ -1193,15 +1237,21 @@ void createDelegate(struct Application *app) {
} }
bool windowShouldClose(id self, SEL cmd, id sender) { bool windowShouldClose(id self, SEL cmd, id sender) {
msg(sender, s("orderBack:")); msg(sender, s("orderOut:"));
return false; return false;
} }
bool windowShouldExit(id self, SEL cmd, id sender) {
msg(sender, s("orderOut:"));
messageFromWindowCallback("WC");
return false;
}
void createMainWindow(struct Application *app) { void createMainWindow(struct Application *app) {
// Create main window // Create main window
id mainWindow = ALLOC("NSWindow"); id mainWindow = ALLOC("NSWindow");
mainWindow = msg(mainWindow, s("initWithContentRect:styleMask:backing:defer:"), mainWindow = msg(mainWindow, s("initWithContentRect:styleMask:backing:defer:"),
CGRectMake(0, 0, app->width, app->height), app->decorations, NSBackingStoreBuffered, NO); CGRectMake(0, 0, app->width, app->height), app->decorations, NSBackingStoreBuffered, NO);
msg(mainWindow, s("autorelease")); msg(mainWindow, s("autorelease"));
// Set Appearance // Set Appearance
@@ -1215,14 +1265,16 @@ void createMainWindow(struct Application *app) {
msg(mainWindow, s("setTitlebarAppearsTransparent:"), app->titlebarAppearsTransparent ? YES : NO); msg(mainWindow, s("setTitlebarAppearsTransparent:"), app->titlebarAppearsTransparent ? YES : NO);
msg(mainWindow, s("setTitleVisibility:"), app->hideTitle); msg(mainWindow, s("setTitleVisibility:"), app->hideTitle);
if( app->hideWindowOnClose ) { // Create window delegate to override windowShouldClose
// Create window delegate to override windowShouldClose Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "WindowDelegate", 0);
Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "WindowDelegate", 0); bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSWindowDelegate"));
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSWindowDelegate")); if( app->hideWindowOnClose ) {
class_replaceMethod(delegateClass, s("windowShouldClose:"), (IMP) windowShouldClose, "v@:@"); class_replaceMethod(delegateClass, s("windowShouldClose:"), (IMP) windowShouldClose, "v@:@");
app->windowDelegate = msg((id)delegateClass, s("new")); } else {
msg(mainWindow, s("setDelegate:"), app->windowDelegate); class_replaceMethod(delegateClass, s("windowShouldClose:"), (IMP) windowShouldExit, "v@:@");
} }
app->windowDelegate = msg((id)delegateClass, s("new"));
msg(mainWindow, s("setDelegate:"), app->windowDelegate);
app->mainWindow = mainWindow; app->mainWindow = mainWindow;
} }
@@ -1641,6 +1693,35 @@ void processUserDialogIcons(struct Application *app) {
} }
void TrayMenuWillOpen(id self, SEL selector, id menu) {
// Extract tray menu id from menu
id trayMenuIDStr = objc_getAssociatedObject(menu, "trayMenuID");
const char* trayMenuID = cstr(trayMenuIDStr);
const char *message = concat("Mo", trayMenuID);
messageFromWindowCallback(message);
MEMFREE(message);
}
void TrayMenuDidClose(id self, SEL selector, id menu) {
// Extract tray menu id from menu
id trayMenuIDStr = objc_getAssociatedObject(menu, "trayMenuID");
const char* trayMenuID = cstr(trayMenuIDStr);
const char *message = concat("Mc", trayMenuID);
messageFromWindowCallback(message);
MEMFREE(message);
}
void createTrayMenuDelegate() {
// Define delegate
trayMenuDelegateClass = objc_allocateClassPair((Class) c("NSObject"), "MenuDelegate", 0);
class_addProtocol(trayMenuDelegateClass, objc_getProtocol("NSMenuDelegate"));
class_addMethod(trayMenuDelegateClass, s("menuWillOpen:"), (IMP) TrayMenuWillOpen, "v@:@");
class_addMethod(trayMenuDelegateClass, s("menuDidClose:"), (IMP) TrayMenuDidClose, "v@:@");
// Script handler
objc_registerClassPair(trayMenuDelegateClass);
}
void Run(struct Application *app, int argc, char **argv) { void Run(struct Application *app, int argc, char **argv) {
@@ -1653,6 +1734,9 @@ void Run(struct Application *app, int argc, char **argv) {
// Define delegate // Define delegate
createDelegate(app); createDelegate(app);
// Define tray delegate
createTrayMenuDelegate();
// Create the main window // Create the main window
createMainWindow(app); createMainWindow(app);
@@ -1823,7 +1907,7 @@ void Run(struct Application *app, int argc, char **argv) {
} }
// Setup initial trays // Setup initial trays
ShowTrayMenusInStore(app->trayMenuStore); ShowTrayMenusInStore(TrayMenuStoreSingleton);
// Process dialog icons // Process dialog icons
processUserDialogIcons(app); processUserDialogIcons(app);
@@ -1837,6 +1921,23 @@ void Run(struct Application *app, int argc, char **argv) {
MEMFREE(internalCode); MEMFREE(internalCode);
} }
void SetActivationPolicy(struct Application* app, int policy) {
app->activationPolicy = policy;
}
void HasURLHandlers(struct Application* app) {
app->hasURLHandlers = 1;
}
// Quit will stop the cocoa application and free up all the memory
// used by the application
void Quit(struct Application *app) {
Debug(app, "Quit Called");
msg(app->application, s("stop:"), NULL);
SetSize(app, 0, 0);
Show(app);
Hide(app);
}
void* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose) { void* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose) {
@@ -1883,7 +1984,7 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
result->applicationMenu = NULL; result->applicationMenu = NULL;
// Tray // Tray
result->trayMenuStore = NewTrayMenuStore(); TrayMenuStoreSingleton = NewTrayMenuStore();
// Context Menus // Context Menus
result->contextMenuStore = NewContextMenuStore(); result->contextMenuStore = NewContextMenuStore();
@@ -1899,6 +2000,14 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
result->shuttingDown = false; result->shuttingDown = false;
result->activationPolicy = NSApplicationActivationPolicyRegular;
result->hasURLHandlers = 0;
result->startupURL = NULL;
result->running = false;
return (void*) result; return (void*) result;
} }

View File

@@ -48,6 +48,9 @@ func (a *Application) processPlatformSettings() error {
C.SetAppearance(a.app, a.string2CString(string(mac.Appearance))) C.SetAppearance(a.app, a.string2CString(string(mac.Appearance)))
} }
// Set activation policy
C.SetActivationPolicy(a.app, C.int(mac.ActivationPolicy))
// Check if the webview should be transparent // Check if the webview should be transparent
if mac.WebviewIsTransparent { if mac.WebviewIsTransparent {
C.WebviewIsTransparent(a.app) C.WebviewIsTransparent(a.app)
@@ -87,5 +90,10 @@ func (a *Application) processPlatformSettings() error {
} }
} }
// Process URL Handlers
if a.config.Mac.URLHandlers != nil {
C.HasURLHandlers(a.app)
}
return nil return nil
} }

View File

@@ -14,6 +14,10 @@
// Macros to make it slightly more sane // Macros to make it slightly more sane
#define msg objc_msgSend #define msg objc_msgSend
#define kInternetEventClass 'GURL'
#define kAEGetURL 'GURL'
#define keyDirectObject '----'
#define c(str) (id)objc_getClass(str) #define c(str) (id)objc_getClass(str)
#define s(str) sel_registerName(str) #define s(str) sel_registerName(str)
#define u(str) sel_getUid(str) #define u(str) sel_getUid(str)
@@ -66,6 +70,10 @@
#define NSControlStateValueOff 0 #define NSControlStateValueOff 0
#define NSControlStateValueOn 1 #define NSControlStateValueOn 1
#define NSApplicationActivationPolicyRegular 0
#define NSApplicationActivationPolicyAccessory 1
#define NSApplicationActivationPolicyProhibited 2
// Unbelievably, if the user swaps their button preference // Unbelievably, if the user swaps their button preference
// then right buttons are reported as left buttons // then right buttons are reported as left buttons
#define NSEventMaskLeftMouseDown 1 << 1 #define NSEventMaskLeftMouseDown 1 << 1
@@ -110,6 +118,10 @@ void SetTray(struct Application* app, const char *, const char *, const char *);
//void SetContextMenus(struct Application* app, const char *); //void SetContextMenus(struct Application* app, const char *);
void AddTrayMenu(struct Application* app, const char *); void AddTrayMenu(struct Application* app, const char *);
void SetActivationPolicy(struct Application* app, int policy);
void* lookupStringConstant(id constantName); void* lookupStringConstant(id constantName);
void HasURLHandlers(struct Application* app);
#endif #endif

View File

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

View File

@@ -180,151 +180,151 @@ id processAcceleratorKey(const char *key) {
return str(""); return str("");
} }
if( STREQ(key, "Backspace") ) { if( STREQ(key, "backspace") ) {
return strunicode(0x0008); return strunicode(0x0008);
} }
if( STREQ(key, "Tab") ) { if( STREQ(key, "tab") ) {
return strunicode(0x0009); return strunicode(0x0009);
} }
if( STREQ(key, "Return") ) { if( STREQ(key, "return") ) {
return strunicode(0x000d); return strunicode(0x000d);
} }
if( STREQ(key, "Escape") ) { if( STREQ(key, "escape") ) {
return strunicode(0x001b); return strunicode(0x001b);
} }
if( STREQ(key, "Left") ) { if( STREQ(key, "left") ) {
return strunicode(0x001c); return strunicode(0x001c);
} }
if( STREQ(key, "Right") ) { if( STREQ(key, "right") ) {
return strunicode(0x001d); return strunicode(0x001d);
} }
if( STREQ(key, "Up") ) { if( STREQ(key, "up") ) {
return strunicode(0x001e); return strunicode(0x001e);
} }
if( STREQ(key, "Down") ) { if( STREQ(key, "down") ) {
return strunicode(0x001f); return strunicode(0x001f);
} }
if( STREQ(key, "Space") ) { if( STREQ(key, "space") ) {
return strunicode(0x0020); return strunicode(0x0020);
} }
if( STREQ(key, "Delete") ) { if( STREQ(key, "delete") ) {
return strunicode(0x007f); return strunicode(0x007f);
} }
if( STREQ(key, "Home") ) { if( STREQ(key, "home") ) {
return strunicode(0x2196); return strunicode(0x2196);
} }
if( STREQ(key, "End") ) { if( STREQ(key, "end") ) {
return strunicode(0x2198); return strunicode(0x2198);
} }
if( STREQ(key, "Page Up") ) { if( STREQ(key, "page up") ) {
return strunicode(0x21de); return strunicode(0x21de);
} }
if( STREQ(key, "Page Down") ) { if( STREQ(key, "page down") ) {
return strunicode(0x21df); return strunicode(0x21df);
} }
if( STREQ(key, "F1") ) { if( STREQ(key, "f1") ) {
return strunicode(0xf704); return strunicode(0xf704);
} }
if( STREQ(key, "F2") ) { if( STREQ(key, "f2") ) {
return strunicode(0xf705); return strunicode(0xf705);
} }
if( STREQ(key, "F3") ) { if( STREQ(key, "f3") ) {
return strunicode(0xf706); return strunicode(0xf706);
} }
if( STREQ(key, "F4") ) { if( STREQ(key, "f4") ) {
return strunicode(0xf707); return strunicode(0xf707);
} }
if( STREQ(key, "F5") ) { if( STREQ(key, "f5") ) {
return strunicode(0xf708); return strunicode(0xf708);
} }
if( STREQ(key, "F6") ) { if( STREQ(key, "f6") ) {
return strunicode(0xf709); return strunicode(0xf709);
} }
if( STREQ(key, "F7") ) { if( STREQ(key, "f7") ) {
return strunicode(0xf70a); return strunicode(0xf70a);
} }
if( STREQ(key, "F8") ) { if( STREQ(key, "f8") ) {
return strunicode(0xf70b); return strunicode(0xf70b);
} }
if( STREQ(key, "F9") ) { if( STREQ(key, "f9") ) {
return strunicode(0xf70c); return strunicode(0xf70c);
} }
if( STREQ(key, "F10") ) { if( STREQ(key, "f10") ) {
return strunicode(0xf70d); return strunicode(0xf70d);
} }
if( STREQ(key, "F11") ) { if( STREQ(key, "f11") ) {
return strunicode(0xf70e); return strunicode(0xf70e);
} }
if( STREQ(key, "F12") ) { if( STREQ(key, "f12") ) {
return strunicode(0xf70f); return strunicode(0xf70f);
} }
if( STREQ(key, "F13") ) { if( STREQ(key, "f13") ) {
return strunicode(0xf710); return strunicode(0xf710);
} }
if( STREQ(key, "F14") ) { if( STREQ(key, "f14") ) {
return strunicode(0xf711); return strunicode(0xf711);
} }
if( STREQ(key, "F15") ) { if( STREQ(key, "f15") ) {
return strunicode(0xf712); return strunicode(0xf712);
} }
if( STREQ(key, "F16") ) { if( STREQ(key, "f16") ) {
return strunicode(0xf713); return strunicode(0xf713);
} }
if( STREQ(key, "F17") ) { if( STREQ(key, "f17") ) {
return strunicode(0xf714); return strunicode(0xf714);
} }
if( STREQ(key, "F18") ) { if( STREQ(key, "f18") ) {
return strunicode(0xf715); return strunicode(0xf715);
} }
if( STREQ(key, "F19") ) { if( STREQ(key, "f19") ) {
return strunicode(0xf716); return strunicode(0xf716);
} }
if( STREQ(key, "F20") ) { if( STREQ(key, "f20") ) {
return strunicode(0xf717); return strunicode(0xf717);
} }
if( STREQ(key, "F21") ) { if( STREQ(key, "f21") ) {
return strunicode(0xf718); return strunicode(0xf718);
} }
if( STREQ(key, "F22") ) { if( STREQ(key, "f22") ) {
return strunicode(0xf719); return strunicode(0xf719);
} }
if( STREQ(key, "F23") ) { if( STREQ(key, "f23") ) {
return strunicode(0xf71a); return strunicode(0xf71a);
} }
if( STREQ(key, "F24") ) { if( STREQ(key, "f24") ) {
return strunicode(0xf71b); return strunicode(0xf71b);
} }
if( STREQ(key, "F25") ) { if( STREQ(key, "f25") ) {
return strunicode(0xf71c); return strunicode(0xf71c);
} }
if( STREQ(key, "F26") ) { if( STREQ(key, "f26") ) {
return strunicode(0xf71d); return strunicode(0xf71d);
} }
if( STREQ(key, "F27") ) { if( STREQ(key, "f27") ) {
return strunicode(0xf71e); return strunicode(0xf71e);
} }
if( STREQ(key, "F28") ) { if( STREQ(key, "f28") ) {
return strunicode(0xf71f); return strunicode(0xf71f);
} }
if( STREQ(key, "F29") ) { if( STREQ(key, "f29") ) {
return strunicode(0xf720); return strunicode(0xf720);
} }
if( STREQ(key, "F30") ) { if( STREQ(key, "f30") ) {
return strunicode(0xf721); return strunicode(0xf721);
} }
if( STREQ(key, "F31") ) { if( STREQ(key, "f31") ) {
return strunicode(0xf722); return strunicode(0xf722);
} }
if( STREQ(key, "F32") ) { if( STREQ(key, "f32") ) {
return strunicode(0xf723); return strunicode(0xf723);
} }
if( STREQ(key, "F33") ) { if( STREQ(key, "f33") ) {
return strunicode(0xf724); return strunicode(0xf724);
} }
if( STREQ(key, "F34") ) { if( STREQ(key, "f34") ) {
return strunicode(0xf725); return strunicode(0xf725);
} }
if( STREQ(key, "F35") ) { if( STREQ(key, "f35") ) {
return strunicode(0xf726); return strunicode(0xf726);
} }
// if( STREQ(key, "Insert") ) { // if( STREQ(key, "Insert") ) {
@@ -336,7 +336,7 @@ id processAcceleratorKey(const char *key) {
// if( STREQ(key, "ScrollLock") ) { // if( STREQ(key, "ScrollLock") ) {
// return strunicode(0xf72f); // return strunicode(0xf72f);
// } // }
if( STREQ(key, "NumLock") ) { if( STREQ(key, "numLock") ) {
return strunicode(0xf739); return strunicode(0xf739);
} }
@@ -508,20 +508,21 @@ unsigned long parseModifiers(const char **modifiers) {
const char *thisModifier = modifiers[0]; const char *thisModifier = modifiers[0];
int count = 0; int count = 0;
while( thisModifier != NULL ) { while( thisModifier != NULL ) {
// Determine flags // Determine flags
if( STREQ(thisModifier, "CmdOrCtrl") ) { if( STREQ(thisModifier, "cmdorctrl") ) {
result |= NSEventModifierFlagCommand; result |= NSEventModifierFlagCommand;
} }
if( STREQ(thisModifier, "OptionOrAlt") ) { if( STREQ(thisModifier, "optionoralt") ) {
result |= NSEventModifierFlagOption; result |= NSEventModifierFlagOption;
} }
if( STREQ(thisModifier, "Shift") ) { if( STREQ(thisModifier, "shift") ) {
result |= NSEventModifierFlagShift; result |= NSEventModifierFlagShift;
} }
if( STREQ(thisModifier, "Super") ) { if( STREQ(thisModifier, "super") ) {
result |= NSEventModifierFlagCommand; result |= NSEventModifierFlagCommand;
} }
if( STREQ(thisModifier, "Control") ) { if( STREQ(thisModifier, "ctrl") ) {
result |= NSEventModifierFlagControl; result |= NSEventModifierFlagControl;
} }
count++; count++;
@@ -575,34 +576,7 @@ id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const c
return item; return item;
} }
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage) { id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA) {
id item = ALLOC("NSMenuItem");
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
id key = processAcceleratorKey(acceleratorkey);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
s("menuItemCallback:"), key);
if( tooltip != NULL ) {
msg(item, s("setToolTip:"), str(tooltip));
}
// Process image
if( image != NULL && strlen(image) > 0) {
id data = ALLOC("NSData");
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(image), 0);
id nsimage = ALLOC("NSImage");
msg(nsimage, s("initWithData:"), imageData);
if( templateImage ) {
msg(nsimage, s("template"), YES);
}
msg(item, s("setImage:"), nsimage);
}
// Process Menu Item attributes // Process Menu Item attributes
id dictionary = ALLOC_INIT("NSMutableDictionary"); id dictionary = ALLOC_INIT("NSMutableDictionary");
@@ -653,19 +627,61 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
id attributedString = ALLOC("NSMutableAttributedString"); id attributedString = ALLOC("NSMutableAttributedString");
msg(attributedString, s("initWithString:attributes:"), str(title), dictionary); msg(attributedString, s("initWithString:attributes:"), str(title), dictionary);
msg(dictionary, s("release"));
msg(item, s("setAttributedTitle:"), attributedString);
msg(attributedString, s("autorelease")); msg(attributedString, s("autorelease"));
msg(dictionary, s("release"));
return attributedString;
}
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate) {
id item = ALLOC("NSMenuItem");
// Create a MenuItemCallbackData
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
msg(item, s("setRepresentedObject:"), wrappedId);
if( !alternate ) {
id key = processAcceleratorKey(acceleratorkey);
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
s("menuItemCallback:"), key);
} else {
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(""));
}
if( tooltip != NULL ) {
msg(item, s("setToolTip:"), str(tooltip));
}
// Process image
if( image != NULL && strlen(image) > 0) {
id data = ALLOC("NSData");
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(image), 0);
id nsimage = ALLOC("NSImage");
msg(nsimage, s("initWithData:"), imageData);
if( templateImage ) {
msg(nsimage, s("setTemplate:"), YES);
}
msg(item, s("setImage:"), nsimage);
}
id attributedString = createAttributedString(title, fontName, fontSize, RGBA);
msg(item, s("setAttributedTitle:"), attributedString);
msg(item, s("setEnabled:"), !disabled); msg(item, s("setEnabled:"), !disabled);
msg(item, s("autorelease")); msg(item, s("autorelease"));
// Process modifiers // Process modifiers
if( modifiers != NULL ) { if( modifiers != NULL && !alternate) {
unsigned long modifierFlags = parseModifiers(modifiers); unsigned long modifierFlags = parseModifiers(modifiers);
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags); msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
} }
// alternate
if( alternate ) {
msg(item, s("setAlternate:"), true);
msg(item, s("setKeyEquivalentModifierMask:"), NSEventModifierFlagOption);
}
msg(parentMenu, s("addItem:"), item); msg(parentMenu, s("addItem:"), item);
return item; return item;
@@ -726,6 +742,11 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
label = "(empty)"; label = "(empty)";
} }
// Is this an alternate menu item?
bool alternate = false;
getJSONBool(item, "MacAlternate", &alternate);
const char *menuid = getJSONString(item, "ID"); const char *menuid = getJSONString(item, "ID");
if ( menuid == NULL) { if ( menuid == NULL) {
menuid = ""; menuid = "";
@@ -746,7 +767,7 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
bool templateImage = false; bool templateImage = false;
getJSONBool(item, "MacTemplateImage", &templateImage); getJSONBool(item, "MacTemplateImage", &templateImage);
int fontSize = 12; int fontSize = 0;
getJSONInt(item, "FontSize", &fontSize); getJSONInt(item, "FontSize", &fontSize);
// If we have an accelerator // If we have an accelerator
@@ -781,7 +802,7 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
if( type != NULL ) { if( type != NULL ) {
if( STREQ(type->string_, "Text")) { if( STREQ(type->string_, "Text")) {
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage); processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage, alternate);
} }
else if ( STREQ(type->string_, "Separator")) { else if ( STREQ(type->string_, "Separator")) {
addSeparator(parentMenu); addSeparator(parentMenu);

View File

@@ -105,10 +105,12 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key); id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key);
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage); id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate);
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item); void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
void processMenuData(Menu *menu, JsonNode *menuData); void processMenuData(Menu *menu, JsonNode *menuData);
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) ; void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup);
id GetMenu(Menu *menu); id GetMenu(Menu *menu);
id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA);
#endif //ASSETS_C_MENU_DARWIN_H #endif //ASSETS_C_MENU_DARWIN_H

View File

@@ -6,6 +6,8 @@
#include "traymenu_darwin.h" #include "traymenu_darwin.h"
#include "trayicons.h" #include "trayicons.h"
extern Class trayMenuDelegateClass;
// A cache for all our tray menu icons // A cache for all our tray menu icons
// Global because it's a singleton // Global because it's a singleton
struct hashmap_s trayIconCache; struct hashmap_s trayIconCache;
@@ -29,12 +31,23 @@ TrayMenu* NewTrayMenu(const char* menuJSON) {
result->ID = mustJSONString(processedJSON, "ID"); result->ID = mustJSONString(processedJSON, "ID");
result->label = mustJSONString(processedJSON, "Label"); result->label = mustJSONString(processedJSON, "Label");
result->icon = mustJSONString(processedJSON, "Icon"); result->icon = mustJSONString(processedJSON, "Image");
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu"); result->fontName = getJSONString(processedJSON, "FontName");
result->RGBA = getJSONString(processedJSON, "RGBA");
getJSONBool(processedJSON, "MacTemplateImage", &result->templateImage);
result->fontSize = 0;
getJSONInt(processedJSON, "FontSize", &result->fontSize);
result->tooltip = NULL;
result->tooltip = getJSONString(processedJSON, "Tooltip");
result->disabled = false;
getJSONBool(processedJSON, "Disabled", &result->disabled);
// Create the menu // Create the menu
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
result->menu = NewMenu(processedMenu); result->menu = NewMenu(processedMenu);
result->delegate = NULL;
// Init tray status bar item // Init tray status bar item
result->statusbaritem = NULL; result->statusbaritem = NULL;
@@ -50,7 +63,7 @@ void DumpTrayMenu(TrayMenu* trayMenu) {
} }
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) { void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled) {
// Exit early if NULL // Exit early if NULL
if( trayMenu->label == NULL ) { if( trayMenu->label == NULL ) {
@@ -58,7 +71,15 @@ void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) {
} }
// Update button label // Update button label
id statusBarButton = msg(trayMenu->statusbaritem, s("button")); id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
msg(statusBarButton, s("setTitle:"), str(label)); id attributedString = createAttributedString(label, fontName, fontSize, RGBA);
if( tooltip != NULL ) {
msg(statusBarButton, s("setToolTip:"), str(tooltip));
}
msg(statusBarButton, s("setEnabled:"), !disabled);
msg(statusBarButton, s("setAttributedTitle:"), attributedString);
} }
void UpdateTrayIcon(TrayMenu *trayMenu) { void UpdateTrayIcon(TrayMenu *trayMenu) {
@@ -78,12 +99,24 @@ void UpdateTrayIcon(TrayMenu *trayMenu) {
} }
id trayImage = hashmap_get(&trayIconCache, trayMenu->icon, strlen(trayMenu->icon)); id trayImage = hashmap_get(&trayIconCache, trayMenu->icon, strlen(trayMenu->icon));
// If we don't have the image in the icon cache then assume it's base64 encoded image data
if (trayImage == NULL) {
id data = ALLOC("NSData");
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(trayMenu->icon), 0);
trayImage = ALLOC("NSImage");
msg(trayImage, s("initWithData:"), imageData);
if( trayMenu->templateImage ) {
msg(trayImage, s("setTemplate:"), YES);
}
}
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition); msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
msg(statusBarButton, s("setImage:"), trayImage); msg(statusBarButton, s("setImage:"), trayImage);
} }
void ShowTrayMenu(TrayMenu* trayMenu) { void ShowTrayMenu(TrayMenu* trayMenu) {
// Create a status bar item if we don't have one // Create a status bar item if we don't have one
@@ -91,7 +124,6 @@ void ShowTrayMenu(TrayMenu* trayMenu) {
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") ); id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
trayMenu->statusbaritem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength); trayMenu->statusbaritem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
msg(trayMenu->statusbaritem, s("retain")); msg(trayMenu->statusbaritem, s("retain"));
} }
id statusBarButton = msg(trayMenu->statusbaritem, s("button")); id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
@@ -101,10 +133,20 @@ void ShowTrayMenu(TrayMenu* trayMenu) {
UpdateTrayIcon(trayMenu); UpdateTrayIcon(trayMenu);
// Update the label if needed // Update the label if needed
UpdateTrayLabel(trayMenu, trayMenu->label); UpdateTrayLabel(trayMenu, trayMenu->label, trayMenu->fontName, trayMenu->fontSize, trayMenu->RGBA, trayMenu->tooltip, trayMenu->disabled);
// Update the menu // Update the menu
id menu = GetMenu(trayMenu->menu); id menu = GetMenu(trayMenu->menu);
objc_setAssociatedObject(menu, "trayMenuID", str(trayMenu->ID), OBJC_ASSOCIATION_ASSIGN);
// Create delegate
id trayMenuDelegate = msg((id)trayMenuDelegateClass, s("new"));
msg(menu, s("setDelegate:"), trayMenuDelegate);
objc_setAssociatedObject(trayMenuDelegate, "menu", menu, OBJC_ASSOCIATION_ASSIGN);
// Create menu delegate
trayMenu->delegate = trayMenuDelegate;
msg(trayMenu->statusbaritem, s("setMenu:"), menu); msg(trayMenu->statusbaritem, s("setMenu:"), menu);
} }
@@ -153,6 +195,10 @@ void DeleteTrayMenu(TrayMenu* trayMenu) {
trayMenu->statusbaritem = NULL; trayMenu->statusbaritem = NULL;
} }
if ( trayMenu->delegate != NULL ) {
msg(trayMenu->delegate, s("release"));
}
// Free the tray menu memory // Free the tray menu memory
MEMFREE(trayMenu); MEMFREE(trayMenu);
} }

View File

@@ -13,6 +13,14 @@ typedef struct {
const char *label; const char *label;
const char *icon; const char *icon;
const char *ID; const char *ID;
const char *tooltip;
bool templateImage;
const char *fontName;
int fontSize;
const char *RGBA;
bool disabled;
Menu* menu; Menu* menu;
@@ -21,6 +29,8 @@ typedef struct {
JsonNode* processedJSON; JsonNode* processedJSON;
id delegate;
} TrayMenu; } TrayMenu;
TrayMenu* NewTrayMenu(const char *trayJSON); TrayMenu* NewTrayMenu(const char *trayJSON);
@@ -28,7 +38,7 @@ void DumpTrayMenu(TrayMenu* trayMenu);
void ShowTrayMenu(TrayMenu* trayMenu); void ShowTrayMenu(TrayMenu* trayMenu);
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu); void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);
void UpdateTrayIcon(TrayMenu *trayMenu); void UpdateTrayIcon(TrayMenu *trayMenu);
void UpdateTrayLabel(TrayMenu *trayMenu, const char*); void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled);
void LoadTrayIcons(); void LoadTrayIcons();
void UnloadTrayIcons(); void UnloadTrayIcons();

View File

@@ -16,6 +16,11 @@ TrayMenuStore* NewTrayMenuStore() {
ABORT("[NewTrayMenuStore] Not enough memory to allocate trayMenuMap!"); ABORT("[NewTrayMenuStore] Not enough memory to allocate trayMenuMap!");
} }
if (pthread_mutex_init(&result->lock, NULL) != 0) {
printf("\n mutex init has failed\n");
exit(1);
}
return result; return result;
} }
@@ -25,15 +30,19 @@ int dumpTrayMenu(void *const context, struct hashmap_element_s *const e) {
} }
void DumpTrayMenuStore(TrayMenuStore* store) { void DumpTrayMenuStore(TrayMenuStore* store) {
pthread_mutex_lock(&store->lock);
hashmap_iterate_pairs(&store->trayMenuMap, dumpTrayMenu, NULL); hashmap_iterate_pairs(&store->trayMenuMap, dumpTrayMenu, NULL);
pthread_mutex_unlock(&store->lock);
} }
void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON) { void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON) {
TrayMenu* newMenu = NewTrayMenu(menuJSON); TrayMenu* newMenu = NewTrayMenu(menuJSON);
pthread_mutex_lock(&store->lock);
//TODO: check if there is already an entry for this menu //TODO: check if there is already an entry for this menu
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu); hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
pthread_mutex_unlock(&store->lock);
} }
int showTrayMenu(void *const context, struct hashmap_element_s *const e) { int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
@@ -43,12 +52,13 @@ int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
} }
void ShowTrayMenusInStore(TrayMenuStore* store) { void ShowTrayMenusInStore(TrayMenuStore* store) {
pthread_mutex_lock(&store->lock);
if( hashmap_num_entries(&store->trayMenuMap) > 0 ) { if( hashmap_num_entries(&store->trayMenuMap) > 0 ) {
hashmap_iterate_pairs(&store->trayMenuMap, showTrayMenu, NULL); hashmap_iterate_pairs(&store->trayMenuMap, showTrayMenu, NULL);
} }
pthread_mutex_unlock(&store->lock);
} }
int freeTrayMenu(void *const context, struct hashmap_element_s *const e) { int freeTrayMenu(void *const context, struct hashmap_element_s *const e) {
DeleteTrayMenu(e->data); DeleteTrayMenu(e->data);
return -1; return -1;
@@ -65,22 +75,39 @@ void DeleteTrayMenuStore(TrayMenuStore *store) {
// Destroy tray menu map // Destroy tray menu map
hashmap_destroy(&store->trayMenuMap); hashmap_destroy(&store->trayMenuMap);
pthread_mutex_destroy(&store->lock);
} }
TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) { TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
// Get the current menu // Get the current menu
return hashmap_get(&store->trayMenuMap, menuID, strlen(menuID)); pthread_mutex_lock(&store->lock);
TrayMenu* result = hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
pthread_mutex_unlock(&store->lock);
return result;
} }
TrayMenu* MustGetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) { TrayMenu* MustGetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
// Get the current menu // Get the current menu
pthread_mutex_lock(&store->lock);
TrayMenu* result = hashmap_get(&store->trayMenuMap, menuID, strlen(menuID)); TrayMenu* result = hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
pthread_mutex_unlock(&store->lock);
if (result == NULL ) { if (result == NULL ) {
ABORT("Unable to find TrayMenu with ID '%s' in the TrayMenuStore!", menuID); ABORT("Unable to find TrayMenu with ID '%s' in the TrayMenuStore!", menuID);
} }
return result; return result;
} }
void DeleteTrayMenuInStore(TrayMenuStore* store, const char* ID) {
TrayMenu *menu = MustGetTrayMenuFromStore(store, ID);
pthread_mutex_lock(&store->lock);
hashmap_remove(&store->trayMenuMap, ID, strlen(ID));
pthread_mutex_unlock(&store->lock);
DeleteTrayMenu(menu);
}
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) { void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
// Parse the JSON // Parse the JSON
JsonNode *parsedUpdate = mustParseJSON(JSON); JsonNode *parsedUpdate = mustParseJSON(JSON);
@@ -91,7 +118,17 @@ void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
// Check we have this menu // Check we have this menu
TrayMenu *menu = MustGetTrayMenuFromStore(store, ID); TrayMenu *menu = MustGetTrayMenuFromStore(store, ID);
UpdateTrayLabel(menu, Label);
const char *fontName = getJSONString(parsedUpdate, "FontName");
const char *RGBA = getJSONString(parsedUpdate, "RGBA");
int fontSize = 0;
getJSONInt(parsedUpdate, "FontSize", &fontSize);
const char *tooltip = getJSONString(parsedUpdate, "Tooltip");
bool disabled = false;
getJSONBool(parsedUpdate, "Disabled", &disabled);
UpdateTrayLabel(menu, Label, fontName, fontSize, RGBA, tooltip, disabled);
} }
@@ -105,7 +142,9 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
// If we don't have a menu, we create one // If we don't have a menu, we create one
if ( currentMenu == NULL ) { if ( currentMenu == NULL ) {
// Store the new menu // Store the new menu
pthread_mutex_lock(&store->lock);
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu); hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
pthread_mutex_unlock(&store->lock);
// Show it // Show it
ShowTrayMenu(newMenu); ShowTrayMenu(newMenu);
@@ -116,7 +155,9 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
// Save the status bar reference // Save the status bar reference
newMenu->statusbaritem = currentMenu->statusbaritem; newMenu->statusbaritem = currentMenu->statusbaritem;
pthread_mutex_lock(&store->lock);
hashmap_remove(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID)); hashmap_remove(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID));
pthread_mutex_unlock(&store->lock);
// Delete the current menu // Delete the current menu
DeleteMenu(currentMenu->menu); DeleteMenu(currentMenu->menu);
@@ -125,9 +166,10 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
// Free the tray menu memory // Free the tray menu memory
MEMFREE(currentMenu); MEMFREE(currentMenu);
pthread_mutex_lock(&store->lock);
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu); hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
pthread_mutex_unlock(&store->lock);
// Show the updated menu // Show the updated menu
ShowTrayMenu(newMenu); ShowTrayMenu(newMenu);
} }

View File

@@ -5,6 +5,10 @@
#ifndef TRAYMENUSTORE_DARWIN_H #ifndef TRAYMENUSTORE_DARWIN_H
#define TRAYMENUSTORE_DARWIN_H #define TRAYMENUSTORE_DARWIN_H
#include "traymenu_darwin.h"
#include <pthread.h>
typedef struct { typedef struct {
int dummy; int dummy;
@@ -13,6 +17,8 @@ typedef struct {
// It maps tray IDs to TrayMenu* // It maps tray IDs to TrayMenu*
struct hashmap_s trayMenuMap; struct hashmap_s trayMenuMap;
pthread_mutex_t lock;
} TrayMenuStore; } TrayMenuStore;
TrayMenuStore* NewTrayMenuStore(); TrayMenuStore* NewTrayMenuStore();
@@ -22,6 +28,9 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON);
void ShowTrayMenusInStore(TrayMenuStore* store); void ShowTrayMenusInStore(TrayMenuStore* store);
void DeleteTrayMenuStore(TrayMenuStore* store); void DeleteTrayMenuStore(TrayMenuStore* store);
TrayMenu* GetTrayMenuByID(TrayMenuStore* store, const char* menuID);
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON); void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON);
void DeleteTrayMenuInStore(TrayMenuStore* store, const char* id);
#endif //TRAYMENUSTORE_DARWIN_H #endif //TRAYMENUSTORE_DARWIN_H

View File

@@ -9,8 +9,6 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"unsafe" "unsafe"
"github.com/leaanthony/slicer"
) )
// LocalDirectory gets the caller's file directory // LocalDirectory gets the caller's file directory
@@ -20,45 +18,11 @@ func LocalDirectory() string {
return filepath.Dir(thisFile) return filepath.Dir(thisFile)
} }
// RelativeToCwd returns an absolute path based on the cwd
// and the given relative path
func RelativeToCwd(relativePath string) (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", err
}
return filepath.Join(cwd, relativePath), nil
}
// Mkdir will create the given directory // Mkdir will create the given directory
func Mkdir(dirname string) error { func Mkdir(dirname string) error {
return os.Mkdir(dirname, 0755) return os.Mkdir(dirname, 0755)
} }
// MkDirs creates the given nested directories.
// Returns error on failure
func MkDirs(fullPath string, mode ...os.FileMode) error {
var perms os.FileMode
perms = 0700
if len(mode) == 1 {
perms = mode[0]
}
return os.MkdirAll(fullPath, perms)
}
// MoveFile attempts to move the source file to the target
// Target is a fully qualified path to a file *name*, not a
// directory
func MoveFile(source string, target string) error {
return os.Rename(source, target)
}
// DeleteFile will delete the given file
func DeleteFile(filename string) error {
return os.Remove(filename)
}
// CopyFile from source to target // CopyFile from source to target
func CopyFile(source string, target string) error { func CopyFile(source string, target string) error {
s, err := os.Open(source) s, err := os.Open(source)
@@ -180,100 +144,3 @@ func fatal(message ...string) {
} }
os.Exit(1) os.Exit(1)
} }
// GetSubdirectories returns a list of subdirectories for the given root directory
func GetSubdirectories(rootDir string) (*slicer.StringSlicer, error) {
var result slicer.StringSlicer
// Iterate root dir
err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// If we have a directory, save it
if info.IsDir() {
result.Add(path)
}
return nil
})
return &result, err
}
func DirIsEmpty(dir string) (bool, error) {
if !DirExists(dir) {
return false, fmt.Errorf("DirIsEmpty called with a non-existant directory: %s", dir)
}
// CREDIT: https://stackoverflow.com/a/30708914/8325411
f, err := os.Open(dir)
if err != nil {
return false, err
}
defer f.Close()
_, err = f.Readdirnames(1) // Or f.Readdir(1)
if err == io.EOF {
return true, nil
}
return false, err // Either not empty or error, suits both cases
}
// Credit: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04
// CopyDir recursively copies a directory tree, attempting to preserve permissions.
// Source directory must exist, destination directory must *not* exist.
// Symlinks are ignored and skipped.
func CopyDir(src string, dst string) (err error) {
src = filepath.Clean(src)
dst = filepath.Clean(dst)
si, err := os.Stat(src)
if err != nil {
return err
}
if !si.IsDir() {
return fmt.Errorf("source is not a directory")
}
_, err = os.Stat(dst)
if err != nil && !os.IsNotExist(err) {
return
}
if err == nil {
return fmt.Errorf("destination already exists")
}
err = os.MkdirAll(dst, si.Mode())
if err != nil {
return
}
entries, err := ioutil.ReadDir(src)
if err != nil {
return
}
for _, entry := range entries {
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())
if entry.IsDir() {
err = CopyDir(srcPath, dstPath)
if err != nil {
return
}
} else {
// Skip symlinks.
if entry.Mode()&os.ModeSymlink != 0 {
continue
}
err = CopyFile(srcPath, dstPath)
if err != nil {
return
}
}
}
return
}

View File

@@ -3,15 +3,11 @@ package html
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"net/url" "net/url"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
"unsafe" "unsafe"
"github.com/tdewolff/minify"
"github.com/tdewolff/minify/js"
) )
type assetTypes struct { type assetTypes struct {
@@ -67,10 +63,6 @@ func (a *Asset) AsCHexData() string {
result = strings.ReplaceAll(result, "\n", "") result = strings.ReplaceAll(result, "\n", "")
result = strings.ReplaceAll(result, "\r\n", "") result = strings.ReplaceAll(result, "\r\n", "")
result = strings.ReplaceAll(result, "\n", "") result = strings.ReplaceAll(result, "\n", "")
// Inject wailsloader code
result = strings.Replace(result, `</body>`, `<script id='wailsloader'>window.wailsloader = { html: true, runtime: false, userjs: false, usercss: false };var self=document.querySelector('#wailsloader');self.parentNode.removeChild(self);</script></body>`, 1)
url := url.URL{Path: result} url := url.URL{Path: result}
urlString := strings.ReplaceAll(url.String(), "/", "%2f") urlString := strings.ReplaceAll(url.String(), "/", "%2f")
@@ -92,16 +84,6 @@ func (a *Asset) AsCHexData() string {
result = strings.ReplaceAll(result, ` {`, `{`) result = strings.ReplaceAll(result, ` {`, `{`)
result = strings.ReplaceAll(result, `: `, `:`) result = strings.ReplaceAll(result, `: `, `:`)
dataString = fmt.Sprintf("window.wails._.InjectCSS(\"%s\");", result) dataString = fmt.Sprintf("window.wails._.InjectCSS(\"%s\");", result)
case AssetTypes.JS:
m := minify.New()
m.AddFunc("application/javascript", js.Minify)
var err error
dataString, err = m.String("application/javascript", a.Data+";")
if err != nil {
log.Fatal(err)
}
a.Data = dataString
} }
// Get byte data of the string // Get byte data of the string
@@ -121,7 +103,6 @@ func (a *Asset) AsCHexData() string {
return cdata.String() return cdata.String()
} }
// Dump will output the asset to the terminal
func (a *Asset) Dump() { func (a *Asset) Dump() {
fmt.Printf("{ Type: %s, Path: %s, Data: %+v }\n", a.Type, a.Path, a.Data[:10]) fmt.Printf("{ Type: %s, Path: %s, Data: %+v }\n", a.Type, a.Path, a.Data[:10])
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -37,6 +37,7 @@ type ProcessedMenuItem struct {
// Image - base64 image data // Image - base64 image data
Image string `json:",omitempty"` Image string `json:",omitempty"`
MacTemplateImage bool `json:", omitempty"` MacTemplateImage bool `json:", omitempty"`
MacAlternate bool `json:", omitempty"`
// Tooltip // Tooltip
Tooltip string `json:",omitempty"` Tooltip string `json:",omitempty"`
@@ -60,6 +61,7 @@ func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *Pr
FontName: menuItem.FontName, FontName: menuItem.FontName,
Image: menuItem.Image, Image: menuItem.Image,
MacTemplateImage: menuItem.MacTemplateImage, MacTemplateImage: menuItem.MacTemplateImage,
MacAlternate: menuItem.MacAlternate,
Tooltip: menuItem.Tooltip, Tooltip: menuItem.Tooltip,
} }

View File

@@ -3,29 +3,39 @@ package menumanager
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strconv"
"sync"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/wailsapp/wails/v2/pkg/menu" "github.com/wailsapp/wails/v2/pkg/menu"
"sync"
) )
var trayMenuID int var trayMenuID int
var trayMenuIDMutex sync.Mutex var trayMenuIDMutex sync.Mutex
func generateTrayID() string { func generateTrayID() string {
var idStr string
trayMenuIDMutex.Lock() trayMenuIDMutex.Lock()
result := fmt.Sprintf("%d", trayMenuID) idStr = strconv.Itoa(trayMenuID)
trayMenuID++ trayMenuID++
trayMenuIDMutex.Unlock() trayMenuIDMutex.Unlock()
return result return idStr
} }
type TrayMenu struct { type TrayMenu struct {
ID string ID string
Label string Label string
Icon string FontSize int
menuItemMap *MenuItemMap FontName string
menu *menu.Menu Disabled bool
ProcessedMenu *WailsMenu Tooltip string `json:",omitempty"`
Image string
MacTemplateImage bool
RGBA string
menuItemMap *MenuItemMap
menu *menu.Menu
ProcessedMenu *WailsMenu
trayMenu *menu.TrayMenu
} }
func (t *TrayMenu) AsJSON() (string, error) { func (t *TrayMenu) AsJSON() (string, error) {
@@ -39,10 +49,17 @@ func (t *TrayMenu) AsJSON() (string, error) {
func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu { func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
result := &TrayMenu{ result := &TrayMenu{
Label: trayMenu.Label, Label: trayMenu.Label,
Icon: trayMenu.Icon, FontName: trayMenu.FontName,
menu: trayMenu.Menu, FontSize: trayMenu.FontSize,
menuItemMap: NewMenuItemMap(), Disabled: trayMenu.Disabled,
Tooltip: trayMenu.Tooltip,
Image: trayMenu.Image,
MacTemplateImage: trayMenu.MacTemplateImage,
menu: trayMenu.Menu,
RGBA: trayMenu.RGBA,
menuItemMap: NewMenuItemMap(),
trayMenu: trayMenu,
} }
result.menuItemMap.AddMenu(trayMenu.Menu) result.menuItemMap.AddMenu(trayMenu.Menu)
@@ -51,6 +68,28 @@ func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
return result return result
} }
func (m *Manager) OnTrayMenuOpen(id string) {
trayMenu, ok := m.trayMenus[id]
if !ok {
return
}
if trayMenu.trayMenu.OnOpen == nil {
return
}
go trayMenu.trayMenu.OnOpen()
}
func (m *Manager) OnTrayMenuClose(id string) {
trayMenu, ok := m.trayMenus[id]
if !ok {
return
}
if trayMenu.trayMenu.OnClose == nil {
return
}
go trayMenu.trayMenu.OnClose()
}
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) { func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
newTrayMenu := NewTrayMenu(trayMenu) newTrayMenu := NewTrayMenu(trayMenu)
@@ -65,6 +104,14 @@ func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
return newTrayMenu.AsJSON() return newTrayMenu.AsJSON()
} }
func (m *Manager) GetTrayID(trayMenu *menu.TrayMenu) (string, error) {
trayID, exists := m.trayMenuPointers[trayMenu]
if !exists {
return "", fmt.Errorf("Unable to find menu ID for tray menu!")
}
return trayID, nil
}
// SetTrayMenu updates or creates a menu // SetTrayMenu updates or creates a menu
func (m *Manager) SetTrayMenu(trayMenu *menu.TrayMenu) (string, error) { func (m *Manager) SetTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu] trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
@@ -102,13 +149,27 @@ func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) {
} }
type LabelUpdate struct { type LabelUpdate struct {
ID string ID string
Label string Label string
FontName string
FontSize int
RGBA string
Disabled bool
Tooltip string
Image string
MacTemplateImage bool
} }
update := &LabelUpdate{ update := &LabelUpdate{
ID: trayID, ID: trayID,
Label: trayMenu.Label, Label: trayMenu.Label,
FontName: trayMenu.FontName,
FontSize: trayMenu.FontSize,
Disabled: trayMenu.Disabled,
Tooltip: trayMenu.Tooltip,
Image: trayMenu.Image,
MacTemplateImage: trayMenu.MacTemplateImage,
RGBA: trayMenu.RGBA,
} }
data, err := json.Marshal(update) data, err := json.Marshal(update)

View File

@@ -6,37 +6,19 @@ import (
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message" "github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
"github.com/wailsapp/wails/v2/internal/servicebus" "github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
) )
// Client defines what a frontend client can do
type Client interface { type Client interface {
Quit() Quit()
NotifyEvent(message string) NotifyEvent(message string)
CallResult(message string) CallResult(message string)
OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) SaveFileDialog(title string) string
SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) OpenFileDialog(title string) string
MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) OpenDirectoryDialog(title string) string
WindowSetTitle(title string) WindowSetTitle(title string)
WindowShow()
WindowHide()
WindowCenter()
WindowMaximise()
WindowUnmaximise()
WindowMinimise()
WindowUnminimise()
WindowPosition(x int, y int)
WindowSize(width int, height int)
WindowSetMinSize(width int, height int)
WindowSetMaxSize(width int, height int)
WindowFullscreen() WindowFullscreen()
WindowUnFullscreen() WindowUnFullscreen()
WindowSetColour(colour int) WindowSetColour(colour string) bool
DarkModeEnabled(callbackID string)
SetApplicationMenu(menuJSON string)
SetTrayMenu(trayMenuJSON string)
UpdateTrayMenuLabel(JSON string)
UpdateContextMenu(contextMenuJSON string)
} }
// DispatchClient is what the frontends use to interface with the // DispatchClient is what the frontends use to interface with the
@@ -71,7 +53,7 @@ func (d *DispatchClient) DispatchMessage(incomingMessage string) {
d.logger.Trace(fmt.Sprintf("Received message: %+v", incomingMessage)) d.logger.Trace(fmt.Sprintf("Received message: %+v", incomingMessage))
parsedMessage, err := message.Parse(incomingMessage) parsedMessage, err := message.Parse(incomingMessage)
if err != nil { if err != nil {
d.logger.Error(err.Error()) d.logger.Trace("Error: " + err.Error())
return return
} }
@@ -80,6 +62,14 @@ func (d *DispatchClient) DispatchMessage(incomingMessage string) {
d.logger.Trace("I got a parsedMessage: %+v", parsedMessage) d.logger.Trace("I got a parsedMessage: %+v", parsedMessage)
// Check error
if err != nil {
d.logger.Trace("Error: " + err.Error())
// Hrm... what do we do with this?
d.bus.PublishForTarget("generic:message", incomingMessage, d.id)
return
}
// Publish the parsed message // Publish the parsed message
d.bus.PublishForTarget(parsedMessage.Topic, parsedMessage.Data, d.id) d.bus.PublishForTarget(parsedMessage.Topic, parsedMessage.Data, d.id)

View File

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

View File

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

View File

@@ -3,14 +3,11 @@ package message
import "fmt" import "fmt"
var logMessageMap = map[byte]string{ var logMessageMap = map[byte]string{
'P': "log:print",
'T': "log:trace",
'D': "log:debug", 'D': "log:debug",
'I': "log:info", 'I': "log:info",
'W': "log:warning", 'W': "log:warning",
'E': "log:error", 'E': "log:error",
'F': "log:fatal", 'F': "log:fatal",
'S': "log:setlevel",
} }
// logMessageParser does what it says on the tin! // logMessageParser does what it says on the tin!
@@ -26,7 +23,7 @@ func logMessageParser(message string) (*parsedMessage, error) {
// If the type is invalid, raise error // If the type is invalid, raise error
if messageTopic == "" { if messageTopic == "" {
return nil, fmt.Errorf("log message type '%c' invalid", message[1]) return nil, fmt.Errorf("log message type '%b' invalid", message[1])
} }
// Create a new parsed message struct // Create a new parsed message struct

View File

@@ -32,6 +32,14 @@ func menuMessageParser(message string) (*parsedMessage, error) {
callbackid := message[2:] callbackid := message[2:]
topic = "menu:clicked" topic = "menu:clicked"
data = callbackid data = callbackid
case 'o':
callbackid := message[2:]
topic = "menu:ontrayopen"
data = callbackid
case 'c':
callbackid := message[2:]
topic = "menu:ontrayclose"
data = callbackid
default: default:
return nil, fmt.Errorf("invalid menu message: %s", message) return nil, fmt.Errorf("invalid menu message: %s", message)
} }

View File

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

View File

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

View File

@@ -40,7 +40,8 @@ func systemMessageParser(message string) (*parsedMessage, error) {
// This is our startup hook - the frontend is now ready // This is our startup hook - the frontend is now ready
case 'S': case 'S':
topic := "hooks:startup" topic := "hooks:startup"
responseMessage = &parsedMessage{Topic: topic, Data: nil} startupURL := message[1:]
responseMessage = &parsedMessage{Topic: topic, Data: startupURL}
default: default:
return nil, fmt.Errorf("Invalid message to systemMessageParser()") return nil, fmt.Errorf("Invalid message to systemMessageParser()")
} }

View File

@@ -0,0 +1,20 @@
package message
import "fmt"
// urlMessageParser does what it says on the tin!
func urlMessageParser(message string) (*parsedMessage, error) {
// Sanity check: URL messages must be at least 2 bytes
if len(message) < 2 {
return nil, fmt.Errorf("log message was an invalid length")
}
// Switch on the log type
switch message[1] {
case 'C':
return &parsedMessage{Topic: "url:handler", Data: message[2:]}, nil
default:
return nil, fmt.Errorf("url message type '%c' invalid", message[1])
}
}

View File

@@ -22,66 +22,6 @@ func windowMessageParser(message string) (*parsedMessage, error) {
parsedMessage.Topic = "quit" parsedMessage.Topic = "quit"
parsedMessage.Data = "Window Closed" parsedMessage.Data = "Window Closed"
// Center window
case 'c':
parsedMessage.Topic = "window:center"
parsedMessage.Data = ""
// Hide window
case 'H':
parsedMessage.Topic = "window:hide"
parsedMessage.Data = ""
// Show window
case 'S':
parsedMessage.Topic = "window:show"
parsedMessage.Data = ""
// Position window
case 'p':
parsedMessage.Topic = "window:position:" + message[3:]
parsedMessage.Data = ""
// Set window size
case 's':
parsedMessage.Topic = "window:size:" + message[3:]
parsedMessage.Data = ""
// Maximise window
case 'M':
parsedMessage.Topic = "window:maximise"
parsedMessage.Data = ""
// Unmaximise window
case 'U':
parsedMessage.Topic = "window:unmaximise"
parsedMessage.Data = ""
// Minimise window
case 'm':
parsedMessage.Topic = "window:minimise"
parsedMessage.Data = ""
// Unminimise window
case 'u':
parsedMessage.Topic = "window:unminimise"
parsedMessage.Data = ""
// Fullscreen window
case 'F':
parsedMessage.Topic = "window:fullscreen"
parsedMessage.Data = ""
// UnFullscreen window
case 'f':
parsedMessage.Topic = "window:unfullscreen"
parsedMessage.Data = ""
// Set Title
case 'T':
parsedMessage.Topic = "window:settitle"
parsedMessage.Data = message[2:]
// Unknown event type // Unknown event type
default: default:
return nil, fmt.Errorf("unknown message: %s", message) return nil, fmt.Errorf("unknown message: %s", message)

View File

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

View File

@@ -10,6 +10,8 @@ import (
"golang.org/x/tools/go/packages" "golang.org/x/tools/go/packages"
) )
var internalMethods = slicer.String([]string{"WailsInit", "Wails Shutdown"})
var structCache = make(map[string]*ParsedStruct) var structCache = make(map[string]*ParsedStruct)
var boundStructs = make(map[string]*ParsedStruct) var boundStructs = make(map[string]*ParsedStruct)
var boundMethods = []string{} var boundMethods = []string{}
@@ -47,7 +49,7 @@ func ParseProject(projectPath string) (BoundStructs, error) {
cfg := &packages.Config{Mode: packages.NeedFiles | packages.NeedSyntax | packages.NeedTypesInfo} cfg := &packages.Config{Mode: packages.NeedFiles | packages.NeedSyntax | packages.NeedTypesInfo}
pkgs, err := packages.Load(cfg, projectPath) pkgs, err := packages.Load(cfg, projectPath)
if err != nil { if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "load: %v\n", err) fmt.Fprintf(os.Stderr, "load: %v\n", err)
os.Exit(1) os.Exit(1)
} }
if packages.PrintErrors(pkgs) > 0 { if packages.PrintErrors(pkgs) > 0 {
@@ -63,6 +65,7 @@ func ParseProject(projectPath string) (BoundStructs, error) {
var wailsPkgVar = "" var wailsPkgVar = ""
ast.Inspect(file, func(n ast.Node) bool { ast.Inspect(file, func(n ast.Node) bool {
var s string
switch x := n.(type) { switch x := n.(type) {
// Parse import declarations // Parse import declarations
case *ast.ImportSpec: case *ast.ImportSpec:
@@ -201,6 +204,10 @@ func ParseProject(projectPath string) (BoundStructs, error) {
// This is a struct pointer method // This is a struct pointer method
i, ok := se.X.(*ast.Ident) i, ok := se.X.(*ast.Ident)
if ok { if ok {
// We want to ignore Internal functions
if internalMethods.Contains(x.Name.Name) {
continue
}
// If we haven't already found this struct, // If we haven't already found this struct,
// Create a placeholder in the cache // Create a placeholder in the cache
parsedStruct := structCache[i.Name] parsedStruct := structCache[i.Name]
@@ -431,6 +438,4 @@ func ParseProject(projectPath string) (BoundStructs, error) {
println() println()
println("}") println("}")
println() println()
return nil, nil
} }

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
package goruntime
import (
"os"
"testing"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/servicebus"
)
func TestBrowserOpen(t *testing.T) {
mylogger := logger.New(os.Stdout)
myServiceBus := servicebus.New(mylogger)
myRuntime := New(myServiceBus)
myRuntime.Browser.Open("http://www.google.com")
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -65,7 +65,7 @@ export function SetBindings(bindingsMap) {
window.backend[packageName][structName][methodName] = function () { window.backend[packageName][structName][methodName] = function () {
// No timeout by default // No timeout by default
let timeout = 0; var timeout = 0;
// Actual function // Actual function
function dynamic() { function dynamic() {
@@ -89,3 +89,19 @@ export function SetBindings(bindingsMap) {
}); });
}); });
} }
// /**
// * Determines if the given identifier is valid Javascript
// *
// * @param {boolean} name
// * @returns
// */
// function isValidIdentifier(name) {
// // Don't xss yourself :-)
// try {
// new Function('var ' + name);
// return true;
// } catch (e) {
// return false;
// }
// }

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,26 +25,6 @@ function sendLogMessage(level, message) {
SendMessage('L' + level + message); SendMessage('L' + level + message);
} }
/**
* Log the given trace message with the backend
*
* @export
* @param {string} message
*/
export function Trace(message) {
sendLogMessage('T', message);
}
/**
* Log the given message with the backend
*
* @export
* @param {string} message
*/
export function Print(message) {
sendLogMessage('P', message);
}
/** /**
* Log the given debug message with the backend * Log the given debug message with the backend
* *
@@ -94,22 +74,3 @@ export function Error(message) {
export function Fatal(message) { export function Fatal(message) {
sendLogMessage('F', message); sendLogMessage('F', message);
} }
/**
* Sets the Log level to the given log level
*
* @export
* @param {number} loglevel
*/
export function SetLogLevel(loglevel) {
sendLogMessage('S', loglevel);
}
// Log levels
export const Level = {
TRACE: 1,
DEBUG: 2,
INFO: 3,
WARNING: 4,
ERROR: 5,
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -620,7 +620,7 @@
} }
/** Menubar **/ /** Menubar **/
const menuVisible = writable(true); const menuVisible = writable(false);
/** Trays **/ /** Trays **/
@@ -649,6 +649,18 @@
}); });
} }
function deleteTrayMenu(id) {
trays.update((current) => {
// Remove existing if it exists, else add
const index = current.findIndex(item => item.ID === id);
if ( index === -1 ) {
return log("ERROR: Attempted to delete tray index ")
}
current.splice(index, 1);
return current;
});
}
let selectedMenu = writable(null); let selectedMenu = writable(null);
function fade(node, { delay = 0, duration = 400, easing = identity } = {}) { function fade(node, { delay = 0, duration = 400, easing = identity } = {}) {
@@ -1220,11 +1232,11 @@
function get_each_context$1(ctx, list, i) { function get_each_context$1(ctx, list, i) {
const child_ctx = ctx.slice(); const child_ctx = ctx.slice();
child_ctx[8] = list[i]; child_ctx[9] = list[i];
return child_ctx; return child_ctx;
} }
// (29:0) {#if $menuVisible } // (38:0) {#if $menuVisible }
function create_if_block$3(ctx) { function create_if_block$3(ctx) {
let div; let div;
let span1; let span1;
@@ -1336,11 +1348,11 @@
}; };
} }
// (32:4) {#each $trays as tray} // (41:4) {#each $trays as tray}
function create_each_block$1(ctx) { function create_each_block$1(ctx) {
let traymenu; let traymenu;
let current; let current;
traymenu = new TrayMenu({ props: { tray: /*tray*/ ctx[8] } }); traymenu = new TrayMenu({ props: { tray: /*tray*/ ctx[9] } });
return { return {
c() { c() {
@@ -1352,7 +1364,7 @@
}, },
p(ctx, dirty) { p(ctx, dirty) {
const traymenu_changes = {}; const traymenu_changes = {};
if (dirty & /*$trays*/ 4) traymenu_changes.tray = /*tray*/ ctx[8]; if (dirty & /*$trays*/ 4) traymenu_changes.tray = /*tray*/ ctx[9];
traymenu.$set(traymenu_changes); traymenu.$set(traymenu_changes);
}, },
i(local) { i(local) {
@@ -1373,6 +1385,8 @@
function create_fragment$3(ctx) { function create_fragment$3(ctx) {
let if_block_anchor; let if_block_anchor;
let current; let current;
let mounted;
let dispose;
let if_block = /*$menuVisible*/ ctx[1] && create_if_block$3(ctx); let if_block = /*$menuVisible*/ ctx[1] && create_if_block$3(ctx);
return { return {
@@ -1384,6 +1398,11 @@
if (if_block) if_block.m(target, anchor); if (if_block) if_block.m(target, anchor);
insert(target, if_block_anchor, anchor); insert(target, if_block_anchor, anchor);
current = true; current = true;
if (!mounted) {
dispose = listen(window, "keydown", /*handleKeydown*/ ctx[3]);
mounted = true;
}
}, },
p(ctx, [dirty]) { p(ctx, [dirty]) {
if (/*$menuVisible*/ ctx[1]) { if (/*$menuVisible*/ ctx[1]) {
@@ -1421,6 +1440,8 @@
d(detaching) { d(detaching) {
if (if_block) if_block.d(detaching); if (if_block) if_block.d(detaching);
if (detaching) detach(if_block_anchor); if (detaching) detach(if_block_anchor);
mounted = false;
dispose();
} }
}; };
} }
@@ -1440,7 +1461,7 @@
onMount(() => { onMount(() => {
const interval = setInterval( const interval = setInterval(
() => { () => {
$$invalidate(3, time = new Date()); $$invalidate(4, time = new Date());
}, },
1000 1000
); );
@@ -1450,33 +1471,52 @@
}; };
}); });
function handleKeydown(e) {
// Backtick toggle
if (e.keyCode == 192) {
menuVisible.update(current => {
return !current;
});
}
}
$$self.$$.update = () => { $$self.$$.update = () => {
if ($$self.$$.dirty & /*time*/ 8) { if ($$self.$$.dirty & /*time*/ 16) {
$$invalidate(4, day = time.toLocaleString("default", { weekday: "short" })); $$invalidate(5, day = time.toLocaleString("default", { weekday: "short" }));
} }
if ($$self.$$.dirty & /*time*/ 8) { if ($$self.$$.dirty & /*time*/ 16) {
$$invalidate(5, dom = time.getDate()); $$invalidate(6, dom = time.getDate());
} }
if ($$self.$$.dirty & /*time*/ 8) { if ($$self.$$.dirty & /*time*/ 16) {
$$invalidate(6, mon = time.toLocaleString("default", { month: "short" })); $$invalidate(7, mon = time.toLocaleString("default", { month: "short" }));
} }
if ($$self.$$.dirty & /*time*/ 8) { if ($$self.$$.dirty & /*time*/ 16) {
$$invalidate(7, currentTime = time.toLocaleString("en-US", { $$invalidate(8, currentTime = time.toLocaleString("en-US", {
hour: "numeric", hour: "numeric",
minute: "numeric", minute: "numeric",
hour12: true hour12: true
}).toLowerCase()); }).toLowerCase());
} }
if ($$self.$$.dirty & /*day, dom, mon, currentTime*/ 240) { if ($$self.$$.dirty & /*day, dom, mon, currentTime*/ 480) {
$$invalidate(0, dateTimeString = `${day} ${dom} ${mon} ${currentTime}`); $$invalidate(0, dateTimeString = `${day} ${dom} ${mon} ${currentTime}`);
} }
}; };
return [dateTimeString, $menuVisible, $trays, time, day, dom, mon, currentTime]; return [
dateTimeString,
$menuVisible,
$trays,
handleKeydown,
time,
day,
dom,
mon,
currentTime
];
} }
class Menubar extends SvelteComponent { class Menubar extends SvelteComponent {
@@ -1638,6 +1678,11 @@
let trayLabelData = JSON.parse(updateTrayLabelJSON); let trayLabelData = JSON.parse(updateTrayLabelJSON);
updateTrayLabel(trayLabelData); updateTrayLabel(trayLabelData);
break break
case 'D':
// Delete Tray Menu
const id = trayMessage.slice(1);
deleteTrayMenu(id);
break
default: default:
log('Unknown tray message: ' + message.data); log('Unknown tray message: ' + message.data);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,6 +24,15 @@
}; };
}); });
function handleKeydown(e) {
// Backtick toggle
if( e.keyCode == 192 ) {
menuVisible.update( (current) => {
return !current;
});
}
}
</script> </script>
{#if $menuVisible } {#if $menuVisible }
@@ -37,6 +46,8 @@
</div> </div>
{/if} {/if}
<svelte:window on:keydown={handleKeydown}/>
<style> <style>
.tray-menus { .tray-menus {

View File

@@ -13,7 +13,7 @@ export function hideOverlay() {
} }
/** Menubar **/ /** Menubar **/
export const menuVisible = writable(true); export const menuVisible = writable(false);
export function showMenuBar() { export function showMenuBar() {
menuVisible.set(true); menuVisible.set(true);
@@ -49,4 +49,16 @@ export function updateTrayLabel(tray) {
}) })
} }
export function deleteTrayMenu(id) {
trays.update((current) => {
// Remove existing if it exists, else add
const index = current.findIndex(item => item.ID === id);
if ( index === -1 ) {
return log("ERROR: Attempted to delete tray index ", id, "but it doesn't exist")
}
current.splice(index, 1);
return current;
})
}
export let selectedMenu = writable(null); export let selectedMenu = writable(null);

View File

@@ -10,7 +10,7 @@ The lightweight framework for web-like apps
/* jshint esversion: 6 */ /* jshint esversion: 6 */
import {setTray, hideOverlay, showOverlay, updateTrayLabel} from "./store"; import {setTray, hideOverlay, showOverlay, updateTrayLabel, deleteTrayMenu} from "./store";
import {log} from "./log"; import {log} from "./log";
let websocket = null; let websocket = null;
@@ -154,6 +154,11 @@ function handleMessage(message) {
let trayLabelData = JSON.parse(updateTrayLabelJSON) let trayLabelData = JSON.parse(updateTrayLabelJSON)
updateTrayLabel(trayLabelData) updateTrayLabel(trayLabelData)
break break
case 'D':
// Delete Tray Menu
const id = trayMessage.slice(1);
deleteTrayMenu(id)
break
default: default:
log('Unknown tray message: ' + message.data); log('Unknown tray message: ' + message.data);
} }

View File

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

View File

@@ -10,6 +10,7 @@ type Menu interface {
UpdateApplicationMenu() UpdateApplicationMenu()
UpdateContextMenu(contextMenu *menu.ContextMenu) UpdateContextMenu(contextMenu *menu.ContextMenu)
SetTrayMenu(trayMenu *menu.TrayMenu) SetTrayMenu(trayMenu *menu.TrayMenu)
DeleteTrayMenu(trayMenu *menu.TrayMenu)
UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu)
} }
@@ -39,3 +40,7 @@ func (m *menuRuntime) SetTrayMenu(trayMenu *menu.TrayMenu) {
func (m *menuRuntime) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) { func (m *menuRuntime) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) {
m.bus.Publish("menu:updatetraymenulabel", trayMenu) m.bus.Publish("menu:updatetraymenulabel", trayMenu)
} }
func (m *menuRuntime) DeleteTrayMenu(trayMenu *menu.TrayMenu) {
m.bus.Publish("menu:deletetraymenu", trayMenu)
}

View File

@@ -6,20 +6,19 @@ import (
// Runtime is a means for the user to interact with the application at runtime // Runtime is a means for the user to interact with the application at runtime
type Runtime struct { type Runtime struct {
Browser Browser Browser Browser
Events Events Events Events
Window Window Window Window
Dialog Dialog Dialog Dialog
System System System System
Menu Menu Menu Menu
Store *StoreProvider Store *StoreProvider
Log Log Log Log
bus *servicebus.ServiceBus bus *servicebus.ServiceBus
shutdownCallback func()
} }
// New creates a new runtime // New creates a new runtime
func New(serviceBus *servicebus.ServiceBus, shutdownCallback func()) *Runtime { func New(serviceBus *servicebus.ServiceBus) *Runtime {
result := &Runtime{ result := &Runtime{
Browser: newBrowser(), Browser: newBrowser(),
Events: newEvents(serviceBus), Events: newEvents(serviceBus),
@@ -36,11 +35,6 @@ func New(serviceBus *servicebus.ServiceBus, shutdownCallback func()) *Runtime {
// Quit the application // Quit the application
func (r *Runtime) Quit() { func (r *Runtime) Quit() {
// Call back to user's shutdown method if defined
if r.shutdownCallback != nil {
r.shutdownCallback()
}
// Start shutdown of Wails // Start shutdown of Wails
r.bus.Publish("quit", "runtime.Quit()") r.bus.Publish("quit", "runtime.Quit()")
} }

View File

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

View File

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

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