mirror of
https://github.com/taigrr/wails.git
synced 2026-04-04 06:02:43 -07:00
Compare commits
146 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 | ||
|
|
bbb07e17d9 | ||
|
|
e6b40b55c4 | ||
|
|
7573f68df3 | ||
|
|
ceaacc7ba9 | ||
|
|
0e24f75753 | ||
|
|
82b9deeee4 | ||
|
|
cfa40b797f | ||
|
|
5aeb68acb7 | ||
|
|
b81101414f | ||
|
|
7ae89d04bb | ||
|
|
1c566f3802 | ||
|
|
c9c3c9ab90 | ||
|
|
56394ac50e | ||
|
|
f7c2f12ab2 | ||
|
|
a6bb6e0c93 | ||
|
|
4a5863e6ac | ||
|
|
913fe8d184 | ||
|
|
4ce8130cdf | ||
|
|
fe87463b78 | ||
|
|
fe0f0e29e8 | ||
|
|
83d6dac7cf | ||
|
|
02500e0930 | ||
|
|
5e1187f437 | ||
|
|
064ff3b65e | ||
|
|
b5c7019bf0 | ||
|
|
e9d16e77a3 | ||
|
|
2415d4c531 | ||
|
|
3f75213ce3 | ||
|
|
6120ceabf1 | ||
|
|
95a95d1750 | ||
|
|
d923e84456 | ||
|
|
343f573e78 | ||
|
|
c6d87da4f0 | ||
|
|
a9faebe51a | ||
|
|
d436f5d1be | ||
|
|
f40899821f | ||
|
|
2a64ed19a3 | ||
|
|
47bca0be88 | ||
|
|
7ac8cc6b8b |
@@ -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": [
|
||||
|
||||
@@ -26,7 +26,7 @@ export function OpenURL(url) {
|
||||
* Opens the given filename using the system's default file handler
|
||||
*
|
||||
* @export
|
||||
* @param {sting} filename
|
||||
* @param {string} filename
|
||||
* @returns
|
||||
*/
|
||||
export function OpenFile(filename) {
|
||||
|
||||
@@ -62,7 +62,7 @@ if (window.crypto) {
|
||||
export function Call(bindingName, data, timeout) {
|
||||
|
||||
// Timeout infinite by default
|
||||
if (timeout == null || timeout == undefined) {
|
||||
if (timeout == null) {
|
||||
timeout = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ function Invoke(message) {
|
||||
*
|
||||
* @export
|
||||
* @param {string} type
|
||||
* @param {string} payload
|
||||
* @param {Object} payload
|
||||
* @param {string=} callbackID
|
||||
*/
|
||||
export function SendMessage(type, payload, callbackID) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
164
v2/cmd/wails/internal/commands/update/update.go
Normal file
164
v2/cmd/wails/internal/commands/update/update.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/shell"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/github"
|
||||
|
||||
"github.com/leaanthony/clir"
|
||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
||||
)
|
||||
|
||||
// AddSubcommand adds the `init` command for the Wails application
|
||||
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 the Wails CLI.`)
|
||||
|
||||
// Setup flags
|
||||
var prereleaseRequired bool
|
||||
command.BoolFlag("pre", "Update CLI to latest Prerelease", &prereleaseRequired)
|
||||
|
||||
var specificVersion string
|
||||
command.StringFlag("version", "Install a specific version (Overrides other flags) of the CLI", &specificVersion)
|
||||
|
||||
command.Action(func() error {
|
||||
|
||||
// Create logger
|
||||
logger := clilogger.New(w)
|
||||
|
||||
// Print banner
|
||||
app.PrintBanner()
|
||||
logger.Println("Checking for updates...")
|
||||
|
||||
var desiredVersion *github.SemanticVersion
|
||||
var err error
|
||||
var valid bool
|
||||
|
||||
if len(specificVersion) > 0 {
|
||||
// Check if this is a valid version
|
||||
valid, err = github.IsValidTag(specificVersion)
|
||||
if err == nil {
|
||||
if !valid {
|
||||
err = fmt.Errorf("version '%s' is invalid", specificVersion)
|
||||
} else {
|
||||
desiredVersion, err = github.NewSemanticVersion(specificVersion)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if prereleaseRequired {
|
||||
desiredVersion, err = github.GetLatestPreRelease()
|
||||
} else {
|
||||
desiredVersion, err = github.GetLatestStableRelease()
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
fmt.Println(" Current Version : " + currentVersion)
|
||||
|
||||
if len(specificVersion) > 0 {
|
||||
fmt.Printf(" Desired Version : v%s\n", desiredVersion)
|
||||
} else {
|
||||
if prereleaseRequired {
|
||||
fmt.Printf(" Latest Prerelease : v%s\n", desiredVersion)
|
||||
} else {
|
||||
fmt.Printf(" Latest Release : v%s\n", desiredVersion)
|
||||
}
|
||||
}
|
||||
|
||||
return updateToVersion(logger, desiredVersion, len(specificVersion) > 0, currentVersion)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateToVersion(logger *clilogger.CLILogger, targetVersion *github.SemanticVersion, force bool, currentVersion string) error {
|
||||
|
||||
var targetVersionString = "v" + targetVersion.String()
|
||||
|
||||
// Early exit
|
||||
if targetVersionString == currentVersion {
|
||||
logger.Println("Looks like you're up to date!")
|
||||
return nil
|
||||
}
|
||||
|
||||
var desiredVersion string
|
||||
|
||||
if !force {
|
||||
|
||||
compareVersion := currentVersion
|
||||
|
||||
currentVersion, err := github.NewSemanticVersion(compareVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var success bool
|
||||
|
||||
// Release -> Pre-Release = Massage current version to prerelease format
|
||||
if targetVersion.IsPreRelease() && currentVersion.IsRelease() {
|
||||
testVersion, err := github.NewSemanticVersion(compareVersion + "-0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
success, _ = targetVersion.IsGreaterThan(testVersion)
|
||||
}
|
||||
// Pre-Release -> Release = Massage target version to prerelease format
|
||||
if targetVersion.IsRelease() && currentVersion.IsPreRelease() {
|
||||
// We are ok with greater than or equal
|
||||
mainversion := currentVersion.MainVersion()
|
||||
targetVersion, err = github.NewSemanticVersion(targetVersion.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
success, _ = targetVersion.IsGreaterThanOrEqual(mainversion)
|
||||
}
|
||||
|
||||
// Release -> Release = Standard check
|
||||
if (targetVersion.IsRelease() && currentVersion.IsRelease()) ||
|
||||
(targetVersion.IsPreRelease() && currentVersion.IsPreRelease()) {
|
||||
|
||||
success, _ = targetVersion.IsGreaterThan(currentVersion)
|
||||
}
|
||||
|
||||
// Compare
|
||||
if !success {
|
||||
logger.Println("Error: The requested version is lower than the current version.")
|
||||
logger.Println("If this is what you really want to do, use `wails update -version %s`", targetVersionString)
|
||||
return nil
|
||||
}
|
||||
|
||||
desiredVersion = "v" + targetVersion.String()
|
||||
|
||||
} else {
|
||||
desiredVersion = "v" + targetVersion.String()
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
logger.Print("Installing Wails CLI " + desiredVersion + "...")
|
||||
|
||||
// Run command in non module directory
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Fatal("Cannot find home directory! Please file a bug report!")
|
||||
}
|
||||
|
||||
sout, serr, err := shell.RunCommand(homeDir, "go", "get", "github.com/wailsapp/wails/v2/cmd/wails@"+desiredVersion)
|
||||
if err != nil {
|
||||
logger.Println("Failed.")
|
||||
logger.Println(sout + `\n` + serr)
|
||||
return err
|
||||
}
|
||||
fmt.Println()
|
||||
logger.Println("Wails CLI updated to " + desiredVersion)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/colour"
|
||||
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/update"
|
||||
|
||||
"github.com/leaanthony/clir"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/debug"
|
||||
@@ -17,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)
|
||||
@@ -48,6 +59,11 @@ func main() {
|
||||
fatal(err.Error())
|
||||
}
|
||||
|
||||
err = update.AddSubcommand(app, os.Stdout, version)
|
||||
if err != nil {
|
||||
fatal(err.Error())
|
||||
}
|
||||
|
||||
err = app.Run()
|
||||
if err != nil {
|
||||
println("\n\nERROR: " + err.Error())
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v2.0.0-alpha.10"
|
||||
var version = "v2.0.0-alpha.54"
|
||||
|
||||
@@ -1,11 +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
|
||||
@@ -13,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
|
||||
)
|
||||
|
||||
23
v2/go.sum
23
v2/go.sum
@@ -1,3 +1,5 @@
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -9,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=
|
||||
@@ -39,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=
|
||||
@@ -62,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=
|
||||
@@ -84,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=
|
||||
@@ -100,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=
|
||||
|
||||
@@ -2,11 +2,40 @@
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
)
|
||||
|
||||
// Init initialises the application for a debug environment
|
||||
func (a *App) Init() error {
|
||||
// Indicate debug mode
|
||||
a.debug = true
|
||||
// Enable dev tools
|
||||
a.options.DevTools = true
|
||||
|
||||
if a.appType == "desktop" {
|
||||
// Enable dev tools
|
||||
a.options.DevTools = true
|
||||
}
|
||||
|
||||
// Set log levels
|
||||
loglevel := flag.String("loglevel", "debug", "Loglevel to use - Trace, Debug, Info, Warning, Error")
|
||||
flag.Parse()
|
||||
if len(*loglevel) > 0 {
|
||||
switch strings.ToLower(*loglevel) {
|
||||
case "trace":
|
||||
a.logger.SetLogLevel(logger.TRACE)
|
||||
case "info":
|
||||
a.logger.SetLogLevel(logger.INFO)
|
||||
case "warning":
|
||||
a.logger.SetLogLevel(logger.WARNING)
|
||||
case "error":
|
||||
a.logger.SetLogLevel(logger.ERROR)
|
||||
default:
|
||||
a.logger.SetLogLevel(logger.DEBUG)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build !desktop,!hybrid,!server
|
||||
// +build !desktop,!hybrid,!server,!dev
|
||||
|
||||
package app
|
||||
|
||||
@@ -8,9 +8,10 @@ package app
|
||||
// will be unknown and the application will not work as expected.
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"os"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
)
|
||||
|
||||
@@ -38,7 +39,3 @@ func (a *App) Run() error {
|
||||
os.Exit(1)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bind the dummy interface
|
||||
func (a *App) Bind(_ interface{}) {
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -17,6 +20,8 @@ import (
|
||||
|
||||
// App defines a Wails application structure
|
||||
type App struct {
|
||||
appType string
|
||||
|
||||
window *ffenestri.Application
|
||||
servicebus *servicebus.ServiceBus
|
||||
logger *logger.Logger
|
||||
@@ -24,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
|
||||
@@ -79,11 +85,15 @@ func CreateApp(appoptions *options.App) (*App, error) {
|
||||
|
||||
window := ffenestri.NewApplicationWithConfig(appoptions, myLogger, menuManager)
|
||||
|
||||
// Create binding exemptions - Ugly hack. There must be a better way
|
||||
bindingExemptions := []interface{}{appoptions.Startup, appoptions.Shutdown}
|
||||
|
||||
result := &App{
|
||||
appType: "desktop",
|
||||
window: window,
|
||||
servicebus: servicebus.New(myLogger),
|
||||
logger: myLogger,
|
||||
bindings: binding.NewBindings(myLogger),
|
||||
bindings: binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions),
|
||||
menuManager: menuManager,
|
||||
startupCallback: appoptions.Startup,
|
||||
shutdownCallback: appoptions.Shutdown,
|
||||
@@ -103,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()
|
||||
@@ -118,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
|
||||
}
|
||||
@@ -132,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 {
|
||||
@@ -165,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
|
||||
}
|
||||
@@ -188,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
|
||||
@@ -204,23 +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
|
||||
}
|
||||
|
||||
// Bind a struct to the application by passing in
|
||||
// a pointer to it
|
||||
func (a *App) Bind(structPtr interface{}) {
|
||||
|
||||
// Add the struct to the bindings
|
||||
err := a.bindings.Add(structPtr)
|
||||
if err != nil {
|
||||
a.logger.Fatal("Error during binding: " + err.Error())
|
||||
// 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
|
||||
|
||||
}
|
||||
@@ -75,7 +75,7 @@ func CreateApp(options *Options) *App {
|
||||
webserver: webserver.NewWebServer(myLogger),
|
||||
servicebus: servicebus.New(myLogger),
|
||||
logger: myLogger,
|
||||
bindings: binding.NewBindings(myLogger),
|
||||
bindings: binding.NewBindings(myLogger, options.Bind),
|
||||
}
|
||||
|
||||
// Initialise the app
|
||||
@@ -192,14 +192,3 @@ func (a *App) Run() error {
|
||||
|
||||
return cli.Run()
|
||||
}
|
||||
|
||||
// Bind a struct to the application by passing in
|
||||
// a pointer to it
|
||||
func (a *App) Bind(structPtr interface{}) {
|
||||
|
||||
// Add the struct to the bindings
|
||||
err := a.bindings.Add(structPtr)
|
||||
if err != nil {
|
||||
a.logger.Fatal("Error during binding: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,13 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
|
||||
"github.com/leaanthony/clir"
|
||||
"github.com/wailsapp/wails/v2/internal/binding"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher"
|
||||
"github.com/wailsapp/wails/v2/internal/runtime"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
"github.com/wailsapp/wails/v2/internal/subsystem"
|
||||
"github.com/wailsapp/wails/v2/internal/webserver"
|
||||
@@ -17,12 +20,16 @@ import (
|
||||
|
||||
// App defines a Wails application structure
|
||||
type App struct {
|
||||
appType string
|
||||
|
||||
binding *subsystem.Binding
|
||||
call *subsystem.Call
|
||||
event *subsystem.Event
|
||||
log *subsystem.Log
|
||||
runtime *subsystem.Runtime
|
||||
|
||||
options *options.App
|
||||
|
||||
bindings *binding.Bindings
|
||||
logger *logger.Logger
|
||||
dispatcher *messagedispatcher.Dispatcher
|
||||
@@ -30,28 +37,40 @@ type App struct {
|
||||
webserver *webserver.WebServer
|
||||
|
||||
debug bool
|
||||
|
||||
// Application Stores
|
||||
loglevelStore *runtime.Store
|
||||
appconfigStore *runtime.Store
|
||||
|
||||
// Startup/Shutdown
|
||||
startupCallback func(*runtime.Runtime)
|
||||
shutdownCallback func()
|
||||
}
|
||||
|
||||
// Create App
|
||||
func CreateApp(options *Options) *App {
|
||||
options.mergeDefaults()
|
||||
// We ignore the inputs (for now)
|
||||
func CreateApp(appoptions *options.App) (*App, error) {
|
||||
|
||||
// TODO: Allow logger output override on CLI
|
||||
myLogger := logger.New(os.Stdout)
|
||||
myLogger.SetLogLevel(logger.TRACE)
|
||||
// Merge default options
|
||||
options.MergeDefaults(appoptions)
|
||||
|
||||
// Set up logger
|
||||
myLogger := logger.New(appoptions.Logger)
|
||||
myLogger.SetLogLevel(appoptions.LogLevel)
|
||||
|
||||
result := &App{
|
||||
bindings: binding.NewBindings(myLogger),
|
||||
logger: myLogger,
|
||||
servicebus: servicebus.New(myLogger),
|
||||
webserver: webserver.NewWebServer(myLogger),
|
||||
appType: "server",
|
||||
bindings: binding.NewBindings(myLogger, options.Bind),
|
||||
logger: myLogger,
|
||||
servicebus: servicebus.New(myLogger),
|
||||
webserver: webserver.NewWebServer(myLogger),
|
||||
startupCallback: appoptions.Startup,
|
||||
shutdownCallback: appoptions.Shutdown,
|
||||
}
|
||||
|
||||
// Initialise app
|
||||
result.Init()
|
||||
|
||||
return result
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Run the application
|
||||
@@ -88,8 +107,21 @@ func (a *App) Run() error {
|
||||
if debugMode {
|
||||
a.servicebus.Debug()
|
||||
}
|
||||
|
||||
// Start the runtime
|
||||
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.runtime = runtime
|
||||
a.runtime.Start()
|
||||
|
||||
// Application Stores
|
||||
a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
|
||||
a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options)
|
||||
|
||||
a.servicebus.Start()
|
||||
log, err := subsystem.NewLog(a.servicebus, a.logger)
|
||||
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -102,14 +134,6 @@ func (a *App) Run() error {
|
||||
a.dispatcher = dispatcher
|
||||
a.dispatcher.Start()
|
||||
|
||||
// Start the runtime
|
||||
runtime, err := subsystem.NewRuntime(a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.runtime = runtime
|
||||
a.runtime.Start()
|
||||
|
||||
// Start the binding subsystem
|
||||
binding, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, runtime.GoRuntime())
|
||||
if err != nil {
|
||||
@@ -127,7 +151,7 @@ func (a *App) Run() error {
|
||||
a.event.Start()
|
||||
|
||||
// Start the call subsystem
|
||||
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB())
|
||||
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -147,14 +171,3 @@ func (a *App) Run() error {
|
||||
|
||||
return cli.Run()
|
||||
}
|
||||
|
||||
// Bind a struct to the application by passing in
|
||||
// a pointer to it
|
||||
func (a *App) Bind(structPtr interface{}) {
|
||||
|
||||
// Add the struct to the bindings
|
||||
err := a.bindings.Add(structPtr)
|
||||
if err != nil {
|
||||
a.logger.Fatal("Error during binding: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
@@ -2,28 +2,53 @@ package binding
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
)
|
||||
|
||||
type Bindings struct {
|
||||
db *DB
|
||||
logger logger.CustomLogger
|
||||
db *DB
|
||||
logger logger.CustomLogger
|
||||
exemptions slicer.StringSlicer
|
||||
}
|
||||
|
||||
// NewBindings returns a new Bindings object
|
||||
func NewBindings(logger *logger.Logger) *Bindings {
|
||||
return &Bindings{
|
||||
func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}) *Bindings {
|
||||
result := &Bindings{
|
||||
db: newDB(),
|
||||
logger: logger.CustomLogger("Bindings"),
|
||||
}
|
||||
|
||||
for _, exemption := range exemptions {
|
||||
if exemptions == nil {
|
||||
continue
|
||||
}
|
||||
name := runtime.FuncForPC(reflect.ValueOf(exemption).Pointer()).Name()
|
||||
// Yuk yuk yuk! Is there a better way?
|
||||
name = strings.TrimSuffix(name, "-fm")
|
||||
result.exemptions.Add(name)
|
||||
}
|
||||
|
||||
// Add the structs to bind
|
||||
for _, ptr := range structPointersToBind {
|
||||
err := result.Add(ptr)
|
||||
if err != nil {
|
||||
logger.Fatal("Error during binding: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Add the given struct methods to the Bindings
|
||||
func (b *Bindings) Add(structPtr interface{}) error {
|
||||
|
||||
methods, err := getMethods(structPtr)
|
||||
methods, err := b.getMethods(structPtr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot bind value to app: %s", err.Error())
|
||||
}
|
||||
@@ -36,7 +61,6 @@ func (b *Bindings) Add(structPtr interface{}) error {
|
||||
|
||||
// Add it as a regular method
|
||||
b.db.AddMethod(packageName, structName, methodName, method)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ func (b *BoundMethod) OutputCount() int {
|
||||
func (b *BoundMethod) ParseArgs(args []json.RawMessage) ([]interface{}, error) {
|
||||
|
||||
result := make([]interface{}, b.InputCount())
|
||||
if len(args) != b.InputCount() {
|
||||
return nil, fmt.Errorf("received %d arguments to method '%s', expected %d", len(args), b.Name, b.InputCount())
|
||||
}
|
||||
for index, arg := range args {
|
||||
typ := b.Inputs[index].reflectType
|
||||
inputValue := reflect.New(typ).Interface()
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ func isStruct(value interface{}) bool {
|
||||
return reflect.ValueOf(value).Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
func getMethods(value interface{}) ([]*BoundMethod, error) {
|
||||
func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
|
||||
|
||||
// Create result placeholder
|
||||
var result []*BoundMethod
|
||||
@@ -56,6 +56,11 @@ func getMethods(value interface{}) ([]*BoundMethod, error) {
|
||||
fullMethodName := baseName + "." + methodName
|
||||
method := structValue.MethodByName(methodName)
|
||||
|
||||
methodReflectName := runtime.FuncForPC(methodDef.Func.Pointer()).Name()
|
||||
if b.exemptions.Contains(methodReflectName) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Create new method
|
||||
boundMethod := &BoundMethod{
|
||||
Name: fullMethodName,
|
||||
|
||||
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()
|
||||
}
|
||||
21
v2/internal/deepcopy/LICENSE
Normal file
21
v2/internal/deepcopy/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Joel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
125
v2/internal/deepcopy/deepcopy.go
Normal file
125
v2/internal/deepcopy/deepcopy.go
Normal file
@@ -0,0 +1,125 @@
|
||||
// deepcopy makes deep copies of things. A standard copy will copy the
|
||||
// pointers: deep copy copies the values pointed to. Unexported field
|
||||
// values are not copied.
|
||||
//
|
||||
// Copyright (c)2014-2016, Joel Scoble (github.com/mohae), all rights reserved.
|
||||
// License: MIT, for more details check the included LICENSE file.
|
||||
package deepcopy
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Interface for delegating copy process to type
|
||||
type Interface interface {
|
||||
DeepCopy() interface{}
|
||||
}
|
||||
|
||||
// Iface is an alias to Copy; this exists for backwards compatibility reasons.
|
||||
func Iface(iface interface{}) interface{} {
|
||||
return Copy(iface)
|
||||
}
|
||||
|
||||
// Copy creates a deep copy of whatever is passed to it and returns the copy
|
||||
// in an interface{}. The returned value will need to be asserted to the
|
||||
// correct type.
|
||||
func Copy(src interface{}) interface{} {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make the interface a reflect.Value
|
||||
original := reflect.ValueOf(src)
|
||||
|
||||
// Make a copy of the same type as the original.
|
||||
cpy := reflect.New(original.Type()).Elem()
|
||||
|
||||
// Recursively copy the original.
|
||||
copyRecursive(original, cpy)
|
||||
|
||||
// Return the copy as an interface.
|
||||
return cpy.Interface()
|
||||
}
|
||||
|
||||
// copyRecursive does the actual copying of the interface. It currently has
|
||||
// limited support for what it can handle. Add as needed.
|
||||
func copyRecursive(original, cpy reflect.Value) {
|
||||
// check for implement deepcopy.Interface
|
||||
if original.CanInterface() {
|
||||
if copier, ok := original.Interface().(Interface); ok {
|
||||
cpy.Set(reflect.ValueOf(copier.DeepCopy()))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handle according to original's Kind
|
||||
switch original.Kind() {
|
||||
case reflect.Ptr:
|
||||
// Get the actual value being pointed to.
|
||||
originalValue := original.Elem()
|
||||
|
||||
// if it isn't valid, return.
|
||||
if !originalValue.IsValid() {
|
||||
return
|
||||
}
|
||||
cpy.Set(reflect.New(originalValue.Type()))
|
||||
copyRecursive(originalValue, cpy.Elem())
|
||||
|
||||
case reflect.Interface:
|
||||
// If this is a nil, don't do anything
|
||||
if original.IsNil() {
|
||||
return
|
||||
}
|
||||
// Get the value for the interface, not the pointer.
|
||||
originalValue := original.Elem()
|
||||
|
||||
// Get the value by calling Elem().
|
||||
copyValue := reflect.New(originalValue.Type()).Elem()
|
||||
copyRecursive(originalValue, copyValue)
|
||||
cpy.Set(copyValue)
|
||||
|
||||
case reflect.Struct:
|
||||
t, ok := original.Interface().(time.Time)
|
||||
if ok {
|
||||
cpy.Set(reflect.ValueOf(t))
|
||||
return
|
||||
}
|
||||
// Go through each field of the struct and copy it.
|
||||
for i := 0; i < original.NumField(); i++ {
|
||||
// The Type's StructField for a given field is checked to see if StructField.PkgPath
|
||||
// is set to determine if the field is exported or not because CanSet() returns false
|
||||
// for settable fields. I'm not sure why. -mohae
|
||||
if original.Type().Field(i).PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
copyRecursive(original.Field(i), cpy.Field(i))
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
if original.IsNil() {
|
||||
return
|
||||
}
|
||||
// Make a new slice and copy each element.
|
||||
cpy.Set(reflect.MakeSlice(original.Type(), original.Len(), original.Cap()))
|
||||
for i := 0; i < original.Len(); i++ {
|
||||
copyRecursive(original.Index(i), cpy.Index(i))
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
if original.IsNil() {
|
||||
return
|
||||
}
|
||||
cpy.Set(reflect.MakeMap(original.Type()))
|
||||
for _, key := range original.MapKeys() {
|
||||
originalValue := original.MapIndex(key)
|
||||
copyValue := reflect.New(originalValue.Type()).Elem()
|
||||
copyRecursive(originalValue, copyValue)
|
||||
copyKey := Copy(key.Interface())
|
||||
cpy.SetMapIndex(reflect.ValueOf(copyKey), copyValue)
|
||||
}
|
||||
|
||||
default:
|
||||
cpy.Set(original)
|
||||
}
|
||||
}
|
||||
1110
v2/internal/deepcopy/deepcopy_test.go
Normal file
1110
v2/internal/deepcopy/deepcopy_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -86,3 +86,10 @@ bool getJSONInt(JsonNode *item, const char* key, int *result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JsonNode* mustParseJSON(const char* JSON) {
|
||||
JsonNode* parsedUpdate = json_decode(JSON);
|
||||
if ( parsedUpdate == NULL ) {
|
||||
ABORT("Unable to decode JSON: %s\n", JSON);
|
||||
}
|
||||
return parsedUpdate;
|
||||
}
|
||||
@@ -35,4 +35,6 @@ JsonNode* mustJSONObject(JsonNode *node, const char* key);
|
||||
bool getJSONBool(JsonNode *item, const char* key, bool *result);
|
||||
bool getJSONInt(JsonNode *item, const char* key, int *result);
|
||||
|
||||
JsonNode* mustParseJSON(const char* JSON);
|
||||
|
||||
#endif //ASSETS_C_COMMON_H
|
||||
|
||||
@@ -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,8 @@ 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);
|
||||
|
||||
|
||||
@@ -12,9 +12,10 @@ package ffenestri
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
"strconv"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
)
|
||||
|
||||
@@ -113,6 +114,14 @@ func (c *Client) WindowSize(width int, height int) {
|
||||
C.SetSize(c.app.app, C.int(width), C.int(height))
|
||||
}
|
||||
|
||||
func (c *Client) WindowSetMinSize(width int, height int) {
|
||||
C.SetMinWindowSize(c.app.app, C.int(width), C.int(height))
|
||||
}
|
||||
|
||||
func (c *Client) WindowSetMaxSize(width int, height int) {
|
||||
C.SetMaxWindowSize(c.app.app, C.int(width), C.int(height))
|
||||
}
|
||||
|
||||
// WindowSetColour sets the window colour
|
||||
func (c *Client) WindowSetColour(colour int) {
|
||||
r, g, b, a := intToColour(colour)
|
||||
@@ -192,6 +201,14 @@ func (c *Client) SetTrayMenu(trayMenuJSON string) {
|
||||
C.SetTrayMenu(c.app.app, c.app.string2CString(trayMenuJSON))
|
||||
}
|
||||
|
||||
func (c *Client) UpdateTrayMenuLabel(JSON string) {
|
||||
C.UpdateTrayMenuLabel(c.app.app, c.app.string2CString(JSON))
|
||||
}
|
||||
|
||||
func (c *Client) UpdateContextMenu(contextMenuJSON string) {
|
||||
C.UpdateContextMenu(c.app.app, c.app.string2CString(contextMenuJSON))
|
||||
}
|
||||
|
||||
func (c *Client) DeleteTrayMenuByID(id string) {
|
||||
C.DeleteTrayMenuByID(c.app.app, c.app.string2CString(id))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -140,6 +168,16 @@ void Debug(struct Application *app, const char *message, ... ) {
|
||||
}
|
||||
}
|
||||
|
||||
void Error(struct Application *app, const char *message, ... ) {
|
||||
const char *temp = concat("LEFfenestri (C) | ", message);
|
||||
va_list args;
|
||||
va_start(args, message);
|
||||
vsnprintf(logbuffer, MAXMESSAGE, temp, args);
|
||||
app->sendMessageToBackend(&logbuffer[0]);
|
||||
MEMFREE(temp);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void Fatal(struct Application *app, const char *message, ... ) {
|
||||
const char *temp = concat("LFFfenestri (C) | ", message);
|
||||
va_list args;
|
||||
@@ -150,6 +188,12 @@ void Fatal(struct Application *app, const char *message, ... ) {
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
// Requires NSString input EG lookupStringConstant(str("NSFontAttributeName"))
|
||||
void* lookupStringConstant(id constantName) {
|
||||
void ** dataPtr = CFBundleGetDataPointerForName(CFBundleGetBundleWithIdentifier((CFStringRef)str("com.apple.AppKit")), (CFStringRef) constantName);
|
||||
return (dataPtr ? *dataPtr : nil);
|
||||
}
|
||||
|
||||
bool isRetina(struct Application *app) {
|
||||
CGFloat scale = GET_BACKINGSCALEFACTOR(app->mainWindow);
|
||||
if( (int)scale == 1 ) {
|
||||
@@ -206,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;
|
||||
@@ -219,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);
|
||||
@@ -240,7 +293,10 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
|
||||
struct Application *app = (struct Application *)objc_getAssociatedObject(
|
||||
self, "application");
|
||||
const char *name = (const char *)msg(msg(message, s("name")), s("UTF8String"));
|
||||
if( strcmp(name, "completed") == 0) {
|
||||
if( strcmp(name, "error") == 0 ) {
|
||||
printf("There was a Javascript error. Please open the devtools for more information.\n");
|
||||
Show(app);
|
||||
} else if( strcmp(name, "completed") == 0) {
|
||||
// Delete handler
|
||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("completed"));
|
||||
|
||||
@@ -252,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
|
||||
@@ -351,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\":[]}");
|
||||
}
|
||||
|
||||
@@ -375,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;
|
||||
@@ -403,6 +469,7 @@ void freeDialogIconCache(struct Application *app) {
|
||||
}
|
||||
|
||||
void DestroyApplication(struct Application *app) {
|
||||
app->shuttingDown = true;
|
||||
Debug(app, "Destroying Application");
|
||||
|
||||
// Free the bindings
|
||||
@@ -412,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);
|
||||
@@ -426,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);
|
||||
@@ -444,24 +515,25 @@ void DestroyApplication(struct Application *app) {
|
||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("contextMenu"));
|
||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("windowDrag"));
|
||||
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("external"));
|
||||
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));
|
||||
@@ -483,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);
|
||||
@@ -491,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);
|
||||
@@ -498,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");
|
||||
@@ -512,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:");
|
||||
);
|
||||
@@ -552,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);
|
||||
|
||||
@@ -568,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);
|
||||
@@ -593,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;
|
||||
@@ -689,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);
|
||||
@@ -706,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
|
||||
@@ -794,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
|
||||
@@ -871,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));
|
||||
@@ -879,10 +990,27 @@ void setMinMaxSize(struct Application *app)
|
||||
{
|
||||
msg(app->mainWindow, s("setMinSize:"), CGSizeMake(app->minWidth, app->minHeight));
|
||||
}
|
||||
|
||||
// Calculate if window needs resizing
|
||||
int newWidth = app->width;
|
||||
int newHeight = app->height;
|
||||
|
||||
if (app->maxWidth > 0 && app->width > app->maxWidth) newWidth = app->maxWidth;
|
||||
if (app->minWidth > 0 && app->width < app->minWidth) newWidth = app->minWidth;
|
||||
if (app->maxHeight > 0 && app->height > app->maxHeight ) newHeight = app->maxHeight;
|
||||
if (app->minHeight > 0 && app->height < app->minHeight ) newHeight = app->minHeight;
|
||||
|
||||
// If we have any change, resize window
|
||||
if ( newWidth != app->width || newHeight != app->height ) {
|
||||
SetSize(app, newWidth, newHeight);
|
||||
}
|
||||
}
|
||||
|
||||
void SetMinWindowSize(struct Application *app, int minWidth, int minHeight)
|
||||
{
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
app->minWidth = minWidth;
|
||||
app->minHeight = minHeight;
|
||||
|
||||
@@ -896,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;
|
||||
|
||||
@@ -913,25 +1044,55 @@ void SetDebug(void *applicationPointer, int flag) {
|
||||
}
|
||||
|
||||
|
||||
// SetContextMenus sets the context menu map for this application
|
||||
|
||||
// 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(TrayMenuStoreSingleton, JSON);
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void SetBindings(struct Application *app, const char *bindings) {
|
||||
const char* temp = concat("window.wailsbindings = \"", bindings);
|
||||
const char* jscall = concat(temp, "\";");
|
||||
@@ -988,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";
|
||||
|
||||
@@ -1009,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"));
|
||||
@@ -1040,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
|
||||
@@ -1058,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;
|
||||
}
|
||||
|
||||
@@ -1144,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")) {
|
||||
@@ -1422,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;
|
||||
@@ -1472,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) {
|
||||
|
||||
@@ -1484,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);
|
||||
|
||||
@@ -1520,6 +1773,7 @@ void Run(struct Application *app, int argc, char **argv) {
|
||||
id manager = msg(config, s("userContentController"));
|
||||
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("external"));
|
||||
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("completed"));
|
||||
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("error"));
|
||||
app->manager = manager;
|
||||
|
||||
id wkwebview = msg(c("WKWebView"), s("alloc"));
|
||||
@@ -1653,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);
|
||||
@@ -1662,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();
|
||||
@@ -1687,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;
|
||||
@@ -1710,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package ffenestri
|
||||
|
||||
/*
|
||||
#cgo darwin CFLAGS: -DFFENESTRI_DARWIN=1
|
||||
#cgo darwin LDFLAGS: -framework WebKit -lobjc
|
||||
#cgo darwin LDFLAGS: -framework WebKit -framework CoreFoundation -lobjc
|
||||
|
||||
#include "ffenestri.h"
|
||||
#include "ffenestri_darwin.h"
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#define OBJC_OLD_DISPATCH_PROTOTYPES 1
|
||||
#include <objc/objc-runtime.h>
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include "json.h"
|
||||
#include "hashmap.h"
|
||||
#include "stdlib.h"
|
||||
@@ -13,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)
|
||||
@@ -20,7 +25,6 @@
|
||||
#define strunicode(input) msg(c("NSString"), s("stringWithFormat:"), str("%C"), (unsigned short)input)
|
||||
#define cstr(input) (const char *)msg(input, s("UTF8String"))
|
||||
#define url(input) msg(c("NSURL"), s("fileURLWithPath:"), str(input))
|
||||
|
||||
#define ALLOC(classname) msg(c(classname), s("alloc"))
|
||||
#define ALLOC_INIT(classname) msg(msg(c(classname), s("alloc")), s("init"))
|
||||
#define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("frame"))
|
||||
@@ -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
|
||||
@@ -107,7 +115,13 @@ void SetAppearance(struct Application* app, const char *);
|
||||
void WebviewIsTransparent(struct Application* app);
|
||||
void WindowBackgroundIsTranslucent(struct Application* app);
|
||||
void SetTray(struct Application* app, const char *, const char *, const char *);
|
||||
void SetContextMenus(struct Application* app, const char *);
|
||||
//void SetContextMenus(struct Application* app, const char *);
|
||||
void AddTrayMenu(struct Application* app, const char *);
|
||||
|
||||
void SetActivationPolicy(struct Application* app, int policy);
|
||||
|
||||
void* lookupStringConstant(id constantName);
|
||||
|
||||
void HasURLHandlers(struct Application* app);
|
||||
|
||||
#endif
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "ffenestri_darwin.h"
|
||||
#include "menu_darwin.h"
|
||||
#include "contextmenus_darwin.h"
|
||||
#include "common.h"
|
||||
|
||||
// NewMenu creates a new Menu struct, saving the given menu structure as JSON
|
||||
Menu* NewMenu(JsonNode *menuData) {
|
||||
@@ -63,8 +64,6 @@ MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void DeleteMenu(Menu *menu) {
|
||||
|
||||
// Free menu item hashmap
|
||||
@@ -99,7 +98,11 @@ void DeleteMenu(Menu *menu) {
|
||||
|
||||
// Creates a JSON message for the given menuItemID and data
|
||||
const char* createMenuClickedMessage(const char *menuItemID, const char *data, enum MenuType menuType, const char *parentID) {
|
||||
|
||||
JsonNode *jsonObject = json_mkobject();
|
||||
if (menuItemID == NULL ) {
|
||||
ABORT("Item ID NULL for menu!!\n");
|
||||
}
|
||||
json_append_member(jsonObject, "menuItemID", json_mkstring(menuItemID));
|
||||
json_append_member(jsonObject, "menuType", json_mkstring(MenuTypeAsString[(int)menuType]));
|
||||
if (data != NULL) {
|
||||
@@ -177,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") ) {
|
||||
@@ -333,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);
|
||||
}
|
||||
|
||||
@@ -505,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++;
|
||||
@@ -572,7 +576,63 @@ 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) {
|
||||
id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA) {
|
||||
|
||||
// Process Menu Item attributes
|
||||
id dictionary = ALLOC_INIT("NSMutableDictionary");
|
||||
|
||||
// Process font
|
||||
id font;
|
||||
CGFloat fontSizeFloat = (CGFloat)fontSize;
|
||||
|
||||
// Check if valid
|
||||
id fontNameAsNSString = str(fontName);
|
||||
id fontsOnSystem = msg(msg(c("NSFontManager"), s("sharedFontManager")), s("availableFonts"));
|
||||
bool valid = msg(fontsOnSystem, s("containsObject:"), fontNameAsNSString);
|
||||
if( valid ) {
|
||||
font = msg(c("NSFont"), s("fontWithName:size:"), fontNameAsNSString, fontSizeFloat);
|
||||
} else {
|
||||
bool supportsMonospacedDigitSystemFont = (bool) msg(c("NSFont"), s("respondsToSelector:"), s("monospacedDigitSystemFontOfSize:weight:"));
|
||||
if( supportsMonospacedDigitSystemFont ) {
|
||||
font = msg(c("NSFont"), s("monospacedDigitSystemFontOfSize:weight:"), fontSizeFloat, NSFontWeightRegular);
|
||||
} else {
|
||||
font = msg(c("NSFont"), s("menuFontOfSize:"), fontSizeFloat);
|
||||
}
|
||||
}
|
||||
|
||||
// Add font to dictionary
|
||||
msg(dictionary, s("setObject:forKey:"), font, lookupStringConstant(str("NSFontAttributeName")));
|
||||
|
||||
// Add offset to dictionary
|
||||
id offset = msg(c("NSNumber"), s("numberWithFloat:"), 0.0);
|
||||
msg(dictionary, s("setObject:forKey:"), offset, lookupStringConstant(str("NSBaselineOffsetAttributeName")));
|
||||
|
||||
// RGBA
|
||||
if( RGBA != NULL && strlen(RGBA) > 0) {
|
||||
unsigned short r, g, b, a;
|
||||
|
||||
// white by default
|
||||
r = g = b = a = 255;
|
||||
int count = sscanf(RGBA, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a);
|
||||
if (count > 0) {
|
||||
id colour = msg(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
|
||||
(float)r / 255.0,
|
||||
(float)g / 255.0,
|
||||
(float)b / 255.0,
|
||||
(float)a / 255.0);
|
||||
msg(dictionary, s("setObject:forKey:"), colour, lookupStringConstant(str("NSForegroundColorAttributeName")));
|
||||
msg(colour, s("release"));
|
||||
}
|
||||
}
|
||||
|
||||
id attributedString = ALLOC("NSMutableAttributedString");
|
||||
msg(attributedString, s("initWithString:attributes:"), str(title), dictionary);
|
||||
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
|
||||
@@ -581,18 +641,47 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
|
||||
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( !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;
|
||||
@@ -653,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 = "";
|
||||
@@ -666,6 +760,16 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
const char *acceleratorkey = NULL;
|
||||
const char **modifiers = NULL;
|
||||
|
||||
const char *tooltip = getJSONString(item, "Tooltip");
|
||||
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);
|
||||
|
||||
// If we have an accelerator
|
||||
if( accelerator != NULL ) {
|
||||
// Get the key
|
||||
@@ -698,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);
|
||||
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage, alternate);
|
||||
}
|
||||
else if ( STREQ(type->string_, "Separator")) {
|
||||
addSeparator(parentMenu);
|
||||
|
||||
@@ -14,6 +14,21 @@ static const char *MenuTypeAsString[] = {
|
||||
"ApplicationMenu", "ContextMenu", "TrayMenu",
|
||||
};
|
||||
|
||||
typedef struct _NSRange {
|
||||
unsigned long location;
|
||||
unsigned long length;
|
||||
} NSRange;
|
||||
|
||||
#define NSFontWeightUltraLight -0.8
|
||||
#define NSFontWeightThin -0.6
|
||||
#define NSFontWeightLight -0.4
|
||||
#define NSFontWeightRegular 0.0
|
||||
#define NSFontWeightMedium 0.23
|
||||
#define NSFontWeightSemibold 0.3
|
||||
#define NSFontWeightBold 0.4
|
||||
#define NSFontWeightHeavy 0.56
|
||||
#define NSFontWeightBlack 0.62
|
||||
|
||||
extern void messageFromWindowCallback(const char *);
|
||||
|
||||
typedef struct {
|
||||
@@ -90,11 +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);
|
||||
|
||||
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;
|
||||
|
||||
@@ -49,42 +62,27 @@ void DumpTrayMenu(TrayMenu* trayMenu) {
|
||||
printf(" ['%s':%p] = { label: '%s', icon: '%s', menu: %p, statusbar: %p }\n", trayMenu->ID, trayMenu, trayMenu->label, trayMenu->icon, trayMenu->menu, trayMenu->statusbaritem );
|
||||
}
|
||||
|
||||
void ShowTrayMenu(TrayMenu* trayMenu) {
|
||||
|
||||
// Create a status bar item if we don't have one
|
||||
if( trayMenu->statusbaritem == NULL ) {
|
||||
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"));
|
||||
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
|
||||
|
||||
// Update the icon if needed
|
||||
UpdateTrayMenuIcon(trayMenu);
|
||||
|
||||
// Update the label if needed
|
||||
UpdateTrayMenuLabel(trayMenu);
|
||||
|
||||
// Update the menu
|
||||
id menu = GetMenu(trayMenu->menu);
|
||||
msg(trayMenu->statusbaritem, s("setMenu:"), menu);
|
||||
}
|
||||
|
||||
void UpdateTrayMenuLabel(TrayMenu *trayMenu) {
|
||||
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 ) {
|
||||
return;
|
||||
}
|
||||
// We don't check for a
|
||||
// Update button label
|
||||
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
|
||||
msg(statusBarButton, s("setTitle:"), str(trayMenu->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 UpdateTrayMenuIcon(TrayMenu *trayMenu) {
|
||||
void UpdateTrayIcon(TrayMenu *trayMenu) {
|
||||
|
||||
// Exit early if NULL
|
||||
if( trayMenu->icon == NULL ) {
|
||||
@@ -101,8 +99,55 @@ void UpdateTrayMenuIcon(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
|
||||
if( trayMenu->statusbaritem == NULL ) {
|
||||
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"));
|
||||
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
|
||||
|
||||
// Update the icon if needed
|
||||
UpdateTrayIcon(trayMenu);
|
||||
|
||||
// Update the label if needed
|
||||
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);
|
||||
}
|
||||
|
||||
// UpdateTrayMenuInPlace receives 2 menus. The current menu gets
|
||||
@@ -150,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,14 +29,16 @@ typedef struct {
|
||||
|
||||
JsonNode* processedJSON;
|
||||
|
||||
id delegate;
|
||||
|
||||
} TrayMenu;
|
||||
|
||||
TrayMenu* NewTrayMenu(const char *trayJSON);
|
||||
void DumpTrayMenu(TrayMenu* trayMenu);
|
||||
void ShowTrayMenu(TrayMenu* trayMenu);
|
||||
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);
|
||||
void UpdateTrayMenuIcon(TrayMenu *trayMenu);
|
||||
void UpdateTrayMenuLabel(TrayMenu *trayMenu);
|
||||
void UpdateTrayIcon(TrayMenu *trayMenu);
|
||||
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,51 +75,101 @@ 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);
|
||||
|
||||
// Get the data out
|
||||
const char* ID = mustJSONString(parsedUpdate, "ID");
|
||||
const char* Label = mustJSONString(parsedUpdate, "Label");
|
||||
|
||||
// Check we have this menu
|
||||
TrayMenu *menu = MustGetTrayMenuFromStore(store, ID);
|
||||
|
||||
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);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
TrayMenu* newMenu = NewTrayMenu(menuJSON);
|
||||
// DumpTrayMenu(newMenu);
|
||||
|
||||
// Get the current menu
|
||||
TrayMenu *currentMenu = GetTrayMenuFromStore(store, newMenu->ID);
|
||||
|
||||
// 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);
|
||||
return;
|
||||
}
|
||||
// DumpTrayMenu(currentMenu);
|
||||
|
||||
// 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);
|
||||
currentMenu->menu = NULL;
|
||||
|
||||
// Free JSON
|
||||
if (currentMenu->processedJSON != NULL ) {
|
||||
json_delete(currentMenu->processedJSON);
|
||||
currentMenu->processedJSON = NULL;
|
||||
}
|
||||
|
||||
// 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,4 +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
|
||||
}
|
||||
|
||||
103
v2/internal/github/github.go
Normal file
103
v2/internal/github/github.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetVersionTags gets the list of tags on the Wails repo
|
||||
// It returns a list of sorted tags in descending order
|
||||
func GetVersionTags() ([]*SemanticVersion, error) {
|
||||
|
||||
result := []*SemanticVersion{}
|
||||
var err error
|
||||
|
||||
resp, err := http.Get("https://api.github.com/repos/wailsapp/wails/tags")
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
data := []map[string]interface{}{}
|
||||
err = json.Unmarshal(body, &data)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Convert tag data to Version structs
|
||||
for _, tag := range data {
|
||||
version := tag["name"].(string)
|
||||
if !strings.HasPrefix(version, "v2") {
|
||||
continue
|
||||
}
|
||||
semver, err := NewSemanticVersion(version)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result = append(result, semver)
|
||||
}
|
||||
|
||||
// Reverse Sort
|
||||
sort.Sort(sort.Reverse(SemverCollection(result)))
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
// GetLatestStableRelease gets the latest stable release on GitHub
|
||||
func GetLatestStableRelease() (result *SemanticVersion, err error) {
|
||||
|
||||
tags, err := GetVersionTags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
if tag.IsRelease() {
|
||||
return tag, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no release tag found")
|
||||
}
|
||||
|
||||
// GetLatestPreRelease gets the latest prerelease on GitHub
|
||||
func GetLatestPreRelease() (result *SemanticVersion, err error) {
|
||||
|
||||
tags, err := GetVersionTags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
if tag.IsPreRelease() {
|
||||
return tag, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no prerelease tag found")
|
||||
}
|
||||
|
||||
// IsValidTag returns true if the given string is a valid tag
|
||||
func IsValidTag(tagVersion string) (bool, error) {
|
||||
if tagVersion[0] == 'v' {
|
||||
tagVersion = tagVersion[1:]
|
||||
}
|
||||
tags, err := GetVersionTags()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
if tag.String() == tagVersion {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
106
v2/internal/github/semver.go
Normal file
106
v2/internal/github/semver.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
)
|
||||
|
||||
// SemanticVersion is a struct containing a semantic version
|
||||
type SemanticVersion struct {
|
||||
Version *semver.Version
|
||||
}
|
||||
|
||||
// NewSemanticVersion creates a new SemanticVersion object with the given version string
|
||||
func NewSemanticVersion(version string) (*SemanticVersion, error) {
|
||||
semverVersion, err := semver.NewVersion(version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SemanticVersion{
|
||||
Version: semverVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IsRelease returns true if it's a release version
|
||||
func (s *SemanticVersion) IsRelease() bool {
|
||||
// Limit to v2
|
||||
if s.Version.Major() != 2 {
|
||||
return false
|
||||
}
|
||||
return len(s.Version.Prerelease()) == 0 && len(s.Version.Metadata()) == 0
|
||||
}
|
||||
|
||||
// IsPreRelease returns true if it's a prerelease version
|
||||
func (s *SemanticVersion) IsPreRelease() bool {
|
||||
// Limit to v1
|
||||
if s.Version.Major() != 2 {
|
||||
return false
|
||||
}
|
||||
return len(s.Version.Prerelease()) > 0
|
||||
}
|
||||
|
||||
func (s *SemanticVersion) String() string {
|
||||
return s.Version.String()
|
||||
}
|
||||
|
||||
// IsGreaterThan returns true if this version is greater than the given version
|
||||
func (s *SemanticVersion) IsGreaterThan(version *SemanticVersion) (bool, error) {
|
||||
// Set up new constraint
|
||||
constraint, err := semver.NewConstraint("> " + version.Version.String())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Check if the desired one is greater than the requested on
|
||||
success, msgs := constraint.Validate(s.Version)
|
||||
if !success {
|
||||
return false, msgs[0]
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// IsGreaterThanOrEqual returns true if this version is greater than or equal the given version
|
||||
func (s *SemanticVersion) IsGreaterThanOrEqual(version *SemanticVersion) (bool, error) {
|
||||
// Set up new constraint
|
||||
constraint, err := semver.NewConstraint(">= " + version.Version.String())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Check if the desired one is greater than the requested on
|
||||
success, msgs := constraint.Validate(s.Version)
|
||||
if !success {
|
||||
return false, msgs[0]
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// MainVersion returns the main version of any version+prerelease+metadata
|
||||
// EG: MainVersion("1.2.3-pre") => "1.2.3"
|
||||
func (s *SemanticVersion) MainVersion() *SemanticVersion {
|
||||
mainVersion := fmt.Sprintf("%d.%d.%d", s.Version.Major(), s.Version.Minor(), s.Version.Patch())
|
||||
result, _ := NewSemanticVersion(mainVersion)
|
||||
return result
|
||||
}
|
||||
|
||||
// SemverCollection is a collection of SemanticVersion objects
|
||||
type SemverCollection []*SemanticVersion
|
||||
|
||||
// Len returns the length of a collection. The number of Version instances
|
||||
// on the slice.
|
||||
func (c SemverCollection) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
// Less is needed for the sort interface to compare two Version objects on the
|
||||
// slice. If checks if one is less than the other.
|
||||
func (c SemverCollection) Less(i, j int) bool {
|
||||
return c[i].Version.LessThan(c[j].Version)
|
||||
}
|
||||
|
||||
// Swap is needed for the sort interface to replace the Version objects
|
||||
// at two different positions in the slice.
|
||||
func (c SemverCollection) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package menumanager
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||
)
|
||||
@@ -9,44 +10,59 @@ import (
|
||||
type ProcessedMenuItem struct {
|
||||
ID string
|
||||
// Label is what appears as the menu text
|
||||
Label string
|
||||
Label string `json:",omitempty"`
|
||||
// Role is a predefined menu type
|
||||
Role menu.Role `json:"Role,omitempty"`
|
||||
Role menu.Role `json:",omitempty"`
|
||||
// Accelerator holds a representation of a key binding
|
||||
Accelerator *keys.Accelerator `json:"Accelerator,omitempty"`
|
||||
Accelerator *keys.Accelerator `json:",omitempty"`
|
||||
// Type of MenuItem, EG: Checkbox, Text, Separator, Radio, Submenu
|
||||
Type menu.Type
|
||||
// Disabled makes the item unselectable
|
||||
Disabled bool
|
||||
Disabled bool `json:",omitempty"`
|
||||
// Hidden ensures that the item is not shown in the menu
|
||||
Hidden bool
|
||||
Hidden bool `json:",omitempty"`
|
||||
// Checked indicates if the item is selected (used by Checkbox and Radio types only)
|
||||
Checked bool
|
||||
Checked bool `json:",omitempty"`
|
||||
// Submenu contains a list of menu items that will be shown as a submenu
|
||||
//SubMenu []*MenuItem `json:"SubMenu,omitempty"`
|
||||
SubMenu *ProcessedMenu `json:"SubMenu,omitempty"`
|
||||
SubMenu *ProcessedMenu `json:",omitempty"`
|
||||
|
||||
// Foreground colour in hex RGBA format EG: 0xFF0000FF = #FF0000FF = red
|
||||
Foreground int
|
||||
// Colour
|
||||
RGBA string `json:",omitempty"`
|
||||
|
||||
// Background colour
|
||||
Background int
|
||||
// Font
|
||||
FontSize int `json:",omitempty"`
|
||||
FontName string `json:",omitempty"`
|
||||
|
||||
// Image - base64 image data
|
||||
Image string `json:",omitempty"`
|
||||
MacTemplateImage bool `json:", omitempty"`
|
||||
MacAlternate bool `json:", omitempty"`
|
||||
|
||||
// Tooltip
|
||||
Tooltip string `json:",omitempty"`
|
||||
}
|
||||
|
||||
func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *ProcessedMenuItem {
|
||||
|
||||
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,
|
||||
Foreground: menuItem.Foreground,
|
||||
Background: menuItem.Background,
|
||||
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,28 +3,39 @@ package menumanager
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
)
|
||||
|
||||
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) {
|
||||
@@ -38,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)
|
||||
@@ -50,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)
|
||||
|
||||
@@ -64,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]
|
||||
@@ -94,6 +142,45 @@ func (m *Manager) GetTrayMenus() ([]string, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) {
|
||||
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
|
||||
if !trayMenuKnown {
|
||||
return "", fmt.Errorf("[UpdateTrayMenuLabel] unknown tray id for tray %s", trayMenu.Label)
|
||||
}
|
||||
|
||||
type LabelUpdate struct {
|
||||
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,
|
||||
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)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "[UpdateTrayMenuLabel] ")
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
|
||||
}
|
||||
|
||||
func (m *Manager) GetContextMenus() ([]string, error) {
|
||||
result := []string{}
|
||||
for _, contextMenu := range m.contextMenus {
|
||||
|
||||
@@ -2,6 +2,7 @@ package messagedispatcher
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
@@ -26,13 +27,17 @@ type Client interface {
|
||||
WindowUnminimise()
|
||||
WindowPosition(x int, y int)
|
||||
WindowSize(width int, height int)
|
||||
WindowSetMinSize(width int, height int)
|
||||
WindowSetMaxSize(width int, height int)
|
||||
WindowFullscreen()
|
||||
WindowUnFullscreen()
|
||||
WindowSetColour(colour int)
|
||||
DarkModeEnabled(callbackID string)
|
||||
SetApplicationMenu(menuJSON string)
|
||||
SetTrayMenu(trayMenuJSON string)
|
||||
UpdateTrayMenuLabel(JSON string)
|
||||
UpdateContextMenu(contextMenuJSON string)
|
||||
DeleteTrayMenuByID(id string)
|
||||
}
|
||||
|
||||
// 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,12 +1,14 @@
|
||||
package messagedispatcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/options/dialog"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/crypto"
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
|
||||
@@ -23,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
|
||||
@@ -31,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.
|
||||
@@ -75,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,
|
||||
@@ -86,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
|
||||
@@ -96,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:
|
||||
@@ -119,9 +135,6 @@ func (d *Dispatcher) Start() error {
|
||||
d.processMenuMessage(menuMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// Call shutdown
|
||||
d.shutdown()
|
||||
}()
|
||||
|
||||
return nil
|
||||
@@ -135,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 {
|
||||
@@ -349,6 +358,38 @@ func (d *Dispatcher) processWindowMessage(result *servicebus.Message) {
|
||||
for _, client := range d.clients {
|
||||
client.frontend.WindowSize(w, h)
|
||||
}
|
||||
case "minsize":
|
||||
// We need 2 arguments
|
||||
if len(splitTopic) != 4 {
|
||||
d.logger.Error("Invalid number of parameters for 'window:minsize' : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
w, err1 := strconv.Atoi(splitTopic[2])
|
||||
h, err2 := strconv.Atoi(splitTopic[3])
|
||||
if err1 != nil || err2 != nil {
|
||||
d.logger.Error("Invalid integer parameters for 'window:minsize' : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
// Notifh clients
|
||||
for _, client := range d.clients {
|
||||
client.frontend.WindowSetMinSize(w, h)
|
||||
}
|
||||
case "maxsize":
|
||||
// We need 2 arguments
|
||||
if len(splitTopic) != 4 {
|
||||
d.logger.Error("Invalid number of parameters for 'window:maxsize' : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
w, err1 := strconv.Atoi(splitTopic[2])
|
||||
h, err2 := strconv.Atoi(splitTopic[3])
|
||||
if err1 != nil || err2 != nil {
|
||||
d.logger.Error("Invalid integer parameters for 'window:maxsize' : %#v", result.Data())
|
||||
return
|
||||
}
|
||||
// Notifh clients
|
||||
for _, client := range d.clients {
|
||||
client.frontend.WindowSetMaxSize(w, h)
|
||||
}
|
||||
default:
|
||||
d.logger.Error("Unknown window command: %s", command)
|
||||
}
|
||||
@@ -473,7 +514,37 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
|
||||
client.frontend.UpdateContextMenu(updatedContextMenu)
|
||||
}
|
||||
|
||||
case "updatetraymenulabel":
|
||||
updatedTrayMenuLabel, ok := result.Data().(string)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid data for 'menufrontend:updatetraymenulabel' : %#v",
|
||||
result.Data())
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Work out what we mean in a multi window environment...
|
||||
// For now we will just pick the first one
|
||||
for _, client := range d.clients {
|
||||
client.frontend.UpdateTrayMenuLabel(updatedTrayMenuLabel)
|
||||
}
|
||||
case "deletetraymenu":
|
||||
traymenuid, ok := result.Data().(string)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid data for 'menufrontend:updatetraymenulabel' : %#v",
|
||||
result.Data())
|
||||
return
|
||||
}
|
||||
|
||||
for _, client := range d.clients {
|
||||
client.frontend.DeleteTrayMenuByID(traymenuid)
|
||||
}
|
||||
|
||||
default:
|
||||
d.logger.Error("Unknown menufrontend command: %s", command)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Dispatcher) Close() {
|
||||
d.cancel()
|
||||
d.wg.Wait()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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;
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ The lightweight framework for web-like apps
|
||||
/* jshint esversion: 6 */
|
||||
import { SetBindings } from './bindings';
|
||||
import { Init } from './main';
|
||||
import {RaiseError} from '../desktop/darwin';
|
||||
|
||||
// Setup global error handler
|
||||
window.onerror = function (msg, url, lineNo, columnNo, error) {
|
||||
@@ -21,7 +22,7 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
|
||||
error: JSON.stringify(error),
|
||||
stack: function() { return JSON.stringify(new Error().stack); }(),
|
||||
};
|
||||
window.wails.Log.Error(JSON.stringify(errorMessage));
|
||||
RaiseError(errorMessage);
|
||||
};
|
||||
|
||||
// Initialise the Runtime
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -25,6 +25,10 @@ export function SendMessage(message) {
|
||||
window.webkit.messageHandlers.external.postMessage(message);
|
||||
}
|
||||
|
||||
export function RaiseError(message) {
|
||||
window.webkit.messageHandlers.error.postMessage(message);
|
||||
}
|
||||
|
||||
export function Init() {
|
||||
|
||||
// Setup drag handler
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -13,7 +13,7 @@ module.exports = {
|
||||
mode: 'production',
|
||||
output: {
|
||||
path: path.resolve(__dirname, '..', 'assets'),
|
||||
filename: 'desktop.js',
|
||||
filename: 'desktop_'+platform+'.js',
|
||||
library: 'Wails'
|
||||
},
|
||||
resolve: {
|
||||
|
||||
@@ -10,6 +10,8 @@ type Menu interface {
|
||||
UpdateApplicationMenu()
|
||||
UpdateContextMenu(contextMenu *menu.ContextMenu)
|
||||
SetTrayMenu(trayMenu *menu.TrayMenu)
|
||||
DeleteTrayMenu(trayMenu *menu.TrayMenu)
|
||||
UpdateTrayMenuLabel(trayMenu *menu.TrayMenu)
|
||||
}
|
||||
|
||||
type menuRuntime struct {
|
||||
@@ -34,3 +36,11 @@ func (m *menuRuntime) UpdateContextMenu(contextMenu *menu.ContextMenu) {
|
||||
func (m *menuRuntime) SetTrayMenu(trayMenu *menu.TrayMenu) {
|
||||
m.bus.Publish("menu:settraymenu", 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()")
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
"github.com/wailsapp/wails/v2/internal/shell"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
"github.com/wailsapp/wails/v2/internal/shell"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -49,12 +50,20 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
wailsJS := fs.RelativePath("../../../internal/runtime/assets/desktop.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.
|
||||
|
||||
@@ -6,9 +6,12 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
golog "log"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/deepcopy"
|
||||
)
|
||||
|
||||
// Options defines the optional data that may be used
|
||||
@@ -64,21 +67,31 @@ func fatal(err error) {
|
||||
// New creates a new store
|
||||
func (p *StoreProvider) New(name string, defaultValue interface{}) *Store {
|
||||
|
||||
dataType := reflect.TypeOf(defaultValue)
|
||||
if defaultValue == nil {
|
||||
golog.Fatal("Cannot initialise a store with nil")
|
||||
}
|
||||
|
||||
result := Store{
|
||||
name: name,
|
||||
runtime: p.runtime,
|
||||
data: reflect.ValueOf(defaultValue),
|
||||
dataType: dataType,
|
||||
name: name,
|
||||
runtime: p.runtime,
|
||||
}
|
||||
|
||||
// Setup the sync listener
|
||||
result.setupListener()
|
||||
|
||||
result.Set(defaultValue)
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
func (s *Store) lock() {
|
||||
s.mux.Lock()
|
||||
}
|
||||
|
||||
func (s *Store) unlock() {
|
||||
s.mux.Unlock()
|
||||
}
|
||||
|
||||
// OnError takes a function that will be called
|
||||
// whenever an error occurs
|
||||
func (s *Store) OnError(callback func(error)) {
|
||||
@@ -105,7 +118,7 @@ func (s *Store) processUpdatedData(data string) error {
|
||||
}
|
||||
|
||||
// Lock mutex for writing
|
||||
s.mux.Lock()
|
||||
s.lock()
|
||||
|
||||
// Handle nulls
|
||||
if newData == nil {
|
||||
@@ -116,7 +129,7 @@ func (s *Store) processUpdatedData(data string) error {
|
||||
}
|
||||
|
||||
// Unlock mutex
|
||||
s.mux.Unlock()
|
||||
s.unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -146,20 +159,34 @@ func (s *Store) setupListener() {
|
||||
// Resetting the curent data will resync
|
||||
s.resync()
|
||||
})
|
||||
|
||||
// Do initial resync
|
||||
s.resync()
|
||||
}
|
||||
|
||||
func (s *Store) resync() {
|
||||
// Stringify data
|
||||
newdata, err := json.Marshal(s.data.Interface())
|
||||
if err != nil {
|
||||
if s.errorHandler != nil {
|
||||
s.errorHandler(err)
|
||||
return
|
||||
|
||||
// Lock
|
||||
s.lock()
|
||||
defer s.unlock()
|
||||
|
||||
var result string
|
||||
|
||||
if s.data.IsValid() {
|
||||
rawdata, err := json.Marshal(s.data.Interface())
|
||||
if err != nil {
|
||||
if s.errorHandler != nil {
|
||||
s.errorHandler(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
result = string(rawdata)
|
||||
} else {
|
||||
result = "{}"
|
||||
}
|
||||
|
||||
// Emit event to front end
|
||||
s.runtime.Events.Emit("wails:sync:store:updatedbybackend:"+s.name, string(newdata))
|
||||
s.runtime.Events.Emit("wails:sync:store:updatedbybackend:"+s.name, result)
|
||||
|
||||
// Notify subscribers
|
||||
s.notify()
|
||||
@@ -172,7 +199,9 @@ func (s *Store) notify() {
|
||||
for _, callback := range s.callbacks {
|
||||
|
||||
// Build args
|
||||
s.lock()
|
||||
args := []reflect.Value{s.data}
|
||||
s.unlock()
|
||||
|
||||
if s.notifySynchronously {
|
||||
callback.Call(args)
|
||||
@@ -187,16 +216,31 @@ func (s *Store) notify() {
|
||||
// and notify listeners of the change
|
||||
func (s *Store) Set(data interface{}) error {
|
||||
|
||||
inType := reflect.TypeOf(data)
|
||||
if data == nil {
|
||||
return fmt.Errorf("cannot set store to nil")
|
||||
}
|
||||
|
||||
if inType != s.dataType {
|
||||
return fmt.Errorf("invalid data given in Store.Set(). Expected %s, got %s", s.dataType.String(), inType.String())
|
||||
s.lock()
|
||||
|
||||
dataCopy := deepcopy.Copy(data)
|
||||
|
||||
if dataCopy != nil {
|
||||
inType := reflect.TypeOf(dataCopy)
|
||||
|
||||
if inType != s.dataType && s.data.IsValid() {
|
||||
s.unlock()
|
||||
return fmt.Errorf("invalid data given in Store.Set(). Expected %s, got %s", s.dataType.String(), inType.String())
|
||||
}
|
||||
}
|
||||
|
||||
if s.dataType == nil {
|
||||
s.dataType = reflect.TypeOf(dataCopy)
|
||||
}
|
||||
|
||||
// Save data
|
||||
s.mux.Lock()
|
||||
s.data = reflect.ValueOf(data)
|
||||
s.mux.Unlock()
|
||||
s.data = reflect.ValueOf(dataCopy)
|
||||
|
||||
s.unlock()
|
||||
|
||||
// Resync with subscribers
|
||||
s.resync()
|
||||
@@ -247,7 +291,9 @@ func (s *Store) Subscribe(callback interface{}) {
|
||||
|
||||
callbackFunc := reflect.ValueOf(callback)
|
||||
|
||||
s.lock()
|
||||
s.callbacks = append(s.callbacks, callbackFunc)
|
||||
s.unlock()
|
||||
}
|
||||
|
||||
// updaterCheck ensures the given function to Update() is
|
||||
@@ -297,7 +343,9 @@ func (s *Store) Update(updater interface{}) {
|
||||
}
|
||||
|
||||
// Build args
|
||||
s.lock()
|
||||
args := []reflect.Value{s.data}
|
||||
s.unlock()
|
||||
|
||||
// Make call
|
||||
results := reflect.ValueOf(updater).Call(args)
|
||||
@@ -308,5 +356,12 @@ func (s *Store) Update(updater interface{}) {
|
||||
|
||||
// Get returns the value of the data that's kept in the current state / Store
|
||||
func (s *Store) Get() interface{} {
|
||||
s.lock()
|
||||
defer s.unlock()
|
||||
|
||||
if !s.data.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.data.Interface()
|
||||
}
|
||||
|
||||
165
v2/internal/runtime/store_test.go
Normal file
165
v2/internal/runtime/store_test.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
internallogger "github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
|
||||
is2 "github.com/matryer/is"
|
||||
)
|
||||
|
||||
func TestStoreProvider_NewWithNilDefault(t *testing.T) {
|
||||
is := is2.New(t)
|
||||
|
||||
defaultLogger := logger.NewDefaultLogger()
|
||||
testLogger := internallogger.New(defaultLogger)
|
||||
//testLogger.SetLogLevel(logger.TRACE)
|
||||
serviceBus := servicebus.New(testLogger)
|
||||
err := serviceBus.Start()
|
||||
is.NoErr(err)
|
||||
defer serviceBus.Stop()
|
||||
|
||||
testRuntime := New(serviceBus)
|
||||
storeProvider := newStore(testRuntime)
|
||||
|
||||
testStore := storeProvider.New("test", 0)
|
||||
|
||||
// You should be able to write a new value into a
|
||||
// store initialised with nil
|
||||
err = testStore.Set(100)
|
||||
is.NoErr(err)
|
||||
|
||||
// You shouldn't be able to write different types to the
|
||||
// store
|
||||
err = testStore.Set(false)
|
||||
is.True(err != nil)
|
||||
}
|
||||
|
||||
func TestStoreProvider_NewWithScalarDefault(t *testing.T) {
|
||||
is := is2.New(t)
|
||||
|
||||
defaultLogger := logger.NewDefaultLogger()
|
||||
testLogger := internallogger.New(defaultLogger)
|
||||
//testLogger.SetLogLevel(logger.TRACE)
|
||||
serviceBus := servicebus.New(testLogger)
|
||||
err := serviceBus.Start()
|
||||
is.NoErr(err)
|
||||
defer serviceBus.Stop()
|
||||
|
||||
testRuntime := New(serviceBus)
|
||||
storeProvider := newStore(testRuntime)
|
||||
testStore := storeProvider.New("test", 100)
|
||||
value := testStore.Get()
|
||||
is.Equal(value, 100)
|
||||
testStore.resync()
|
||||
value = testStore.Get()
|
||||
is.Equal(value, 100)
|
||||
}
|
||||
|
||||
func TestStoreProvider_NewWithStructDefault(t *testing.T) {
|
||||
is := is2.New(t)
|
||||
|
||||
defaultLogger := logger.NewDefaultLogger()
|
||||
testLogger := internallogger.New(defaultLogger)
|
||||
//testLogger.SetLogLevel(logger.TRACE)
|
||||
serviceBus := servicebus.New(testLogger)
|
||||
err := serviceBus.Start()
|
||||
is.NoErr(err)
|
||||
defer serviceBus.Stop()
|
||||
|
||||
testRuntime := New(serviceBus)
|
||||
storeProvider := newStore(testRuntime)
|
||||
|
||||
type TestValue struct {
|
||||
Name string
|
||||
}
|
||||
testValue := &TestValue{
|
||||
Name: "hi",
|
||||
}
|
||||
|
||||
testStore := storeProvider.New("test", testValue)
|
||||
|
||||
err = testStore.Set(testValue)
|
||||
is.NoErr(err)
|
||||
testStore.resync()
|
||||
value := testStore.Get()
|
||||
is.Equal(value, testValue)
|
||||
is.Equal(value.(*TestValue).Name, "hi")
|
||||
|
||||
testValue = &TestValue{
|
||||
Name: "there",
|
||||
}
|
||||
err = testStore.Set(testValue)
|
||||
is.NoErr(err)
|
||||
testStore.resync()
|
||||
value = testStore.Get()
|
||||
is.Equal(value, testValue)
|
||||
is.Equal(value.(*TestValue).Name, "there")
|
||||
|
||||
}
|
||||
|
||||
func TestStoreProvider_RapidReadWrite(t *testing.T) {
|
||||
is := is2.New(t)
|
||||
|
||||
defaultLogger := logger.NewDefaultLogger()
|
||||
testLogger := internallogger.New(defaultLogger)
|
||||
//testLogger.SetLogLevel(logger.TRACE)
|
||||
serviceBus := servicebus.New(testLogger)
|
||||
err := serviceBus.Start()
|
||||
is.NoErr(err)
|
||||
defer serviceBus.Stop()
|
||||
|
||||
testRuntime := New(serviceBus)
|
||||
storeProvider := newStore(testRuntime)
|
||||
|
||||
testStore := storeProvider.New("test", 1)
|
||||
|
||||
ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
readers := 100
|
||||
writers := 100
|
||||
wg.Add(readers + writers)
|
||||
// Setup readers
|
||||
go func(testStore *Store, ctx context.Context) {
|
||||
for readerCount := 0; readerCount < readers; readerCount++ {
|
||||
go func(store *Store, ctx context.Context, id int) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
wg.Done()
|
||||
return
|
||||
default:
|
||||
store.Get()
|
||||
}
|
||||
}
|
||||
}(testStore, ctx, readerCount)
|
||||
}
|
||||
}(testStore, ctx)
|
||||
|
||||
// Setup writers
|
||||
go func(testStore *Store, ctx context.Context) {
|
||||
for writerCount := 0; writerCount < writers; writerCount++ {
|
||||
go func(store *Store, ctx context.Context, id int) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
wg.Done()
|
||||
return
|
||||
default:
|
||||
err := store.Set(rand.Int())
|
||||
is.NoErr(err)
|
||||
}
|
||||
}
|
||||
}(testStore, ctx, writerCount)
|
||||
}
|
||||
}(testStore, ctx)
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
@@ -18,6 +18,8 @@ type Window interface {
|
||||
Unminimise()
|
||||
SetTitle(title string)
|
||||
SetSize(width int, height int)
|
||||
SetMinSize(width int, height int)
|
||||
SetMaxSize(width int, height int)
|
||||
SetPosition(x int, y int)
|
||||
Fullscreen()
|
||||
UnFullscreen()
|
||||
@@ -85,6 +87,18 @@ func (w *window) SetSize(width int, height int) {
|
||||
w.bus.Publish(message, "")
|
||||
}
|
||||
|
||||
// SetSize sets the size of the window
|
||||
func (w *window) SetMinSize(width int, height int) {
|
||||
message := fmt.Sprintf("window:minsize:%d:%d", width, height)
|
||||
w.bus.Publish(message, "")
|
||||
}
|
||||
|
||||
// SetSize sets the size of the window
|
||||
func (w *window) SetMaxSize(width int, height int) {
|
||||
message := fmt.Sprintf("window:maxsize:%d:%d", width, height)
|
||||
w.bus.Publish(message, "")
|
||||
}
|
||||
|
||||
// SetPosition sets the position of the window
|
||||
func (w *window) SetPosition(x int, y int) {
|
||||
message := fmt.Sprintf("window:position:%d:%d", x, y)
|
||||
|
||||
@@ -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,23 +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
|
||||
go func() {
|
||||
s.logger.Trace("Starting")
|
||||
|
||||
quit := false
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
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:
|
||||
@@ -90,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
|
||||
@@ -116,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()
|
||||
@@ -134,7 +127,6 @@ func (s *ServiceBus) Stop() error {
|
||||
// Close message queue
|
||||
close(s.messageQueue)
|
||||
|
||||
s.logger.Trace("Stopped")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -171,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
|
||||
}
|
||||
|
||||
@@ -183,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,19 +143,34 @@ 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)
|
||||
if err != nil {
|
||||
m.logger.Trace("%s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Notify frontend of menu change
|
||||
m.bus.Publish("menufrontend:updatetraymenulabel", updatedLabel)
|
||||
|
||||
default:
|
||||
m.logger.Error("unknown menu message: %+v", menuMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call shutdown
|
||||
m.shutdown()
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Menu) shutdown() {
|
||||
m.logger.Trace("Shutdown")
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user