mirror of
https://github.com/taigrr/wails.git
synced 2026-04-04 06:02:43 -07:00
Compare commits
107 Commits
v2.0.0-alp
...
feature/v2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbd98b5a1a | ||
|
|
c8e0aea69c | ||
|
|
7c0b236eb0 | ||
|
|
16debbd109 | ||
|
|
39bfa5d910 | ||
|
|
6424579a9e | ||
|
|
a962ae6f63 | ||
|
|
c7dee158ba | ||
|
|
237d25089d | ||
|
|
265328d648 | ||
|
|
6f40e85a6e | ||
|
|
96d8509da3 | ||
|
|
598445ab0f | ||
|
|
24788e2fd3 | ||
|
|
bbf4dde43f | ||
|
|
0afd27ab45 | ||
|
|
d498423ec2 | ||
|
|
66ce84973c | ||
|
|
55e6a0f312 | ||
|
|
81e83fdf18 | ||
|
|
f9b79d24f8 | ||
|
|
0599a47bfe | ||
|
|
817c55d318 | ||
|
|
14146c8c0c | ||
|
|
18adac20d4 | ||
|
|
eb4bff89da | ||
|
|
c66dc777f3 | ||
|
|
9003462457 | ||
|
|
e124f0a220 | ||
|
|
c6d3f57712 | ||
|
|
b4c669ff86 | ||
|
|
2d1b2c0947 | ||
|
|
4a0c5aa785 | ||
|
|
f48d7f8f60 | ||
|
|
651f24f641 | ||
|
|
8fd77148ca | ||
|
|
0dc0762fdf | ||
|
|
1a92550709 | ||
|
|
bffc15bc14 | ||
|
|
198d206c46 | ||
|
|
bb8e848ef6 | ||
|
|
bac3e9e5c1 | ||
|
|
bc5eddeb66 | ||
|
|
8e7258d812 | ||
|
|
7118762cec | ||
|
|
6af92cf0a4 | ||
|
|
1bb91634f7 | ||
|
|
f71ce7913f | ||
|
|
53db687a26 | ||
|
|
13939d3d6b | ||
|
|
552c6b8711 | ||
|
|
feee2b3db2 | ||
|
|
9889c2bdbb | ||
|
|
2432fccf71 | ||
|
|
70510fd180 | ||
|
|
17c6201469 | ||
|
|
0f209c8900 | ||
|
|
cbf043585c | ||
|
|
5ae621ceaa | ||
|
|
1231b59443 | ||
|
|
b18d4fbf41 | ||
|
|
9ec5605e63 | ||
|
|
98a4de8878 | ||
|
|
5fe709f558 | ||
|
|
5231a6893b | ||
|
|
74f3ce990f | ||
|
|
998a913853 | ||
|
|
964844835c | ||
|
|
4e152bb849 | ||
|
|
51133d098c | ||
|
|
d4191e7d1b | ||
|
|
9c273bc745 | ||
|
|
c6f6ad6beb | ||
|
|
4362a14459 | ||
|
|
0080e9e311 | ||
|
|
83d9297cac | ||
|
|
d8ad250608 | ||
|
|
eac8880f6d | ||
|
|
f47100e71c | ||
|
|
9a81a57d13 | ||
|
|
354429bc28 | ||
|
|
99d4d9490c | ||
|
|
61afe711bd | ||
|
|
6e3cfc157f | ||
|
|
30d762372f | ||
|
|
2dbaabb74c | ||
|
|
f8bae0430f | ||
|
|
21c07497d7 | ||
|
|
9b9bcd657f | ||
|
|
02038aa543 | ||
|
|
9910d1127a | ||
|
|
21a0245985 | ||
|
|
e860f3a00e | ||
|
|
2e480d2c52 | ||
|
|
2c0f93d647 | ||
|
|
41f973c7d5 | ||
|
|
2d1447d558 | ||
|
|
7c22cbcf38 | ||
|
|
e4b03f510b | ||
|
|
8dfd206aa9 | ||
|
|
6dabab1d2e | ||
|
|
c3bd8b1a85 | ||
|
|
e1b7332c47 | ||
|
|
5cd08e45f0 | ||
|
|
c2d03f0e6e | ||
|
|
d3501f4cb7 | ||
|
|
ee82cd25b7 |
@@ -3,12 +3,12 @@
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"amd": true,
|
||||
"node": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2016,
|
||||
"sourceType": "module",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"indent": [
|
||||
|
||||
@@ -1 +1 @@
|
||||
bridge.js
|
||||
index.js
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "**** Checking if Wails passes unit tests ****"
|
||||
if ! go test ./...
|
||||
if ! go test ./lib/... ./runtime/... ./cmd/...
|
||||
then
|
||||
echo ""
|
||||
echo "ERROR: Unit tests failed!"
|
||||
|
||||
@@ -57,6 +57,11 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
||||
keepAssets := false
|
||||
command.BoolFlag("k", "Keep generated assets", &keepAssets)
|
||||
|
||||
appleIdentity := ""
|
||||
if runtime.GOOS == "darwin" {
|
||||
command.StringFlag("sign", "Signs your app with the given identity.", &appleIdentity)
|
||||
}
|
||||
|
||||
command.Action(func() error {
|
||||
|
||||
// Create logger
|
||||
@@ -72,6 +77,11 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
||||
app.PrintBanner()
|
||||
}
|
||||
|
||||
// Ensure package is used with apple identity
|
||||
if appleIdentity != "" && pack == false {
|
||||
return fmt.Errorf("must use `-package` flag when using `-sign`")
|
||||
}
|
||||
|
||||
task := fmt.Sprintf("Building %s Application", strings.Title(outputType))
|
||||
logger.Println(task)
|
||||
logger.Println(strings.Repeat("-", len(task)))
|
||||
@@ -84,14 +94,15 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
||||
|
||||
// Create BuildOptions
|
||||
buildOptions := &build.Options{
|
||||
Logger: logger,
|
||||
OutputType: outputType,
|
||||
Mode: mode,
|
||||
Pack: pack,
|
||||
Platform: platform,
|
||||
LDFlags: ldflags,
|
||||
Compiler: compilerCommand,
|
||||
KeepAssets: keepAssets,
|
||||
Logger: logger,
|
||||
OutputType: outputType,
|
||||
Mode: mode,
|
||||
Pack: pack,
|
||||
Platform: platform,
|
||||
LDFlags: ldflags,
|
||||
Compiler: compilerCommand,
|
||||
KeepAssets: keepAssets,
|
||||
AppleIdentity: appleIdentity,
|
||||
}
|
||||
|
||||
return doBuild(buildOptions)
|
||||
|
||||
@@ -5,33 +5,42 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/colour"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/leaanthony/clir"
|
||||
"github.com/leaanthony/slicer"
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
"github.com/wailsapp/wails/v2/internal/process"
|
||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
||||
"github.com/wailsapp/wails/v2/pkg/commands/build"
|
||||
)
|
||||
|
||||
func LogGreen(message string, args ...interface{}) {
|
||||
text := fmt.Sprintf(message, args...)
|
||||
println(colour.Green(text))
|
||||
}
|
||||
|
||||
func LogRed(message string, args ...interface{}) {
|
||||
text := fmt.Sprintf(message, args...)
|
||||
println(colour.Red(text))
|
||||
}
|
||||
|
||||
func LogDarkYellow(message string, args ...interface{}) {
|
||||
text := fmt.Sprintf(message, args...)
|
||||
println(colour.DarkYellow(text))
|
||||
}
|
||||
|
||||
// AddSubcommand adds the `dev` command for the Wails application
|
||||
func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
|
||||
command := app.NewSubCommand("dev", "Development mode")
|
||||
|
||||
outputType := "desktop"
|
||||
|
||||
validTargetTypes := slicer.String([]string{"desktop", "hybrid", "server"})
|
||||
|
||||
// Setup target type flag
|
||||
description := "Type of application to develop. Valid types: " + validTargetTypes.Join(",")
|
||||
command.StringFlag("t", description, &outputType)
|
||||
|
||||
// Passthrough ldflags
|
||||
ldflags := ""
|
||||
command.StringFlag("ldflags", "optional ldflags", &ldflags)
|
||||
@@ -42,15 +51,17 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
|
||||
// extensions to trigger rebuilds
|
||||
extensions := "go"
|
||||
command.StringFlag("m", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions)
|
||||
command.StringFlag("e", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions)
|
||||
|
||||
// extensions to trigger rebuilds
|
||||
showWarnings := false
|
||||
command.BoolFlag("w", "Show warnings", &showWarnings)
|
||||
|
||||
loglevel := ""
|
||||
command.StringFlag("loglevel", "Loglevel to use - Trace, Debug, Info, Warning, Error", &loglevel)
|
||||
|
||||
command.Action(func() error {
|
||||
|
||||
// Validate inputs
|
||||
if !validTargetTypes.Contains(outputType) {
|
||||
return fmt.Errorf("output type '%s' is not valid", outputType)
|
||||
}
|
||||
|
||||
// Create logger
|
||||
logger := clilogger.New(w)
|
||||
app.PrintBanner()
|
||||
@@ -64,7 +75,6 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
defer watcher.Close()
|
||||
|
||||
var debugBinaryProcess *process.Process = nil
|
||||
var buildFrontend bool = true
|
||||
var extensionsThatTriggerARebuild = strings.Split(extensions, ",")
|
||||
|
||||
// Setup signal handler
|
||||
@@ -75,8 +85,13 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
|
||||
// Do initial build
|
||||
logger.Println("Building application for development...")
|
||||
debugBinaryProcess = restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess)
|
||||
|
||||
newProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess, loglevel)
|
||||
if newProcess != nil {
|
||||
debugBinaryProcess = newProcess
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go debounce(100*time.Millisecond, watcher.Events, debounceQuit, func(event fsnotify.Event) {
|
||||
// logger.Println("event: %+v", event)
|
||||
|
||||
@@ -89,7 +104,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
if err != nil {
|
||||
logger.Fatal("%s", err.Error())
|
||||
}
|
||||
logger.Println("Watching directory: %s", event.Name)
|
||||
LogGreen("[New Directory] Watching new directory: %s", event.Name)
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -98,38 +113,33 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
// Check for file writes
|
||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||
|
||||
// logger.Println("modified file: %s", event.Name)
|
||||
var rebuild bool = false
|
||||
|
||||
// Iterate all file patterns
|
||||
for _, pattern := range extensionsThatTriggerARebuild {
|
||||
rebuild = strings.HasSuffix(event.Name, pattern)
|
||||
if err != nil {
|
||||
logger.Fatal(err.Error())
|
||||
}
|
||||
if rebuild {
|
||||
// Only build frontend when the file isn't a Go file
|
||||
buildFrontend = !strings.HasSuffix(event.Name, "go")
|
||||
if strings.HasSuffix(event.Name, pattern) {
|
||||
rebuild = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !rebuild {
|
||||
logger.Println("Filename change: %s did not match extension list %s", event.Name, extensions)
|
||||
if showWarnings {
|
||||
LogDarkYellow("[File change] %s did not match extension list (%s)", event.Name, extensions)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if buildFrontend {
|
||||
logger.Println("Full rebuild triggered: %s updated", event.Name)
|
||||
} else {
|
||||
logger.Println("Partial build triggered: %s updated", event.Name)
|
||||
}
|
||||
LogGreen("[Attempting rebuild] %s updated", event.Name)
|
||||
|
||||
// Do a rebuild
|
||||
|
||||
// Try and build the app
|
||||
newBinaryProcess := restartApp(logger, outputType, ldflags, compilerCommand, buildFrontend, debugBinaryProcess)
|
||||
|
||||
newBinaryProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess, loglevel)
|
||||
if err != nil {
|
||||
fmt.Printf("Error during build: %s", err.Error())
|
||||
return
|
||||
}
|
||||
// If we have a new process, save it
|
||||
if newBinaryProcess != nil {
|
||||
debugBinaryProcess = newBinaryProcess
|
||||
@@ -139,23 +149,28 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
})
|
||||
|
||||
// Get project dir
|
||||
dir, err := os.Getwd()
|
||||
projectDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get all subdirectories
|
||||
dirs, err := fs.GetSubdirectories(dir)
|
||||
dirs, err := fs.GetSubdirectories(projectDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
LogGreen("Watching (sub)/directory: %s", projectDir)
|
||||
|
||||
// Setup a watcher for non-node_modules directories
|
||||
dirs.Each(func(dir string) {
|
||||
if strings.Contains(dir, "node_modules") {
|
||||
return
|
||||
}
|
||||
logger.Println("Watching directory: %s", dir)
|
||||
// Ignore build directory
|
||||
if strings.HasPrefix(dir, filepath.Join(projectDir, "build")) {
|
||||
return
|
||||
}
|
||||
err = watcher.Add(dir)
|
||||
if err != nil {
|
||||
logger.Fatal(err.Error())
|
||||
@@ -167,7 +182,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
for quit == false {
|
||||
select {
|
||||
case <-quitChannel:
|
||||
println()
|
||||
LogGreen("\nCaught quit")
|
||||
// Notify debouncer to quit
|
||||
debounceQuit <- true
|
||||
quit = true
|
||||
@@ -182,7 +197,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
}
|
||||
}
|
||||
|
||||
logger.Println("Development mode exited")
|
||||
LogGreen("Development mode exited")
|
||||
|
||||
return nil
|
||||
})
|
||||
@@ -209,15 +224,15 @@ exit:
|
||||
}
|
||||
}
|
||||
|
||||
func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, buildFrontend bool, debugBinaryProcess *process.Process) *process.Process {
|
||||
func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, debugBinaryProcess *process.Process, loglevel string) (*process.Process, error) {
|
||||
|
||||
appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand, buildFrontend)
|
||||
appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand)
|
||||
println()
|
||||
if err != nil {
|
||||
logger.Println("[ERROR] Build Failed: %s", err.Error())
|
||||
return nil
|
||||
LogRed("Build error - continuing to run current version")
|
||||
LogDarkYellow(err.Error())
|
||||
return nil, nil
|
||||
}
|
||||
logger.Println("Build new binary: %s", appBinary)
|
||||
|
||||
// Kill existing binary if need be
|
||||
if debugBinaryProcess != nil {
|
||||
@@ -233,7 +248,7 @@ func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string,
|
||||
// TODO: Generate `backend.js`
|
||||
|
||||
// Start up new binary
|
||||
newProcess := process.NewProcess(logger, appBinary)
|
||||
newProcess := process.NewProcess(logger, appBinary, "-loglevel", loglevel)
|
||||
err = newProcess.Start()
|
||||
if err != nil {
|
||||
// Remove binary
|
||||
@@ -244,13 +259,13 @@ func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string,
|
||||
logger.Fatal("Unable to start application: %s", err.Error())
|
||||
}
|
||||
|
||||
return newProcess
|
||||
return newProcess, nil
|
||||
}
|
||||
|
||||
func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, buildFrontend bool) (string, error) {
|
||||
func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string) (string, error) {
|
||||
|
||||
// Create random output file
|
||||
outputFile := fmt.Sprintf("debug-%d", time.Now().Unix())
|
||||
outputFile := fmt.Sprintf("dev-%d", time.Now().Unix())
|
||||
|
||||
// Create BuildOptions
|
||||
buildOptions := &build.Options{
|
||||
@@ -262,7 +277,7 @@ func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, co
|
||||
LDFlags: ldflags,
|
||||
Compiler: compilerCommand,
|
||||
OutputFile: outputFile,
|
||||
IgnoreFrontend: !buildFrontend,
|
||||
IgnoreFrontend: true,
|
||||
}
|
||||
|
||||
return build.Build(buildOptions)
|
||||
|
||||
@@ -18,14 +18,14 @@ import (
|
||||
func AddSubcommand(app *clir.Cli, w io.Writer, currentVersion string) error {
|
||||
|
||||
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
|
||||
var prereleaseRequired bool
|
||||
command.BoolFlag("pre", "Update to latest Prerelease", &prereleaseRequired)
|
||||
command.BoolFlag("pre", "Update CLI to latest Prerelease", &prereleaseRequired)
|
||||
|
||||
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 {
|
||||
|
||||
@@ -143,7 +143,7 @@ func updateToVersion(logger *clilogger.CLILogger, targetVersion *github.Semantic
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
logger.Print("Installing Wails " + desiredVersion + "...")
|
||||
logger.Print("Installing Wails CLI " + desiredVersion + "...")
|
||||
|
||||
// Run command in non module directory
|
||||
homeDir, err := os.UserHomeDir()
|
||||
@@ -158,7 +158,7 @@ func updateToVersion(logger *clilogger.CLILogger, targetVersion *github.Semantic
|
||||
return err
|
||||
}
|
||||
fmt.Println()
|
||||
logger.Println("Wails updated to " + desiredVersion)
|
||||
logger.Println("Wails CLI updated to " + desiredVersion)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/colour"
|
||||
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/update"
|
||||
|
||||
"github.com/leaanthony/clir"
|
||||
@@ -19,11 +22,17 @@ func fatal(message string) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func banner(_ *clir.Cli) string {
|
||||
return fmt.Sprintf("%s %s", colour.Yellow("Wails CLI"), colour.DarkRed(version))
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
var err error
|
||||
|
||||
app := clir.NewCli("Wails", "Go/HTML Application Framework", version)
|
||||
app := clir.NewCli("Wails", "Go/HTML Appkit", version)
|
||||
|
||||
app.SetBannerFunction(banner)
|
||||
|
||||
build.AddBuildSubcommand(app, os.Stdout)
|
||||
err = initialise.AddSubcommand(app, os.Stdout)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v2.0.0-alpha.18"
|
||||
var version = "v2.0.0-alpha.54"
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
module github.com/wailsapp/wails/v2
|
||||
|
||||
go 1.15
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/fatih/structtag v1.2.0
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/jackmordaunt/icns v1.0.0
|
||||
github.com/leaanthony/clir v1.0.4
|
||||
@@ -14,16 +15,15 @@ require (
|
||||
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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/tdewolff/minify v2.3.6+incompatible
|
||||
github.com/tdewolff/parse v2.3.4+incompatible // indirect
|
||||
github.com/tdewolff/test v1.0.6 // indirect
|
||||
github.com/wzshiming/ctc v1.2.3
|
||||
github.com/xyproto/xpm v1.2.1
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
|
||||
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
||||
nhooyr.io/websocket v1.8.6
|
||||
)
|
||||
|
||||
21
v2/go.sum
21
v2/go.sum
@@ -11,7 +11,6 @@ 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 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
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=
|
||||
@@ -41,9 +40,6 @@ github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGn
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU=
|
||||
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
|
||||
github.com/leaanthony/gosod v0.0.4 h1:v4hepo4IyL8E8c9qzDsvYcA0KGh7Npf8As74K5ibQpI=
|
||||
@@ -64,17 +60,13 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLD
|
||||
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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo=
|
||||
github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs=
|
||||
@@ -86,6 +78,10 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/wzshiming/ctc v1.2.3 h1:q+hW3IQNsjIlOFBTGZZZeIXTElFM4grF4spW/errh/c=
|
||||
github.com/wzshiming/ctc v1.2.3/go.mod h1:2tVAtIY7SUyraSk0JxvwmONNPFL4ARavPuEsg5+KA28=
|
||||
github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae h1:tpXvBXC3hpQBDCc9OojJZCQMVRAbT3TTdUMP8WguXkY=
|
||||
github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae/go.mod h1:VTAq37rkGeV+WOybvZwjXiJOicICdpLCN8ifpISjK20=
|
||||
github.com/xyproto/xpm v1.2.1 h1:trdvGjjWBsOOKzBBUPT6JvaIQM3acJEEYfbxN7M96wg=
|
||||
github.com/xyproto/xpm v1.2.1/go.mod h1:cMnesLsD0PBXLgjDfTDEaKr8XyTFsnP1QycSqRw7BiY=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -102,33 +98,26 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
|
||||
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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
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 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
@@ -4,8 +4,9 @@ package app
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
)
|
||||
|
||||
// Init initialises the application for a debug environment
|
||||
@@ -19,10 +20,10 @@ func (a *App) Init() error {
|
||||
}
|
||||
|
||||
// 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()
|
||||
if len(*greeting) > 0 {
|
||||
switch strings.ToLower(*greeting) {
|
||||
if len(*loglevel) > 0 {
|
||||
switch strings.ToLower(*loglevel) {
|
||||
case "trace":
|
||||
a.logger.SetLogLevel(logger.TRACE)
|
||||
case "info":
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build !desktop,!hybrid,!server
|
||||
// +build !desktop,!hybrid,!server,!dev
|
||||
|
||||
package app
|
||||
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/ffenestri"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
@@ -26,12 +29,13 @@ type App struct {
|
||||
options *options.App
|
||||
|
||||
// Subsystems
|
||||
log *subsystem.Log
|
||||
runtime *subsystem.Runtime
|
||||
event *subsystem.Event
|
||||
binding *subsystem.Binding
|
||||
log *subsystem.Log
|
||||
runtime *subsystem.Runtime
|
||||
event *subsystem.Event
|
||||
//binding *subsystem.Binding
|
||||
call *subsystem.Call
|
||||
menu *subsystem.Menu
|
||||
url *subsystem.URL
|
||||
dispatcher *messagedispatcher.Dispatcher
|
||||
|
||||
menuManager *menumanager.Manager
|
||||
@@ -109,13 +113,10 @@ func (a *App) Run() error {
|
||||
|
||||
var err error
|
||||
|
||||
// Setup signal handler
|
||||
signalsubsystem, err := signal.NewManager(a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.signal = signalsubsystem
|
||||
a.signal.Start()
|
||||
// Setup a context
|
||||
var subsystemWaitGroup sync.WaitGroup
|
||||
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
|
||||
ctx, cancel := context.WithCancel(parentContext)
|
||||
|
||||
// Start the service bus
|
||||
a.servicebus.Debug()
|
||||
@@ -124,7 +125,7 @@ func (a *App) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
|
||||
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -138,17 +139,6 @@ func (a *App) Run() error {
|
||||
a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
|
||||
a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options)
|
||||
|
||||
// Start the binding subsystem
|
||||
bindingsubsystem, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, a.runtime.GoRuntime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.binding = bindingsubsystem
|
||||
err = a.binding.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Start the logging subsystem
|
||||
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
|
||||
if err != nil {
|
||||
@@ -171,19 +161,32 @@ func (a *App) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if a.options.Mac.URLHandlers != nil {
|
||||
// Start the url handler subsystem
|
||||
url, err := subsystem.NewURL(a.servicebus, a.logger, a.options.Mac.URLHandlers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.url = url
|
||||
err = a.url.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Start the eventing subsystem
|
||||
event, err := subsystem.NewEvent(a.servicebus, a.logger)
|
||||
eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.event = event
|
||||
a.event = eventsubsystem
|
||||
err = a.event.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Start the menu subsystem
|
||||
menusubsystem, err := subsystem.NewMenu(a.servicebus, a.logger, a.menuManager)
|
||||
menusubsystem, err := subsystem.NewMenu(ctx, a.servicebus, a.logger, a.menuManager)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -194,11 +197,11 @@ func (a *App) Run() error {
|
||||
}
|
||||
|
||||
// Start the call subsystem
|
||||
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
|
||||
callSubsystem, err := subsystem.NewCall(ctx, a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.call = call
|
||||
a.call = callSubsystem
|
||||
err = a.call.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -210,12 +213,42 @@ func (a *App) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
result := a.window.Run(dispatcher, bindingDump, a.debug)
|
||||
// Setup signal handler
|
||||
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.signal = signalsubsystem
|
||||
a.signal.Start()
|
||||
|
||||
err = a.window.Run(dispatcher, bindingDump, a.debug)
|
||||
a.logger.Trace("Ffenestri.Run() exited")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Close down all the subsystems
|
||||
a.logger.Trace("Cancelling subsystems")
|
||||
cancel()
|
||||
subsystemWaitGroup.Wait()
|
||||
|
||||
a.logger.Trace("Cancelling dispatcher")
|
||||
dispatcher.Close()
|
||||
|
||||
// Close log
|
||||
a.logger.Trace("Stopping log")
|
||||
log.Close()
|
||||
|
||||
a.logger.Trace("Stopping Service bus")
|
||||
err = a.servicebus.Stop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return result
|
||||
// Shutdown callback
|
||||
if a.shutdownCallback != nil {
|
||||
a.shutdownCallback()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
244
v2/internal/app/dev.go
Normal file
244
v2/internal/app/dev.go
Normal file
@@ -0,0 +1,244 @@
|
||||
// +build dev
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/bridge"
|
||||
"github.com/wailsapp/wails/v2/internal/menumanager"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
|
||||
"github.com/wailsapp/wails/v2/internal/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
"github.com/wailsapp/wails/v2/internal/signal"
|
||||
"github.com/wailsapp/wails/v2/internal/subsystem"
|
||||
)
|
||||
|
||||
// App defines a Wails application structure
|
||||
type App struct {
|
||||
appType string
|
||||
|
||||
servicebus *servicebus.ServiceBus
|
||||
logger *logger.Logger
|
||||
signal *signal.Manager
|
||||
options *options.App
|
||||
|
||||
// Subsystems
|
||||
log *subsystem.Log
|
||||
runtime *subsystem.Runtime
|
||||
event *subsystem.Event
|
||||
//binding *subsystem.Binding
|
||||
call *subsystem.Call
|
||||
menu *subsystem.Menu
|
||||
dispatcher *messagedispatcher.Dispatcher
|
||||
|
||||
menuManager *menumanager.Manager
|
||||
|
||||
// Indicates if the app is in debug mode
|
||||
debug bool
|
||||
|
||||
// This is our binding DB
|
||||
bindings *binding.Bindings
|
||||
|
||||
// Application Stores
|
||||
loglevelStore *runtime.Store
|
||||
appconfigStore *runtime.Store
|
||||
|
||||
// Startup/Shutdown
|
||||
startupCallback func(*runtime.Runtime)
|
||||
shutdownCallback func()
|
||||
|
||||
// Bridge
|
||||
bridge *bridge.Bridge
|
||||
}
|
||||
|
||||
// Create App
|
||||
func CreateApp(appoptions *options.App) (*App, error) {
|
||||
|
||||
// Merge default options
|
||||
options.MergeDefaults(appoptions)
|
||||
|
||||
// Set up logger
|
||||
myLogger := logger.New(appoptions.Logger)
|
||||
|
||||
// Create the menu manager
|
||||
menuManager := menumanager.NewManager()
|
||||
|
||||
// Process the application menu
|
||||
menuManager.SetApplicationMenu(options.GetApplicationMenu(appoptions))
|
||||
|
||||
// Process context menus
|
||||
contextMenus := options.GetContextMenus(appoptions)
|
||||
for _, contextMenu := range contextMenus {
|
||||
menuManager.AddContextMenu(contextMenu)
|
||||
}
|
||||
|
||||
// Process tray menus
|
||||
trayMenus := options.GetTrayMenus(appoptions)
|
||||
for _, trayMenu := range trayMenus {
|
||||
menuManager.AddTrayMenu(trayMenu)
|
||||
}
|
||||
|
||||
// Create binding exemptions - Ugly hack. There must be a better way
|
||||
bindingExemptions := []interface{}{appoptions.Startup, appoptions.Shutdown}
|
||||
|
||||
result := &App{
|
||||
appType: "dev",
|
||||
bindings: binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions),
|
||||
logger: myLogger,
|
||||
servicebus: servicebus.New(myLogger),
|
||||
startupCallback: appoptions.Startup,
|
||||
shutdownCallback: appoptions.Shutdown,
|
||||
bridge: bridge.NewBridge(myLogger),
|
||||
menuManager: menuManager,
|
||||
}
|
||||
|
||||
result.options = appoptions
|
||||
|
||||
// Initialise the app
|
||||
err := result.Init()
|
||||
|
||||
return result, err
|
||||
|
||||
}
|
||||
|
||||
// Run the application
|
||||
func (a *App) Run() error {
|
||||
|
||||
var err error
|
||||
|
||||
// Setup a context
|
||||
var subsystemWaitGroup sync.WaitGroup
|
||||
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
|
||||
ctx, cancel := context.WithCancel(parentContext)
|
||||
|
||||
// Start the service bus
|
||||
a.servicebus.Debug()
|
||||
err = a.servicebus.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.runtime = runtimesubsystem
|
||||
err = a.runtime.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Application Stores
|
||||
a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
|
||||
a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options)
|
||||
|
||||
// Start the logging subsystem
|
||||
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.log = log
|
||||
err = a.log.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create the dispatcher
|
||||
dispatcher, err := messagedispatcher.New(a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.dispatcher = dispatcher
|
||||
err = dispatcher.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Start the eventing subsystem
|
||||
eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.event = eventsubsystem
|
||||
err = a.event.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Start the menu subsystem
|
||||
menusubsystem, err := subsystem.NewMenu(ctx, a.servicebus, a.logger, a.menuManager)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.menu = menusubsystem
|
||||
err = a.menu.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Start the call subsystem
|
||||
callSubsystem, err := subsystem.NewCall(ctx, a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.call = callSubsystem
|
||||
err = a.call.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Dump bindings as a debug
|
||||
bindingDump, err := a.bindings.ToJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate backend.js
|
||||
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)
|
||||
a.logger.Trace("Bridge.Run() exited")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Close down all the subsystems
|
||||
a.logger.Trace("Cancelling subsystems")
|
||||
cancel()
|
||||
subsystemWaitGroup.Wait()
|
||||
|
||||
a.logger.Trace("Cancelling dispatcher")
|
||||
dispatcher.Close()
|
||||
|
||||
// Close log
|
||||
a.logger.Trace("Stopping log")
|
||||
log.Close()
|
||||
|
||||
a.logger.Trace("Stopping Service bus")
|
||||
err = a.servicebus.Stop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Shutdown callback
|
||||
if a.shutdownCallback != nil {
|
||||
a.shutdownCallback()
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
11
v2/internal/binding/assets/package.json
Normal file
11
v2/internal/binding/assets/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"description": "Package to wrap backend method calls",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
174
v2/internal/binding/generate.go
Normal file
174
v2/internal/binding/generate.go
Normal file
@@ -0,0 +1,174 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
)
|
||||
|
||||
const _comment = `
|
||||
|
||||
const backend = {
|
||||
main: {
|
||||
"xbarApp": {
|
||||
"GetCategories": () => {
|
||||
window.backend.main.xbarApp.GetCategories.call(arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} arg1
|
||||
*/
|
||||
"InstallPlugin": (arg1) => {
|
||||
window.backend.main.xbarApp.InstallPlugin.call(arguments);
|
||||
},
|
||||
"GetPlugins": () => {
|
||||
window.backend.main.xbarApp.GetPlugins.call(arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default backend;`
|
||||
|
||||
//go:embed assets/package.json
|
||||
var packageJSON []byte
|
||||
|
||||
func (b *Bindings) GenerateBackendJS() {
|
||||
|
||||
store := b.db.store
|
||||
var output bytes.Buffer
|
||||
|
||||
output.WriteString(`// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Ă‚ MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
const backend = {`)
|
||||
output.WriteString("\n")
|
||||
|
||||
var sortedPackageNames slicer.StringSlicer
|
||||
for packageName := range store {
|
||||
sortedPackageNames.Add(packageName)
|
||||
}
|
||||
sortedPackageNames.Sort()
|
||||
sortedPackageNames.Each(func(packageName string) {
|
||||
packages := store[packageName]
|
||||
output.WriteString(fmt.Sprintf(" \"%s\": {", packageName))
|
||||
output.WriteString("\n")
|
||||
var sortedStructNames slicer.StringSlicer
|
||||
for structName := range packages {
|
||||
sortedStructNames.Add(structName)
|
||||
}
|
||||
sortedStructNames.Sort()
|
||||
|
||||
sortedStructNames.Each(func(structName string) {
|
||||
structs := packages[structName]
|
||||
output.WriteString(fmt.Sprintf(" \"%s\": {", structName))
|
||||
output.WriteString("\n")
|
||||
|
||||
var sortedMethodNames slicer.StringSlicer
|
||||
for methodName := range structs {
|
||||
sortedMethodNames.Add(methodName)
|
||||
}
|
||||
sortedMethodNames.Sort()
|
||||
|
||||
sortedMethodNames.Each(func(methodName string) {
|
||||
methodDetails := structs[methodName]
|
||||
output.WriteString(" /**\n")
|
||||
output.WriteString(" * " + methodName + "\n")
|
||||
var args slicer.StringSlicer
|
||||
for count, input := range methodDetails.Inputs {
|
||||
arg := fmt.Sprintf("arg%d", count+1)
|
||||
args.Add(arg)
|
||||
output.WriteString(fmt.Sprintf(" * @param {%s} %s - Go Type: %s\n", goTypeToJSDocType(input.TypeName), arg, input.TypeName))
|
||||
}
|
||||
returnType := "Promise"
|
||||
returnTypeDetails := ""
|
||||
if methodDetails.OutputCount() > 0 {
|
||||
firstType := goTypeToJSDocType(methodDetails.Outputs[0].TypeName)
|
||||
returnType += "<" + firstType
|
||||
if methodDetails.OutputCount() == 2 {
|
||||
secondType := goTypeToJSDocType(methodDetails.Outputs[1].TypeName)
|
||||
returnType += "|" + secondType
|
||||
}
|
||||
returnType += ">"
|
||||
returnTypeDetails = " - Go Type: " + methodDetails.Outputs[0].TypeName
|
||||
}
|
||||
output.WriteString(" * @returns {" + returnType + "} " + returnTypeDetails + "\n")
|
||||
output.WriteString(" */\n")
|
||||
argsString := args.Join(", ")
|
||||
output.WriteString(fmt.Sprintf(" \"%s\": (%s) => {", methodName, argsString))
|
||||
output.WriteString("\n")
|
||||
output.WriteString(fmt.Sprintf(" return window.backend.%s.%s.%s(%s);", packageName, structName, methodName, argsString))
|
||||
output.WriteString("\n")
|
||||
output.WriteString(fmt.Sprintf(" },"))
|
||||
output.WriteString("\n")
|
||||
|
||||
})
|
||||
|
||||
output.WriteString(fmt.Sprintf(" }"))
|
||||
output.WriteString("\n")
|
||||
})
|
||||
|
||||
output.WriteString(fmt.Sprintf(" }\n"))
|
||||
output.WriteString("\n")
|
||||
})
|
||||
|
||||
output.WriteString(`};
|
||||
export default backend;`)
|
||||
output.WriteString("\n")
|
||||
|
||||
dirname, err := fs.RelativeToCwd("frontend/src/backend")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if !fs.DirExists(dirname) {
|
||||
err := fs.Mkdir(dirname)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
packageJsonFile := filepath.Join(dirname, "package.json")
|
||||
if !fs.FileExists(packageJsonFile) {
|
||||
err := os.WriteFile(packageJsonFile, packageJSON, 0755)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
filename := filepath.Join(dirname, "index.js")
|
||||
err = os.WriteFile(filename, output.Bytes(), 0755)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func goTypeToJSDocType(input string) string {
|
||||
switch true {
|
||||
case input == "string":
|
||||
return "string"
|
||||
case input == "error":
|
||||
return "Error"
|
||||
case
|
||||
strings.HasPrefix(input, "int"),
|
||||
strings.HasPrefix(input, "uint"),
|
||||
strings.HasPrefix(input, "float"):
|
||||
return "number"
|
||||
case input == "bool":
|
||||
return "boolean"
|
||||
case strings.HasPrefix(input, "[]"):
|
||||
arrayType := goTypeToJSDocType(input[2:])
|
||||
return "Array.<" + arrayType + ">"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
}
|
||||
113
v2/internal/bridge/bridge.go
Normal file
113
v2/internal/bridge/bridge.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/menumanager"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
)
|
||||
|
||||
type Bridge struct {
|
||||
upgrader websocket.Upgrader
|
||||
server *http.Server
|
||||
myLogger *logger.Logger
|
||||
|
||||
bindings string
|
||||
dispatcher *messagedispatcher.Dispatcher
|
||||
|
||||
mu sync.Mutex
|
||||
sessions map[string]*session
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
// Dialog client
|
||||
dialog *messagedispatcher.DispatchClient
|
||||
|
||||
// Menus
|
||||
menumanager *menumanager.Manager
|
||||
}
|
||||
|
||||
func NewBridge(myLogger *logger.Logger) *Bridge {
|
||||
result := &Bridge{
|
||||
myLogger: myLogger,
|
||||
upgrader: websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }},
|
||||
sessions: make(map[string]*session),
|
||||
}
|
||||
|
||||
myLogger.SetLogLevel(1)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
result.ctx = ctx
|
||||
result.cancel = cancel
|
||||
result.server = &http.Server{Addr: ":34115"}
|
||||
http.HandleFunc("/bridge", result.wsBridgeHandler)
|
||||
return result
|
||||
}
|
||||
|
||||
func (b *Bridge) Run(dispatcher *messagedispatcher.Dispatcher, menumanager *menumanager.Manager, bindings string, debug bool) error {
|
||||
|
||||
// Ensure we cancel the context when we shutdown
|
||||
defer b.cancel()
|
||||
|
||||
b.bindings = bindings
|
||||
b.dispatcher = dispatcher
|
||||
b.menumanager = menumanager
|
||||
|
||||
// Setup dialog handler
|
||||
dialogClient := NewDialogClient(b.myLogger)
|
||||
b.dialog = dispatcher.RegisterClient(dialogClient)
|
||||
dialogClient.dispatcher = b.dialog
|
||||
|
||||
b.myLogger.Info("Bridge mode started.")
|
||||
|
||||
err := b.server.ListenAndServe()
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bridge) wsBridgeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := b.upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Print("upgrade:", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "Could not open websocket connection", http.StatusBadRequest)
|
||||
}
|
||||
b.myLogger.Info("Connection from frontend accepted [%s].", c.RemoteAddr().String())
|
||||
b.startSession(c)
|
||||
|
||||
}
|
||||
|
||||
func (b *Bridge) startSession(conn *websocket.Conn) {
|
||||
|
||||
// Create a new session for this connection
|
||||
s := newSession(conn, b.menumanager, b.bindings, b.dispatcher, b.myLogger, b.ctx)
|
||||
|
||||
// Setup the close handler
|
||||
conn.SetCloseHandler(func(int, string) error {
|
||||
b.myLogger.Info("Connection dropped [%s].", s.Identifier())
|
||||
b.dispatcher.RemoveClient(s.client)
|
||||
b.mu.Lock()
|
||||
delete(b.sessions, s.Identifier())
|
||||
b.mu.Unlock()
|
||||
return nil
|
||||
})
|
||||
|
||||
b.mu.Lock()
|
||||
go s.start(len(b.sessions) == 0)
|
||||
b.sessions[s.Identifier()] = s
|
||||
b.mu.Unlock()
|
||||
}
|
||||
134
v2/internal/bridge/client.go
Normal file
134
v2/internal/bridge/client.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
)
|
||||
|
||||
type BridgeClient struct {
|
||||
session *session
|
||||
|
||||
// Tray menu cache to send to reconnecting clients
|
||||
messageCache chan string
|
||||
}
|
||||
|
||||
func (b BridgeClient) DeleteTrayMenuByID(id string) {
|
||||
b.session.sendMessage("TD" + id)
|
||||
}
|
||||
|
||||
func NewBridgeClient() *BridgeClient {
|
||||
return &BridgeClient{
|
||||
messageCache: make(chan string, 100),
|
||||
}
|
||||
}
|
||||
|
||||
func (b BridgeClient) Quit() {
|
||||
b.session.log.Info("Quit unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) NotifyEvent(message string) {
|
||||
//b.session.sendMessage("n" + message)
|
||||
b.session.log.Info("NotifyEvent: %s", message)
|
||||
b.session.log.Info("NotifyEvent unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) CallResult(message string) {
|
||||
b.session.sendMessage("c" + message)
|
||||
}
|
||||
|
||||
func (b BridgeClient) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
|
||||
// Handled by dialog_client
|
||||
}
|
||||
|
||||
func (b BridgeClient) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
|
||||
// Handled by dialog_client
|
||||
}
|
||||
|
||||
func (b BridgeClient) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
|
||||
// Handled by dialog_client
|
||||
}
|
||||
|
||||
func (b BridgeClient) WindowSetTitle(title string) {
|
||||
b.session.log.Info("WindowSetTitle unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) WindowShow() {
|
||||
b.session.log.Info("WindowShow unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) WindowHide() {
|
||||
b.session.log.Info("WindowHide unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) WindowCenter() {
|
||||
b.session.log.Info("WindowCenter unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) WindowMaximise() {
|
||||
b.session.log.Info("WindowMaximise unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) WindowUnmaximise() {
|
||||
b.session.log.Info("WindowUnmaximise unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) WindowMinimise() {
|
||||
b.session.log.Info("WindowMinimise unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) WindowUnminimise() {
|
||||
b.session.log.Info("WindowUnminimise unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) WindowPosition(x int, y int) {
|
||||
b.session.log.Info("WindowPosition unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) WindowSize(width int, height int) {
|
||||
b.session.log.Info("WindowSize unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) WindowSetMinSize(width int, height int) {
|
||||
b.session.log.Info("WindowSetMinSize unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) WindowSetMaxSize(width int, height int) {
|
||||
b.session.log.Info("WindowSetMaxSize unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) WindowFullscreen() {
|
||||
b.session.log.Info("WindowFullscreen unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) WindowUnFullscreen() {
|
||||
b.session.log.Info("WindowUnFullscreen unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) WindowSetColour(colour int) {
|
||||
b.session.log.Info("WindowSetColour unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) DarkModeEnabled(callbackID string) {
|
||||
b.session.log.Info("DarkModeEnabled unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) SetApplicationMenu(menuJSON string) {
|
||||
b.session.log.Info("SetApplicationMenu unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func (b BridgeClient) SetTrayMenu(trayMenuJSON string) {
|
||||
b.session.sendMessage("TS" + trayMenuJSON)
|
||||
}
|
||||
|
||||
func (b BridgeClient) UpdateTrayMenuLabel(trayMenuJSON string) {
|
||||
b.session.sendMessage("TU" + trayMenuJSON)
|
||||
}
|
||||
|
||||
func (b BridgeClient) UpdateContextMenu(contextMenuJSON string) {
|
||||
b.session.log.Info("UpdateContextMenu unsupported in Bridge mode")
|
||||
}
|
||||
|
||||
func newBridgeClient(session *session) *BridgeClient {
|
||||
return &BridgeClient{
|
||||
session: session,
|
||||
}
|
||||
}
|
||||
1
v2/internal/bridge/darwin.js
Normal file
1
v2/internal/bridge/darwin.js
Normal file
File diff suppressed because one or more lines are too long
144
v2/internal/bridge/dialog_client.go
Normal file
144
v2/internal/bridge/dialog_client.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
)
|
||||
|
||||
type DialogClient struct {
|
||||
dispatcher *messagedispatcher.DispatchClient
|
||||
log *logger.Logger
|
||||
}
|
||||
|
||||
func (d *DialogClient) DeleteTrayMenuByID(id string) {
|
||||
}
|
||||
|
||||
func NewDialogClient(log *logger.Logger) *DialogClient {
|
||||
return &DialogClient{
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DialogClient) Quit() {
|
||||
}
|
||||
|
||||
func (d *DialogClient) NotifyEvent(message string) {
|
||||
}
|
||||
|
||||
func (d *DialogClient) CallResult(message string) {
|
||||
}
|
||||
|
||||
func (d *DialogClient) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
|
||||
}
|
||||
|
||||
func (d *DialogClient) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
|
||||
}
|
||||
|
||||
func (d *DialogClient) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
|
||||
|
||||
osa, err := exec.LookPath("osascript")
|
||||
if err != nil {
|
||||
d.log.Info("MessageDialog unavailable (osascript not found)")
|
||||
return
|
||||
}
|
||||
|
||||
var btns slicer.StringSlicer
|
||||
defaultButton := ""
|
||||
cancelButton := ""
|
||||
for index, btn := range dialogOptions.Buttons {
|
||||
btns.Add(strconv.Quote(btn))
|
||||
if btn == dialogOptions.DefaultButton {
|
||||
defaultButton = fmt.Sprintf("default button %d", index+1)
|
||||
}
|
||||
if btn == dialogOptions.CancelButton {
|
||||
cancelButton = fmt.Sprintf("cancel button %d", index+1)
|
||||
}
|
||||
}
|
||||
buttons := "{" + btns.Join(",") + "}"
|
||||
script := fmt.Sprintf("display dialog \"%s\" buttons %s %s %s with title \"%s\"", dialogOptions.Message, buttons, defaultButton, cancelButton, dialogOptions.Title)
|
||||
go func() {
|
||||
out, err := exec.Command(osa, "-e", script).Output()
|
||||
if err != nil {
|
||||
// Assume user has pressed cancel button
|
||||
if dialogOptions.CancelButton != "" {
|
||||
d.dispatcher.DispatchMessage("DM" + callbackID + "|" + dialogOptions.CancelButton)
|
||||
return
|
||||
}
|
||||
d.log.Error("Dialog had bad exit code. If this was a Cancel button, add 'CancelButton' to the dialog.MessageDialog struct. Error: %s", err.Error())
|
||||
d.dispatcher.DispatchMessage("DM" + callbackID + "|error - check logs")
|
||||
return
|
||||
}
|
||||
|
||||
buttonPressed := strings.TrimSpace(strings.TrimPrefix(string(out), "button returned:"))
|
||||
d.dispatcher.DispatchMessage("DM" + callbackID + "|" + buttonPressed)
|
||||
}()
|
||||
}
|
||||
|
||||
func (d *DialogClient) WindowSetTitle(title string) {
|
||||
}
|
||||
|
||||
func (d *DialogClient) WindowShow() {
|
||||
}
|
||||
|
||||
func (d *DialogClient) WindowHide() {
|
||||
}
|
||||
|
||||
func (d *DialogClient) WindowCenter() {
|
||||
}
|
||||
|
||||
func (d *DialogClient) WindowMaximise() {
|
||||
}
|
||||
|
||||
func (d *DialogClient) WindowUnmaximise() {
|
||||
}
|
||||
|
||||
func (d *DialogClient) WindowMinimise() {
|
||||
}
|
||||
|
||||
func (d *DialogClient) WindowUnminimise() {
|
||||
}
|
||||
|
||||
func (d *DialogClient) WindowPosition(x int, y int) {
|
||||
}
|
||||
|
||||
func (d *DialogClient) WindowSize(width int, height int) {
|
||||
}
|
||||
|
||||
func (d *DialogClient) WindowSetMinSize(width int, height int) {
|
||||
}
|
||||
|
||||
func (d *DialogClient) WindowSetMaxSize(width int, height int) {
|
||||
}
|
||||
|
||||
func (d *DialogClient) WindowFullscreen() {
|
||||
}
|
||||
|
||||
func (d *DialogClient) WindowUnFullscreen() {
|
||||
}
|
||||
|
||||
func (d *DialogClient) WindowSetColour(colour int) {
|
||||
}
|
||||
|
||||
func (d *DialogClient) DarkModeEnabled(callbackID string) {
|
||||
}
|
||||
|
||||
func (d *DialogClient) SetApplicationMenu(menuJSON string) {
|
||||
}
|
||||
|
||||
func (d *DialogClient) SetTrayMenu(trayMenuJSON string) {
|
||||
}
|
||||
|
||||
func (d *DialogClient) UpdateTrayMenuLabel(trayMenuJSON string) {
|
||||
}
|
||||
|
||||
func (d *DialogClient) UpdateContextMenu(contextMenuJSON string) {
|
||||
}
|
||||
162
v2/internal/bridge/session.go
Normal file
162
v2/internal/bridge/session.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"log"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/menumanager"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
)
|
||||
|
||||
//go:embed darwin.js
|
||||
var darwinRuntime string
|
||||
|
||||
// session represents a single websocket session
|
||||
type session struct {
|
||||
bindings string
|
||||
conn *websocket.Conn
|
||||
//eventManager interfaces.EventManager
|
||||
log *logger.Logger
|
||||
//ipc interfaces.IPCManager
|
||||
|
||||
// Mutex for writing to the socket
|
||||
shutdown chan bool
|
||||
writeChan chan []byte
|
||||
|
||||
done bool
|
||||
|
||||
// context
|
||||
ctx context.Context
|
||||
|
||||
// client
|
||||
client *messagedispatcher.DispatchClient
|
||||
|
||||
// Menus
|
||||
menumanager *menumanager.Manager
|
||||
}
|
||||
|
||||
func newSession(conn *websocket.Conn, menumanager *menumanager.Manager, bindings string, dispatcher *messagedispatcher.Dispatcher, logger *logger.Logger, ctx context.Context) *session {
|
||||
result := &session{
|
||||
conn: conn,
|
||||
bindings: bindings,
|
||||
log: logger,
|
||||
shutdown: make(chan bool),
|
||||
writeChan: make(chan []byte, 100),
|
||||
ctx: ctx,
|
||||
menumanager: menumanager,
|
||||
}
|
||||
|
||||
result.client = dispatcher.RegisterClient(newBridgeClient(result))
|
||||
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
// Identifier returns a string identifier for the remote connection.
|
||||
// Taking the form of the client's <ip address>:<port>.
|
||||
func (s *session) Identifier() string {
|
||||
if s.conn != nil {
|
||||
return s.conn.RemoteAddr().String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *session) sendMessage(msg string) error {
|
||||
if !s.done {
|
||||
s.writeChan <- []byte(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *session) start(firstSession bool) {
|
||||
s.log.SetLogLevel(1)
|
||||
s.log.Info("Connected to frontend.")
|
||||
go s.writePump()
|
||||
|
||||
var wailsRuntime string
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
wailsRuntime = darwinRuntime
|
||||
default:
|
||||
log.Fatal("platform not supported")
|
||||
}
|
||||
|
||||
bindingsMessage := "window.wailsbindings = `" + s.bindings + "`;"
|
||||
s.log.Info(bindingsMessage)
|
||||
bootstrapMessage := bindingsMessage + wailsRuntime
|
||||
|
||||
s.sendMessage("b" + bootstrapMessage)
|
||||
|
||||
// Send menus
|
||||
traymenus, err := s.menumanager.GetTrayMenus()
|
||||
if err != nil {
|
||||
s.log.Error(err.Error())
|
||||
}
|
||||
|
||||
for _, trayMenu := range traymenus {
|
||||
s.sendMessage("TS" + trayMenu)
|
||||
}
|
||||
|
||||
for {
|
||||
messageType, buffer, err := s.conn.ReadMessage()
|
||||
if messageType == -1 {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
s.log.Error("Error reading message: %v", err)
|
||||
err = s.conn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
message := string(buffer)
|
||||
|
||||
s.log.Debug("Got message: %#v\n", message)
|
||||
|
||||
// Dispatch message as normal
|
||||
s.client.DispatchMessage(message)
|
||||
|
||||
if s.done {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown
|
||||
func (s *session) Shutdown() {
|
||||
s.conn.Close()
|
||||
s.done = true
|
||||
s.log.Info("session %v exit", s.Identifier())
|
||||
}
|
||||
|
||||
// writePump pulls messages from the writeChan and sends them to the client
|
||||
// since it uses a channel to read the messages the socket is protected without locks
|
||||
func (s *session) writePump() {
|
||||
s.log.Debug("Session %v - writePump start", s.Identifier())
|
||||
defer s.log.Debug("Session %v - writePump shutdown", s.Identifier())
|
||||
for {
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
s.Shutdown()
|
||||
return
|
||||
case msg, ok := <-s.writeChan:
|
||||
s.conn.SetWriteDeadline(time.Now().Add(1 * time.Second))
|
||||
if !ok {
|
||||
s.log.Debug("writeChan was closed!")
|
||||
s.conn.WriteMessage(websocket.CloseMessage, []byte{})
|
||||
return
|
||||
}
|
||||
|
||||
if err := s.conn.WriteMessage(websocket.TextMessage, msg); err != nil {
|
||||
s.log.Debug(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
v2/internal/colour/colour.go
Normal file
89
v2/internal/colour/colour.go
Normal 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()
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
package ffenestri
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/menumanager"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/menumanager"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
@@ -118,7 +119,8 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug
|
||||
fullscreen := a.bool2Cint(a.config.Fullscreen)
|
||||
startHidden := a.bool2Cint(a.config.StartHidden)
|
||||
logLevel := C.int(a.config.LogLevel)
|
||||
app := C.NewApplication(title, width, height, resizable, devtools, fullscreen, startHidden, logLevel)
|
||||
hideWindowOnClose := a.bool2Cint(a.config.HideWindowOnClose)
|
||||
app := C.NewApplication(title, width, height, resizable, devtools, fullscreen, startHidden, logLevel, hideWindowOnClose)
|
||||
|
||||
// Save app reference
|
||||
a.app = (*C.struct_Application)(app)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#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);
|
||||
extern struct Application *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose);
|
||||
extern void SetMinWindowSize(struct Application*, int minWidth, int minHeight);
|
||||
extern void SetMaxWindowSize(struct Application*, int maxWidth, int maxHeight);
|
||||
extern void Run(struct Application*, int argc, char **argv);
|
||||
@@ -37,6 +37,7 @@ extern void DarkModeEnabled(struct Application*, char *callbackID);
|
||||
extern void SetApplicationMenu(struct Application*, const char *);
|
||||
extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
|
||||
extern void SetTrayMenu(struct Application*, const char *menuTrayJSON);
|
||||
extern void DeleteTrayMenuByID(struct Application*, const char *id);
|
||||
extern void UpdateTrayMenuLabel(struct Application*, const char* JSON);
|
||||
extern void AddContextMenu(struct Application*, char *contextMenuJSON);
|
||||
extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);
|
||||
|
||||
@@ -208,3 +208,7 @@ func (c *Client) UpdateTrayMenuLabel(JSON string) {
|
||||
func (c *Client) UpdateContextMenu(contextMenuJSON string) {
|
||||
C.UpdateContextMenu(c.app.app, c.app.string2CString(contextMenuJSON))
|
||||
}
|
||||
|
||||
func (c *Client) DeleteTrayMenuByID(id string) {
|
||||
C.DeleteTrayMenuByID(c.app.app, c.app.string2CString(id))
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ struct hashmap_s dialogIconCache;
|
||||
// Dispatch Method
|
||||
typedef void (^dispatchMethod)(void);
|
||||
|
||||
TrayMenuStore *TrayMenuStoreSingleton;
|
||||
|
||||
// dispatch will execute the given `func` pointer
|
||||
void dispatch(dispatchMethod func) {
|
||||
dispatch_async(dispatch_get_main_queue(), func);
|
||||
@@ -34,12 +36,30 @@ BOOL yes(id self, SEL cmd)
|
||||
return YES;
|
||||
}
|
||||
|
||||
// no command simply returns NO!
|
||||
BOOL no(id self, SEL cmd)
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Prints a hashmap entry
|
||||
int hashmap_log(void *const context, struct hashmap_element_s *const e) {
|
||||
printf("%s: %p ", (char*)e->key, e->data);
|
||||
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
|
||||
void dumpHashmap(const char *name, struct hashmap_s *hashmap) {
|
||||
printf("%s = { ", name);
|
||||
@@ -65,6 +85,7 @@ struct Application {
|
||||
// Cocoa data
|
||||
id application;
|
||||
id delegate;
|
||||
id windowDelegate;
|
||||
id mainWindow;
|
||||
id wkwebview;
|
||||
id manager;
|
||||
@@ -72,6 +93,7 @@ struct Application {
|
||||
id mouseEvent;
|
||||
id mouseDownMonitor;
|
||||
id mouseUpMonitor;
|
||||
int activationPolicy;
|
||||
|
||||
// Window Data
|
||||
const char *title;
|
||||
@@ -92,6 +114,7 @@ struct Application {
|
||||
const char *appearance;
|
||||
int decorations;
|
||||
int logLevel;
|
||||
int hideWindowOnClose;
|
||||
|
||||
// Features
|
||||
int frame;
|
||||
@@ -104,13 +127,12 @@ struct Application {
|
||||
int useToolBar;
|
||||
int hideToolbarSeparator;
|
||||
int windowBackgroundIsTranslucent;
|
||||
int hasURLHandlers;
|
||||
const char *startupURL;
|
||||
|
||||
// Menu
|
||||
Menu *applicationMenu;
|
||||
|
||||
// Tray
|
||||
TrayMenuStore* trayMenuStore;
|
||||
|
||||
// Context Menus
|
||||
ContextMenuStore *contextMenuStore;
|
||||
|
||||
@@ -120,6 +142,12 @@ struct Application {
|
||||
// Bindings
|
||||
const char *bindings;
|
||||
|
||||
// shutting down flag
|
||||
bool shuttingDown;
|
||||
|
||||
// Running flag
|
||||
bool running;
|
||||
|
||||
};
|
||||
|
||||
// Debug works like sprintf but mutes if the global debug flag is true
|
||||
@@ -222,6 +250,9 @@ void applyWindowColour(struct Application *app) {
|
||||
}
|
||||
|
||||
void SetColour(struct Application *app, int red, int green, int blue, int alpha) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
app->red = red;
|
||||
app->green = green;
|
||||
app->blue = blue;
|
||||
@@ -235,12 +266,18 @@ void FullSizeContent(struct Application *app) {
|
||||
}
|
||||
|
||||
void Hide(struct Application *app) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
msg(app->application, s("hide:"))
|
||||
msg(app->mainWindow, s("orderOut:"));
|
||||
);
|
||||
}
|
||||
|
||||
void Show(struct Application *app) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
msg(app->mainWindow, s("makeKeyAndOrderFront:"), NULL);
|
||||
msg(app->application, s("activateIgnoringOtherApps:"), YES);
|
||||
@@ -271,8 +308,19 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
||||
// TODO: Check this actually does reduce flicker
|
||||
msg(app->config, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("suppressesIncrementalRendering"));
|
||||
|
||||
// We are now running!
|
||||
app->running = true;
|
||||
|
||||
|
||||
// Notify backend we are ready (system startup)
|
||||
app->sendMessageToBackend("SS");
|
||||
const char *readyMessage = "SS";
|
||||
if( app->startupURL == NULL ) {
|
||||
app->sendMessageToBackend("SS");
|
||||
return;
|
||||
}
|
||||
readyMessage = concat("SS", app->startupURL);
|
||||
app->sendMessageToBackend(readyMessage);
|
||||
MEMFREE(readyMessage);
|
||||
|
||||
} else if( strcmp(name, "windowDrag") == 0 ) {
|
||||
// Guard against null events
|
||||
@@ -370,6 +418,11 @@ void ExecJS(struct Application *app, const char *js) {
|
||||
|
||||
void willFinishLaunching(id self, SEL cmd, id sender) {
|
||||
struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application");
|
||||
// If there are URL Handlers, register a listener for them
|
||||
if( app->hasURLHandlers ) {
|
||||
id eventManager = msg(c("NSAppleEventManager"), s("sharedAppleEventManager"));
|
||||
msg(eventManager, s("setEventHandler:andSelector:forEventClass:andEventID:"), self, s("getUrl:withReplyEvent:"), kInternetEventClass, kAEGetURL);
|
||||
}
|
||||
messageFromWindowCallback("Ej{\"name\":\"wails:launched\",\"data\":[]}");
|
||||
}
|
||||
|
||||
@@ -394,12 +447,6 @@ void themeChanged(id self, SEL cmd, id sender) {
|
||||
}
|
||||
}
|
||||
|
||||
// void willFinishLaunching(id self) {
|
||||
// struct Application *app = (struct Application *) objc_getAssociatedObject(self, "application");
|
||||
// Debug(app, "willFinishLaunching called!");
|
||||
// }
|
||||
|
||||
|
||||
int releaseNSObject(void *const context, struct hashmap_element_s *const e) {
|
||||
msg(e->data, s("release"));
|
||||
return -1;
|
||||
@@ -422,6 +469,7 @@ void freeDialogIconCache(struct Application *app) {
|
||||
}
|
||||
|
||||
void DestroyApplication(struct Application *app) {
|
||||
app->shuttingDown = true;
|
||||
Debug(app, "Destroying Application");
|
||||
|
||||
// Free the bindings
|
||||
@@ -431,6 +479,10 @@ void DestroyApplication(struct Application *app) {
|
||||
Debug(app, "Almost a double free for app->bindings");
|
||||
}
|
||||
|
||||
if( app->startupURL != NULL ) {
|
||||
MEMFREE(app->startupURL);
|
||||
}
|
||||
|
||||
// Remove mouse monitors
|
||||
if( app->mouseDownMonitor != NULL ) {
|
||||
msg( c("NSEvent"), s("removeMonitor:"), app->mouseDownMonitor);
|
||||
@@ -445,7 +497,7 @@ void DestroyApplication(struct Application *app) {
|
||||
}
|
||||
|
||||
// Delete the tray menu store
|
||||
DeleteTrayMenuStore(app->trayMenuStore);
|
||||
DeleteTrayMenuStore(TrayMenuStoreSingleton);
|
||||
|
||||
// Delete the context menu store
|
||||
DeleteContextMenuStore(app->contextMenuStore);
|
||||
@@ -466,22 +518,22 @@ void DestroyApplication(struct Application *app) {
|
||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("error"));
|
||||
|
||||
// Close main window
|
||||
msg(app->mainWindow, s("close"));
|
||||
if( app->windowDelegate != NULL ) {
|
||||
msg(app->windowDelegate, s("release"));
|
||||
msg(app->mainWindow, s("setDelegate:"), NULL);
|
||||
}
|
||||
|
||||
// msg(app->mainWindow, s("close"));
|
||||
|
||||
|
||||
// Terminate app
|
||||
msg(c("NSApp"), s("terminate:"), NULL);
|
||||
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");
|
||||
DestroyApplication(app);
|
||||
}
|
||||
|
||||
// SetTitle sets the main window title to the given string
|
||||
void SetTitle(struct Application *app, const char *title) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
Debug(app, "SetTitle Called");
|
||||
ON_MAIN_THREAD(
|
||||
msg(app->mainWindow, s("setTitle:"), str(title));
|
||||
@@ -503,6 +555,9 @@ bool isFullScreen(struct Application *app) {
|
||||
|
||||
// Fullscreen sets the main window to be fullscreen
|
||||
void Fullscreen(struct Application *app) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
Debug(app, "Fullscreen Called");
|
||||
if( ! isFullScreen(app) ) {
|
||||
ToggleFullscreen(app);
|
||||
@@ -511,6 +566,9 @@ void Fullscreen(struct Application *app) {
|
||||
|
||||
// UnFullscreen resets the main window after a fullscreen
|
||||
void UnFullscreen(struct Application *app) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
Debug(app, "UnFullscreen Called");
|
||||
if( isFullScreen(app) ) {
|
||||
ToggleFullscreen(app);
|
||||
@@ -518,6 +576,9 @@ void UnFullscreen(struct Application *app) {
|
||||
}
|
||||
|
||||
void Center(struct Application *app) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
Debug(app, "Center Called");
|
||||
ON_MAIN_THREAD(
|
||||
MAIN_WINDOW_CALL("center");
|
||||
@@ -532,23 +593,35 @@ void ToggleMaximise(struct Application *app) {
|
||||
}
|
||||
|
||||
void Maximise(struct Application *app) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
if( app->maximised == 0) {
|
||||
ToggleMaximise(app);
|
||||
}
|
||||
}
|
||||
|
||||
void Unmaximise(struct Application *app) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
if( app->maximised == 1) {
|
||||
ToggleMaximise(app);
|
||||
}
|
||||
}
|
||||
|
||||
void Minimise(struct Application *app) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
MAIN_WINDOW_CALL("miniaturize:");
|
||||
);
|
||||
}
|
||||
void Unminimise(struct Application *app) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
MAIN_WINDOW_CALL("deminiaturize:");
|
||||
);
|
||||
@@ -572,6 +645,9 @@ void dumpFrame(struct Application *app, const char *message, CGRect frame) {
|
||||
}
|
||||
|
||||
void SetSize(struct Application *app, int width, int height) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
id screen = getCurrentScreen(app);
|
||||
|
||||
@@ -588,6 +664,9 @@ void SetSize(struct Application *app, int width, int height) {
|
||||
}
|
||||
|
||||
void SetPosition(struct Application *app, int x, int y) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
id screen = getCurrentScreen(app);
|
||||
CGRect screenFrame = GET_FRAME(screen);
|
||||
@@ -613,6 +692,9 @@ void processDialogButton(id alert, char *buttonTitle, char *cancelButton, char *
|
||||
}
|
||||
|
||||
extern void MessageDialog(struct Application *app, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
id alert = ALLOC_INIT("NSAlert");
|
||||
char *dialogType = type;
|
||||
@@ -709,7 +791,7 @@ extern void MessageDialog(struct Application *app, char *callbackID, char *type,
|
||||
buttonPressed = button4;
|
||||
}
|
||||
|
||||
// Construct callback message. Format "DS<callbackID>|<selected button index>"
|
||||
// Construct callback message. Format "DM<callbackID>|<selected button index>"
|
||||
const char *callback = concat("DM", callbackID);
|
||||
const char *header = concat(callback, "|");
|
||||
const char *responseMessage = concat(header, buttonPressed);
|
||||
@@ -726,6 +808,9 @@ extern void MessageDialog(struct Application *app, char *callbackID, char *type,
|
||||
|
||||
// OpenDialog opens a dialog to select files/directories
|
||||
void OpenDialog(struct Application *app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
Debug(app, "OpenDialog Called with callback id: %s", callbackID);
|
||||
|
||||
// Create an open panel
|
||||
@@ -814,6 +899,9 @@ void OpenDialog(struct Application *app, char *callbackID, char *title, char *fi
|
||||
|
||||
// SaveDialog opens a dialog to select files/directories
|
||||
void SaveDialog(struct Application *app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
Debug(app, "SaveDialog Called with callback id: %s", callbackID);
|
||||
|
||||
// Create an open panel
|
||||
@@ -891,6 +979,9 @@ void DisableFrame(struct Application *app)
|
||||
|
||||
void setMinMaxSize(struct Application *app)
|
||||
{
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
if (app->maxHeight > 0 && app->maxWidth > 0)
|
||||
{
|
||||
msg(app->mainWindow, s("setMaxSize:"), CGSizeMake(app->maxWidth, app->maxHeight));
|
||||
@@ -917,6 +1008,9 @@ void setMinMaxSize(struct Application *app)
|
||||
|
||||
void SetMinWindowSize(struct Application *app, int minWidth, int minHeight)
|
||||
{
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
app->minWidth = minWidth;
|
||||
app->minHeight = minHeight;
|
||||
|
||||
@@ -930,6 +1024,9 @@ void SetMinWindowSize(struct Application *app, int minWidth, int minHeight)
|
||||
|
||||
void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight)
|
||||
{
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
app->maxWidth = maxWidth;
|
||||
app->maxHeight = maxHeight;
|
||||
|
||||
@@ -950,26 +1047,48 @@ void SetDebug(void *applicationPointer, int flag) {
|
||||
|
||||
// AddContextMenu sets the context menu map for this application
|
||||
void AddContextMenu(struct Application *app, const char *contextMenuJSON) {
|
||||
AddContextMenuToStore(app->contextMenuStore, contextMenuJSON);
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
AddContextMenuToStore(app->contextMenuStore, contextMenuJSON);
|
||||
}
|
||||
|
||||
void UpdateContextMenu(struct Application *app, const char* contextMenuJSON) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
UpdateContextMenuInStore(app->contextMenuStore, contextMenuJSON);
|
||||
}
|
||||
|
||||
void AddTrayMenu(struct Application *app, const char *trayMenuJSON) {
|
||||
AddTrayMenuToStore(app->trayMenuStore, trayMenuJSON);
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
AddTrayMenuToStore(TrayMenuStoreSingleton, trayMenuJSON);
|
||||
}
|
||||
|
||||
void SetTrayMenu(struct Application *app, const char* trayMenuJSON) {
|
||||
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
UpdateTrayMenuInStore(app->trayMenuStore, trayMenuJSON);
|
||||
UpdateTrayMenuInStore(TrayMenuStoreSingleton, trayMenuJSON);
|
||||
);
|
||||
}
|
||||
|
||||
void DeleteTrayMenuByID(struct Application *app, const char *id) {
|
||||
ON_MAIN_THREAD(
|
||||
DeleteTrayMenuInStore(TrayMenuStoreSingleton, id);
|
||||
);
|
||||
}
|
||||
|
||||
void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
UpdateTrayMenuLabelInStore(app->trayMenuStore, JSON);
|
||||
UpdateTrayMenuLabelInStore(TrayMenuStoreSingleton, JSON);
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1030,10 +1149,13 @@ void processDecorations(struct Application *app) {
|
||||
void createApplication(struct Application *app) {
|
||||
id application = msg(c("NSApplication"), s("sharedApplication"));
|
||||
app->application = application;
|
||||
msg(application, s("setActivationPolicy:"), 0);
|
||||
msg(application, s("setActivationPolicy:"), app->activationPolicy);
|
||||
}
|
||||
|
||||
void DarkModeEnabled(struct Application *app, const char *callbackID) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
const char *result = isDarkMode(app) ? "T" : "F";
|
||||
|
||||
@@ -1051,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) {
|
||||
// Define delegate
|
||||
Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "AppDelegate", 0);
|
||||
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSApplicationDelegate"));
|
||||
class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) yes, "c@:@");
|
||||
class_addMethod(delegateClass, s("applicationWillTerminate:"), (IMP) closeWindow, "v@:@");
|
||||
class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
|
||||
|
||||
// Define delegate
|
||||
Class appDelegate = objc_allocateClassPair((Class) c("NSResponder"), "AppDelegate", 0);
|
||||
class_addProtocol(appDelegate, objc_getProtocol("NSTouchBarProvider"));
|
||||
|
||||
class_addMethod(appDelegate, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) no, "c@:@");
|
||||
class_addMethod(appDelegate, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
|
||||
|
||||
// All Menu Items use a common callback
|
||||
class_addMethod(delegateClass, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@");
|
||||
class_addMethod(appDelegate, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@");
|
||||
|
||||
// If there are URL Handlers, register the callback method
|
||||
if( app->hasURLHandlers ) {
|
||||
class_addMethod(appDelegate, s("getUrl:withReplyEvent:"), (IMP) getURL, "i@:@@");
|
||||
}
|
||||
|
||||
// Script handler
|
||||
class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
|
||||
objc_registerClassPair(delegateClass);
|
||||
class_addMethod(appDelegate, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
|
||||
objc_registerClassPair(appDelegate);
|
||||
|
||||
// Create delegate
|
||||
id delegate = msg((id)delegateClass, s("new"));
|
||||
id delegate = msg((id)appDelegate, s("new"));
|
||||
objc_setAssociatedObject(delegate, "application", (id)app, OBJC_ASSOCIATION_ASSIGN);
|
||||
|
||||
// Theme change listener
|
||||
class_addMethod(delegateClass, s("themeChanged:"), (IMP) themeChanged, "v@:@@");
|
||||
class_addMethod(appDelegate, s("themeChanged:"), (IMP) themeChanged, "v@:@@");
|
||||
|
||||
// Get defaultCenter
|
||||
id defaultCenter = msg(c("NSDistributedNotificationCenter"), s("defaultCenter"));
|
||||
@@ -1082,11 +1236,22 @@ void createDelegate(struct Application *app) {
|
||||
msg(app->application, s("setDelegate:"), delegate);
|
||||
}
|
||||
|
||||
bool windowShouldClose(id self, SEL cmd, id sender) {
|
||||
msg(sender, s("orderOut:"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool windowShouldExit(id self, SEL cmd, id sender) {
|
||||
msg(sender, s("orderOut:"));
|
||||
messageFromWindowCallback("WC");
|
||||
return false;
|
||||
}
|
||||
|
||||
void createMainWindow(struct Application *app) {
|
||||
// Create main window
|
||||
id mainWindow = ALLOC("NSWindow");
|
||||
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"));
|
||||
|
||||
// Set Appearance
|
||||
@@ -1100,6 +1265,17 @@ void createMainWindow(struct Application *app) {
|
||||
msg(mainWindow, s("setTitlebarAppearsTransparent:"), app->titlebarAppearsTransparent ? YES : NO);
|
||||
msg(mainWindow, s("setTitleVisibility:"), app->hideTitle);
|
||||
|
||||
// Create window delegate to override windowShouldClose
|
||||
Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "WindowDelegate", 0);
|
||||
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSWindowDelegate"));
|
||||
if( app->hideWindowOnClose ) {
|
||||
class_replaceMethod(delegateClass, s("windowShouldClose:"), (IMP) windowShouldClose, "v@:@");
|
||||
} else {
|
||||
class_replaceMethod(delegateClass, s("windowShouldClose:"), (IMP) windowShouldExit, "v@:@");
|
||||
}
|
||||
app->windowDelegate = msg((id)delegateClass, s("new"));
|
||||
msg(mainWindow, s("setDelegate:"), app->windowDelegate);
|
||||
|
||||
app->mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
@@ -1186,7 +1362,7 @@ void parseMenuRole(struct Application *app, id parentMenu, JsonNode *item) {
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "quit")) {
|
||||
addMenuItem(parentMenu, "Quit (More work TBD)", "terminate:", "q", FALSE);
|
||||
addMenuItem(parentMenu, "Quit", "terminate:", "q", FALSE);
|
||||
return;
|
||||
}
|
||||
if ( STREQ(roleName, "togglefullscreen")) {
|
||||
@@ -1464,6 +1640,9 @@ void updateMenu(struct Application *app, const char *menuAsJSON) {
|
||||
|
||||
// SetApplicationMenu sets the initial menu for the application
|
||||
void SetApplicationMenu(struct Application *app, const char *menuAsJSON) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
if ( app->applicationMenu == NULL ) {
|
||||
app->applicationMenu = NewApplicationMenu(menuAsJSON);
|
||||
return;
|
||||
@@ -1514,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) {
|
||||
|
||||
@@ -1526,6 +1734,9 @@ void Run(struct Application *app, int argc, char **argv) {
|
||||
// Define delegate
|
||||
createDelegate(app);
|
||||
|
||||
// Define tray delegate
|
||||
createTrayMenuDelegate();
|
||||
|
||||
// Create the main window
|
||||
createMainWindow(app);
|
||||
|
||||
@@ -1696,7 +1907,7 @@ void Run(struct Application *app, int argc, char **argv) {
|
||||
}
|
||||
|
||||
// Setup initial trays
|
||||
ShowTrayMenusInStore(app->trayMenuStore);
|
||||
ShowTrayMenusInStore(TrayMenuStoreSingleton);
|
||||
|
||||
// Process dialog icons
|
||||
processUserDialogIcons(app);
|
||||
@@ -1705,11 +1916,30 @@ void Run(struct Application *app, int argc, char **argv) {
|
||||
Debug(app, "Run called");
|
||||
msg(app->application, s("run"));
|
||||
|
||||
DestroyApplication(app);
|
||||
|
||||
MEMFREE(internalCode);
|
||||
}
|
||||
|
||||
void SetActivationPolicy(struct Application* app, int policy) {
|
||||
app->activationPolicy = policy;
|
||||
}
|
||||
|
||||
void* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel) {
|
||||
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) {
|
||||
|
||||
// Load the tray icons
|
||||
LoadTrayIcons();
|
||||
@@ -1730,6 +1960,7 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
|
||||
result->startHidden = startHidden;
|
||||
result->decorations = 0;
|
||||
result->logLevel = logLevel;
|
||||
result->hideWindowOnClose = hideWindowOnClose;
|
||||
|
||||
result->mainWindow = NULL;
|
||||
result->mouseEvent = NULL;
|
||||
@@ -1753,17 +1984,30 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
|
||||
result->applicationMenu = NULL;
|
||||
|
||||
// Tray
|
||||
result->trayMenuStore = NewTrayMenuStore();
|
||||
TrayMenuStoreSingleton = NewTrayMenuStore();
|
||||
|
||||
// Context Menus
|
||||
result->contextMenuStore = NewContextMenuStore();
|
||||
|
||||
// Window delegate
|
||||
result->windowDelegate = NULL;
|
||||
|
||||
// Window Appearance
|
||||
result->titlebarAppearsTransparent = 0;
|
||||
result->webviewIsTranparent = 0;
|
||||
|
||||
result->sendMessageToBackend = (ffenestriCallback) messageFromWindowCallback;
|
||||
|
||||
result->shuttingDown = false;
|
||||
|
||||
result->activationPolicy = NSApplicationActivationPolicyRegular;
|
||||
|
||||
result->hasURLHandlers = 0;
|
||||
|
||||
result->startupURL = NULL;
|
||||
|
||||
result->running = false;
|
||||
|
||||
return (void*) result;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,9 @@ func (a *Application) processPlatformSettings() error {
|
||||
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
|
||||
if mac.WebviewIsTransparent {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
// Macros to make it slightly more sane
|
||||
#define msg objc_msgSend
|
||||
|
||||
#define kInternetEventClass 'GURL'
|
||||
#define kAEGetURL 'GURL'
|
||||
#define keyDirectObject '----'
|
||||
|
||||
#define c(str) (id)objc_getClass(str)
|
||||
#define s(str) sel_registerName(str)
|
||||
#define u(str) sel_getUid(str)
|
||||
@@ -66,6 +70,10 @@
|
||||
#define NSControlStateValueOff 0
|
||||
#define NSControlStateValueOn 1
|
||||
|
||||
#define NSApplicationActivationPolicyRegular 0
|
||||
#define NSApplicationActivationPolicyAccessory 1
|
||||
#define NSApplicationActivationPolicyProhibited 2
|
||||
|
||||
// Unbelievably, if the user swaps their button preference
|
||||
// then right buttons are reported as left buttons
|
||||
#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 AddTrayMenu(struct Application* app, const char *);
|
||||
|
||||
void SetActivationPolicy(struct Application* app, int policy);
|
||||
|
||||
void* lookupStringConstant(id constantName);
|
||||
|
||||
void HasURLHandlers(struct Application* app);
|
||||
|
||||
#endif
|
||||
@@ -180,151 +180,151 @@ id processAcceleratorKey(const char *key) {
|
||||
return str("");
|
||||
}
|
||||
|
||||
if( STREQ(key, "Backspace") ) {
|
||||
if( STREQ(key, "backspace") ) {
|
||||
return strunicode(0x0008);
|
||||
}
|
||||
if( STREQ(key, "Tab") ) {
|
||||
if( STREQ(key, "tab") ) {
|
||||
return strunicode(0x0009);
|
||||
}
|
||||
if( STREQ(key, "Return") ) {
|
||||
if( STREQ(key, "return") ) {
|
||||
return strunicode(0x000d);
|
||||
}
|
||||
if( STREQ(key, "Escape") ) {
|
||||
if( STREQ(key, "escape") ) {
|
||||
return strunicode(0x001b);
|
||||
}
|
||||
if( STREQ(key, "Left") ) {
|
||||
if( STREQ(key, "left") ) {
|
||||
return strunicode(0x001c);
|
||||
}
|
||||
if( STREQ(key, "Right") ) {
|
||||
if( STREQ(key, "right") ) {
|
||||
return strunicode(0x001d);
|
||||
}
|
||||
if( STREQ(key, "Up") ) {
|
||||
if( STREQ(key, "up") ) {
|
||||
return strunicode(0x001e);
|
||||
}
|
||||
if( STREQ(key, "Down") ) {
|
||||
if( STREQ(key, "down") ) {
|
||||
return strunicode(0x001f);
|
||||
}
|
||||
if( STREQ(key, "Space") ) {
|
||||
if( STREQ(key, "space") ) {
|
||||
return strunicode(0x0020);
|
||||
}
|
||||
if( STREQ(key, "Delete") ) {
|
||||
if( STREQ(key, "delete") ) {
|
||||
return strunicode(0x007f);
|
||||
}
|
||||
if( STREQ(key, "Home") ) {
|
||||
if( STREQ(key, "home") ) {
|
||||
return strunicode(0x2196);
|
||||
}
|
||||
if( STREQ(key, "End") ) {
|
||||
if( STREQ(key, "end") ) {
|
||||
return strunicode(0x2198);
|
||||
}
|
||||
if( STREQ(key, "Page Up") ) {
|
||||
if( STREQ(key, "page up") ) {
|
||||
return strunicode(0x21de);
|
||||
}
|
||||
if( STREQ(key, "Page Down") ) {
|
||||
if( STREQ(key, "page down") ) {
|
||||
return strunicode(0x21df);
|
||||
}
|
||||
if( STREQ(key, "F1") ) {
|
||||
if( STREQ(key, "f1") ) {
|
||||
return strunicode(0xf704);
|
||||
}
|
||||
if( STREQ(key, "F2") ) {
|
||||
if( STREQ(key, "f2") ) {
|
||||
return strunicode(0xf705);
|
||||
}
|
||||
if( STREQ(key, "F3") ) {
|
||||
if( STREQ(key, "f3") ) {
|
||||
return strunicode(0xf706);
|
||||
}
|
||||
if( STREQ(key, "F4") ) {
|
||||
if( STREQ(key, "f4") ) {
|
||||
return strunicode(0xf707);
|
||||
}
|
||||
if( STREQ(key, "F5") ) {
|
||||
if( STREQ(key, "f5") ) {
|
||||
return strunicode(0xf708);
|
||||
}
|
||||
if( STREQ(key, "F6") ) {
|
||||
if( STREQ(key, "f6") ) {
|
||||
return strunicode(0xf709);
|
||||
}
|
||||
if( STREQ(key, "F7") ) {
|
||||
if( STREQ(key, "f7") ) {
|
||||
return strunicode(0xf70a);
|
||||
}
|
||||
if( STREQ(key, "F8") ) {
|
||||
if( STREQ(key, "f8") ) {
|
||||
return strunicode(0xf70b);
|
||||
}
|
||||
if( STREQ(key, "F9") ) {
|
||||
if( STREQ(key, "f9") ) {
|
||||
return strunicode(0xf70c);
|
||||
}
|
||||
if( STREQ(key, "F10") ) {
|
||||
if( STREQ(key, "f10") ) {
|
||||
return strunicode(0xf70d);
|
||||
}
|
||||
if( STREQ(key, "F11") ) {
|
||||
if( STREQ(key, "f11") ) {
|
||||
return strunicode(0xf70e);
|
||||
}
|
||||
if( STREQ(key, "F12") ) {
|
||||
if( STREQ(key, "f12") ) {
|
||||
return strunicode(0xf70f);
|
||||
}
|
||||
if( STREQ(key, "F13") ) {
|
||||
if( STREQ(key, "f13") ) {
|
||||
return strunicode(0xf710);
|
||||
}
|
||||
if( STREQ(key, "F14") ) {
|
||||
if( STREQ(key, "f14") ) {
|
||||
return strunicode(0xf711);
|
||||
}
|
||||
if( STREQ(key, "F15") ) {
|
||||
if( STREQ(key, "f15") ) {
|
||||
return strunicode(0xf712);
|
||||
}
|
||||
if( STREQ(key, "F16") ) {
|
||||
if( STREQ(key, "f16") ) {
|
||||
return strunicode(0xf713);
|
||||
}
|
||||
if( STREQ(key, "F17") ) {
|
||||
if( STREQ(key, "f17") ) {
|
||||
return strunicode(0xf714);
|
||||
}
|
||||
if( STREQ(key, "F18") ) {
|
||||
if( STREQ(key, "f18") ) {
|
||||
return strunicode(0xf715);
|
||||
}
|
||||
if( STREQ(key, "F19") ) {
|
||||
if( STREQ(key, "f19") ) {
|
||||
return strunicode(0xf716);
|
||||
}
|
||||
if( STREQ(key, "F20") ) {
|
||||
if( STREQ(key, "f20") ) {
|
||||
return strunicode(0xf717);
|
||||
}
|
||||
if( STREQ(key, "F21") ) {
|
||||
if( STREQ(key, "f21") ) {
|
||||
return strunicode(0xf718);
|
||||
}
|
||||
if( STREQ(key, "F22") ) {
|
||||
if( STREQ(key, "f22") ) {
|
||||
return strunicode(0xf719);
|
||||
}
|
||||
if( STREQ(key, "F23") ) {
|
||||
if( STREQ(key, "f23") ) {
|
||||
return strunicode(0xf71a);
|
||||
}
|
||||
if( STREQ(key, "F24") ) {
|
||||
if( STREQ(key, "f24") ) {
|
||||
return strunicode(0xf71b);
|
||||
}
|
||||
if( STREQ(key, "F25") ) {
|
||||
if( STREQ(key, "f25") ) {
|
||||
return strunicode(0xf71c);
|
||||
}
|
||||
if( STREQ(key, "F26") ) {
|
||||
if( STREQ(key, "f26") ) {
|
||||
return strunicode(0xf71d);
|
||||
}
|
||||
if( STREQ(key, "F27") ) {
|
||||
if( STREQ(key, "f27") ) {
|
||||
return strunicode(0xf71e);
|
||||
}
|
||||
if( STREQ(key, "F28") ) {
|
||||
if( STREQ(key, "f28") ) {
|
||||
return strunicode(0xf71f);
|
||||
}
|
||||
if( STREQ(key, "F29") ) {
|
||||
if( STREQ(key, "f29") ) {
|
||||
return strunicode(0xf720);
|
||||
}
|
||||
if( STREQ(key, "F30") ) {
|
||||
if( STREQ(key, "f30") ) {
|
||||
return strunicode(0xf721);
|
||||
}
|
||||
if( STREQ(key, "F31") ) {
|
||||
if( STREQ(key, "f31") ) {
|
||||
return strunicode(0xf722);
|
||||
}
|
||||
if( STREQ(key, "F32") ) {
|
||||
if( STREQ(key, "f32") ) {
|
||||
return strunicode(0xf723);
|
||||
}
|
||||
if( STREQ(key, "F33") ) {
|
||||
if( STREQ(key, "f33") ) {
|
||||
return strunicode(0xf724);
|
||||
}
|
||||
if( STREQ(key, "F34") ) {
|
||||
if( STREQ(key, "f34") ) {
|
||||
return strunicode(0xf725);
|
||||
}
|
||||
if( STREQ(key, "F35") ) {
|
||||
if( STREQ(key, "f35") ) {
|
||||
return strunicode(0xf726);
|
||||
}
|
||||
// if( STREQ(key, "Insert") ) {
|
||||
@@ -336,7 +336,7 @@ id processAcceleratorKey(const char *key) {
|
||||
// if( STREQ(key, "ScrollLock") ) {
|
||||
// return strunicode(0xf72f);
|
||||
// }
|
||||
if( STREQ(key, "NumLock") ) {
|
||||
if( STREQ(key, "numLock") ) {
|
||||
return strunicode(0xf739);
|
||||
}
|
||||
|
||||
@@ -508,20 +508,21 @@ unsigned long parseModifiers(const char **modifiers) {
|
||||
const char *thisModifier = modifiers[0];
|
||||
int count = 0;
|
||||
while( thisModifier != NULL ) {
|
||||
|
||||
// Determine flags
|
||||
if( STREQ(thisModifier, "CmdOrCtrl") ) {
|
||||
if( STREQ(thisModifier, "cmdorctrl") ) {
|
||||
result |= NSEventModifierFlagCommand;
|
||||
}
|
||||
if( STREQ(thisModifier, "OptionOrAlt") ) {
|
||||
if( STREQ(thisModifier, "optionoralt") ) {
|
||||
result |= NSEventModifierFlagOption;
|
||||
}
|
||||
if( STREQ(thisModifier, "Shift") ) {
|
||||
if( STREQ(thisModifier, "shift") ) {
|
||||
result |= NSEventModifierFlagShift;
|
||||
}
|
||||
if( STREQ(thisModifier, "Super") ) {
|
||||
if( STREQ(thisModifier, "super") ) {
|
||||
result |= NSEventModifierFlagCommand;
|
||||
}
|
||||
if( STREQ(thisModifier, "Control") ) {
|
||||
if( STREQ(thisModifier, "ctrl") ) {
|
||||
result |= NSEventModifierFlagControl;
|
||||
}
|
||||
count++;
|
||||
@@ -575,31 +576,7 @@ id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const c
|
||||
return item;
|
||||
}
|
||||
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA) {
|
||||
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);
|
||||
msg(item, s("setImage:"), nsimage);
|
||||
}
|
||||
id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA) {
|
||||
|
||||
// Process Menu Item attributes
|
||||
id dictionary = ALLOC_INIT("NSMutableDictionary");
|
||||
@@ -650,19 +627,61 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
|
||||
|
||||
id attributedString = ALLOC("NSMutableAttributedString");
|
||||
msg(attributedString, s("initWithString:attributes:"), str(title), dictionary);
|
||||
msg(dictionary, s("release"));
|
||||
|
||||
msg(item, s("setAttributedTitle:"), attributedString);
|
||||
msg(attributedString, s("autorelease"));
|
||||
msg(dictionary, s("release"));
|
||||
return attributedString;
|
||||
}
|
||||
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
|
||||
// Create a MenuItemCallbackData
|
||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
|
||||
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
|
||||
if( !alternate ) {
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
||||
s("menuItemCallback:"), key);
|
||||
} else {
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(""));
|
||||
}
|
||||
|
||||
if( tooltip != NULL ) {
|
||||
msg(item, s("setToolTip:"), str(tooltip));
|
||||
}
|
||||
|
||||
// Process image
|
||||
if( image != NULL && strlen(image) > 0) {
|
||||
id data = ALLOC("NSData");
|
||||
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(image), 0);
|
||||
id nsimage = ALLOC("NSImage");
|
||||
msg(nsimage, s("initWithData:"), imageData);
|
||||
if( templateImage ) {
|
||||
msg(nsimage, s("setTemplate:"), YES);
|
||||
}
|
||||
msg(item, s("setImage:"), nsimage);
|
||||
}
|
||||
|
||||
id attributedString = createAttributedString(title, fontName, fontSize, RGBA);
|
||||
msg(item, s("setAttributedTitle:"), attributedString);
|
||||
|
||||
msg(item, s("setEnabled:"), !disabled);
|
||||
msg(item, s("autorelease"));
|
||||
|
||||
// Process modifiers
|
||||
if( modifiers != NULL ) {
|
||||
if( modifiers != NULL && !alternate) {
|
||||
unsigned long modifierFlags = parseModifiers(modifiers);
|
||||
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
|
||||
}
|
||||
|
||||
// alternate
|
||||
if( alternate ) {
|
||||
msg(item, s("setAlternate:"), true);
|
||||
msg(item, s("setKeyEquivalentModifierMask:"), NSEventModifierFlagOption);
|
||||
}
|
||||
msg(parentMenu, s("addItem:"), item);
|
||||
|
||||
return item;
|
||||
@@ -723,6 +742,11 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
label = "(empty)";
|
||||
}
|
||||
|
||||
|
||||
// Is this an alternate menu item?
|
||||
bool alternate = false;
|
||||
getJSONBool(item, "MacAlternate", &alternate);
|
||||
|
||||
const char *menuid = getJSONString(item, "ID");
|
||||
if ( menuid == NULL) {
|
||||
menuid = "";
|
||||
@@ -740,6 +764,9 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
const char *image = getJSONString(item, "Image");
|
||||
const char *fontName = getJSONString(item, "FontName");
|
||||
const char *RGBA = getJSONString(item, "RGBA");
|
||||
bool templateImage = false;
|
||||
getJSONBool(item, "MacTemplateImage", &templateImage);
|
||||
|
||||
int fontSize = 0;
|
||||
getJSONInt(item, "FontSize", &fontSize);
|
||||
|
||||
@@ -775,7 +802,7 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
if( type != NULL ) {
|
||||
|
||||
if( STREQ(type->string_, "Text")) {
|
||||
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA);
|
||||
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage, alternate);
|
||||
}
|
||||
else if ( STREQ(type->string_, "Separator")) {
|
||||
addSeparator(parentMenu);
|
||||
|
||||
@@ -22,7 +22,7 @@ typedef struct _NSRange {
|
||||
#define NSFontWeightUltraLight -0.8
|
||||
#define NSFontWeightThin -0.6
|
||||
#define NSFontWeightLight -0.4
|
||||
#define NSFontWeightRegular 0
|
||||
#define NSFontWeightRegular 0.0
|
||||
#define NSFontWeightMedium 0.23
|
||||
#define NSFontWeightSemibold 0.3
|
||||
#define NSFontWeightBold 0.4
|
||||
@@ -105,10 +105,12 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char
|
||||
|
||||
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key);
|
||||
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA);
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate);
|
||||
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
|
||||
void processMenuData(Menu *menu, JsonNode *menuData);
|
||||
|
||||
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) ;
|
||||
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup);
|
||||
id GetMenu(Menu *menu);
|
||||
id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA);
|
||||
|
||||
#endif //ASSETS_C_MENU_DARWIN_H
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6,6 +6,8 @@
|
||||
#include "traymenu_darwin.h"
|
||||
#include "trayicons.h"
|
||||
|
||||
extern Class trayMenuDelegateClass;
|
||||
|
||||
// A cache for all our tray menu icons
|
||||
// Global because it's a singleton
|
||||
struct hashmap_s trayIconCache;
|
||||
@@ -29,12 +31,23 @@ TrayMenu* NewTrayMenu(const char* menuJSON) {
|
||||
|
||||
result->ID = mustJSONString(processedJSON, "ID");
|
||||
result->label = mustJSONString(processedJSON, "Label");
|
||||
result->icon = mustJSONString(processedJSON, "Icon");
|
||||
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
|
||||
result->icon = mustJSONString(processedJSON, "Image");
|
||||
result->fontName = getJSONString(processedJSON, "FontName");
|
||||
result->RGBA = getJSONString(processedJSON, "RGBA");
|
||||
getJSONBool(processedJSON, "MacTemplateImage", &result->templateImage);
|
||||
result->fontSize = 0;
|
||||
getJSONInt(processedJSON, "FontSize", &result->fontSize);
|
||||
result->tooltip = NULL;
|
||||
result->tooltip = getJSONString(processedJSON, "Tooltip");
|
||||
result->disabled = false;
|
||||
getJSONBool(processedJSON, "Disabled", &result->disabled);
|
||||
|
||||
// Create the menu
|
||||
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
|
||||
result->menu = NewMenu(processedMenu);
|
||||
|
||||
result->delegate = NULL;
|
||||
|
||||
// Init tray status bar item
|
||||
result->statusbaritem = NULL;
|
||||
|
||||
@@ -50,7 +63,7 @@ void DumpTrayMenu(TrayMenu* trayMenu) {
|
||||
}
|
||||
|
||||
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) {
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled) {
|
||||
|
||||
// Exit early if NULL
|
||||
if( trayMenu->label == NULL ) {
|
||||
@@ -58,7 +71,15 @@ void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) {
|
||||
}
|
||||
// Update button label
|
||||
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
|
||||
msg(statusBarButton, s("setTitle:"), str(label));
|
||||
id attributedString = createAttributedString(label, fontName, fontSize, RGBA);
|
||||
|
||||
if( tooltip != NULL ) {
|
||||
msg(statusBarButton, s("setToolTip:"), str(tooltip));
|
||||
}
|
||||
|
||||
msg(statusBarButton, s("setEnabled:"), !disabled);
|
||||
|
||||
msg(statusBarButton, s("setAttributedTitle:"), attributedString);
|
||||
}
|
||||
|
||||
void UpdateTrayIcon(TrayMenu *trayMenu) {
|
||||
@@ -78,12 +99,24 @@ void UpdateTrayIcon(TrayMenu *trayMenu) {
|
||||
}
|
||||
|
||||
id trayImage = hashmap_get(&trayIconCache, trayMenu->icon, strlen(trayMenu->icon));
|
||||
|
||||
// If we don't have the image in the icon cache then assume it's base64 encoded image data
|
||||
if (trayImage == NULL) {
|
||||
id data = ALLOC("NSData");
|
||||
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(trayMenu->icon), 0);
|
||||
trayImage = ALLOC("NSImage");
|
||||
msg(trayImage, s("initWithData:"), imageData);
|
||||
|
||||
if( trayMenu->templateImage ) {
|
||||
msg(trayImage, s("setTemplate:"), YES);
|
||||
}
|
||||
}
|
||||
|
||||
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
|
||||
msg(statusBarButton, s("setImage:"), trayImage);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ShowTrayMenu(TrayMenu* trayMenu) {
|
||||
|
||||
// Create a status bar item if we don't have one
|
||||
@@ -91,7 +124,6 @@ void ShowTrayMenu(TrayMenu* trayMenu) {
|
||||
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
|
||||
trayMenu->statusbaritem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
|
||||
msg(trayMenu->statusbaritem, s("retain"));
|
||||
|
||||
}
|
||||
|
||||
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
|
||||
@@ -101,10 +133,20 @@ void ShowTrayMenu(TrayMenu* trayMenu) {
|
||||
UpdateTrayIcon(trayMenu);
|
||||
|
||||
// Update the label if needed
|
||||
UpdateTrayLabel(trayMenu, trayMenu->label);
|
||||
UpdateTrayLabel(trayMenu, trayMenu->label, trayMenu->fontName, trayMenu->fontSize, trayMenu->RGBA, trayMenu->tooltip, trayMenu->disabled);
|
||||
|
||||
// Update the menu
|
||||
id menu = GetMenu(trayMenu->menu);
|
||||
objc_setAssociatedObject(menu, "trayMenuID", str(trayMenu->ID), OBJC_ASSOCIATION_ASSIGN);
|
||||
|
||||
// Create delegate
|
||||
id trayMenuDelegate = msg((id)trayMenuDelegateClass, s("new"));
|
||||
msg(menu, s("setDelegate:"), trayMenuDelegate);
|
||||
objc_setAssociatedObject(trayMenuDelegate, "menu", menu, OBJC_ASSOCIATION_ASSIGN);
|
||||
|
||||
// Create menu delegate
|
||||
trayMenu->delegate = trayMenuDelegate;
|
||||
|
||||
msg(trayMenu->statusbaritem, s("setMenu:"), menu);
|
||||
}
|
||||
|
||||
@@ -153,6 +195,10 @@ void DeleteTrayMenu(TrayMenu* trayMenu) {
|
||||
trayMenu->statusbaritem = NULL;
|
||||
}
|
||||
|
||||
if ( trayMenu->delegate != NULL ) {
|
||||
msg(trayMenu->delegate, s("release"));
|
||||
}
|
||||
|
||||
// Free the tray menu memory
|
||||
MEMFREE(trayMenu);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,14 @@ typedef struct {
|
||||
const char *label;
|
||||
const char *icon;
|
||||
const char *ID;
|
||||
const char *tooltip;
|
||||
|
||||
bool templateImage;
|
||||
const char *fontName;
|
||||
int fontSize;
|
||||
const char *RGBA;
|
||||
|
||||
bool disabled;
|
||||
|
||||
Menu* menu;
|
||||
|
||||
@@ -21,6 +29,8 @@ typedef struct {
|
||||
|
||||
JsonNode* processedJSON;
|
||||
|
||||
id delegate;
|
||||
|
||||
} TrayMenu;
|
||||
|
||||
TrayMenu* NewTrayMenu(const char *trayJSON);
|
||||
@@ -28,7 +38,7 @@ void DumpTrayMenu(TrayMenu* trayMenu);
|
||||
void ShowTrayMenu(TrayMenu* trayMenu);
|
||||
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);
|
||||
void UpdateTrayIcon(TrayMenu *trayMenu);
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char*);
|
||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled);
|
||||
|
||||
void LoadTrayIcons();
|
||||
void UnloadTrayIcons();
|
||||
|
||||
@@ -16,6 +16,11 @@ TrayMenuStore* NewTrayMenuStore() {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -25,15 +30,19 @@ int dumpTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
||||
}
|
||||
|
||||
void DumpTrayMenuStore(TrayMenuStore* store) {
|
||||
pthread_mutex_lock(&store->lock);
|
||||
hashmap_iterate_pairs(&store->trayMenuMap, dumpTrayMenu, NULL);
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
}
|
||||
|
||||
void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
|
||||
TrayMenu* newMenu = NewTrayMenu(menuJSON);
|
||||
|
||||
pthread_mutex_lock(&store->lock);
|
||||
//TODO: check if there is already an entry for this menu
|
||||
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) {
|
||||
@@ -43,12 +52,13 @@ int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
||||
}
|
||||
|
||||
void ShowTrayMenusInStore(TrayMenuStore* store) {
|
||||
pthread_mutex_lock(&store->lock);
|
||||
if( hashmap_num_entries(&store->trayMenuMap) > 0 ) {
|
||||
hashmap_iterate_pairs(&store->trayMenuMap, showTrayMenu, NULL);
|
||||
}
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
}
|
||||
|
||||
|
||||
int freeTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
||||
DeleteTrayMenu(e->data);
|
||||
return -1;
|
||||
@@ -65,22 +75,39 @@ void DeleteTrayMenuStore(TrayMenuStore *store) {
|
||||
|
||||
// Destroy tray menu map
|
||||
hashmap_destroy(&store->trayMenuMap);
|
||||
|
||||
pthread_mutex_destroy(&store->lock);
|
||||
}
|
||||
|
||||
TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
||||
// 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) {
|
||||
// Get the current menu
|
||||
pthread_mutex_lock(&store->lock);
|
||||
TrayMenu* result = hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
|
||||
if (result == NULL ) {
|
||||
ABORT("Unable to find TrayMenu with ID '%s' in the TrayMenuStore!", menuID);
|
||||
}
|
||||
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) {
|
||||
// Parse the JSON
|
||||
JsonNode *parsedUpdate = mustParseJSON(JSON);
|
||||
@@ -91,7 +118,17 @@ void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
|
||||
|
||||
// Check we have this menu
|
||||
TrayMenu *menu = MustGetTrayMenuFromStore(store, ID);
|
||||
UpdateTrayLabel(menu, Label);
|
||||
|
||||
const char *fontName = getJSONString(parsedUpdate, "FontName");
|
||||
const char *RGBA = getJSONString(parsedUpdate, "RGBA");
|
||||
int fontSize = 0;
|
||||
getJSONInt(parsedUpdate, "FontSize", &fontSize);
|
||||
const char *tooltip = getJSONString(parsedUpdate, "Tooltip");
|
||||
bool disabled = false;
|
||||
getJSONBool(parsedUpdate, "Disabled", &disabled);
|
||||
|
||||
UpdateTrayLabel(menu, Label, fontName, fontSize, RGBA, tooltip, disabled);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -105,7 +142,9 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
// If we don't have a menu, we create one
|
||||
if ( currentMenu == NULL ) {
|
||||
// Store the new menu
|
||||
pthread_mutex_lock(&store->lock);
|
||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
|
||||
// Show it
|
||||
ShowTrayMenu(newMenu);
|
||||
@@ -116,7 +155,9 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
// Save the status bar reference
|
||||
newMenu->statusbaritem = currentMenu->statusbaritem;
|
||||
|
||||
pthread_mutex_lock(&store->lock);
|
||||
hashmap_remove(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID));
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
|
||||
// Delete the current menu
|
||||
DeleteMenu(currentMenu->menu);
|
||||
@@ -125,9 +166,10 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
// Free the tray menu memory
|
||||
MEMFREE(currentMenu);
|
||||
|
||||
pthread_mutex_lock(&store->lock);
|
||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
|
||||
// Show the updated menu
|
||||
ShowTrayMenu(newMenu);
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
#ifndef TRAYMENUSTORE_DARWIN_H
|
||||
#define TRAYMENUSTORE_DARWIN_H
|
||||
|
||||
#include "traymenu_darwin.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
typedef struct {
|
||||
|
||||
int dummy;
|
||||
@@ -13,6 +17,8 @@ typedef struct {
|
||||
// It maps tray IDs to TrayMenu*
|
||||
struct hashmap_s trayMenuMap;
|
||||
|
||||
pthread_mutex_t lock;
|
||||
|
||||
} TrayMenuStore;
|
||||
|
||||
TrayMenuStore* NewTrayMenuStore();
|
||||
@@ -22,6 +28,9 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON);
|
||||
void ShowTrayMenusInStore(TrayMenuStore* store);
|
||||
void DeleteTrayMenuStore(TrayMenuStore* store);
|
||||
|
||||
TrayMenu* GetTrayMenuByID(TrayMenuStore* store, const char* menuID);
|
||||
|
||||
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON);
|
||||
void DeleteTrayMenuInStore(TrayMenuStore* store, const char* id);
|
||||
|
||||
#endif //TRAYMENUSTORE_DARWIN_H
|
||||
|
||||
@@ -40,7 +40,7 @@ func Mkdir(dirname string) error {
|
||||
// Returns error on failure
|
||||
func MkDirs(fullPath string, mode ...os.FileMode) error {
|
||||
var perms os.FileMode
|
||||
perms = 0700
|
||||
perms = 0755
|
||||
if len(mode) == 1 {
|
||||
perms = mode[0]
|
||||
}
|
||||
@@ -243,7 +243,7 @@ func CopyDir(src string, dst string) (err error) {
|
||||
return fmt.Errorf("destination already exists")
|
||||
}
|
||||
|
||||
err = os.MkdirAll(dst, si.Mode())
|
||||
err = MkDirs(dst)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package menumanager
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||
)
|
||||
@@ -34,7 +35,9 @@ type ProcessedMenuItem struct {
|
||||
FontName string `json:",omitempty"`
|
||||
|
||||
// Image - base64 image data
|
||||
Image string `json:",omitempty"`
|
||||
Image string `json:",omitempty"`
|
||||
MacTemplateImage bool `json:", omitempty"`
|
||||
MacAlternate bool `json:", omitempty"`
|
||||
|
||||
// Tooltip
|
||||
Tooltip string `json:",omitempty"`
|
||||
@@ -44,20 +47,22 @@ func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *Pr
|
||||
|
||||
ID := menuItemMap.menuItemToIDMap[menuItem]
|
||||
result := &ProcessedMenuItem{
|
||||
ID: ID,
|
||||
Label: menuItem.Label,
|
||||
Role: menuItem.Role,
|
||||
Accelerator: menuItem.Accelerator,
|
||||
Type: menuItem.Type,
|
||||
Disabled: menuItem.Disabled,
|
||||
Hidden: menuItem.Hidden,
|
||||
Checked: menuItem.Checked,
|
||||
SubMenu: nil,
|
||||
RGBA: menuItem.RGBA,
|
||||
FontSize: menuItem.FontSize,
|
||||
FontName: menuItem.FontName,
|
||||
Image: menuItem.Image,
|
||||
Tooltip: menuItem.Tooltip,
|
||||
ID: ID,
|
||||
Label: menuItem.Label,
|
||||
Role: menuItem.Role,
|
||||
Accelerator: menuItem.Accelerator,
|
||||
Type: menuItem.Type,
|
||||
Disabled: menuItem.Disabled,
|
||||
Hidden: menuItem.Hidden,
|
||||
Checked: menuItem.Checked,
|
||||
SubMenu: nil,
|
||||
RGBA: menuItem.RGBA,
|
||||
FontSize: menuItem.FontSize,
|
||||
FontName: menuItem.FontName,
|
||||
Image: menuItem.Image,
|
||||
MacTemplateImage: menuItem.MacTemplateImage,
|
||||
MacAlternate: menuItem.MacAlternate,
|
||||
Tooltip: menuItem.Tooltip,
|
||||
}
|
||||
|
||||
if menuItem.SubMenu != nil {
|
||||
|
||||
@@ -3,29 +3,39 @@ package menumanager
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var trayMenuID int
|
||||
var trayMenuIDMutex sync.Mutex
|
||||
|
||||
func generateTrayID() string {
|
||||
var idStr string
|
||||
trayMenuIDMutex.Lock()
|
||||
result := fmt.Sprintf("%d", trayMenuID)
|
||||
idStr = strconv.Itoa(trayMenuID)
|
||||
trayMenuID++
|
||||
trayMenuIDMutex.Unlock()
|
||||
return result
|
||||
return idStr
|
||||
}
|
||||
|
||||
type TrayMenu struct {
|
||||
ID string
|
||||
Label string
|
||||
Icon string
|
||||
menuItemMap *MenuItemMap
|
||||
menu *menu.Menu
|
||||
ProcessedMenu *WailsMenu
|
||||
ID string
|
||||
Label string
|
||||
FontSize int
|
||||
FontName string
|
||||
Disabled bool
|
||||
Tooltip string `json:",omitempty"`
|
||||
Image string
|
||||
MacTemplateImage bool
|
||||
RGBA string
|
||||
menuItemMap *MenuItemMap
|
||||
menu *menu.Menu
|
||||
ProcessedMenu *WailsMenu
|
||||
trayMenu *menu.TrayMenu
|
||||
}
|
||||
|
||||
func (t *TrayMenu) AsJSON() (string, error) {
|
||||
@@ -39,10 +49,17 @@ func (t *TrayMenu) AsJSON() (string, error) {
|
||||
func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||
|
||||
result := &TrayMenu{
|
||||
Label: trayMenu.Label,
|
||||
Icon: trayMenu.Icon,
|
||||
menu: trayMenu.Menu,
|
||||
menuItemMap: NewMenuItemMap(),
|
||||
Label: trayMenu.Label,
|
||||
FontName: trayMenu.FontName,
|
||||
FontSize: trayMenu.FontSize,
|
||||
Disabled: trayMenu.Disabled,
|
||||
Tooltip: trayMenu.Tooltip,
|
||||
Image: trayMenu.Image,
|
||||
MacTemplateImage: trayMenu.MacTemplateImage,
|
||||
menu: trayMenu.Menu,
|
||||
RGBA: trayMenu.RGBA,
|
||||
menuItemMap: NewMenuItemMap(),
|
||||
trayMenu: trayMenu,
|
||||
}
|
||||
|
||||
result.menuItemMap.AddMenu(trayMenu.Menu)
|
||||
@@ -51,6 +68,28 @@ func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *Manager) OnTrayMenuOpen(id string) {
|
||||
trayMenu, ok := m.trayMenus[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if trayMenu.trayMenu.OnOpen == nil {
|
||||
return
|
||||
}
|
||||
go trayMenu.trayMenu.OnOpen()
|
||||
}
|
||||
|
||||
func (m *Manager) OnTrayMenuClose(id string) {
|
||||
trayMenu, ok := m.trayMenus[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if trayMenu.trayMenu.OnClose == nil {
|
||||
return
|
||||
}
|
||||
go trayMenu.trayMenu.OnClose()
|
||||
}
|
||||
|
||||
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||
newTrayMenu := NewTrayMenu(trayMenu)
|
||||
|
||||
@@ -65,6 +104,14 @@ func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||
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
|
||||
func (m *Manager) SetTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
|
||||
@@ -102,13 +149,27 @@ func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) {
|
||||
}
|
||||
|
||||
type LabelUpdate struct {
|
||||
ID string
|
||||
Label string
|
||||
ID string
|
||||
Label string
|
||||
FontName string
|
||||
FontSize int
|
||||
RGBA string
|
||||
Disabled bool
|
||||
Tooltip string
|
||||
Image string
|
||||
MacTemplateImage bool
|
||||
}
|
||||
|
||||
update := &LabelUpdate{
|
||||
ID: trayID,
|
||||
Label: trayMenu.Label,
|
||||
ID: trayID,
|
||||
Label: trayMenu.Label,
|
||||
FontName: trayMenu.FontName,
|
||||
FontSize: trayMenu.FontSize,
|
||||
Disabled: trayMenu.Disabled,
|
||||
Tooltip: trayMenu.Tooltip,
|
||||
Image: trayMenu.Image,
|
||||
MacTemplateImage: trayMenu.MacTemplateImage,
|
||||
RGBA: trayMenu.RGBA,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(update)
|
||||
|
||||
@@ -37,6 +37,7 @@ type Client interface {
|
||||
SetTrayMenu(trayMenuJSON string)
|
||||
UpdateTrayMenuLabel(JSON string)
|
||||
UpdateContextMenu(contextMenuJSON string)
|
||||
DeleteTrayMenuByID(id string)
|
||||
}
|
||||
|
||||
// DispatchClient is what the frontends use to interface with the
|
||||
|
||||
@@ -27,7 +27,7 @@ func dialogMessageParser(message string) (*parsedMessage, error) {
|
||||
if idx < 0 {
|
||||
return nil, fmt.Errorf("Invalid dialog response message format: %+v", message)
|
||||
}
|
||||
callbackID := message[:idx+1]
|
||||
callbackID := message[:idx]
|
||||
payloadData := message[idx+1:]
|
||||
|
||||
switch dialogType {
|
||||
|
||||
@@ -32,6 +32,14 @@ func menuMessageParser(message string) (*parsedMessage, error) {
|
||||
callbackid := message[2:]
|
||||
topic = "menu:clicked"
|
||||
data = callbackid
|
||||
case 'o':
|
||||
callbackid := message[2:]
|
||||
topic = "menu:ontrayopen"
|
||||
data = callbackid
|
||||
case 'c':
|
||||
callbackid := message[2:]
|
||||
topic = "menu:ontrayclose"
|
||||
data = callbackid
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid menu message: %s", message)
|
||||
}
|
||||
|
||||
@@ -21,13 +21,14 @@ var messageParsers = map[byte]func(string) (*parsedMessage, error){
|
||||
'M': menuMessageParser,
|
||||
'T': trayMessageParser,
|
||||
'X': contextMenusMessageParser,
|
||||
'U': urlMessageParser,
|
||||
}
|
||||
|
||||
// Parse will attempt to parse the given message
|
||||
func Parse(message string) (*parsedMessage, error) {
|
||||
|
||||
if len(message) == 0 {
|
||||
return nil, fmt.Errorf("MessageParser received blank message");
|
||||
return nil, fmt.Errorf("MessageParser received blank message")
|
||||
}
|
||||
|
||||
parseMethod := messageParsers[message[0]]
|
||||
|
||||
@@ -40,7 +40,8 @@ func systemMessageParser(message string) (*parsedMessage, error) {
|
||||
// This is our startup hook - the frontend is now ready
|
||||
case 'S':
|
||||
topic := "hooks:startup"
|
||||
responseMessage = &parsedMessage{Topic: topic, Data: nil}
|
||||
startupURL := message[1:]
|
||||
responseMessage = &parsedMessage{Topic: topic, Data: startupURL}
|
||||
default:
|
||||
return nil, fmt.Errorf("Invalid message to systemMessageParser()")
|
||||
}
|
||||
|
||||
20
v2/internal/messagedispatcher/message/url.go
Normal file
20
v2/internal/messagedispatcher/message/url.go
Normal 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])
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package messagedispatcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -24,7 +25,6 @@ type Dispatcher struct {
|
||||
dialogChannel <-chan *servicebus.Message
|
||||
systemChannel <-chan *servicebus.Message
|
||||
menuChannel <-chan *servicebus.Message
|
||||
running bool
|
||||
|
||||
servicebus *servicebus.ServiceBus
|
||||
logger logger.CustomLogger
|
||||
@@ -32,6 +32,13 @@ type Dispatcher struct {
|
||||
// Clients
|
||||
clients map[string]*DispatchClient
|
||||
lock sync.RWMutex
|
||||
|
||||
// Context for cancellation
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
// internal wait group
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// New dispatcher. Needs a service bus to send to.
|
||||
@@ -76,6 +83,9 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create context
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
result := &Dispatcher{
|
||||
servicebus: servicebus,
|
||||
eventChannel: eventChannel,
|
||||
@@ -87,6 +97,8 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher,
|
||||
dialogChannel: dialogChannel,
|
||||
systemChannel: systemChannel,
|
||||
menuChannel: menuChannel,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
@@ -97,15 +109,18 @@ func (d *Dispatcher) Start() error {
|
||||
|
||||
d.logger.Trace("Starting")
|
||||
|
||||
d.running = true
|
||||
d.wg.Add(1)
|
||||
|
||||
// Spin off a go routine
|
||||
go func() {
|
||||
for d.running {
|
||||
defer d.logger.Trace("Shutdown")
|
||||
for {
|
||||
select {
|
||||
case <-d.ctx.Done():
|
||||
d.wg.Done()
|
||||
return
|
||||
case <-d.quitChannel:
|
||||
d.processQuit()
|
||||
d.running = false
|
||||
case resultMessage := <-d.resultChannel:
|
||||
d.processCallResult(resultMessage)
|
||||
case eventMessage := <-d.eventChannel:
|
||||
@@ -120,9 +135,6 @@ func (d *Dispatcher) Start() error {
|
||||
d.processMenuMessage(menuMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// Call shutdown
|
||||
d.shutdown()
|
||||
}()
|
||||
|
||||
return nil
|
||||
@@ -136,10 +148,6 @@ func (d *Dispatcher) processQuit() {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Dispatcher) shutdown() {
|
||||
d.logger.Trace("Shutdown")
|
||||
}
|
||||
|
||||
// RegisterClient will register the given callback with the dispatcher
|
||||
// and return a DispatchClient that the caller can use to send messages
|
||||
func (d *Dispatcher) RegisterClient(client Client) *DispatchClient {
|
||||
@@ -519,8 +527,24 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
|
||||
for _, client := range d.clients {
|
||||
client.frontend.UpdateTrayMenuLabel(updatedTrayMenuLabel)
|
||||
}
|
||||
case "deletetraymenu":
|
||||
traymenuid, ok := result.Data().(string)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid data for 'menufrontend:updatetraymenulabel' : %#v",
|
||||
result.Data())
|
||||
return
|
||||
}
|
||||
|
||||
for _, client := range d.clients {
|
||||
client.frontend.DeleteTrayMenuByID(traymenuid)
|
||||
}
|
||||
|
||||
default:
|
||||
d.logger.Error("Unknown menufrontend command: %s", command)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Dispatcher) Close() {
|
||||
d.cancel()
|
||||
d.wg.Wait()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
||||
@@ -16,11 +17,14 @@ type Process struct {
|
||||
|
||||
// NewProcess creates a new process struct
|
||||
func NewProcess(logger *clilogger.CLILogger, cmd string, args ...string) *Process {
|
||||
return &Process{
|
||||
result := &Process{
|
||||
logger: logger,
|
||||
cmd: exec.Command(cmd, args...),
|
||||
exitChannel: make(chan bool, 1),
|
||||
}
|
||||
result.cmd.Stdout = os.Stdout
|
||||
result.cmd.Stderr = os.Stderr
|
||||
return result
|
||||
}
|
||||
|
||||
// Start the process
|
||||
@@ -35,7 +39,12 @@ func (p *Process) Start() error {
|
||||
|
||||
go func(cmd *exec.Cmd, running *bool, logger *clilogger.CLILogger, exitChannel chan bool) {
|
||||
logger.Println("Starting process (PID: %d)", cmd.Process.Pid)
|
||||
cmd.Wait()
|
||||
err := cmd.Wait()
|
||||
if err != nil {
|
||||
if err.Error() != "signal: killed" {
|
||||
logger.Fatal("Fatal error from app: " + err.Error())
|
||||
}
|
||||
}
|
||||
logger.Println("Exiting process (PID: %d)", cmd.Process.Pid)
|
||||
*running = false
|
||||
exitChannel <- true
|
||||
|
||||
@@ -65,7 +65,7 @@ export function SetBindings(bindingsMap) {
|
||||
window.backend[packageName][structName][methodName] = function () {
|
||||
|
||||
// No timeout by default
|
||||
var timeout = 0;
|
||||
let timeout = 0;
|
||||
|
||||
// Actual function
|
||||
function dynamic() {
|
||||
@@ -89,19 +89,3 @@ 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;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Error } from './log';
|
||||
import { SendMessage } from 'ipc';
|
||||
|
||||
// Defines a single listener with a maximum number of times to callback
|
||||
|
||||
/**
|
||||
* The Listener class defines a listener! :-)
|
||||
*
|
||||
@@ -43,7 +44,7 @@ class Listener {
|
||||
}
|
||||
}
|
||||
|
||||
var eventListeners = {};
|
||||
let eventListeners = {};
|
||||
|
||||
/**
|
||||
* Registers an event listener that will be invoked `maxCallbacks` times before being destroyed
|
||||
@@ -96,7 +97,7 @@ export function Once(eventName, callback) {
|
||||
function notifyListeners(eventData) {
|
||||
|
||||
// Get the event name
|
||||
var eventName = eventData.name;
|
||||
let eventName = eventData.name;
|
||||
|
||||
// Check if we have any listeners for this event
|
||||
if (eventListeners[eventName]) {
|
||||
@@ -110,7 +111,7 @@ function notifyListeners(eventData) {
|
||||
// Get next listener
|
||||
const listener = eventListeners[eventName][count];
|
||||
|
||||
var data = eventData.data;
|
||||
let data = eventData.data;
|
||||
|
||||
// Do the callback
|
||||
const destroy = listener.Callback(data);
|
||||
@@ -120,7 +121,7 @@ function notifyListeners(eventData) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update callbacks with new list of listners
|
||||
// Update callbacks with new list of listeners
|
||||
eventListeners[eventName] = newEventListenerList;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,4 @@ export function Init() {
|
||||
|
||||
// Do platform specific Init
|
||||
Platform.Init();
|
||||
|
||||
window.wailsloader.runtime = true;
|
||||
}
|
||||
@@ -27,7 +27,7 @@ export function Init() {
|
||||
// Setup drag handler
|
||||
// Based on code from: https://github.com/patr0nus/DeskGap
|
||||
window.addEventListener('mousedown', function (e) {
|
||||
var currentElement = e.target;
|
||||
let currentElement = e.target;
|
||||
while (currentElement != null) {
|
||||
if (currentElement.hasAttribute('data-wails-no-drag')) {
|
||||
break;
|
||||
|
||||
@@ -1 +1 @@
|
||||
bridge.js
|
||||
src
|
||||
1738
v2/internal/runtime/js/runtime/bridge.js
Normal file
1738
v2/internal/runtime/js/runtime/bridge.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,13 +9,25 @@ The lightweight framework for web-like apps
|
||||
*/
|
||||
/* jshint esversion: 6 */
|
||||
|
||||
import bridge from './bridge';
|
||||
|
||||
/**
|
||||
* Initialises the Wails runtime
|
||||
* ready will execute the callback when Wails has loaded
|
||||
* and initialised.
|
||||
*
|
||||
* @param {function} callback
|
||||
*/
|
||||
function Init(callback) {
|
||||
window.wails._.Init(callback);
|
||||
function ready(callback) {
|
||||
|
||||
// If window.wails exists, we are ready
|
||||
if( window.wails ) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
// If not we need to setup the bridge
|
||||
bridge.InitBridge(callback);
|
||||
}
|
||||
|
||||
module.exports = Init;
|
||||
module.exports = {
|
||||
ready: ready,
|
||||
};
|
||||
@@ -23,7 +23,7 @@ module.exports = {
|
||||
Browser: Browser,
|
||||
Dialog: Dialog,
|
||||
Events: Events,
|
||||
Init: Init,
|
||||
ready: Init.ready,
|
||||
Log: Log,
|
||||
System: System,
|
||||
Store: Store,
|
||||
|
||||
2
v2/internal/runtime/js/runtime/package-lock.json
generated
2
v2/internal/runtime/js/runtime/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wails/runtime",
|
||||
"version": "1.2.13",
|
||||
"version": "1.2.24",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wails/runtime",
|
||||
"version": "1.2.22",
|
||||
"version": "1.3.12",
|
||||
"description": "Wails V2 Javascript runtime library",
|
||||
"main": "main.js",
|
||||
"types": "runtime.d.ts",
|
||||
|
||||
56
v2/internal/runtime/js/runtime/src/Menu.svelte
Normal file
56
v2/internal/runtime/js/runtime/src/Menu.svelte
Normal file
@@ -0,0 +1,56 @@
|
||||
<script>
|
||||
|
||||
export let menu;
|
||||
|
||||
export let hidden = true;
|
||||
</script>
|
||||
|
||||
{#if !hidden}
|
||||
<div class="menu">
|
||||
{#if menu.Menu }
|
||||
{#each menu.Menu.Items as menuItem}
|
||||
<div class="menuitem">
|
||||
{#if menuItem.Image }
|
||||
<div><img alt="" src="data:image/png;base64,{menuItem.Image}"/></div>
|
||||
{/if}
|
||||
<div class="menulabel">{menuItem.Label}</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
|
||||
.menu {
|
||||
padding: 3px;
|
||||
background-color: #0008;
|
||||
color: #EEF;
|
||||
border-radius: 5px;
|
||||
margin-top: 5px;
|
||||
position: absolute;
|
||||
backdrop-filter: blur(3px) saturate(160%) contrast(45%) brightness(140%);
|
||||
border: 1px solid rgb(88,88,88);
|
||||
box-shadow: 0 0 1px rgb(146,146,148) inset;
|
||||
|
||||
}
|
||||
|
||||
.menuitem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 1px 5px;
|
||||
}
|
||||
|
||||
.menuitem:hover {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: rgb(57,131,223);
|
||||
padding: 1px 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.menuitem img {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
</style>
|
||||
72
v2/internal/runtime/js/runtime/src/Menubar.svelte
Normal file
72
v2/internal/runtime/js/runtime/src/Menubar.svelte
Normal file
@@ -0,0 +1,72 @@
|
||||
<script>
|
||||
|
||||
import {menuVisible} from './store'
|
||||
import {fade} from 'svelte/transition';
|
||||
|
||||
import {trays} from './store'
|
||||
import TrayMenu from "./TrayMenu.svelte";
|
||||
import {onMount} from "svelte";
|
||||
|
||||
let time = new Date();
|
||||
$: day = time.toLocaleString("default", { weekday: "short" })
|
||||
$: dom = time.getDate()
|
||||
$: mon = time.toLocaleString("default", { month: "short" })
|
||||
$: currentTime = time.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }).toLowerCase()
|
||||
$: dateTimeString = `${day} ${dom} ${mon} ${currentTime}`
|
||||
|
||||
onMount(() => {
|
||||
const interval = setInterval(() => {
|
||||
time = new Date();
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
});
|
||||
|
||||
function handleKeydown(e) {
|
||||
// Backtick toggle
|
||||
if( e.keyCode == 192 ) {
|
||||
menuVisible.update( (current) => {
|
||||
return !current;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#if $menuVisible }
|
||||
<div class="wails-menubar" transition:fade>
|
||||
<span class="tray-menus">
|
||||
{#each $trays as tray}
|
||||
<TrayMenu {tray}/>
|
||||
{/each}
|
||||
<span class="time">{dateTimeString}</span>
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<svelte:window on:keydown={handleKeydown}/>
|
||||
|
||||
<style>
|
||||
|
||||
.tray-menus {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.wails-menubar { position: relative;
|
||||
display: block;
|
||||
top: 0;
|
||||
height: 2rem;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #b3b3b3;
|
||||
box-shadow: 0 0 10px 0 #33333360;
|
||||
}
|
||||
.time {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 1.5rem;
|
||||
overflow: visible;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
55
v2/internal/runtime/js/runtime/src/Overlay.svelte
Normal file
55
v2/internal/runtime/js/runtime/src/Overlay.svelte
Normal file
@@ -0,0 +1,55 @@
|
||||
<script>
|
||||
|
||||
import { overlayVisible } from './store'
|
||||
import { fade, } from 'svelte/transition';
|
||||
|
||||
</script>
|
||||
|
||||
{#if $overlayVisible }
|
||||
<div class="wails-reconnect-overlay" transition:fade="{{ duration: 200 }}">
|
||||
<div class="wails-reconnect-overlay-content">
|
||||
<div class="wails-reconnect-overlay-loadingspinner"></div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.wails-reconnect-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
backdrop-filter: blur(20px) saturate(160%) contrast(45%) brightness(140%);
|
||||
z-index: 999999
|
||||
}
|
||||
|
||||
.wails-reconnect-overlay-content {
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
margin: 0;
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC8AAAAuCAMAAACPpbA7AAAAflBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAAAAAAAAAAAABAQEEBAQAAAAAAAAEBAQAAAADAwMAAAABAQEAAAAAAAAAAAAAAAAAAAACAgICAgIBAQEAAAAAAAAAAAAAAAAAAAACAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBQWCC3waAAAAKXRSTlMALgUMIBk0+xEqJs70Xhb3lu3EjX2EZTlv5eHXvbarQj3cdmpXSqOeUDwaqNAAAAKCSURBVEjHjZTntqsgEIUPVVCwtxg1vfD+L3hHRe8K6snZf+KKn8OewvzsSSeXLruLnz+KHs0gr6DkT3xsRkU6VVn4Ha/UxLe1Z4y64i847sykPBh/AvQ7ry3eFN70oKrfcBJYvm/tQ1qxP4T3emXPeXAkvodPUvtdjbhk+Ft4c0hslTiXVOzxOJ15NWUblQhRsdu3E1AfCjj3Gdm18zSOsiH8Lk4TB480ksy62fiqNo4OpyU8O21l6+hyRtS6z8r1pHlmle5sR1/WXS6Mq2Nl+YeKt3vr+vdH/q4O68tzXuwkiZmngYb4R8Co1jh0+Ww2UTyWxBvtyxLO7QVjO3YOD/lWZpbXDGellFG2Mws58mMnjVZSn7p+XvZ6IF4nn02OJZV0aTO22arp/DgLPtrgpVoi6TPbZm4XQBjY159w02uO0BDdYsfrOEi0M2ulRXlCIPAOuN1NOVhi+riBR3dgwQplYsZRZJLXq23Mlo5njkbY0rZFu3oiNIYG2kqsbVz67OlNuZZIOlfxHDl0UpyRX86z/OYC/3qf1A1xTrMp/PWWM4ePzf8DDp1nesQRpcFk7BlwdzN08ZIALJpCaciQXO0f6k4dnuT/Ewg4l7qSTNzm2SykdHn6GJ12mWc6aCNj/g1cTXpB8YFfr0uVc96aFkkqiIiX4nO+salKwGtIkvfB+Ja8DxMeD3hIXP5mTOYPB4eVT0+32I5ykvPZjesnkGgIREgYnmLrPb0PdV3hoLup2TjcGBPM4mgsfF5BrawZR4/GpzYQzQfrUZCf0TCWYo2DqhdhTJBQ6j4xqmmLN5LjdRIY8LWExiFUsSrza/nmFBqw3I9tEZB9h0lIQSO9if8DkISDAj8CDawAAAAASUVORK5CYII=);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center
|
||||
}
|
||||
|
||||
.wails-reconnect-overlay-loadingspinner {
|
||||
pointer-events: none;
|
||||
width: 2.5em;
|
||||
height: 2.5em;
|
||||
border: .4em solid transparent;
|
||||
border-color: #f00 #eee0 #f00 #eee0;
|
||||
border-radius: 50%;
|
||||
animation: loadingspin 1s linear infinite;
|
||||
margin: auto;
|
||||
padding: 2.5em
|
||||
}
|
||||
|
||||
@keyframes loadingspin {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
65
v2/internal/runtime/js/runtime/src/TrayMenu.svelte
Normal file
65
v2/internal/runtime/js/runtime/src/TrayMenu.svelte
Normal file
@@ -0,0 +1,65 @@
|
||||
<script>
|
||||
import Menu from "./Menu.svelte";
|
||||
import { selectedMenu } from "./store";
|
||||
|
||||
export let tray = null;
|
||||
|
||||
$: hidden = $selectedMenu !== tray;
|
||||
|
||||
function closeMenu() {
|
||||
selectedMenu.set(null);
|
||||
}
|
||||
|
||||
function trayClicked() {
|
||||
if ( $selectedMenu !== tray ) {
|
||||
selectedMenu.set(tray);
|
||||
} else {
|
||||
selectedMenu.set(null);
|
||||
}
|
||||
}
|
||||
// Source: https://svelte.dev/repl/0ace7a508bd843b798ae599940a91783?version=3.16.7
|
||||
/** Dispatch event on click outside of node */
|
||||
function clickOutside(node) {
|
||||
|
||||
const handleClick = event => {
|
||||
if (node && !node.contains(event.target) && !event.defaultPrevented) {
|
||||
node.dispatchEvent(
|
||||
new CustomEvent('click_outside', node)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('click', handleClick, true);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
document.removeEventListener('click', handleClick, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<span class="tray-menu" use:clickOutside on:click_outside={closeMenu}>
|
||||
<!--{#if tray.Image && tray.Image.length > 0}-->
|
||||
<!-- <img alt="" src="data:image/png;base64,{tray.Image}"/>-->
|
||||
<!--{/if}-->
|
||||
<span class="label" on:click={trayClicked}>{tray.Label}</span>
|
||||
{#if tray.ProcessedMenu }
|
||||
<Menu menu="{tray.ProcessedMenu}" {hidden}/>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<style>
|
||||
|
||||
.tray-menu {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
overflow: visible;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.label {
|
||||
text-align: right;
|
||||
padding-right: 10px;
|
||||
}
|
||||
</style>
|
||||
9
v2/internal/runtime/js/runtime/src/log.js
Normal file
9
v2/internal/runtime/js/runtime/src/log.js
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
export function log(message) {
|
||||
// eslint-disable-next-line
|
||||
console.log(
|
||||
'%c wails bridge %c ' + message + ' ',
|
||||
'background: #aa0000; color: #fff; border-radius: 3px 0px 0px 3px; padding: 1px; font-size: 0.7rem',
|
||||
'background: #009900; color: #fff; border-radius: 0px 3px 3px 0px; padding: 1px; font-size: 0.7rem'
|
||||
);
|
||||
}
|
||||
45
v2/internal/runtime/js/runtime/src/main.js
Normal file
45
v2/internal/runtime/js/runtime/src/main.js
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The lightweight framework for web-like apps
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
/* jshint esversion: 6 */
|
||||
|
||||
import Overlay from './Overlay.svelte';
|
||||
import MenuBar from './Menubar.svelte';
|
||||
import {showOverlay} from "./store";
|
||||
import {StartWebsocket} from "./websocket";
|
||||
|
||||
let components = {};
|
||||
|
||||
function setupMenuBar() {
|
||||
components.menubar = new MenuBar({
|
||||
target: document.body,
|
||||
});
|
||||
}
|
||||
|
||||
// Sets up the overlay
|
||||
function setupOverlay() {
|
||||
components.overlay = new Overlay({
|
||||
target: document.body,
|
||||
anchor: document.querySelector('#wails-bridge'),
|
||||
});
|
||||
}
|
||||
|
||||
export function InitBridge(callback) {
|
||||
|
||||
setupMenuBar()
|
||||
|
||||
// Setup the overlay
|
||||
setupOverlay();
|
||||
|
||||
// Start by showing the overlay...
|
||||
showOverlay();
|
||||
|
||||
// ...and attempt to connect
|
||||
StartWebsocket(callback);
|
||||
}
|
||||
525
v2/internal/runtime/js/runtime/src/package-lock.json
generated
Normal file
525
v2/internal/runtime/js/runtime/src/package-lock.json
generated
Normal file
@@ -0,0 +1,525 @@
|
||||
{
|
||||
"name": "bridge",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
|
||||
"integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.12.13"
|
||||
}
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
|
||||
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz",
|
||||
"integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
"chalk": "^2.0.0",
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-commonjs": {
|
||||
"version": "17.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-17.1.0.tgz",
|
||||
"integrity": "sha512-PoMdXCw0ZyvjpCMT5aV4nkL0QywxP29sODQsSGeDpr/oI49Qq9tRtAsb/LbYbDzFlOydVEqHmmZWFtXJEAX9ew==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^3.1.0",
|
||||
"commondir": "^1.0.1",
|
||||
"estree-walker": "^2.0.1",
|
||||
"glob": "^7.1.6",
|
||||
"is-reference": "^1.2.1",
|
||||
"magic-string": "^0.25.7",
|
||||
"resolve": "^1.17.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-node-resolve": {
|
||||
"version": "11.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.1.1.tgz",
|
||||
"integrity": "sha512-zlBXR4eRS+2m79TsUZWhsd0slrHUYdRx4JF+aVQm+MI0wsKdlpC2vlDVjmlGvtZY1vsefOT9w3JxvmWSBei+Lg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^3.1.0",
|
||||
"@types/resolve": "1.17.1",
|
||||
"builtin-modules": "^3.1.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"is-module": "^1.0.0",
|
||||
"resolve": "^1.19.0"
|
||||
}
|
||||
},
|
||||
"@rollup/pluginutils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
|
||||
"integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/estree": "0.0.39",
|
||||
"estree-walker": "^1.0.1",
|
||||
"picomatch": "^2.2.2"
|
||||
}
|
||||
},
|
||||
"@types/estree": {
|
||||
"version": "0.0.39",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
|
||||
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "14.14.25",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.25.tgz",
|
||||
"integrity": "sha512-EPpXLOVqDvisVxtlbvzfyqSsFeQxltFbluZNRndIb8tr9KiBnYNLzrc1N3pyKUCww2RNrfHDViqDWWE1LCJQtQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/resolve": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
|
||||
"integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"buffer-from": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
|
||||
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
|
||||
"dev": true
|
||||
},
|
||||
"builtin-modules": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz",
|
||||
"integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||
"dev": true
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"commondir": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||
"dev": true
|
||||
},
|
||||
"deepmerge": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
|
||||
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
|
||||
"dev": true
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||
"dev": true
|
||||
},
|
||||
"estree-walker": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
|
||||
"integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
|
||||
"dev": true
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||
"dev": true
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
||||
"dev": true
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"is-core-module": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz",
|
||||
"integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"is-module": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||
"integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
|
||||
"dev": true
|
||||
},
|
||||
"is-reference": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
|
||||
"integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"jest-worker": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
|
||||
"integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"merge-stream": "^2.0.0",
|
||||
"supports-color": "^7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"magic-string": {
|
||||
"version": "0.25.7",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
|
||||
"integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"sourcemap-codec": "^1.4.4"
|
||||
}
|
||||
},
|
||||
"merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"dev": true
|
||||
},
|
||||
"path-parse": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
||||
"dev": true
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
|
||||
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
|
||||
"dev": true
|
||||
},
|
||||
"randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"require-relative": {
|
||||
"version": "0.8.7",
|
||||
"resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz",
|
||||
"integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=",
|
||||
"dev": true
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz",
|
||||
"integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-core-module": "^2.1.0",
|
||||
"path-parse": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"rollup": {
|
||||
"version": "2.38.5",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.38.5.tgz",
|
||||
"integrity": "sha512-VoWt8DysFGDVRGWuHTqZzT02J0ASgjVq/hPs9QcBOGMd7B+jfTr/iqMVEyOi901rE3xq+Deq66GzIT1yt7sGwQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fsevents": "~2.3.1"
|
||||
}
|
||||
},
|
||||
"rollup-plugin-svelte": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-svelte/-/rollup-plugin-svelte-7.1.0.tgz",
|
||||
"integrity": "sha512-vopCUq3G+25sKjwF5VilIbiY6KCuMNHP1PFvx2Vr3REBNMDllKHFZN2B9jwwC+MqNc3UPKkjXnceLPEjTjXGXg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"require-relative": "^0.8.7",
|
||||
"rollup-pluginutils": "^2.8.2"
|
||||
}
|
||||
},
|
||||
"rollup-plugin-terser": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz",
|
||||
"integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"jest-worker": "^26.2.1",
|
||||
"serialize-javascript": "^4.0.0",
|
||||
"terser": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"rollup-pluginutils": {
|
||||
"version": "2.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
|
||||
"integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"estree-walker": "^0.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"estree-walker": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
|
||||
"integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true
|
||||
},
|
||||
"serialize-javascript": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
|
||||
"integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
|
||||
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"sourcemap-codec": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
|
||||
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"svelte": {
|
||||
"version": "3.32.2",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.32.2.tgz",
|
||||
"integrity": "sha512-Zxh1MQQl/+vnToKbU1Per+PoMN8Jb2MeKJcGxiOsCGR677hXw7jkMfbnNXq33+dxIzV/HfA4xtoSPJrqeB0VUg==",
|
||||
"dev": true
|
||||
},
|
||||
"terser": {
|
||||
"version": "5.5.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.5.1.tgz",
|
||||
"integrity": "sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "^2.20.0",
|
||||
"source-map": "~0.7.2",
|
||||
"source-map-support": "~0.5.19"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
21
v2/internal/runtime/js/runtime/src/package.json
Normal file
21
v2/internal/runtime/js/runtime/src/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "bridge",
|
||||
"version": "1.0.0",
|
||||
"description": "Wails bridge",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "npx rollup -c",
|
||||
"watch": "npx rollup -c -w"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^17.1.0",
|
||||
"@rollup/plugin-node-resolve": "^11.1.1",
|
||||
"rollup": "^2.38.5",
|
||||
"rollup-plugin-svelte": "^7.1.0",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"svelte": "^3.32.2"
|
||||
}
|
||||
}
|
||||
37
v2/internal/runtime/js/runtime/src/rollup.config.js
Normal file
37
v2/internal/runtime/js/runtime/src/rollup.config.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
// import commonjs from '@rollup/plugin-commonjs';
|
||||
import svelte from 'rollup-plugin-svelte';
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
|
||||
export default [
|
||||
// browser-friendly UMD build
|
||||
{
|
||||
input: 'main.js',
|
||||
output: {
|
||||
name: 'bridge',
|
||||
file: '../bridge.js',
|
||||
format: 'umd',
|
||||
exports: "named"
|
||||
},
|
||||
plugins: [
|
||||
svelte({
|
||||
// Optionally, preprocess components with svelte.preprocess:
|
||||
// https://svelte.dev/docs#svelte_preprocess
|
||||
// preprocess: {
|
||||
// style: ({content}) => {
|
||||
// return transformStyles(content);
|
||||
// }
|
||||
// },
|
||||
|
||||
// Emit CSS as "files" for other plugins to process. default is true
|
||||
emitCss: false,
|
||||
|
||||
}),
|
||||
resolve({browser: true}), // so Rollup can find `ms`
|
||||
// commonjs() // so Rollup can convert `ms` to an ES module
|
||||
// terser(),
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
];
|
||||
64
v2/internal/runtime/js/runtime/src/store.js
Normal file
64
v2/internal/runtime/js/runtime/src/store.js
Normal file
@@ -0,0 +1,64 @@
|
||||
|
||||
import { writable } from 'svelte/store';
|
||||
import {log} from "./log";
|
||||
|
||||
/** Overlay */
|
||||
export const overlayVisible = writable(false);
|
||||
|
||||
export function showOverlay() {
|
||||
overlayVisible.set(true);
|
||||
}
|
||||
export function hideOverlay() {
|
||||
overlayVisible.set(false);
|
||||
}
|
||||
|
||||
/** Menubar **/
|
||||
export const menuVisible = writable(false);
|
||||
|
||||
export function showMenuBar() {
|
||||
menuVisible.set(true);
|
||||
}
|
||||
export function hideMenuBar() {
|
||||
menuVisible.set(false);
|
||||
}
|
||||
|
||||
/** Trays **/
|
||||
|
||||
export const trays = writable([]);
|
||||
export function setTray(tray) {
|
||||
trays.update((current) => {
|
||||
// Remove existing if it exists, else add
|
||||
const index = current.findIndex(item => item.ID === tray.ID);
|
||||
if ( index === -1 ) {
|
||||
current.push(tray);
|
||||
} else {
|
||||
current[index] = tray;
|
||||
}
|
||||
return current;
|
||||
})
|
||||
}
|
||||
export function updateTrayLabel(tray) {
|
||||
trays.update((current) => {
|
||||
// Remove existing if it exists, else add
|
||||
const index = current.findIndex(item => item.ID === tray.ID);
|
||||
if ( index === -1 ) {
|
||||
return log("ERROR: Attempted to update tray index ", tray.ID, "but it doesn't exist")
|
||||
}
|
||||
current[index].Label = tray.Label;
|
||||
return current;
|
||||
})
|
||||
}
|
||||
|
||||
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);
|
||||
170
v2/internal/runtime/js/runtime/src/websocket.js
Normal file
170
v2/internal/runtime/js/runtime/src/websocket.js
Normal file
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The lightweight framework for web-like apps
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
/* jshint esversion: 6 */
|
||||
|
||||
|
||||
import {setTray, hideOverlay, showOverlay, updateTrayLabel, deleteTrayMenu} from "./store";
|
||||
import {log} from "./log";
|
||||
|
||||
let websocket = null;
|
||||
let callback = null;
|
||||
let connectTimer;
|
||||
|
||||
export function StartWebsocket(userCallback) {
|
||||
|
||||
callback = userCallback;
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
if( websocket ) {
|
||||
websocket.onclose = function () { };
|
||||
websocket.close();
|
||||
websocket = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ...and attempt to connect
|
||||
connect();
|
||||
|
||||
}
|
||||
|
||||
function setupIPCBridge() {
|
||||
// darwin
|
||||
window.webkit = {
|
||||
messageHandlers: {
|
||||
external: {
|
||||
postMessage: (message) => {
|
||||
websocket.send(message);
|
||||
}
|
||||
},
|
||||
windowDrag: {
|
||||
postMessage: () => {
|
||||
// Ignore window drag events
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Handles incoming websocket connections
|
||||
function handleConnect() {
|
||||
log('Connected to backend');
|
||||
setupIPCBridge();
|
||||
hideOverlay();
|
||||
clearInterval(connectTimer);
|
||||
websocket.onclose = handleDisconnect;
|
||||
websocket.onmessage = handleMessage;
|
||||
}
|
||||
|
||||
// Handles websocket disconnects
|
||||
function handleDisconnect() {
|
||||
log('Disconnected from backend');
|
||||
websocket = null;
|
||||
showOverlay();
|
||||
connect();
|
||||
}
|
||||
|
||||
// Try to connect to the backend every 1s (default value).
|
||||
function connect() {
|
||||
connectTimer = setInterval(function () {
|
||||
if (websocket == null) {
|
||||
websocket = new WebSocket('ws://' + window.location.hostname + ':34115/bridge');
|
||||
websocket.onopen = handleConnect;
|
||||
websocket.onerror = function (e) {
|
||||
e.stopImmediatePropagation();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
websocket = null;
|
||||
return false;
|
||||
};
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Adds a script to the Dom.
|
||||
// Removes it if second parameter is true.
|
||||
function addScript(script, remove) {
|
||||
const s = document.createElement('script');
|
||||
s.setAttribute('type', 'text/javascript');
|
||||
s.textContent = script;
|
||||
document.head.appendChild(s);
|
||||
|
||||
// Remove internal messages from the DOM
|
||||
if (remove) {
|
||||
s.parentNode.removeChild(s);
|
||||
}
|
||||
}
|
||||
|
||||
function handleMessage(message) {
|
||||
// As a bridge we ignore js and css injections
|
||||
switch (message.data[0]) {
|
||||
// Wails library - inject!
|
||||
case 'b':
|
||||
message = message.data.slice(1)
|
||||
addScript(message);
|
||||
log('Loaded Wails Runtime');
|
||||
|
||||
// We need to now send a message to the backend telling it
|
||||
// we have loaded (System Start)
|
||||
window.webkit.messageHandlers.external.postMessage("SS");
|
||||
|
||||
// Now wails runtime is loaded, wails for the ready event
|
||||
// and callback to the main app
|
||||
// window.wails.Events.On('wails:loaded', function () {
|
||||
if (callback) {
|
||||
log('Notifying application');
|
||||
callback(window.wails);
|
||||
}
|
||||
// });
|
||||
break;
|
||||
// // Notifications
|
||||
// case 'n':
|
||||
// addScript(message.data.slice(1), true);
|
||||
// break;
|
||||
// // Binding
|
||||
// case 'b':
|
||||
// const binding = message.data.slice(1);
|
||||
// //log("Binding: " + binding)
|
||||
// window.wails._.NewBinding(binding);
|
||||
// break;
|
||||
// // Call back
|
||||
case 'c':
|
||||
const callbackData = message.data.slice(1);
|
||||
window.wails._.Callback(callbackData);
|
||||
break;
|
||||
// Tray
|
||||
case 'T':
|
||||
const trayMessage = message.data.slice(1);
|
||||
switch (trayMessage[0]) {
|
||||
case 'S':
|
||||
// Set tray
|
||||
const trayJSON = trayMessage.slice(1);
|
||||
let tray = JSON.parse(trayJSON)
|
||||
setTray(tray)
|
||||
break
|
||||
case 'U':
|
||||
// Update label
|
||||
const updateTrayLabelJSON = trayMessage.slice(1);
|
||||
let trayLabelData = JSON.parse(updateTrayLabelJSON)
|
||||
updateTrayLabel(trayLabelData)
|
||||
break
|
||||
case 'D':
|
||||
// Delete Tray Menu
|
||||
const id = trayMessage.slice(1);
|
||||
deleteTrayMenu(id)
|
||||
break
|
||||
default:
|
||||
log('Unknown tray message: ' + message.data);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
log('Unknown message: ' + message.data);
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ const Events = require('./events');
|
||||
|
||||
/**
|
||||
* Registers an event listener that will be invoked when the user changes the
|
||||
* desktop theme (light mode / dark mode). The callback receives a boolean which
|
||||
* desktop theme (light mode / dark mode). The callback receives a booleanean which
|
||||
* indicates if dark mode is enabled.
|
||||
*
|
||||
* @export
|
||||
@@ -43,12 +43,12 @@ function DarkModeEnabled() {
|
||||
* Mac Title Bar Config
|
||||
* Check out https://github.com/lukakerr/NSWindowStyles for some examples of these settings
|
||||
* @typedef {Object} MacTitleBar
|
||||
* @param {bool} TitleBarAppearsTransparent - NSWindow.titleBarAppearsTransparent
|
||||
* @param {bool} HideTitle - NSWindow.hideTitle
|
||||
* @param {bool} HideTitleBar - NSWindow.hideTitleBar
|
||||
* @param {bool} FullSizeContent - Makes the webview portion of the window the full size of the window, even over the titlebar
|
||||
* @param {bool} UseToolbar - Set true to add a blank toolbar to the window (makes the title bar larger)
|
||||
* @param {bool} HideToolbarSeparator - Set true to remove the separator between the toolbar and the main content area
|
||||
* @param {boolean} TitleBarAppearsTransparent - NSWindow.titleBarAppearsTransparent
|
||||
* @param {boolean} HideTitle - NSWindow.hideTitle
|
||||
* @param {boolean} HideTitleBar - NSWindow.hideTitleBar
|
||||
* @param {boolean} FullSizeContent - Makes the webview portion of the window the full size of the window, even over the titlebar
|
||||
* @param {boolean} UseToolbar - Set true to add a blank toolbar to the window (makes the title bar larger)
|
||||
* @param {boolean} HideToolbarSeparator - Set true to remove the separator between the toolbar and the main content area
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -65,8 +65,8 @@ function DarkModeEnabled() {
|
||||
* @param {number} MinHeight - Window Minimum Height
|
||||
* @param {number} MaxWidth - Window Maximum Width
|
||||
* @param {number} MaxHeight - Window Maximum Height
|
||||
* @param {bool} StartHidden - Start with window hidden
|
||||
* @param {bool} DevTools - Enables the window devtools
|
||||
* @param {boolean} StartHidden - Start with window hidden
|
||||
* @param {boolean} DevTools - Enables the window devtools
|
||||
* @param {number} RBGA - The initial window colour. Convert to hex then it'll mean 0xRRGGBBAA
|
||||
* @param {MacAppConfig} [Mac] - Configuration when running on Mac
|
||||
* @param {LinuxAppConfig} [Linux] - Configuration when running on Linux
|
||||
@@ -88,11 +88,23 @@ function AppConfig() {
|
||||
return window.wails.System.AppConfig.get();
|
||||
}
|
||||
|
||||
function LogLevel() {
|
||||
return window.wails.System.LogLevel();
|
||||
}
|
||||
|
||||
function Platform() {
|
||||
return window.wails.System.Platform();
|
||||
}
|
||||
|
||||
function AppType() {
|
||||
return window.wails.System.AppType();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
OnThemeChange: OnThemeChange,
|
||||
DarkModeEnabled: DarkModeEnabled,
|
||||
LogLevel: window.wails.System.LogLevel,
|
||||
Platform: window.wails.System.Platform,
|
||||
AppType: window.wails.System.AppType,
|
||||
LogLevel: LogLevel,
|
||||
Platform: Platform,
|
||||
AppType: AppType,
|
||||
AppConfig: AppConfig,
|
||||
};
|
||||
@@ -10,6 +10,7 @@ type Menu interface {
|
||||
UpdateApplicationMenu()
|
||||
UpdateContextMenu(contextMenu *menu.ContextMenu)
|
||||
SetTrayMenu(trayMenu *menu.TrayMenu)
|
||||
DeleteTrayMenu(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) {
|
||||
m.bus.Publish("menu:updatetraymenulabel", trayMenu)
|
||||
}
|
||||
|
||||
func (m *menuRuntime) DeleteTrayMenu(trayMenu *menu.TrayMenu) {
|
||||
m.bus.Publish("menu:deletetraymenu", trayMenu)
|
||||
}
|
||||
|
||||
@@ -35,5 +35,6 @@ func New(serviceBus *servicebus.ServiceBus) *Runtime {
|
||||
|
||||
// Quit the application
|
||||
func (r *Runtime) Quit() {
|
||||
// Start shutdown of Wails
|
||||
r.bus.Publish("quit", "runtime.Quit()")
|
||||
}
|
||||
|
||||
@@ -50,12 +50,20 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
wailsJS := fs.RelativePath("../../../internal/runtime/assets/desktop_" + platform + ".js")
|
||||
wailsJS := fs.RelativePath("../assets/desktop_" + platform + ".js")
|
||||
runtimeData, err := ioutil.ReadFile(wailsJS)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Copy this file to bridge directory for embedding
|
||||
bridgeDir := fs.RelativePath("../../bridge/" + platform + ".js")
|
||||
println("Copying", wailsJS, "to", bridgeDir)
|
||||
err = fs.CopyFile(wailsJS, bridgeDir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Convert to C structure
|
||||
runtimeC := `
|
||||
// runtime.c (c) 2019-Present Lea Anthony.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package servicebus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -12,23 +13,26 @@ import (
|
||||
type ServiceBus struct {
|
||||
listeners map[string][]chan *Message
|
||||
messageQueue chan *Message
|
||||
quitChannel chan struct{}
|
||||
wg sync.WaitGroup
|
||||
lock sync.RWMutex
|
||||
closed bool
|
||||
debug bool
|
||||
logger logger.CustomLogger
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// New creates a new ServiceBus
|
||||
// The internal message queue is set to 100 messages
|
||||
// Listener queues are set to 10
|
||||
func New(logger *logger.Logger) *ServiceBus {
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
return &ServiceBus{
|
||||
listeners: make(map[string][]chan *Message),
|
||||
messageQueue: make(chan *Message, 100),
|
||||
quitChannel: make(chan struct{}, 1),
|
||||
logger: logger.CustomLogger("Service Bus"),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,24 +67,22 @@ func (s *ServiceBus) Debug() {
|
||||
// Start the service bus
|
||||
func (s *ServiceBus) Start() error {
|
||||
|
||||
s.logger.Trace("Starting")
|
||||
|
||||
// Prevent starting when closed
|
||||
if s.closed {
|
||||
return fmt.Errorf("cannot call start on closed servicebus")
|
||||
}
|
||||
|
||||
// We run in a different thread
|
||||
s.wg.Add(1)
|
||||
s.logger.Trace("Starting")
|
||||
|
||||
go func() {
|
||||
|
||||
quit := false
|
||||
defer s.logger.Trace("Stopped")
|
||||
|
||||
// Loop until we get a quit message
|
||||
for !quit {
|
||||
for {
|
||||
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
return
|
||||
|
||||
// Listen for messages
|
||||
case message := <-s.messageQueue:
|
||||
@@ -91,16 +93,9 @@ func (s *ServiceBus) Start() error {
|
||||
}
|
||||
// Dispatch message
|
||||
s.dispatchMessage(message)
|
||||
|
||||
// Listen for quit messages
|
||||
case <-s.quitChannel:
|
||||
quit = true
|
||||
}
|
||||
}
|
||||
|
||||
// Indicate we have shut down
|
||||
s.wg.Done()
|
||||
|
||||
}()
|
||||
|
||||
return nil
|
||||
@@ -117,10 +112,7 @@ func (s *ServiceBus) Stop() error {
|
||||
s.closed = true
|
||||
|
||||
// Send quit message
|
||||
s.quitChannel <- struct{}{}
|
||||
|
||||
// Wait for dispatcher to stop
|
||||
s.wg.Wait()
|
||||
s.cancel()
|
||||
|
||||
// Close down subscriber channels
|
||||
s.lock.Lock()
|
||||
@@ -135,7 +127,6 @@ func (s *ServiceBus) Stop() error {
|
||||
// Close message queue
|
||||
close(s.messageQueue)
|
||||
|
||||
s.logger.Trace("Stopped")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -172,7 +163,6 @@ func (s *ServiceBus) Subscribe(topic string) (<-chan *Message, error) {
|
||||
func (s *ServiceBus) Publish(topic string, data interface{}) {
|
||||
// Prevent publish when closed
|
||||
if s.closed {
|
||||
s.logger.Fatal("cannot call publish on closed servicebus")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -184,7 +174,6 @@ func (s *ServiceBus) Publish(topic string, data interface{}) {
|
||||
func (s *ServiceBus) PublishForTarget(topic string, data interface{}, target string) {
|
||||
// Prevent publish when closed
|
||||
if s.closed {
|
||||
s.logger.Fatal("cannot call publish on closed servicebus")
|
||||
return
|
||||
}
|
||||
message := NewMessageForTarget(topic, data, target)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package signal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
gosignal "os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
@@ -20,24 +22,24 @@ type Manager struct {
|
||||
// signalChannel
|
||||
signalchannel chan os.Signal
|
||||
|
||||
// Quit channel
|
||||
quitChannel <-chan *servicebus.Message
|
||||
// ctx
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
// Parent waitgroup
|
||||
wg *sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewManager creates a new signal manager
|
||||
func NewManager(bus *servicebus.ServiceBus, logger *logger.Logger) (*Manager, error) {
|
||||
|
||||
// Register quit channel
|
||||
quitChannel, err := bus.Subscribe("quit")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func NewManager(ctx context.Context, cancel context.CancelFunc, bus *servicebus.ServiceBus, logger *logger.Logger) (*Manager, error) {
|
||||
|
||||
result := &Manager{
|
||||
bus: bus,
|
||||
logger: logger.CustomLogger("Event Manager"),
|
||||
signalchannel: make(chan os.Signal, 2),
|
||||
quitChannel: quitChannel,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
|
||||
}
|
||||
|
||||
return result, nil
|
||||
@@ -49,20 +51,23 @@ func (m *Manager) Start() {
|
||||
// Hook into interrupts
|
||||
gosignal.Notify(m.signalchannel, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
// Spin off signal listener
|
||||
m.wg.Add(1)
|
||||
|
||||
// Spin off signal listener and wait for either a cancellation
|
||||
// or signal
|
||||
go func() {
|
||||
running := true
|
||||
for running {
|
||||
select {
|
||||
case <-m.signalchannel:
|
||||
println()
|
||||
m.logger.Trace("Ctrl+C detected. Shutting down...")
|
||||
m.bus.Publish("quit", "ctrl-c pressed")
|
||||
case <-m.quitChannel:
|
||||
running = false
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-m.signalchannel:
|
||||
println()
|
||||
m.logger.Trace("Ctrl+C detected. Shutting down...")
|
||||
m.bus.Publish("quit", "ctrl-c pressed")
|
||||
|
||||
// Start shutdown of Wails
|
||||
m.cancel()
|
||||
|
||||
case <-m.ctx.Done():
|
||||
}
|
||||
m.logger.Trace("Shutdown")
|
||||
m.wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
// Binding is the Binding subsystem. It manages all service bus messages
|
||||
// starting with "binding".
|
||||
type Binding struct {
|
||||
quitChannel <-chan *servicebus.Message
|
||||
bindingChannel <-chan *servicebus.Message
|
||||
running bool
|
||||
|
||||
running bool
|
||||
|
||||
// Binding db
|
||||
bindings *binding.Bindings
|
||||
@@ -27,12 +27,6 @@ type Binding struct {
|
||||
// NewBinding creates a new binding subsystem. Uses the given bindings db for reference.
|
||||
func NewBinding(bus *servicebus.ServiceBus, logger *logger.Logger, bindings *binding.Bindings, runtime *runtime.Runtime) (*Binding, error) {
|
||||
|
||||
// Register quit channel
|
||||
quitChannel, err := bus.Subscribe("quit")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Subscribe to event messages
|
||||
bindingChannel, err := bus.Subscribe("binding")
|
||||
if err != nil {
|
||||
@@ -40,7 +34,6 @@ func NewBinding(bus *servicebus.ServiceBus, logger *logger.Logger, bindings *bin
|
||||
}
|
||||
|
||||
result := &Binding{
|
||||
quitChannel: quitChannel,
|
||||
bindingChannel: bindingChannel,
|
||||
logger: logger.CustomLogger("Binding Subsystem"),
|
||||
bindings: bindings,
|
||||
@@ -61,20 +54,16 @@ func (b *Binding) Start() error {
|
||||
go func() {
|
||||
for b.running {
|
||||
select {
|
||||
case <-b.quitChannel:
|
||||
b.running = false
|
||||
case bindingMessage := <-b.bindingChannel:
|
||||
b.logger.Trace("Got binding message: %+v", bindingMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// Call shutdown
|
||||
b.shutdown()
|
||||
b.logger.Trace("Shutdown")
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Binding) shutdown() {
|
||||
b.logger.Trace("Shutdown")
|
||||
func (b *Binding) Close() {
|
||||
b.running = false
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package subsystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
@@ -16,9 +19,10 @@ import (
|
||||
// Call is the Call subsystem. It manages all service bus messages
|
||||
// starting with "call".
|
||||
type Call struct {
|
||||
quitChannel <-chan *servicebus.Message
|
||||
callChannel <-chan *servicebus.Message
|
||||
running bool
|
||||
|
||||
// quit flag
|
||||
shouldQuit bool
|
||||
|
||||
// bindings DB
|
||||
DB *binding.DB
|
||||
@@ -31,16 +35,16 @@ type Call struct {
|
||||
|
||||
// runtime
|
||||
runtime *runtime.Runtime
|
||||
|
||||
// context
|
||||
ctx context.Context
|
||||
|
||||
// parent waitgroup
|
||||
wg *sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewCall creates a new call subsystem
|
||||
func NewCall(bus *servicebus.ServiceBus, logger *logger.Logger, DB *binding.DB, runtime *runtime.Runtime) (*Call, error) {
|
||||
|
||||
// Register quit channel
|
||||
quitChannel, err := bus.Subscribe("quit")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func NewCall(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger, DB *binding.DB, runtime *runtime.Runtime) (*Call, error) {
|
||||
|
||||
// Subscribe to event messages
|
||||
callChannel, err := bus.Subscribe("call:invoke")
|
||||
@@ -49,12 +53,13 @@ func NewCall(bus *servicebus.ServiceBus, logger *logger.Logger, DB *binding.DB,
|
||||
}
|
||||
|
||||
result := &Call{
|
||||
quitChannel: quitChannel,
|
||||
callChannel: callChannel,
|
||||
logger: logger.CustomLogger("Call Subsystem"),
|
||||
DB: DB,
|
||||
bus: bus,
|
||||
runtime: runtime,
|
||||
ctx: ctx,
|
||||
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
|
||||
}
|
||||
|
||||
return result, nil
|
||||
@@ -63,22 +68,21 @@ func NewCall(bus *servicebus.ServiceBus, logger *logger.Logger, DB *binding.DB,
|
||||
// Start the subsystem
|
||||
func (c *Call) Start() error {
|
||||
|
||||
c.running = true
|
||||
c.wg.Add(1)
|
||||
|
||||
// Spin off a go routine
|
||||
go func() {
|
||||
for c.running {
|
||||
defer c.logger.Trace("Shutdown")
|
||||
for {
|
||||
select {
|
||||
case <-c.quitChannel:
|
||||
c.running = false
|
||||
case <-c.ctx.Done():
|
||||
c.wg.Done()
|
||||
return
|
||||
case callMessage := <-c.callChannel:
|
||||
// TODO: Check if this works ok in a goroutine
|
||||
c.processCall(callMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// Call shutdown
|
||||
c.shutdown()
|
||||
}()
|
||||
|
||||
return nil
|
||||
@@ -190,10 +194,6 @@ func (c *Call) sendError(err error, payload *message.CallMessage, clientID strin
|
||||
c.bus.PublishForTarget("call:result", string(messageData), clientID)
|
||||
}
|
||||
|
||||
func (c *Call) shutdown() {
|
||||
c.logger.Trace("Shutdown")
|
||||
}
|
||||
|
||||
// CallbackMessage defines a message that contains the result of a call
|
||||
type CallbackMessage struct {
|
||||
Result interface{} `json:"result"`
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package subsystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -22,9 +23,7 @@ type eventListener struct {
|
||||
// Event is the Eventing subsystem. It manages all service bus messages
|
||||
// starting with "event".
|
||||
type Event struct {
|
||||
quitChannel <-chan *servicebus.Message
|
||||
eventChannel <-chan *servicebus.Message
|
||||
running bool
|
||||
|
||||
// Event listeners
|
||||
listeners map[string][]*eventListener
|
||||
@@ -32,16 +31,16 @@ type Event struct {
|
||||
|
||||
// logger
|
||||
logger logger.CustomLogger
|
||||
|
||||
// ctx
|
||||
ctx context.Context
|
||||
|
||||
// parent waitgroup
|
||||
wg *sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewEvent creates a new log subsystem
|
||||
func NewEvent(bus *servicebus.ServiceBus, logger *logger.Logger) (*Event, error) {
|
||||
|
||||
// Register quit channel
|
||||
quitChannel, err := bus.Subscribe("quit")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func NewEvent(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger) (*Event, error) {
|
||||
|
||||
// Subscribe to event messages
|
||||
eventChannel, err := bus.Subscribe("event")
|
||||
@@ -50,10 +49,11 @@ func NewEvent(bus *servicebus.ServiceBus, logger *logger.Logger) (*Event, error)
|
||||
}
|
||||
|
||||
result := &Event{
|
||||
quitChannel: quitChannel,
|
||||
eventChannel: eventChannel,
|
||||
logger: logger.CustomLogger("Event Subsystem"),
|
||||
listeners: make(map[string][]*eventListener),
|
||||
ctx: ctx,
|
||||
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
|
||||
}
|
||||
|
||||
return result, nil
|
||||
@@ -80,15 +80,16 @@ func (e *Event) Start() error {
|
||||
|
||||
e.logger.Trace("Starting")
|
||||
|
||||
e.running = true
|
||||
e.wg.Add(1)
|
||||
|
||||
// Spin off a go routine
|
||||
go func() {
|
||||
for e.running {
|
||||
defer e.logger.Trace("Shutdown")
|
||||
for {
|
||||
select {
|
||||
case <-e.quitChannel:
|
||||
e.running = false
|
||||
break
|
||||
case <-e.ctx.Done():
|
||||
e.wg.Done()
|
||||
return
|
||||
case eventMessage := <-e.eventChannel:
|
||||
splitTopic := strings.Split(eventMessage.Topic(), ":")
|
||||
eventType := splitTopic[1]
|
||||
@@ -128,8 +129,6 @@ func (e *Event) Start() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Call shutdown
|
||||
e.shutdown()
|
||||
}()
|
||||
|
||||
return nil
|
||||
@@ -190,7 +189,3 @@ func (e *Event) notifyListeners(eventName string, message *message.EventMessage)
|
||||
// Unlock
|
||||
e.notifyLock.Unlock()
|
||||
}
|
||||
|
||||
func (e *Event) shutdown() {
|
||||
e.logger.Trace("Shutdown")
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package subsystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/runtime"
|
||||
@@ -12,15 +14,23 @@ import (
|
||||
// Log is the Logging subsystem. It handles messages with topics starting
|
||||
// with "log:"
|
||||
type Log struct {
|
||||
logChannel <-chan *servicebus.Message
|
||||
quitChannel <-chan *servicebus.Message
|
||||
running bool
|
||||
logChannel <-chan *servicebus.Message
|
||||
|
||||
// quit flag
|
||||
shouldQuit bool
|
||||
|
||||
// Logger!
|
||||
logger *logger.Logger
|
||||
|
||||
// Loglevel store
|
||||
logLevelStore *runtime.Store
|
||||
|
||||
// Context for shutdown
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
// internal waitgroup
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewLog creates a new log subsystem
|
||||
@@ -32,17 +42,14 @@ func NewLog(bus *servicebus.ServiceBus, logger *logger.Logger, logLevelStore *ru
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Subscribe to quit messages
|
||||
quitChannel, err := bus.Subscribe("quit")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
result := &Log{
|
||||
logChannel: logChannel,
|
||||
quitChannel: quitChannel,
|
||||
logger: logger,
|
||||
logLevelStore: logLevelStore,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
@@ -51,15 +58,17 @@ func NewLog(bus *servicebus.ServiceBus, logger *logger.Logger, logLevelStore *ru
|
||||
// Start the subsystem
|
||||
func (l *Log) Start() error {
|
||||
|
||||
l.running = true
|
||||
l.wg.Add(1)
|
||||
|
||||
// Spin off a go routine
|
||||
go func() {
|
||||
for l.running {
|
||||
defer l.logger.Trace("Logger Shutdown")
|
||||
|
||||
for l.shouldQuit == false {
|
||||
select {
|
||||
case <-l.quitChannel:
|
||||
l.running = false
|
||||
break
|
||||
case <-l.ctx.Done():
|
||||
l.wg.Done()
|
||||
return
|
||||
case logMessage := <-l.logChannel:
|
||||
logType := strings.TrimPrefix(logMessage.Topic(), "log:")
|
||||
switch logType {
|
||||
@@ -98,8 +107,12 @@ func (l *Log) Start() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
l.logger.Trace("Logger Shutdown")
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Log) Close() {
|
||||
l.cancel()
|
||||
l.wg.Wait()
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package subsystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/menumanager"
|
||||
@@ -13,9 +16,10 @@ import (
|
||||
// Menu is the subsystem that handles the operation of menus. It manages all service bus messages
|
||||
// starting with "menu".
|
||||
type Menu struct {
|
||||
quitChannel <-chan *servicebus.Message
|
||||
menuChannel <-chan *servicebus.Message
|
||||
running bool
|
||||
|
||||
// shutdown flag
|
||||
shouldQuit bool
|
||||
|
||||
// logger
|
||||
logger logger.CustomLogger
|
||||
@@ -25,16 +29,16 @@ type Menu struct {
|
||||
|
||||
// Menu Manager
|
||||
menuManager *menumanager.Manager
|
||||
|
||||
// ctx
|
||||
ctx context.Context
|
||||
|
||||
// parent waitgroup
|
||||
wg *sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewMenu creates a new menu subsystem
|
||||
func NewMenu(bus *servicebus.ServiceBus, logger *logger.Logger, menuManager *menumanager.Manager) (*Menu, error) {
|
||||
|
||||
// Register quit channel
|
||||
quitChannel, err := bus.Subscribe("quit")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func NewMenu(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger, menuManager *menumanager.Manager) (*Menu, error) {
|
||||
|
||||
// Subscribe to menu messages
|
||||
menuChannel, err := bus.Subscribe("menu:")
|
||||
@@ -43,11 +47,12 @@ func NewMenu(bus *servicebus.ServiceBus, logger *logger.Logger, menuManager *men
|
||||
}
|
||||
|
||||
result := &Menu{
|
||||
quitChannel: quitChannel,
|
||||
menuChannel: menuChannel,
|
||||
logger: logger.CustomLogger("Menu Subsystem"),
|
||||
bus: bus,
|
||||
menuManager: menuManager,
|
||||
ctx: ctx,
|
||||
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
|
||||
}
|
||||
|
||||
return result, nil
|
||||
@@ -58,19 +63,26 @@ func (m *Menu) Start() error {
|
||||
|
||||
m.logger.Trace("Starting")
|
||||
|
||||
m.running = true
|
||||
m.wg.Add(1)
|
||||
|
||||
// Spin off a go routine
|
||||
go func() {
|
||||
for m.running {
|
||||
defer m.logger.Trace("Shutdown")
|
||||
for {
|
||||
select {
|
||||
case <-m.quitChannel:
|
||||
m.running = false
|
||||
break
|
||||
case <-m.ctx.Done():
|
||||
m.wg.Done()
|
||||
return
|
||||
case menuMessage := <-m.menuChannel:
|
||||
splitTopic := strings.Split(menuMessage.Topic(), ":")
|
||||
menuMessageType := splitTopic[1]
|
||||
switch menuMessageType {
|
||||
case "ontrayopen":
|
||||
trayID := menuMessage.Data().(string)
|
||||
m.menuManager.OnTrayMenuOpen(trayID)
|
||||
case "ontrayclose":
|
||||
trayID := menuMessage.Data().(string)
|
||||
m.menuManager.OnTrayMenuClose(trayID)
|
||||
case "clicked":
|
||||
if len(splitTopic) != 2 {
|
||||
m.logger.Error("Received clicked message with invalid topic format. Expected 2 sections in topic, got %s", splitTopic)
|
||||
@@ -131,6 +143,17 @@ func (m *Menu) Start() error {
|
||||
// Notify frontend of menu change
|
||||
m.bus.Publish("menufrontend:settraymenu", updatedMenu)
|
||||
|
||||
case "deletetraymenu":
|
||||
trayMenu := menuMessage.Data().(*menu.TrayMenu)
|
||||
trayID, err := m.menuManager.GetTrayID(trayMenu)
|
||||
if err != nil {
|
||||
m.logger.Trace("%s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Notify frontend of menu change
|
||||
m.bus.Publish("menufrontend:deletetraymenu", trayID)
|
||||
|
||||
case "updatetraymenulabel":
|
||||
trayMenu := menuMessage.Data().(*menu.TrayMenu)
|
||||
updatedLabel, err := m.menuManager.UpdateTrayMenuLabel(trayMenu)
|
||||
@@ -147,14 +170,7 @@ func (m *Menu) Start() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call shutdown
|
||||
m.shutdown()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Menu) shutdown() {
|
||||
m.logger.Trace("Shutdown")
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package subsystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/runtime"
|
||||
@@ -12,7 +14,6 @@ import (
|
||||
// Runtime is the Runtime subsystem. It handles messages with topics starting
|
||||
// with "runtime:"
|
||||
type Runtime struct {
|
||||
quitChannel <-chan *servicebus.Message
|
||||
runtimeChannel <-chan *servicebus.Message
|
||||
|
||||
// The hooks channel allows us to hook into frontend startup
|
||||
@@ -20,22 +21,26 @@ type Runtime struct {
|
||||
startupCallback func(*runtime.Runtime)
|
||||
shutdownCallback func()
|
||||
|
||||
running bool
|
||||
// quit flag
|
||||
shouldQuit bool
|
||||
|
||||
logger logger.CustomLogger
|
||||
|
||||
// Runtime library
|
||||
runtime *runtime.Runtime
|
||||
|
||||
//ctx
|
||||
ctx context.Context
|
||||
|
||||
// Startup Hook
|
||||
startupOnce sync.Once
|
||||
|
||||
// Service bus
|
||||
bus *servicebus.ServiceBus
|
||||
}
|
||||
|
||||
// NewRuntime creates a new runtime subsystem
|
||||
func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, startupCallback func(*runtime.Runtime), shutdownCallback func()) (*Runtime, error) {
|
||||
|
||||
// Register quit channel
|
||||
quitChannel, err := bus.Subscribe("quit")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func NewRuntime(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger, startupCallback func(*runtime.Runtime)) (*Runtime, error) {
|
||||
|
||||
// Subscribe to log messages
|
||||
runtimeChannel, err := bus.Subscribe("runtime:")
|
||||
@@ -50,13 +55,13 @@ func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, startupCallba
|
||||
}
|
||||
|
||||
result := &Runtime{
|
||||
quitChannel: quitChannel,
|
||||
runtimeChannel: runtimeChannel,
|
||||
hooksChannel: hooksChannel,
|
||||
logger: logger.CustomLogger("Runtime Subsystem"),
|
||||
runtime: runtime.New(bus),
|
||||
startupCallback: startupCallback,
|
||||
shutdownCallback: shutdownCallback,
|
||||
runtimeChannel: runtimeChannel,
|
||||
hooksChannel: hooksChannel,
|
||||
logger: logger.CustomLogger("Runtime Subsystem"),
|
||||
runtime: runtime.New(bus),
|
||||
startupCallback: startupCallback,
|
||||
ctx: ctx,
|
||||
bus: bus,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
@@ -65,15 +70,11 @@ func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, startupCallba
|
||||
// Start the subsystem
|
||||
func (r *Runtime) Start() error {
|
||||
|
||||
r.running = true
|
||||
|
||||
// Spin off a go routine
|
||||
go func() {
|
||||
for r.running {
|
||||
defer r.logger.Trace("Shutdown")
|
||||
for {
|
||||
select {
|
||||
case <-r.quitChannel:
|
||||
r.running = false
|
||||
break
|
||||
case hooksMessage := <-r.hooksChannel:
|
||||
r.logger.Trace(fmt.Sprintf("Received hooksmessage: %+v", hooksMessage))
|
||||
messageSlice := strings.Split(hooksMessage.Topic(), ":")
|
||||
@@ -81,7 +82,17 @@ func (r *Runtime) Start() error {
|
||||
switch hook {
|
||||
case "startup":
|
||||
if r.startupCallback != nil {
|
||||
go r.startupCallback(r.runtime)
|
||||
r.startupOnce.Do(func() {
|
||||
go func() {
|
||||
r.startupCallback(r.runtime)
|
||||
|
||||
// If we got a url, publish it now startup completed
|
||||
url, ok := hooksMessage.Data().(string)
|
||||
if ok && len(url) > 0 {
|
||||
r.bus.Publish("url:handler", url)
|
||||
}
|
||||
}()
|
||||
})
|
||||
} else {
|
||||
r.logger.Warning("no startup callback registered!")
|
||||
}
|
||||
@@ -113,11 +124,10 @@ func (r *Runtime) Start() error {
|
||||
if err != nil {
|
||||
r.logger.Error(err.Error())
|
||||
}
|
||||
case <-r.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Call shutdown
|
||||
r.shutdown()
|
||||
}()
|
||||
|
||||
return nil
|
||||
@@ -128,15 +138,6 @@ func (r *Runtime) GoRuntime() *runtime.Runtime {
|
||||
return r.runtime
|
||||
}
|
||||
|
||||
func (r *Runtime) shutdown() {
|
||||
if r.shutdownCallback != nil {
|
||||
go r.shutdownCallback()
|
||||
} else {
|
||||
r.logger.Warning("no shutdown callback registered!")
|
||||
}
|
||||
r.logger.Trace("Shutdown")
|
||||
}
|
||||
|
||||
func (r *Runtime) processBrowserMessage(method string, data interface{}) error {
|
||||
switch method {
|
||||
case "open":
|
||||
|
||||
98
v2/internal/subsystem/url.go
Normal file
98
v2/internal/subsystem/url.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package subsystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
)
|
||||
|
||||
// URL is the URL Handler subsystem. It handles messages with topics starting
|
||||
// with "url:"
|
||||
type URL struct {
|
||||
urlChannel <-chan *servicebus.Message
|
||||
|
||||
// quit flag
|
||||
shouldQuit bool
|
||||
|
||||
// Logger!
|
||||
logger *logger.Logger
|
||||
|
||||
// Context for shutdown
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
// internal waitgroup
|
||||
wg sync.WaitGroup
|
||||
|
||||
// Handlers
|
||||
handlers map[string]func(string)
|
||||
}
|
||||
|
||||
// NewURL creates a new log subsystem
|
||||
func NewURL(bus *servicebus.ServiceBus, logger *logger.Logger, handlers map[string]func(string)) (*URL, error) {
|
||||
|
||||
// Subscribe to log messages
|
||||
urlChannel, err := bus.Subscribe("url")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
result := &URL{
|
||||
urlChannel: urlChannel,
|
||||
logger: logger,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
handlers: handlers,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Start the subsystem
|
||||
func (u *URL) Start() error {
|
||||
|
||||
u.wg.Add(1)
|
||||
|
||||
// Spin off a go routine
|
||||
go func() {
|
||||
defer u.logger.Trace("URL Shutdown")
|
||||
|
||||
for u.shouldQuit == false {
|
||||
select {
|
||||
case <-u.ctx.Done():
|
||||
u.wg.Done()
|
||||
return
|
||||
case urlMessage := <-u.urlChannel:
|
||||
// Guard against nil messages
|
||||
if urlMessage == nil {
|
||||
continue
|
||||
}
|
||||
messageType := strings.TrimPrefix(urlMessage.Topic(), "url:")
|
||||
switch messageType {
|
||||
case "handler":
|
||||
url := urlMessage.Data().(string)
|
||||
splitURL := strings.Split(url, ":")
|
||||
protocol := splitURL[0]
|
||||
callback, ok := u.handlers[protocol]
|
||||
if ok {
|
||||
go callback(url)
|
||||
}
|
||||
default:
|
||||
u.logger.Error("unknown url message: %+v", urlMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *URL) Close() {
|
||||
u.cancel()
|
||||
u.wg.Wait()
|
||||
}
|
||||
@@ -12,20 +12,19 @@ type Basic struct {
|
||||
}
|
||||
|
||||
// newBasic creates a new Basic application struct
|
||||
func newBasic() *Basic {
|
||||
func NewBasic() *Basic {
|
||||
return &Basic{}
|
||||
}
|
||||
|
||||
// WailsInit is called at application startup
|
||||
func (b *Basic) WailsInit(runtime *wails.Runtime) error {
|
||||
// startup is called at application startup
|
||||
func (b *Basic) startup(runtime *wails.Runtime) {
|
||||
// Perform your setup here
|
||||
b.runtime = runtime
|
||||
runtime.Window.SetTitle("{{.ProjectName}}")
|
||||
return nil
|
||||
}
|
||||
|
||||
// WailsShutdown is called at application termination
|
||||
func (b *Basic) WailsShutdown() {
|
||||
// shutdown is called at application termination
|
||||
func (b *Basic) shutdown() {
|
||||
// Perform your teardown here
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<link rel="stylesheet" href="/main.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body data-wails-drag>
|
||||
<div id="logo"></div>
|
||||
<div id="input">
|
||||
<input id="name" type="text"></input>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,21 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"log"
|
||||
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// Create application with options
|
||||
app, err := wails.CreateApp("{{.ProjectName}}", 1024, 768)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
app := NewBasic()
|
||||
|
||||
app.Bind(newBasic())
|
||||
|
||||
err = app.Run()
|
||||
err := wails.Run(&options.App{
|
||||
Title: "{{.ProjectName}}",
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
DisableResize: true,
|
||||
Mac: &mac.Options{
|
||||
WebviewIsTransparent: true,
|
||||
WindowBackgroundIsTranslucent: true,
|
||||
TitleBar: mac.TitleBarHiddenInset(),
|
||||
Menu: menu.DefaultMacMenu(),
|
||||
},
|
||||
LogLevel: logger.DEBUG,
|
||||
Startup: app.startup,
|
||||
Shutdown: app.shutdown,
|
||||
Bind: []interface{}{
|
||||
app,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/colour"
|
||||
)
|
||||
|
||||
// CLILogger is used by the cli
|
||||
@@ -51,9 +53,9 @@ func (c *CLILogger) Println(message string, args ...interface{}) {
|
||||
// Fatal prints the given message then aborts
|
||||
func (c *CLILogger) Fatal(message string, args ...interface{}) {
|
||||
temp := fmt.Sprintf(message, args...)
|
||||
_, err := fmt.Fprintln(c.Writer, "FATAL: "+temp)
|
||||
_, err := fmt.Fprintln(c.Writer, colour.Red("FATAL: "+temp))
|
||||
if err != nil {
|
||||
println("FATAL: ", err)
|
||||
println(colour.Red("FATAL: " + err.Error()))
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -145,6 +145,13 @@ func (b *BaseBuilder) CleanUp() {
|
||||
// CompileProject compiles the project
|
||||
func (b *BaseBuilder) CompileProject(options *Options) error {
|
||||
|
||||
// Run go mod tidy first
|
||||
cmd := exec.Command(options.Compiler, "mod", "tidy")
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Default go build command
|
||||
commands := slicer.String([]string{"build"})
|
||||
|
||||
@@ -188,7 +195,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
||||
|
||||
// Get application build directory
|
||||
appDir := options.BuildDirectory
|
||||
err := cleanBuildDirectory(options)
|
||||
err = cleanBuildDirectory(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -211,14 +218,11 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
||||
options.CompiledBinary = compiledBinary
|
||||
|
||||
// Create the command
|
||||
cmd := exec.Command(options.Compiler, commands.AsSlice()...)
|
||||
cmd = exec.Command(options.Compiler, commands.AsSlice()...)
|
||||
|
||||
// Set the directory
|
||||
cmd.Dir = b.projectData.Path
|
||||
|
||||
// Set GO111MODULE environment variable
|
||||
cmd.Env = append(os.Environ(), "GO111MODULE=on")
|
||||
|
||||
// Add CGO flags
|
||||
// We use the project/build dir as a temporary place for our generated c headers
|
||||
buildBaseDir, err := fs.RelativeToCwd("build")
|
||||
@@ -226,7 +230,16 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Env = append(os.Environ(), fmt.Sprintf("CGO_CFLAGS=-I%s", buildBaseDir))
|
||||
cmd.Env = os.Environ() // inherit env
|
||||
|
||||
// Use upsertEnv so we don't overwrite user's CGO_CFLAGS
|
||||
cmd.Env = upsertEnv(cmd.Env, "CGO_CFLAGS", func(v string) string {
|
||||
if v != "" {
|
||||
v += " "
|
||||
}
|
||||
v += "-I" + buildBaseDir
|
||||
return v
|
||||
})
|
||||
|
||||
// Setup buffers
|
||||
var stdo, stde bytes.Buffer
|
||||
@@ -384,3 +397,22 @@ func (b *BaseBuilder) ExtractAssets() (*html.AssetBundle, error) {
|
||||
// Read in html
|
||||
return html.NewAssetBundle(b.projectData.HTML)
|
||||
}
|
||||
|
||||
func upsertEnv(env []string, key string, update func(v string) string) []string {
|
||||
newEnv := make([]string, len(env), len(env)+1)
|
||||
found := false
|
||||
for i := range env {
|
||||
if strings.HasPrefix(env[i], key+"=") {
|
||||
eqIndex := strings.Index(env[i], "=")
|
||||
val := env[i][eqIndex+1:]
|
||||
newEnv[i] = fmt.Sprintf("%s=%v", key, update(val))
|
||||
found = true
|
||||
continue
|
||||
}
|
||||
newEnv[i] = env[i]
|
||||
}
|
||||
if !found {
|
||||
newEnv = append(newEnv, fmt.Sprintf("%s=%v", key, update("")))
|
||||
}
|
||||
return newEnv
|
||||
}
|
||||
|
||||
31
v2/pkg/commands/build/base_test.go
Normal file
31
v2/pkg/commands/build/base_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package build
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestUpdateEnv(t *testing.T) {
|
||||
|
||||
env := []string{"one=1", "two=a=b", "three="}
|
||||
newEnv := upsertEnv(env, "two", func(v string) string {
|
||||
return v + "+added"
|
||||
})
|
||||
newEnv = upsertEnv(newEnv, "newVar", func(v string) string {
|
||||
return "added"
|
||||
})
|
||||
newEnv = upsertEnv(newEnv, "three", func(v string) string {
|
||||
return "3"
|
||||
})
|
||||
|
||||
if len(newEnv) != 4 {
|
||||
t.Errorf("expected: 4, got: %d", len(newEnv))
|
||||
}
|
||||
if newEnv[1] != "two=a=b+added" {
|
||||
t.Errorf("expected: \"two=a=b+added\", got: %q", newEnv[1])
|
||||
}
|
||||
if newEnv[2] != "three=3" {
|
||||
t.Errorf("expected: \"three=3\", got: %q", newEnv[2])
|
||||
}
|
||||
if newEnv[3] != "newVar=added" {
|
||||
t.Errorf("expected: \"newVar=added\", got: %q", newEnv[3])
|
||||
}
|
||||
|
||||
}
|
||||
@@ -38,6 +38,7 @@ type Options struct {
|
||||
BuildDirectory string // Directory to use for building the application
|
||||
CompiledBinary string // Fully qualified path to the compiled binary
|
||||
KeepAssets bool // /Keep the generated assets/files
|
||||
AppleIdentity string
|
||||
}
|
||||
|
||||
// GetModeAsString returns the current mode as a string
|
||||
@@ -86,6 +87,8 @@ func Build(options *Options) (string, error) {
|
||||
builder = newHybridBuilder(options)
|
||||
case "server":
|
||||
builder = newServerBuilder(options)
|
||||
case "dev":
|
||||
builder = newDesktopBuilder(options)
|
||||
default:
|
||||
return "", fmt.Errorf("cannot build assets for output type %s", projectData.OutputType)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package build
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -52,6 +54,14 @@ func packageApplication(options *Options) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Sign app if needed
|
||||
if options.AppleIdentity != "" {
|
||||
err = signApplication(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -176,3 +186,21 @@ func processApplicationIcon(resourceDir string, iconsDir string) (err error) {
|
||||
}()
|
||||
return icns.Encode(dest, srcImg)
|
||||
}
|
||||
|
||||
func signApplication(options *Options) error {
|
||||
bundlename := filepath.Join(options.BuildDirectory, options.ProjectData.Name+".app")
|
||||
identity := fmt.Sprintf(`"%s"`, options.AppleIdentity)
|
||||
cmd := exec.Command("codesign", "--sign", identity, "--deep", "--force", "--verbose", "--timestamp", "--options", "runtime", bundlename)
|
||||
var stdo, stde bytes.Buffer
|
||||
cmd.Stdout = &stdo
|
||||
cmd.Stderr = &stde
|
||||
|
||||
// Run command
|
||||
err := cmd.Run()
|
||||
|
||||
// Format error if we have one
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s\n%s", err, string(stde.Bytes()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
66
v2/pkg/logger/filelogger.go
Normal file
66
v2/pkg/logger/filelogger.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// FileLogger is a utility to log messages to a number of destinations
|
||||
type FileLogger struct {
|
||||
filename string
|
||||
}
|
||||
|
||||
// NewFileLogger creates a new Logger.
|
||||
func NewFileLogger(filename string) Logger {
|
||||
return &FileLogger{
|
||||
filename: filename,
|
||||
}
|
||||
}
|
||||
|
||||
// Print works like Sprintf.
|
||||
func (l *FileLogger) Print(message string) {
|
||||
f, err := os.OpenFile(l.filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if _, err := f.WriteString(message); err != nil {
|
||||
f.Close()
|
||||
log.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
func (l *FileLogger) Println(message string) {
|
||||
l.Print(message + "\n")
|
||||
}
|
||||
|
||||
// Trace level logging. Works like Sprintf.
|
||||
func (l *FileLogger) Trace(message string) {
|
||||
l.Println("TRACE | " + message)
|
||||
}
|
||||
|
||||
// Debug level logging. Works like Sprintf.
|
||||
func (l *FileLogger) Debug(message string) {
|
||||
l.Println("DEBUG | " + message)
|
||||
}
|
||||
|
||||
// Info level logging. Works like Sprintf.
|
||||
func (l *FileLogger) Info(message string) {
|
||||
l.Println("INFO | " + message)
|
||||
}
|
||||
|
||||
// Warning level logging. Works like Sprintf.
|
||||
func (l *FileLogger) Warning(message string) {
|
||||
l.Println("WARN | " + message)
|
||||
}
|
||||
|
||||
// Error level logging. Works like Sprintf.
|
||||
func (l *FileLogger) Error(message string) {
|
||||
l.Println("ERROR | " + message)
|
||||
}
|
||||
|
||||
// Fatal level logging. Works like Sprintf.
|
||||
func (l *FileLogger) Fatal(message string) {
|
||||
l.Println("FATAL | " + message)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -1,21 +1,44 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Modifier is actually a string
|
||||
type Modifier string
|
||||
|
||||
const (
|
||||
// CmdOrCtrlKey represents Command on Mac and Control on other platforms
|
||||
CmdOrCtrlKey Modifier = "CmdOrCtrl"
|
||||
CmdOrCtrlKey Modifier = "cmdorctrl"
|
||||
// OptionOrAltKey represents Option on Mac and Alt on other platforms
|
||||
OptionOrAltKey Modifier = "OptionOrAlt"
|
||||
OptionOrAltKey Modifier = "optionoralt"
|
||||
// ShiftKey represents the shift key on all systems
|
||||
ShiftKey Modifier = "Shift"
|
||||
ShiftKey Modifier = "shift"
|
||||
// SuperKey represents Command on Mac and the Windows key on the other platforms
|
||||
SuperKey Modifier = "Super"
|
||||
SuperKey Modifier = "super"
|
||||
// ControlKey represents the control key on all systems
|
||||
ControlKey Modifier = "Control"
|
||||
ControlKey Modifier = "ctrl"
|
||||
)
|
||||
|
||||
var modifierMap = map[string]Modifier{
|
||||
"cmdorctrl": CmdOrCtrlKey,
|
||||
"optionoralt": OptionOrAltKey,
|
||||
"shift": ShiftKey,
|
||||
"super": SuperKey,
|
||||
"ctrl": ControlKey,
|
||||
}
|
||||
|
||||
func parseModifier(text string) (*Modifier, error) {
|
||||
lowertext := strings.ToLower(text)
|
||||
result, valid := modifierMap[lowertext]
|
||||
if !valid {
|
||||
return nil, fmt.Errorf("'%s' is not a valid modifier", text)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// Accelerator holds the keyboard shortcut for a menu item
|
||||
type Accelerator struct {
|
||||
Key string
|
||||
@@ -25,14 +48,14 @@ type Accelerator struct {
|
||||
// Key creates a standard key Accelerator
|
||||
func Key(key string) *Accelerator {
|
||||
return &Accelerator{
|
||||
Key: key,
|
||||
Key: strings.ToLower(key),
|
||||
}
|
||||
}
|
||||
|
||||
// CmdOrCtrl creates a 'CmdOrCtrl' Accelerator
|
||||
func CmdOrCtrl(key string) *Accelerator {
|
||||
return &Accelerator{
|
||||
Key: key,
|
||||
Key: strings.ToLower(key),
|
||||
Modifiers: []Modifier{CmdOrCtrlKey},
|
||||
}
|
||||
}
|
||||
@@ -40,7 +63,7 @@ func CmdOrCtrl(key string) *Accelerator {
|
||||
// OptionOrAlt creates a 'OptionOrAlt' Accelerator
|
||||
func OptionOrAlt(key string) *Accelerator {
|
||||
return &Accelerator{
|
||||
Key: key,
|
||||
Key: strings.ToLower(key),
|
||||
Modifiers: []Modifier{OptionOrAltKey},
|
||||
}
|
||||
}
|
||||
@@ -48,7 +71,7 @@ func OptionOrAlt(key string) *Accelerator {
|
||||
// Shift creates a 'Shift' Accelerator
|
||||
func Shift(key string) *Accelerator {
|
||||
return &Accelerator{
|
||||
Key: key,
|
||||
Key: strings.ToLower(key),
|
||||
Modifiers: []Modifier{ShiftKey},
|
||||
}
|
||||
}
|
||||
@@ -56,7 +79,7 @@ func Shift(key string) *Accelerator {
|
||||
// Control creates a 'Control' Accelerator
|
||||
func Control(key string) *Accelerator {
|
||||
return &Accelerator{
|
||||
Key: key,
|
||||
Key: strings.ToLower(key),
|
||||
Modifiers: []Modifier{ControlKey},
|
||||
}
|
||||
}
|
||||
@@ -64,7 +87,7 @@ func Control(key string) *Accelerator {
|
||||
// Super creates a 'Super' Accelerator
|
||||
func Super(key string) *Accelerator {
|
||||
return &Accelerator{
|
||||
Key: key,
|
||||
Key: strings.ToLower(key),
|
||||
Modifiers: []Modifier{SuperKey},
|
||||
}
|
||||
}
|
||||
|
||||
90
v2/pkg/menu/keys/parser.go
Normal file
90
v2/pkg/menu/keys/parser.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
)
|
||||
|
||||
var namedKeys = slicer.String([]string{"backspace", "tab", "return", "escape", "left", "right", "up", "down", "space", "delete", "home", "end", "page up", "page down", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23", "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31", "f32", "f33", "f34", "f35", "numlock"})
|
||||
|
||||
func parseKey(key string) (string, bool) {
|
||||
|
||||
// Lowercase!
|
||||
key = strings.ToLower(key)
|
||||
|
||||
// Check special case
|
||||
if key == "plus" {
|
||||
return "+", true
|
||||
}
|
||||
|
||||
// Handle named keys
|
||||
if namedKeys.Contains(key) {
|
||||
return key, true
|
||||
}
|
||||
|
||||
// Check we only have a single character
|
||||
if len(key) != 1 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
runeKey := rune(key[0])
|
||||
|
||||
// This may be too inclusive
|
||||
if strconv.IsPrint(runeKey) {
|
||||
return key, true
|
||||
}
|
||||
|
||||
return "", false
|
||||
|
||||
}
|
||||
|
||||
func Parse(shortcut string) (*Accelerator, error) {
|
||||
|
||||
var result Accelerator
|
||||
|
||||
// Split the shortcut by +
|
||||
components := strings.Split(shortcut, "+")
|
||||
|
||||
// If we only have one it should be a key
|
||||
// We require components
|
||||
if len(components) == 0 {
|
||||
return nil, fmt.Errorf("no components given to validateComponents")
|
||||
}
|
||||
|
||||
// Keep track of modifiers we have processed
|
||||
var modifiersProcessed slicer.StringSlicer
|
||||
|
||||
// Check components
|
||||
for index, component := range components {
|
||||
|
||||
// If last component
|
||||
if index == len(components)-1 {
|
||||
processedkey, validKey := parseKey(component)
|
||||
if !validKey {
|
||||
return nil, fmt.Errorf("'%s' is not a valid key", component)
|
||||
}
|
||||
result.Key = processedkey
|
||||
continue
|
||||
}
|
||||
|
||||
// Not last component - needs to be modifier
|
||||
lowercaseComponent := strings.ToLower(component)
|
||||
thisModifier, valid := modifierMap[lowercaseComponent]
|
||||
if !valid {
|
||||
return nil, fmt.Errorf("'%s' is not a valid modifier", component)
|
||||
}
|
||||
// Needs to be unique
|
||||
if modifiersProcessed.Contains(lowercaseComponent) {
|
||||
return nil, fmt.Errorf("Modifier '%s' is defined twice for shortcut: %s", component, shortcut)
|
||||
}
|
||||
|
||||
// Save this data
|
||||
result.Modifiers = append(result.Modifiers, thisModifier)
|
||||
modifiersProcessed.Add(lowercaseComponent)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
38
v2/pkg/menu/keys/parser_test.go
Normal file
38
v2/pkg/menu/keys/parser_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/matryer/is"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
|
||||
i := is.New(t)
|
||||
|
||||
type args struct {
|
||||
Input string
|
||||
Expected *Accelerator
|
||||
}
|
||||
|
||||
gooddata := []args{
|
||||
{"CmdOrCtrl+A", CmdOrCtrl("A")},
|
||||
{"SHIFT+.", Shift(".")},
|
||||
{"CTRL+plus", Control("+")},
|
||||
{"CTRL+SHIFT+escApe", Combo("escape", ControlKey, ShiftKey)},
|
||||
{";", Key(";")},
|
||||
{"Super+Tab", Super("tab")},
|
||||
{"OptionOrAlt+Page Down", OptionOrAlt("Page Down")},
|
||||
}
|
||||
for _, tt := range gooddata {
|
||||
result, err := Parse(tt.Input)
|
||||
i.NoErr(err)
|
||||
i.Equal(result, tt.Expected)
|
||||
}
|
||||
baddata := []string{"CmdOrCrl+A", "SHIT+.", "CTL+plus", "CTRL+SHIF+esApe", "escap", "Sper+Tab", "OptionOrAlt"}
|
||||
for _, d := range baddata {
|
||||
result, err := Parse(d)
|
||||
i.True(err != nil)
|
||||
i.Equal(result, "")
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package menu
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||
)
|
||||
|
||||
// MenuItem represents a menuitem contained in a menu
|
||||
@@ -28,7 +29,7 @@ type MenuItem struct {
|
||||
// Callback function when menu clicked
|
||||
Click Callback `json:"-"`
|
||||
|
||||
// Colour
|
||||
// Text Colour
|
||||
RGBA string
|
||||
|
||||
// Font
|
||||
@@ -38,6 +39,12 @@ type MenuItem struct {
|
||||
// Image - base64 image data
|
||||
Image string
|
||||
|
||||
// MacTemplateImage indicates that on a Mac, this image is a template image
|
||||
MacTemplateImage bool
|
||||
|
||||
// MacAlternate indicates that this item is an alternative to the previous menu item
|
||||
MacAlternate bool
|
||||
|
||||
// Tooltip
|
||||
Tooltip string
|
||||
|
||||
|
||||
@@ -6,12 +6,38 @@ type TrayMenu struct {
|
||||
// Label is the text we wish to display in the tray
|
||||
Label string
|
||||
|
||||
// Icon is the name of the tray icon we wish to display.
|
||||
// Image is the name of the tray icon we wish to display.
|
||||
// These are read up during build from <projectdir>/trayicons and
|
||||
// the filenames are used as IDs, minus the extension
|
||||
// EG: <projectdir>/trayicons/main.png can be referenced here with "main"
|
||||
Icon string
|
||||
// If the image is not a filename, it will be treated as base64 image data
|
||||
Image string
|
||||
|
||||
// MacTemplateImage indicates that on a Mac, this image is a template image
|
||||
MacTemplateImage bool
|
||||
|
||||
// Text Colour
|
||||
RGBA string
|
||||
|
||||
// Font
|
||||
FontSize int
|
||||
FontName string
|
||||
|
||||
// Tooltip
|
||||
Tooltip string
|
||||
|
||||
// Callback function when menu clicked
|
||||
//Click Callback `json:"-"`
|
||||
|
||||
// Disabled makes the item unselectable
|
||||
Disabled bool
|
||||
|
||||
// Menu is the initial menu we wish to use for the tray
|
||||
Menu *Menu
|
||||
|
||||
// OnOpen is called when the Menu is opened
|
||||
OnOpen func()
|
||||
|
||||
// OnClose is called when the Menu is closed
|
||||
OnClose func()
|
||||
}
|
||||
|
||||
@@ -2,6 +2,14 @@ package mac
|
||||
|
||||
import "github.com/wailsapp/wails/v2/pkg/menu"
|
||||
|
||||
type ActivationPolicy int
|
||||
|
||||
const (
|
||||
NSApplicationActivationPolicyRegular ActivationPolicy = 0
|
||||
NSApplicationActivationPolicyAccessory ActivationPolicy = 1
|
||||
NSApplicationActivationPolicyProhibited ActivationPolicy = 2
|
||||
)
|
||||
|
||||
// Options are options specific to Mac
|
||||
type Options struct {
|
||||
TitleBar *TitleBar
|
||||
@@ -11,4 +19,6 @@ type Options struct {
|
||||
Menu *menu.Menu
|
||||
TrayMenus []*menu.TrayMenu
|
||||
ContextMenus []*menu.ContextMenu
|
||||
ActivationPolicy ActivationPolicy
|
||||
URLHandlers map[string]func(string)
|
||||
}
|
||||
|
||||
@@ -14,27 +14,28 @@ import (
|
||||
|
||||
// App contains options for creating the App
|
||||
type App struct {
|
||||
Title string
|
||||
Width int
|
||||
Height int
|
||||
DisableResize bool
|
||||
Fullscreen bool
|
||||
MinWidth int
|
||||
MinHeight int
|
||||
MaxWidth int
|
||||
MaxHeight int
|
||||
StartHidden bool
|
||||
DevTools bool
|
||||
RGBA int
|
||||
ContextMenus []*menu.ContextMenu
|
||||
TrayMenus []*menu.TrayMenu
|
||||
Menu *menu.Menu
|
||||
Mac *mac.Options
|
||||
Logger logger.Logger `json:"-"`
|
||||
LogLevel logger.LogLevel
|
||||
Startup func(*wailsruntime.Runtime) `json:"-"`
|
||||
Shutdown func() `json:"-"`
|
||||
Bind []interface{}
|
||||
Title string
|
||||
Width int
|
||||
Height int
|
||||
DisableResize bool
|
||||
Fullscreen bool
|
||||
MinWidth int
|
||||
MinHeight int
|
||||
MaxWidth int
|
||||
MaxHeight int
|
||||
StartHidden bool
|
||||
HideWindowOnClose bool
|
||||
DevTools bool
|
||||
RGBA int
|
||||
ContextMenus []*menu.ContextMenu
|
||||
TrayMenus []*menu.TrayMenu
|
||||
Menu *menu.Menu
|
||||
Mac *mac.Options
|
||||
Logger logger.Logger `json:"-"`
|
||||
LogLevel logger.LogLevel
|
||||
Startup func(*wailsruntime.Runtime) `json:"-"`
|
||||
Shutdown func() `json:"-"`
|
||||
Bind []interface{}
|
||||
}
|
||||
|
||||
// MergeDefaults will set the minimum default values for an application
|
||||
|
||||
Reference in New Issue
Block a user