Compare commits

...

39 Commits

Author SHA1 Message Date
Lea Anthony
6dabab1d2e v2.0.0-alpha.21 2021-02-03 07:20:08 +11:00
Lea Anthony
c3bd8b1a85 Remove debug info 2021-02-03 07:15:52 +11:00
Lea Anthony
e1b7332c47 Graceful shutdown 2021-02-03 07:14:44 +11:00
Lea Anthony
5cd08e45f0 v2.0.0-alpha.20 2021-01-31 22:50:01 +11:00
Lea Anthony
c2d03f0e6e Update NSFontWeightRegular to float const 2021-01-31 22:49:29 +11:00
Lea Anthony
d3501f4cb7 v2.0.0-alpha.19 2021-01-31 21:59:16 +11:00
Lea Anthony
ee82cd25b7 Menu Items default to 12 pt on Mac 2021-01-31 21:58:41 +11:00
Lea Anthony
bbb07e17d9 v2.0.0-alpha.18 2021-01-31 21:20:40 +11:00
Lea Anthony
e6b40b55c4 Add nil guard for binding 2021-01-31 21:10:30 +11:00
Lea Anthony
7573f68df3 Add argument guard for methods 2021-01-31 15:35:33 +11:00
Lea Anthony
ceaacc7ba9 Re-added Store.Set() using deep copy 2021-01-30 21:36:59 +11:00
Lea Anthony
0e24f75753 Deep copy initial value in store 2021-01-30 21:29:36 +11:00
Lea Anthony
82b9deeee4 Unexport set 2021-01-30 20:47:10 +11:00
Lea Anthony
cfa40b797f More race condition fixes 2021-01-29 13:03:05 +11:00
Lea Anthony
5aeb68acb7 Fix for calculating new window size 2021-01-28 19:26:26 +11:00
Lea Anthony
b81101414f Support Min/Max Size in Window runtime 2021-01-28 18:48:07 +11:00
Lea Anthony
7ae89d04bb Fixed data race in store. Handle nils better 2021-01-28 07:15:29 +11:00
Lea Anthony
1c566f3802 Fixed data race in servicebus 2021-01-28 06:23:49 +11:00
Lea Anthony
c9c3c9ab90 Don't bind startup/shutdown methods 2021-01-27 21:12:17 +11:00
Lea Anthony
56394ac50e Use mutex when doing a Store resync 2021-01-27 06:03:17 +11:00
Lea Anthony
f7c2f12ab2 Added error handler for dealing with loading user js code 2021-01-26 16:01:06 +11:00
Lea Anthony
a6bb6e0c93 v2.0.0-alpha.17 2021-01-26 12:30:45 +11:00
Lea Anthony
4a5863e6ac Ported update command 2021-01-26 12:30:14 +11:00
Lea Anthony
913fe8d184 v2.0.0-alpha.16 2021-01-26 11:11:23 +11:00
Lea Anthony
4ce8130cdf Replace CreateApp with single Run() method 2021-01-26 11:09:17 +11:00
Lea Anthony
fe87463b78 Move Bind() into app config 2021-01-26 07:04:12 +11:00
Lea Anthony
fe0f0e29e8 Update CreateApp API 2021-01-26 06:45:23 +11:00
Lea Anthony
83d6dac7cf Add appType to builds. Update server code to compile 2021-01-26 06:40:41 +11:00
Lea Anthony
02500e0930 Update vanilla template with new API 2021-01-26 06:40:18 +11:00
Lea Anthony
5e1187f437 Startup/Shutdown hook warnings 2021-01-26 06:39:54 +11:00
Lea Anthony
064ff3b65e Change build wording 2021-01-26 06:38:54 +11:00
Lea Anthony
b5c7019bf0 Fix compile warnings 2021-01-26 06:37:33 +11:00
Lea Anthony
e9d16e77a3 Add support for loglevel flag in debug builds 2021-01-25 21:42:31 +11:00
Lea Anthony
2415d4c531 v2.0.0-alpha.15 2021-01-25 21:21:44 +11:00
Lea Anthony
3f75213ce3 Small linting fixes 2021-01-25 21:05:20 +11:00
Lea Anthony
6120ceabf1 Support Fonts & Colours for menuitems 2021-01-25 21:04:57 +11:00
Lea Anthony
95a95d1750 Ensure store does initial resync 2021-01-25 21:02:36 +11:00
Lea Anthony
d923e84456 v2.0.0-alpha.14 2021-01-23 21:00:04 +11:00
Lea Anthony
343f573e78 Fix menu tooltips 2021-01-23 20:59:02 +11:00
55 changed files with 2655 additions and 381 deletions

View File

@@ -26,7 +26,7 @@ export function OpenURL(url) {
* Opens the given filename using the system's default file handler * Opens the given filename using the system's default file handler
* *
* @export * @export
* @param {sting} filename * @param {string} filename
* @returns * @returns
*/ */
export function OpenFile(filename) { export function OpenFile(filename) {

View File

@@ -62,7 +62,7 @@ if (window.crypto) {
export function Call(bindingName, data, timeout) { export function Call(bindingName, data, timeout) {
// Timeout infinite by default // Timeout infinite by default
if (timeout == null || timeout == undefined) { if (timeout == null) {
timeout = 0; timeout = 0;
} }

View File

@@ -45,7 +45,7 @@ function Invoke(message) {
* *
* @export * @export
* @param {string} type * @param {string} type
* @param {string} payload * @param {Object} payload
* @param {string=} callbackID * @param {string=} callbackID
*/ */
export function SendMessage(type, payload, callbackID) { export function SendMessage(type, payload, callbackID) {

View 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 Wails.`)
// Setup flags
var prereleaseRequired bool
command.BoolFlag("pre", "Update to latest Prerelease", &prereleaseRequired)
var specificVersion string
command.StringFlag("version", "Install a specific version (Overrides other flags)", &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 " + 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 updated to " + desiredVersion)
return nil
}

View File

@@ -3,6 +3,8 @@ package main
import ( import (
"os" "os"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/update"
"github.com/leaanthony/clir" "github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build" "github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/debug" "github.com/wailsapp/wails/v2/cmd/wails/internal/commands/debug"
@@ -48,6 +50,11 @@ func main() {
fatal(err.Error()) fatal(err.Error())
} }
err = update.AddSubcommand(app, os.Stdout, version)
if err != nil {
fatal(err.Error())
}
err = app.Run() err = app.Run()
if err != nil { if err != nil {
println("\n\nERROR: " + err.Error()) println("\n\nERROR: " + err.Error())

View File

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

View File

@@ -3,6 +3,7 @@ module github.com/wailsapp/wails/v2
go 1.15 go 1.15
require ( require (
github.com/Masterminds/semver v1.5.0
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/fatih/structtag v1.2.0 github.com/fatih/structtag v1.2.0
github.com/fsnotify/fsnotify v1.4.9 github.com/fsnotify/fsnotify v1.4.9

View File

@@ -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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

View File

@@ -2,11 +2,39 @@
package app package app
import (
"flag"
"github.com/wailsapp/wails/v2/pkg/logger"
"strings"
)
// Init initialises the application for a debug environment // Init initialises the application for a debug environment
func (a *App) Init() error { func (a *App) Init() error {
// Indicate debug mode // Indicate debug mode
a.debug = true a.debug = true
// Enable dev tools
a.options.DevTools = true if a.appType == "desktop" {
// Enable dev tools
a.options.DevTools = true
}
// Set log levels
greeting := flag.String("loglevel", "debug", "Loglevel to use - Trace, Debug, Info, Warning, Error")
flag.Parse()
if len(*greeting) > 0 {
switch strings.ToLower(*greeting) {
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 return nil
} }

View File

@@ -8,9 +8,10 @@ package app
// will be unknown and the application will not work as expected. // will be unknown and the application will not work as expected.
import ( import (
"github.com/wailsapp/wails/v2/internal/logger"
"os" "os"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options"
) )
@@ -38,7 +39,3 @@ func (a *App) Run() error {
os.Exit(1) os.Exit(1)
return nil return nil
} }
// Bind the dummy interface
func (a *App) Bind(_ interface{}) {
}

View File

@@ -3,6 +3,9 @@
package app package app
import ( import (
"context"
"sync"
"github.com/wailsapp/wails/v2/internal/binding" "github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/ffenestri" "github.com/wailsapp/wails/v2/internal/ffenestri"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
@@ -17,6 +20,8 @@ import (
// App defines a Wails application structure // App defines a Wails application structure
type App struct { type App struct {
appType string
window *ffenestri.Application window *ffenestri.Application
servicebus *servicebus.ServiceBus servicebus *servicebus.ServiceBus
logger *logger.Logger logger *logger.Logger
@@ -24,10 +29,10 @@ type App struct {
options *options.App options *options.App
// Subsystems // Subsystems
log *subsystem.Log log *subsystem.Log
runtime *subsystem.Runtime runtime *subsystem.Runtime
event *subsystem.Event event *subsystem.Event
binding *subsystem.Binding //binding *subsystem.Binding
call *subsystem.Call call *subsystem.Call
menu *subsystem.Menu menu *subsystem.Menu
dispatcher *messagedispatcher.Dispatcher dispatcher *messagedispatcher.Dispatcher
@@ -79,11 +84,15 @@ func CreateApp(appoptions *options.App) (*App, error) {
window := ffenestri.NewApplicationWithConfig(appoptions, myLogger, menuManager) window := ffenestri.NewApplicationWithConfig(appoptions, myLogger, menuManager)
// Create binding exemptions - Ugly hack. There must be a better way
bindingExemptions := []interface{}{appoptions.Startup, appoptions.Shutdown}
result := &App{ result := &App{
appType: "desktop",
window: window, window: window,
servicebus: servicebus.New(myLogger), servicebus: servicebus.New(myLogger),
logger: myLogger, logger: myLogger,
bindings: binding.NewBindings(myLogger), bindings: binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions),
menuManager: menuManager, menuManager: menuManager,
startupCallback: appoptions.Startup, startupCallback: appoptions.Startup,
shutdownCallback: appoptions.Shutdown, shutdownCallback: appoptions.Shutdown,
@@ -103,8 +112,13 @@ func (a *App) Run() error {
var err error var err error
// Setup a context
var subsystemWaitGroup sync.WaitGroup
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
ctx, cancel := context.WithCancel(parentContext)
// Setup signal handler // Setup signal handler
signalsubsystem, err := signal.NewManager(a.servicebus, a.logger) signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger, a.shutdownCallback)
if err != nil { if err != nil {
return err return err
} }
@@ -118,7 +132,7 @@ func (a *App) Run() error {
return err 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, a.shutdownCallback)
if err != nil { if err != nil {
return err return err
} }
@@ -132,17 +146,6 @@ func (a *App) Run() error {
a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel) a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options) 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 // Start the logging subsystem
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore) log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
if err != nil { if err != nil {
@@ -166,18 +169,18 @@ func (a *App) Run() error {
} }
// Start the eventing subsystem // Start the eventing subsystem
event, err := subsystem.NewEvent(a.servicebus, a.logger) eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger)
if err != nil { if err != nil {
return err return err
} }
a.event = event a.event = eventsubsystem
err = a.event.Start() err = a.event.Start()
if err != nil { if err != nil {
return err return err
} }
// Start the menu subsystem // 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 { if err != nil {
return err return err
} }
@@ -188,11 +191,11 @@ func (a *App) Run() error {
} }
// Start the call subsystem // 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 { if err != nil {
return err return err
} }
a.call = call a.call = callSubsystem
err = a.call.Start() err = a.call.Start()
if err != nil { if err != nil {
return err return err
@@ -204,23 +207,31 @@ func (a *App) Run() error {
return err return err
} }
result := a.window.Run(dispatcher, bindingDump, a.debug) err = a.window.Run(dispatcher, bindingDump, a.debug)
a.logger.Trace("Ffenestri.Run() exited") 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() err = a.servicebus.Stop()
if err != nil { if err != nil {
return err return err
} }
return result println("Desktop.Run() finished")
}
// Bind a struct to the application by passing in return nil
// a pointer to it
func (a *App) Bind(structPtr interface{}) {
// Add the struct to the bindings
err := a.bindings.Add(structPtr)
if err != nil {
a.logger.Fatal("Error during binding: " + err.Error())
}
} }

View File

@@ -75,7 +75,7 @@ func CreateApp(options *Options) *App {
webserver: webserver.NewWebServer(myLogger), webserver: webserver.NewWebServer(myLogger),
servicebus: servicebus.New(myLogger), servicebus: servicebus.New(myLogger),
logger: myLogger, logger: myLogger,
bindings: binding.NewBindings(myLogger), bindings: binding.NewBindings(myLogger, options.Bind),
} }
// Initialise the app // Initialise the app
@@ -192,14 +192,3 @@ func (a *App) Run() error {
return cli.Run() return cli.Run()
} }
// Bind a struct to the application by passing in
// a pointer to it
func (a *App) Bind(structPtr interface{}) {
// Add the struct to the bindings
err := a.bindings.Add(structPtr)
if err != nil {
a.logger.Fatal("Error during binding: " + err.Error())
}
}

View File

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

View File

@@ -2,28 +2,53 @@ package binding
import ( import (
"fmt" "fmt"
"reflect"
"runtime"
"strings" "strings"
"github.com/leaanthony/slicer"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
) )
type Bindings struct { type Bindings struct {
db *DB db *DB
logger logger.CustomLogger logger logger.CustomLogger
exemptions slicer.StringSlicer
} }
// NewBindings returns a new Bindings object // NewBindings returns a new Bindings object
func NewBindings(logger *logger.Logger) *Bindings { func NewBindings(logger *logger.Logger, structPointersToBind []interface{}, exemptions []interface{}) *Bindings {
return &Bindings{ result := &Bindings{
db: newDB(), db: newDB(),
logger: logger.CustomLogger("Bindings"), logger: logger.CustomLogger("Bindings"),
} }
for _, exemption := range exemptions {
if exemptions == nil {
continue
}
name := runtime.FuncForPC(reflect.ValueOf(exemption).Pointer()).Name()
// Yuk yuk yuk! Is there a better way?
name = strings.TrimSuffix(name, "-fm")
result.exemptions.Add(name)
}
// Add the structs to bind
for _, ptr := range structPointersToBind {
err := result.Add(ptr)
if err != nil {
logger.Fatal("Error during binding: " + err.Error())
}
}
return result
} }
// Add the given struct methods to the Bindings // Add the given struct methods to the Bindings
func (b *Bindings) Add(structPtr interface{}) error { func (b *Bindings) Add(structPtr interface{}) error {
methods, err := getMethods(structPtr) methods, err := b.getMethods(structPtr)
if err != nil { if err != nil {
return fmt.Errorf("cannot bind value to app: %s", err.Error()) 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 // Add it as a regular method
b.db.AddMethod(packageName, structName, methodName, method) b.db.AddMethod(packageName, structName, methodName, method)
} }
return nil return nil
} }

View File

@@ -30,6 +30,9 @@ func (b *BoundMethod) OutputCount() int {
func (b *BoundMethod) ParseArgs(args []json.RawMessage) ([]interface{}, error) { func (b *BoundMethod) ParseArgs(args []json.RawMessage) ([]interface{}, error) {
result := make([]interface{}, b.InputCount()) 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 { for index, arg := range args {
typ := b.Inputs[index].reflectType typ := b.Inputs[index].reflectType
inputValue := reflect.New(typ).Interface() inputValue := reflect.New(typ).Interface()

View File

@@ -23,7 +23,7 @@ func isStruct(value interface{}) bool {
return reflect.ValueOf(value).Kind() == reflect.Struct return reflect.ValueOf(value).Kind() == reflect.Struct
} }
func getMethods(value interface{}) ([]*BoundMethod, error) { func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
// Create result placeholder // Create result placeholder
var result []*BoundMethod var result []*BoundMethod
@@ -56,6 +56,11 @@ func getMethods(value interface{}) ([]*BoundMethod, error) {
fullMethodName := baseName + "." + methodName fullMethodName := baseName + "." + methodName
method := structValue.MethodByName(methodName) method := structValue.MethodByName(methodName)
methodReflectName := runtime.FuncForPC(methodDef.Func.Pointer()).Name()
if b.exemptions.Contains(methodReflectName) {
continue
}
// Create new method // Create new method
boundMethod := &BoundMethod{ boundMethod := &BoundMethod{
Name: fullMethodName, Name: fullMethodName,

View 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.

View 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)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,12 @@
package ffenestri package ffenestri
import ( import (
"github.com/wailsapp/wails/v2/internal/menumanager"
"runtime" "runtime"
"strings" "strings"
"unsafe" "unsafe"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher" "github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/pkg/options" "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) fullscreen := a.bool2Cint(a.config.Fullscreen)
startHidden := a.bool2Cint(a.config.StartHidden) startHidden := a.bool2Cint(a.config.StartHidden)
logLevel := C.int(a.config.LogLevel) 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 // Save app reference
a.app = (*C.struct_Application)(app) a.app = (*C.struct_Application)(app)
@@ -167,6 +169,7 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug
// Yes - Save memory reference and run app, cleaning up afterwards // Yes - Save memory reference and run app, cleaning up afterwards
a.saveMemoryReference(unsafe.Pointer(app)) a.saveMemoryReference(unsafe.Pointer(app))
C.Run(app, 0, nil) C.Run(app, 0, nil)
println("Back in ffenestri.go")
} else { } else {
// Oh no! We couldn't initialise the application // Oh no! We couldn't initialise the application
a.logger.Fatal("Cannot initialise Application.") a.logger.Fatal("Cannot initialise Application.")

View File

@@ -4,7 +4,7 @@
#include <stdio.h> #include <stdio.h>
struct Application; 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 SetMinWindowSize(struct Application*, int minWidth, int minHeight);
extern void SetMaxWindowSize(struct Application*, int maxWidth, int maxHeight); extern void SetMaxWindowSize(struct Application*, int maxWidth, int maxHeight);
extern void Run(struct Application*, int argc, char **argv); extern void Run(struct Application*, int argc, char **argv);

View File

@@ -12,9 +12,10 @@ package ffenestri
import "C" import "C"
import ( import (
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"strconv" "strconv"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/logger" "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)) C.SetSize(c.app.app, C.int(width), C.int(height))
} }
func (c *Client) WindowSetMinSize(width int, height int) {
C.SetMinWindowSize(c.app.app, C.int(width), C.int(height))
}
func (c *Client) WindowSetMaxSize(width int, height int) {
C.SetMaxWindowSize(c.app.app, C.int(width), C.int(height))
}
// WindowSetColour sets the window colour // WindowSetColour sets the window colour
func (c *Client) WindowSetColour(colour int) { func (c *Client) WindowSetColour(colour int) {
r, g, b, a := intToColour(colour) r, g, b, a := intToColour(colour)

View File

@@ -34,6 +34,12 @@ BOOL yes(id self, SEL cmd)
return YES; return YES;
} }
// no command simply returns NO!
BOOL no(id self, SEL cmd)
{
return NO;
}
// Prints a hashmap entry // Prints a hashmap entry
int hashmap_log(void *const context, struct hashmap_element_s *const e) { int hashmap_log(void *const context, struct hashmap_element_s *const e) {
printf("%s: %p ", (char*)e->key, e->data); printf("%s: %p ", (char*)e->key, e->data);
@@ -65,6 +71,7 @@ struct Application {
// Cocoa data // Cocoa data
id application; id application;
id delegate; id delegate;
id windowDelegate;
id mainWindow; id mainWindow;
id wkwebview; id wkwebview;
id manager; id manager;
@@ -92,6 +99,7 @@ struct Application {
const char *appearance; const char *appearance;
int decorations; int decorations;
int logLevel; int logLevel;
int hideWindowOnClose;
// Features // Features
int frame; int frame;
@@ -120,6 +128,9 @@ struct Application {
// Bindings // Bindings
const char *bindings; const char *bindings;
// shutting down flag
bool shuttingDown;
}; };
// Debug works like sprintf but mutes if the global debug flag is true // Debug works like sprintf but mutes if the global debug flag is true
@@ -140,6 +151,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, ... ) { void Fatal(struct Application *app, const char *message, ... ) {
const char *temp = concat("LFFfenestri (C) | ", message); const char *temp = concat("LFFfenestri (C) | ", message);
va_list args; va_list args;
@@ -150,6 +171,12 @@ void Fatal(struct Application *app, const char *message, ... ) {
va_end(args); 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) { bool isRetina(struct Application *app) {
CGFloat scale = GET_BACKINGSCALEFACTOR(app->mainWindow); CGFloat scale = GET_BACKINGSCALEFACTOR(app->mainWindow);
if( (int)scale == 1 ) { if( (int)scale == 1 ) {
@@ -206,6 +233,9 @@ void applyWindowColour(struct Application *app) {
} }
void SetColour(struct Application *app, int red, int green, int blue, int alpha) { 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->red = red;
app->green = green; app->green = green;
app->blue = blue; app->blue = blue;
@@ -219,12 +249,18 @@ void FullSizeContent(struct Application *app) {
} }
void Hide(struct Application *app) { void Hide(struct Application *app) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
msg(app->application, s("hide:")) msg(app->application, s("hide:"))
); );
} }
void Show(struct Application *app) { void Show(struct Application *app) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
msg(app->mainWindow, s("makeKeyAndOrderFront:"), NULL); msg(app->mainWindow, s("makeKeyAndOrderFront:"), NULL);
msg(app->application, s("activateIgnoringOtherApps:"), YES); msg(app->application, s("activateIgnoringOtherApps:"), YES);
@@ -240,7 +276,10 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
struct Application *app = (struct Application *)objc_getAssociatedObject( struct Application *app = (struct Application *)objc_getAssociatedObject(
self, "application"); self, "application");
const char *name = (const char *)msg(msg(message, s("name")), s("UTF8String")); 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 // Delete handler
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("completed")); msg(app->manager, s("removeScriptMessageHandlerForName:"), str("completed"));
@@ -403,6 +442,7 @@ void freeDialogIconCache(struct Application *app) {
} }
void DestroyApplication(struct Application *app) { void DestroyApplication(struct Application *app) {
app->shuttingDown = true;
Debug(app, "Destroying Application"); Debug(app, "Destroying Application");
// Free the bindings // Free the bindings
@@ -444,12 +484,17 @@ void DestroyApplication(struct Application *app) {
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("contextMenu")); msg(app->manager, s("removeScriptMessageHandlerForName:"), str("contextMenu"));
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("windowDrag")); msg(app->manager, s("removeScriptMessageHandlerForName:"), str("windowDrag"));
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("external")); msg(app->manager, s("removeScriptMessageHandlerForName:"), str("external"));
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("error"));
// Close main window // 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"); Debug(app, "Finished Destroying Application");
} }
@@ -457,11 +502,32 @@ void DestroyApplication(struct Application *app) {
// used by the application // used by the application
void Quit(struct Application *app) { void Quit(struct Application *app) {
Debug(app, "Quit Called"); Debug(app, "Quit Called");
DestroyApplication(app); ON_MAIN_THREAD (
// Terminate app
msg(app->application, s("stop:"), NULL);
id fakeevent = msg(c("NSEvent"),
s("otherEventWithType:location:modifierFlags:timestamp:windowNumber:context:subtype:data1:data2:"),
15, // Type
msg(c("CGPoint"), s("init:x:y:"), 0, 0), // location
0, // flags
0, // timestamp
0, // window
NULL, // context
0, // subtype
0, // data1
0 // data2
);
msg(c("NSApp"), s("postEvent:atStart:"), fakeevent, true);
// msg(c(app->mainWindow), s("performClose:"))
);
} }
// SetTitle sets the main window title to the given string // SetTitle sets the main window title to the given string
void SetTitle(struct Application *app, const char *title) { void SetTitle(struct Application *app, const char *title) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
Debug(app, "SetTitle Called"); Debug(app, "SetTitle Called");
ON_MAIN_THREAD( ON_MAIN_THREAD(
msg(app->mainWindow, s("setTitle:"), str(title)); msg(app->mainWindow, s("setTitle:"), str(title));
@@ -483,6 +549,9 @@ bool isFullScreen(struct Application *app) {
// Fullscreen sets the main window to be fullscreen // Fullscreen sets the main window to be fullscreen
void Fullscreen(struct Application *app) { void Fullscreen(struct Application *app) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
Debug(app, "Fullscreen Called"); Debug(app, "Fullscreen Called");
if( ! isFullScreen(app) ) { if( ! isFullScreen(app) ) {
ToggleFullscreen(app); ToggleFullscreen(app);
@@ -491,6 +560,9 @@ void Fullscreen(struct Application *app) {
// UnFullscreen resets the main window after a fullscreen // UnFullscreen resets the main window after a fullscreen
void UnFullscreen(struct Application *app) { void UnFullscreen(struct Application *app) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
Debug(app, "UnFullscreen Called"); Debug(app, "UnFullscreen Called");
if( isFullScreen(app) ) { if( isFullScreen(app) ) {
ToggleFullscreen(app); ToggleFullscreen(app);
@@ -498,6 +570,9 @@ void UnFullscreen(struct Application *app) {
} }
void Center(struct Application *app) { void Center(struct Application *app) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
Debug(app, "Center Called"); Debug(app, "Center Called");
ON_MAIN_THREAD( ON_MAIN_THREAD(
MAIN_WINDOW_CALL("center"); MAIN_WINDOW_CALL("center");
@@ -512,23 +587,35 @@ void ToggleMaximise(struct Application *app) {
} }
void Maximise(struct Application *app) { void Maximise(struct Application *app) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
if( app->maximised == 0) { if( app->maximised == 0) {
ToggleMaximise(app); ToggleMaximise(app);
} }
} }
void Unmaximise(struct Application *app) { void Unmaximise(struct Application *app) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
if( app->maximised == 1) { if( app->maximised == 1) {
ToggleMaximise(app); ToggleMaximise(app);
} }
} }
void Minimise(struct Application *app) { void Minimise(struct Application *app) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
MAIN_WINDOW_CALL("miniaturize:"); MAIN_WINDOW_CALL("miniaturize:");
); );
} }
void Unminimise(struct Application *app) { void Unminimise(struct Application *app) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
MAIN_WINDOW_CALL("deminiaturize:"); MAIN_WINDOW_CALL("deminiaturize:");
); );
@@ -552,6 +639,9 @@ void dumpFrame(struct Application *app, const char *message, CGRect frame) {
} }
void SetSize(struct Application *app, int width, int height) { void SetSize(struct Application *app, int width, int height) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
id screen = getCurrentScreen(app); id screen = getCurrentScreen(app);
@@ -568,6 +658,9 @@ void SetSize(struct Application *app, int width, int height) {
} }
void SetPosition(struct Application *app, int x, int y) { void SetPosition(struct Application *app, int x, int y) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
id screen = getCurrentScreen(app); id screen = getCurrentScreen(app);
CGRect screenFrame = GET_FRAME(screen); CGRect screenFrame = GET_FRAME(screen);
@@ -593,6 +686,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) { 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( ON_MAIN_THREAD(
id alert = ALLOC_INIT("NSAlert"); id alert = ALLOC_INIT("NSAlert");
char *dialogType = type; char *dialogType = type;
@@ -706,6 +802,9 @@ extern void MessageDialog(struct Application *app, char *callbackID, char *type,
// OpenDialog opens a dialog to select files/directories // 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) { 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); Debug(app, "OpenDialog Called with callback id: %s", callbackID);
// Create an open panel // Create an open panel
@@ -794,6 +893,9 @@ void OpenDialog(struct Application *app, char *callbackID, char *title, char *fi
// SaveDialog opens a dialog to select files/directories // 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) { 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); Debug(app, "SaveDialog Called with callback id: %s", callbackID);
// Create an open panel // Create an open panel
@@ -871,6 +973,9 @@ void DisableFrame(struct Application *app)
void setMinMaxSize(struct Application *app) void setMinMaxSize(struct Application *app)
{ {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
if (app->maxHeight > 0 && app->maxWidth > 0) if (app->maxHeight > 0 && app->maxWidth > 0)
{ {
msg(app->mainWindow, s("setMaxSize:"), CGSizeMake(app->maxWidth, app->maxHeight)); msg(app->mainWindow, s("setMaxSize:"), CGSizeMake(app->maxWidth, app->maxHeight));
@@ -879,10 +984,27 @@ void setMinMaxSize(struct Application *app)
{ {
msg(app->mainWindow, s("setMinSize:"), CGSizeMake(app->minWidth, app->minHeight)); 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) void SetMinWindowSize(struct Application *app, int minWidth, int minHeight)
{ {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
app->minWidth = minWidth; app->minWidth = minWidth;
app->minHeight = minHeight; app->minHeight = minHeight;
@@ -896,6 +1018,9 @@ void SetMinWindowSize(struct Application *app, int minWidth, int minHeight)
void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight) void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight)
{ {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
app->maxWidth = maxWidth; app->maxWidth = maxWidth;
app->maxHeight = maxHeight; app->maxHeight = maxHeight;
@@ -916,24 +1041,40 @@ void SetDebug(void *applicationPointer, int flag) {
// AddContextMenu 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) { 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) { void UpdateContextMenu(struct Application *app, const char* contextMenuJSON) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
UpdateContextMenuInStore(app->contextMenuStore, contextMenuJSON); UpdateContextMenuInStore(app->contextMenuStore, contextMenuJSON);
} }
void AddTrayMenu(struct Application *app, const char *trayMenuJSON) { void AddTrayMenu(struct Application *app, const char *trayMenuJSON) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
AddTrayMenuToStore(app->trayMenuStore, trayMenuJSON); AddTrayMenuToStore(app->trayMenuStore, trayMenuJSON);
} }
void SetTrayMenu(struct Application *app, const char* trayMenuJSON) { void SetTrayMenu(struct Application *app, const char* trayMenuJSON) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
UpdateTrayMenuInStore(app->trayMenuStore, trayMenuJSON); UpdateTrayMenuInStore(app->trayMenuStore, trayMenuJSON);
); );
} }
void UpdateTrayMenuLabel(struct Application* app, const char* JSON) { void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
UpdateTrayMenuLabelInStore(app->trayMenuStore, JSON); UpdateTrayMenuLabelInStore(app->trayMenuStore, JSON);
); );
@@ -1000,6 +1141,9 @@ void createApplication(struct Application *app) {
} }
void DarkModeEnabled(struct Application *app, const char *callbackID) { void DarkModeEnabled(struct Application *app, const char *callbackID) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
const char *result = isDarkMode(app) ? "T" : "F"; const char *result = isDarkMode(app) ? "T" : "F";
@@ -1020,9 +1164,9 @@ void DarkModeEnabled(struct Application *app, const char *callbackID) {
void createDelegate(struct Application *app) { void createDelegate(struct Application *app) {
// Define delegate // Define delegate
Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "AppDelegate", 0); Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "AppDelegate", 0);
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSApplicationDelegate")); bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSApplicationDelegate"));
class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) yes, "c@:@"); class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) no, "c@:@");
class_addMethod(delegateClass, s("applicationWillTerminate:"), (IMP) closeWindow, "v@:@"); // class_addMethod(delegateClass, s("applicationWillTerminate:"), (IMP) closeWindow, "v@:@");
class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@"); class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
// All Menu Items use a common callback // All Menu Items use a common callback
@@ -1048,6 +1192,11 @@ void createDelegate(struct Application *app) {
msg(app->application, s("setDelegate:"), delegate); msg(app->application, s("setDelegate:"), delegate);
} }
bool windowShouldClose(id self, SEL cmd, id sender) {
msg(sender, s("orderBack:"));
return false;
}
void createMainWindow(struct Application *app) { void createMainWindow(struct Application *app) {
// Create main window // Create main window
id mainWindow = ALLOC("NSWindow"); id mainWindow = ALLOC("NSWindow");
@@ -1066,6 +1215,15 @@ void createMainWindow(struct Application *app) {
msg(mainWindow, s("setTitlebarAppearsTransparent:"), app->titlebarAppearsTransparent ? YES : NO); msg(mainWindow, s("setTitlebarAppearsTransparent:"), app->titlebarAppearsTransparent ? YES : NO);
msg(mainWindow, s("setTitleVisibility:"), app->hideTitle); msg(mainWindow, s("setTitleVisibility:"), app->hideTitle);
if( app->hideWindowOnClose ) {
// Create window delegate to override windowShouldClose
Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "WindowDelegate", 0);
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSWindowDelegate"));
class_replaceMethod(delegateClass, s("windowShouldClose:"), (IMP) windowShouldClose, "v@:@");
app->windowDelegate = msg((id)delegateClass, s("new"));
msg(mainWindow, s("setDelegate:"), app->windowDelegate);
}
app->mainWindow = mainWindow; app->mainWindow = mainWindow;
} }
@@ -1152,7 +1310,7 @@ void parseMenuRole(struct Application *app, id parentMenu, JsonNode *item) {
return; return;
} }
if ( STREQ(roleName, "quit")) { if ( STREQ(roleName, "quit")) {
addMenuItem(parentMenu, "Quit (More work TBD)", "terminate:", "q", FALSE); addMenuItem(parentMenu, "Quit", "terminate:", "q", FALSE);
return; return;
} }
if ( STREQ(roleName, "togglefullscreen")) { if ( STREQ(roleName, "togglefullscreen")) {
@@ -1430,6 +1588,9 @@ void updateMenu(struct Application *app, const char *menuAsJSON) {
// SetApplicationMenu sets the initial menu for the application // SetApplicationMenu sets the initial menu for the application
void SetApplicationMenu(struct Application *app, const char *menuAsJSON) { void SetApplicationMenu(struct Application *app, const char *menuAsJSON) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
if ( app->applicationMenu == NULL ) { if ( app->applicationMenu == NULL ) {
app->applicationMenu = NewApplicationMenu(menuAsJSON); app->applicationMenu = NewApplicationMenu(menuAsJSON);
return; return;
@@ -1528,6 +1689,7 @@ void Run(struct Application *app, int argc, char **argv) {
id manager = msg(config, s("userContentController")); id manager = msg(config, s("userContentController"));
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("external")); 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("completed"));
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("error"));
app->manager = manager; app->manager = manager;
id wkwebview = msg(c("WKWebView"), s("alloc")); id wkwebview = msg(c("WKWebView"), s("alloc"));
@@ -1670,11 +1832,13 @@ void Run(struct Application *app, int argc, char **argv) {
Debug(app, "Run called"); Debug(app, "Run called");
msg(app->application, s("run")); msg(app->application, s("run"));
DestroyApplication(app);
MEMFREE(internalCode); MEMFREE(internalCode);
} }
void* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel) { 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 // Load the tray icons
LoadTrayIcons(); LoadTrayIcons();
@@ -1695,6 +1859,7 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
result->startHidden = startHidden; result->startHidden = startHidden;
result->decorations = 0; result->decorations = 0;
result->logLevel = logLevel; result->logLevel = logLevel;
result->hideWindowOnClose = hideWindowOnClose;
result->mainWindow = NULL; result->mainWindow = NULL;
result->mouseEvent = NULL; result->mouseEvent = NULL;
@@ -1723,12 +1888,17 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
// Context Menus // Context Menus
result->contextMenuStore = NewContextMenuStore(); result->contextMenuStore = NewContextMenuStore();
// Window delegate
result->windowDelegate = NULL;
// Window Appearance // Window Appearance
result->titlebarAppearsTransparent = 0; result->titlebarAppearsTransparent = 0;
result->webviewIsTranparent = 0; result->webviewIsTranparent = 0;
result->sendMessageToBackend = (ffenestriCallback) messageFromWindowCallback; result->sendMessageToBackend = (ffenestriCallback) messageFromWindowCallback;
result->shuttingDown = false;
return (void*) result; return (void*) result;
} }

View File

@@ -2,7 +2,7 @@ package ffenestri
/* /*
#cgo darwin CFLAGS: -DFFENESTRI_DARWIN=1 #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.h"
#include "ffenestri_darwin.h" #include "ffenestri_darwin.h"

View File

@@ -6,6 +6,7 @@
#define OBJC_OLD_DISPATCH_PROTOTYPES 1 #define OBJC_OLD_DISPATCH_PROTOTYPES 1
#include <objc/objc-runtime.h> #include <objc/objc-runtime.h>
#include <CoreGraphics/CoreGraphics.h> #include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
#include "json.h" #include "json.h"
#include "hashmap.h" #include "hashmap.h"
#include "stdlib.h" #include "stdlib.h"
@@ -20,7 +21,6 @@
#define strunicode(input) msg(c("NSString"), s("stringWithFormat:"), str("%C"), (unsigned short)input) #define strunicode(input) msg(c("NSString"), s("stringWithFormat:"), str("%C"), (unsigned short)input)
#define cstr(input) (const char *)msg(input, s("UTF8String")) #define cstr(input) (const char *)msg(input, s("UTF8String"))
#define url(input) msg(c("NSURL"), s("fileURLWithPath:"), str(input)) #define url(input) msg(c("NSURL"), s("fileURLWithPath:"), str(input))
#define ALLOC(classname) msg(c(classname), s("alloc")) #define ALLOC(classname) msg(c(classname), s("alloc"))
#define ALLOC_INIT(classname) msg(msg(c(classname), s("alloc")), s("init")) #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")) #define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("frame"))
@@ -110,4 +110,6 @@ void SetTray(struct Application* app, const char *, const char *, const char *);
//void SetContextMenus(struct Application* app, const char *); //void SetContextMenus(struct Application* app, const char *);
void AddTrayMenu(struct Application* app, const char *); void AddTrayMenu(struct Application* app, const char *);
void* lookupStringConstant(id constantName);
#endif #endif

View File

@@ -5,6 +5,7 @@
#include "ffenestri_darwin.h" #include "ffenestri_darwin.h"
#include "menu_darwin.h" #include "menu_darwin.h"
#include "contextmenus_darwin.h" #include "contextmenus_darwin.h"
#include "common.h"
// NewMenu creates a new Menu struct, saving the given menu structure as JSON // NewMenu creates a new Menu struct, saving the given menu structure as JSON
Menu* NewMenu(JsonNode *menuData) { Menu* NewMenu(JsonNode *menuData) {
@@ -574,7 +575,7 @@ id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const c
return item; return item;
} }
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip) { id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA) {
id item = ALLOC("NSMenuItem"); id item = ALLOC("NSMenuItem");
// Create a MenuItemCallbackData // Create a MenuItemCallbackData
@@ -587,6 +588,73 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
s("menuItemCallback:"), key); s("menuItemCallback:"), key);
if( tooltip != NULL ) {
msg(item, s("setToolTip:"), str(tooltip));
}
// Process image
if( image != NULL && strlen(image) > 0) {
id data = ALLOC("NSData");
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(image), 0);
id nsimage = ALLOC("NSImage");
msg(nsimage, s("initWithData:"), imageData);
msg(item, s("setImage:"), nsimage);
}
// 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(dictionary, s("release"));
msg(item, s("setAttributedTitle:"), attributedString);
msg(attributedString, s("autorelease"));
msg(item, s("setEnabled:"), !disabled); msg(item, s("setEnabled:"), !disabled);
msg(item, s("autorelease")); msg(item, s("autorelease"));
@@ -668,6 +736,13 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
const char *acceleratorkey = NULL; const char *acceleratorkey = NULL;
const char **modifiers = 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");
int fontSize = 12;
getJSONInt(item, "FontSize", &fontSize);
// If we have an accelerator // If we have an accelerator
if( accelerator != NULL ) { if( accelerator != NULL ) {
// Get the key // Get the key
@@ -700,7 +775,7 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
if( type != NULL ) { if( type != NULL ) {
if( STREQ(type->string_, "Text")) { if( STREQ(type->string_, "Text")) {
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image); processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA);
} }
else if ( STREQ(type->string_, "Separator")) { else if ( STREQ(type->string_, "Separator")) {
addSeparator(parentMenu); addSeparator(parentMenu);

View File

@@ -14,6 +14,21 @@ static const char *MenuTypeAsString[] = {
"ApplicationMenu", "ContextMenu", "TrayMenu", "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 *); extern void messageFromWindowCallback(const char *);
typedef struct { typedef struct {
@@ -90,8 +105,7 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key); id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key);
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image); 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);
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item); void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
void processMenuData(Menu *menu, JsonNode *menuData); void processMenuData(Menu *menu, JsonNode *menuData);

File diff suppressed because one or more lines are too long

View File

@@ -97,14 +97,13 @@ void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) { void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
TrayMenu* newMenu = NewTrayMenu(menuJSON); TrayMenu* newMenu = NewTrayMenu(menuJSON);
DumpTrayMenu(newMenu); // DumpTrayMenu(newMenu);
// Get the current menu // Get the current menu
TrayMenu *currentMenu = GetTrayMenuFromStore(store, newMenu->ID); TrayMenu *currentMenu = GetTrayMenuFromStore(store, newMenu->ID);
// If we don't have a menu, we create one // If we don't have a menu, we create one
if ( currentMenu == NULL ) { if ( currentMenu == NULL ) {
printf(" currentMenu = NULL\n");
// Store the new menu // Store the new menu
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu); hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
@@ -112,7 +111,7 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
ShowTrayMenu(newMenu); ShowTrayMenu(newMenu);
return; return;
} }
DumpTrayMenu(currentMenu); // DumpTrayMenu(currentMenu);
// Save the status bar reference // Save the status bar reference
newMenu->statusbaritem = currentMenu->statusbaritem; newMenu->statusbaritem = currentMenu->statusbaritem;

View 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
}

View 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]
}

View File

@@ -2,6 +2,7 @@ package messagedispatcher
import ( import (
"fmt" "fmt"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message" "github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
"github.com/wailsapp/wails/v2/internal/servicebus" "github.com/wailsapp/wails/v2/internal/servicebus"
@@ -26,6 +27,8 @@ type Client interface {
WindowUnminimise() WindowUnminimise()
WindowPosition(x int, y int) WindowPosition(x int, y int)
WindowSize(width int, height int) WindowSize(width int, height int)
WindowSetMinSize(width int, height int)
WindowSetMaxSize(width int, height int)
WindowFullscreen() WindowFullscreen()
WindowUnFullscreen() WindowUnFullscreen()
WindowSetColour(colour int) WindowSetColour(colour int)

View File

@@ -1,12 +1,14 @@
package messagedispatcher package messagedispatcher
import ( import (
"context"
"encoding/json" "encoding/json"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/crypto" "github.com/wailsapp/wails/v2/internal/crypto"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message" "github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
@@ -23,7 +25,6 @@ type Dispatcher struct {
dialogChannel <-chan *servicebus.Message dialogChannel <-chan *servicebus.Message
systemChannel <-chan *servicebus.Message systemChannel <-chan *servicebus.Message
menuChannel <-chan *servicebus.Message menuChannel <-chan *servicebus.Message
running bool
servicebus *servicebus.ServiceBus servicebus *servicebus.ServiceBus
logger logger.CustomLogger logger logger.CustomLogger
@@ -31,6 +32,13 @@ type Dispatcher struct {
// Clients // Clients
clients map[string]*DispatchClient clients map[string]*DispatchClient
lock sync.RWMutex lock sync.RWMutex
// Context for cancellation
ctx context.Context
cancel context.CancelFunc
// internal wait group
wg sync.WaitGroup
} }
// New dispatcher. Needs a service bus to send to. // New dispatcher. Needs a service bus to send to.
@@ -75,6 +83,9 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher,
return nil, err return nil, err
} }
// Create context
ctx, cancel := context.WithCancel(context.Background())
result := &Dispatcher{ result := &Dispatcher{
servicebus: servicebus, servicebus: servicebus,
eventChannel: eventChannel, eventChannel: eventChannel,
@@ -86,6 +97,8 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher,
dialogChannel: dialogChannel, dialogChannel: dialogChannel,
systemChannel: systemChannel, systemChannel: systemChannel,
menuChannel: menuChannel, menuChannel: menuChannel,
ctx: ctx,
cancel: cancel,
} }
return result, nil return result, nil
@@ -96,15 +109,18 @@ func (d *Dispatcher) Start() error {
d.logger.Trace("Starting") d.logger.Trace("Starting")
d.running = true d.wg.Add(1)
// Spin off a go routine // Spin off a go routine
go func() { go func() {
for d.running { defer d.logger.Trace("Shutdown")
for {
select { select {
case <-d.ctx.Done():
d.wg.Done()
return
case <-d.quitChannel: case <-d.quitChannel:
d.processQuit() d.processQuit()
d.running = false
case resultMessage := <-d.resultChannel: case resultMessage := <-d.resultChannel:
d.processCallResult(resultMessage) d.processCallResult(resultMessage)
case eventMessage := <-d.eventChannel: case eventMessage := <-d.eventChannel:
@@ -119,9 +135,6 @@ func (d *Dispatcher) Start() error {
d.processMenuMessage(menuMessage) d.processMenuMessage(menuMessage)
} }
} }
// Call shutdown
d.shutdown()
}() }()
return nil 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 // RegisterClient will register the given callback with the dispatcher
// and return a DispatchClient that the caller can use to send messages // and return a DispatchClient that the caller can use to send messages
func (d *Dispatcher) RegisterClient(client Client) *DispatchClient { func (d *Dispatcher) RegisterClient(client Client) *DispatchClient {
@@ -349,6 +358,38 @@ func (d *Dispatcher) processWindowMessage(result *servicebus.Message) {
for _, client := range d.clients { for _, client := range d.clients {
client.frontend.WindowSize(w, h) client.frontend.WindowSize(w, h)
} }
case "minsize":
// We need 2 arguments
if len(splitTopic) != 4 {
d.logger.Error("Invalid number of parameters for 'window:minsize' : %#v", result.Data())
return
}
w, err1 := strconv.Atoi(splitTopic[2])
h, err2 := strconv.Atoi(splitTopic[3])
if err1 != nil || err2 != nil {
d.logger.Error("Invalid integer parameters for 'window:minsize' : %#v", result.Data())
return
}
// Notifh clients
for _, client := range d.clients {
client.frontend.WindowSetMinSize(w, h)
}
case "maxsize":
// We need 2 arguments
if len(splitTopic) != 4 {
d.logger.Error("Invalid number of parameters for 'window:maxsize' : %#v", result.Data())
return
}
w, err1 := strconv.Atoi(splitTopic[2])
h, err2 := strconv.Atoi(splitTopic[3])
if err1 != nil || err2 != nil {
d.logger.Error("Invalid integer parameters for 'window:maxsize' : %#v", result.Data())
return
}
// Notifh clients
for _, client := range d.clients {
client.frontend.WindowSetMaxSize(w, h)
}
default: default:
d.logger.Error("Unknown window command: %s", command) d.logger.Error("Unknown window command: %s", command)
} }
@@ -491,3 +532,8 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
d.logger.Error("Unknown menufrontend command: %s", command) d.logger.Error("Unknown menufrontend command: %s", command)
} }
} }
func (d *Dispatcher) Close() {
d.cancel()
d.wg.Wait()
}

File diff suppressed because one or more lines are too long

View File

@@ -10,6 +10,7 @@ The lightweight framework for web-like apps
/* jshint esversion: 6 */ /* jshint esversion: 6 */
import { SetBindings } from './bindings'; import { SetBindings } from './bindings';
import { Init } from './main'; import { Init } from './main';
import {RaiseError} from '../desktop/darwin';
// Setup global error handler // Setup global error handler
window.onerror = function (msg, url, lineNo, columnNo, error) { window.onerror = function (msg, url, lineNo, columnNo, error) {
@@ -21,7 +22,7 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
error: JSON.stringify(error), error: JSON.stringify(error),
stack: function() { return JSON.stringify(new Error().stack); }(), stack: function() { return JSON.stringify(new Error().stack); }(),
}; };
window.wails.Log.Error(JSON.stringify(errorMessage)); RaiseError(errorMessage);
}; };
// Initialise the Runtime // Initialise the Runtime

View File

@@ -25,6 +25,10 @@ export function SendMessage(message) {
window.webkit.messageHandlers.external.postMessage(message); window.webkit.messageHandlers.external.postMessage(message);
} }
export function RaiseError(message) {
window.webkit.messageHandlers.error.postMessage(message);
}
export function Init() { export function Init() {
// Setup drag handler // Setup drag handler

View File

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

View File

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

View File

@@ -3,12 +3,13 @@ package main
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/shell"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"strings" "strings"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/shell"
) )
func main() { func main() {
@@ -49,7 +50,7 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
wailsJS := fs.RelativePath("../../../internal/runtime/assets/desktop.js") wailsJS := fs.RelativePath("../../../internal/runtime/assets/desktop_" + platform + ".js")
runtimeData, err := ioutil.ReadFile(wailsJS) runtimeData, err := ioutil.ReadFile(wailsJS)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@@ -6,9 +6,12 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
golog "log"
"os" "os"
"reflect" "reflect"
"sync" "sync"
"github.com/wailsapp/wails/v2/internal/deepcopy"
) )
// Options defines the optional data that may be used // Options defines the optional data that may be used
@@ -64,21 +67,31 @@ func fatal(err error) {
// New creates a new store // New creates a new store
func (p *StoreProvider) New(name string, defaultValue interface{}) *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{ result := Store{
name: name, name: name,
runtime: p.runtime, runtime: p.runtime,
data: reflect.ValueOf(defaultValue),
dataType: dataType,
} }
// Setup the sync listener // Setup the sync listener
result.setupListener() result.setupListener()
result.Set(defaultValue)
return &result return &result
} }
func (s *Store) lock() {
s.mux.Lock()
}
func (s *Store) unlock() {
s.mux.Unlock()
}
// OnError takes a function that will be called // OnError takes a function that will be called
// whenever an error occurs // whenever an error occurs
func (s *Store) OnError(callback func(error)) { func (s *Store) OnError(callback func(error)) {
@@ -105,7 +118,7 @@ func (s *Store) processUpdatedData(data string) error {
} }
// Lock mutex for writing // Lock mutex for writing
s.mux.Lock() s.lock()
// Handle nulls // Handle nulls
if newData == nil { if newData == nil {
@@ -116,7 +129,7 @@ func (s *Store) processUpdatedData(data string) error {
} }
// Unlock mutex // Unlock mutex
s.mux.Unlock() s.unlock()
return nil return nil
} }
@@ -146,20 +159,34 @@ func (s *Store) setupListener() {
// Resetting the curent data will resync // Resetting the curent data will resync
s.resync() s.resync()
}) })
// Do initial resync
s.resync()
} }
func (s *Store) resync() { func (s *Store) resync() {
// Stringify data
newdata, err := json.Marshal(s.data.Interface()) // Lock
if err != nil { s.lock()
if s.errorHandler != nil { defer s.unlock()
s.errorHandler(err)
return 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 // 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 // Notify subscribers
s.notify() s.notify()
@@ -172,7 +199,9 @@ func (s *Store) notify() {
for _, callback := range s.callbacks { for _, callback := range s.callbacks {
// Build args // Build args
s.lock()
args := []reflect.Value{s.data} args := []reflect.Value{s.data}
s.unlock()
if s.notifySynchronously { if s.notifySynchronously {
callback.Call(args) callback.Call(args)
@@ -187,16 +216,31 @@ func (s *Store) notify() {
// and notify listeners of the change // and notify listeners of the change
func (s *Store) Set(data interface{}) error { 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 { s.lock()
return fmt.Errorf("invalid data given in Store.Set(). Expected %s, got %s", s.dataType.String(), inType.String())
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 // Save data
s.mux.Lock() s.data = reflect.ValueOf(dataCopy)
s.data = reflect.ValueOf(data)
s.mux.Unlock() s.unlock()
// Resync with subscribers // Resync with subscribers
s.resync() s.resync()
@@ -247,7 +291,9 @@ func (s *Store) Subscribe(callback interface{}) {
callbackFunc := reflect.ValueOf(callback) callbackFunc := reflect.ValueOf(callback)
s.lock()
s.callbacks = append(s.callbacks, callbackFunc) s.callbacks = append(s.callbacks, callbackFunc)
s.unlock()
} }
// updaterCheck ensures the given function to Update() is // updaterCheck ensures the given function to Update() is
@@ -297,7 +343,9 @@ func (s *Store) Update(updater interface{}) {
} }
// Build args // Build args
s.lock()
args := []reflect.Value{s.data} args := []reflect.Value{s.data}
s.unlock()
// Make call // Make call
results := reflect.ValueOf(updater).Call(args) 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 // Get returns the value of the data that's kept in the current state / Store
func (s *Store) Get() interface{} { func (s *Store) Get() interface{} {
s.lock()
defer s.unlock()
if !s.data.IsValid() {
return nil
}
return s.data.Interface() return s.data.Interface()
} }

View 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()
}

View File

@@ -18,6 +18,8 @@ type Window interface {
Unminimise() Unminimise()
SetTitle(title string) SetTitle(title string)
SetSize(width int, height int) SetSize(width int, height int)
SetMinSize(width int, height int)
SetMaxSize(width int, height int)
SetPosition(x int, y int) SetPosition(x int, y int)
Fullscreen() Fullscreen()
UnFullscreen() UnFullscreen()
@@ -85,6 +87,18 @@ func (w *window) SetSize(width int, height int) {
w.bus.Publish(message, "") 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 // SetPosition sets the position of the window
func (w *window) SetPosition(x int, y int) { func (w *window) SetPosition(x int, y int) {
message := fmt.Sprintf("window:position:%d:%d", x, y) message := fmt.Sprintf("window:position:%d:%d", x, y)

View File

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

View File

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

View File

@@ -10,9 +10,9 @@ import (
// Binding is the Binding subsystem. It manages all service bus messages // Binding is the Binding subsystem. It manages all service bus messages
// starting with "binding". // starting with "binding".
type Binding struct { type Binding struct {
quitChannel <-chan *servicebus.Message
bindingChannel <-chan *servicebus.Message bindingChannel <-chan *servicebus.Message
running bool
running bool
// Binding db // Binding db
bindings *binding.Bindings bindings *binding.Bindings
@@ -27,12 +27,6 @@ type Binding struct {
// NewBinding creates a new binding subsystem. Uses the given bindings db for reference. // NewBinding creates a new binding subsystem. Uses the given bindings db for reference.
func NewBinding(bus *servicebus.ServiceBus, logger *logger.Logger, bindings *binding.Bindings, runtime *runtime.Runtime) (*Binding, error) { func NewBinding(bus *servicebus.ServiceBus, logger *logger.Logger, bindings *binding.Bindings, runtime *runtime.Runtime) (*Binding, error) {
// Register quit channel
quitChannel, err := bus.Subscribe("quit")
if err != nil {
return nil, err
}
// Subscribe to event messages // Subscribe to event messages
bindingChannel, err := bus.Subscribe("binding") bindingChannel, err := bus.Subscribe("binding")
if err != nil { if err != nil {
@@ -40,7 +34,6 @@ func NewBinding(bus *servicebus.ServiceBus, logger *logger.Logger, bindings *bin
} }
result := &Binding{ result := &Binding{
quitChannel: quitChannel,
bindingChannel: bindingChannel, bindingChannel: bindingChannel,
logger: logger.CustomLogger("Binding Subsystem"), logger: logger.CustomLogger("Binding Subsystem"),
bindings: bindings, bindings: bindings,
@@ -61,20 +54,16 @@ func (b *Binding) Start() error {
go func() { go func() {
for b.running { for b.running {
select { select {
case <-b.quitChannel:
b.running = false
case bindingMessage := <-b.bindingChannel: case bindingMessage := <-b.bindingChannel:
b.logger.Trace("Got binding message: %+v", bindingMessage) b.logger.Trace("Got binding message: %+v", bindingMessage)
} }
} }
b.logger.Trace("Shutdown")
// Call shutdown
b.shutdown()
}() }()
return nil return nil
} }
func (b *Binding) shutdown() { func (b *Binding) Close() {
b.logger.Trace("Shutdown") b.running = false
} }

View File

@@ -1,10 +1,13 @@
package subsystem package subsystem
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"strings" "strings"
"sync"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/binding" "github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
@@ -16,9 +19,10 @@ import (
// Call is the Call subsystem. It manages all service bus messages // Call is the Call subsystem. It manages all service bus messages
// starting with "call". // starting with "call".
type Call struct { type Call struct {
quitChannel <-chan *servicebus.Message
callChannel <-chan *servicebus.Message callChannel <-chan *servicebus.Message
running bool
// quit flag
shouldQuit bool
// bindings DB // bindings DB
DB *binding.DB DB *binding.DB
@@ -31,16 +35,16 @@ type Call struct {
// runtime // runtime
runtime *runtime.Runtime runtime *runtime.Runtime
// context
ctx context.Context
// parent waitgroup
wg *sync.WaitGroup
} }
// NewCall creates a new call subsystem // NewCall creates a new call subsystem
func NewCall(bus *servicebus.ServiceBus, logger *logger.Logger, DB *binding.DB, runtime *runtime.Runtime) (*Call, error) { func NewCall(ctx context.Context, 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
}
// Subscribe to event messages // Subscribe to event messages
callChannel, err := bus.Subscribe("call:invoke") callChannel, err := bus.Subscribe("call:invoke")
@@ -49,12 +53,13 @@ func NewCall(bus *servicebus.ServiceBus, logger *logger.Logger, DB *binding.DB,
} }
result := &Call{ result := &Call{
quitChannel: quitChannel,
callChannel: callChannel, callChannel: callChannel,
logger: logger.CustomLogger("Call Subsystem"), logger: logger.CustomLogger("Call Subsystem"),
DB: DB, DB: DB,
bus: bus, bus: bus,
runtime: runtime, runtime: runtime,
ctx: ctx,
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
} }
return result, nil return result, nil
@@ -63,22 +68,21 @@ func NewCall(bus *servicebus.ServiceBus, logger *logger.Logger, DB *binding.DB,
// Start the subsystem // Start the subsystem
func (c *Call) Start() error { func (c *Call) Start() error {
c.running = true c.wg.Add(1)
// Spin off a go routine // Spin off a go routine
go func() { go func() {
for c.running { defer c.logger.Trace("Shutdown")
for {
select { select {
case <-c.quitChannel: case <-c.ctx.Done():
c.running = false c.wg.Done()
return
case callMessage := <-c.callChannel: case callMessage := <-c.callChannel:
// TODO: Check if this works ok in a goroutine
c.processCall(callMessage) c.processCall(callMessage)
} }
} }
// Call shutdown
c.shutdown()
}() }()
return nil 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) 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 // CallbackMessage defines a message that contains the result of a call
type CallbackMessage struct { type CallbackMessage struct {
Result interface{} `json:"result"` Result interface{} `json:"result"`

View File

@@ -1,6 +1,7 @@
package subsystem package subsystem
import ( import (
"context"
"strings" "strings"
"sync" "sync"
@@ -22,9 +23,7 @@ type eventListener struct {
// Event is the Eventing subsystem. It manages all service bus messages // Event is the Eventing subsystem. It manages all service bus messages
// starting with "event". // starting with "event".
type Event struct { type Event struct {
quitChannel <-chan *servicebus.Message
eventChannel <-chan *servicebus.Message eventChannel <-chan *servicebus.Message
running bool
// Event listeners // Event listeners
listeners map[string][]*eventListener listeners map[string][]*eventListener
@@ -32,16 +31,16 @@ type Event struct {
// logger // logger
logger logger.CustomLogger logger logger.CustomLogger
// ctx
ctx context.Context
// parent waitgroup
wg *sync.WaitGroup
} }
// NewEvent creates a new log subsystem // NewEvent creates a new log subsystem
func NewEvent(bus *servicebus.ServiceBus, logger *logger.Logger) (*Event, error) { func NewEvent(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger) (*Event, error) {
// Register quit channel
quitChannel, err := bus.Subscribe("quit")
if err != nil {
return nil, err
}
// Subscribe to event messages // Subscribe to event messages
eventChannel, err := bus.Subscribe("event") eventChannel, err := bus.Subscribe("event")
@@ -50,10 +49,11 @@ func NewEvent(bus *servicebus.ServiceBus, logger *logger.Logger) (*Event, error)
} }
result := &Event{ result := &Event{
quitChannel: quitChannel,
eventChannel: eventChannel, eventChannel: eventChannel,
logger: logger.CustomLogger("Event Subsystem"), logger: logger.CustomLogger("Event Subsystem"),
listeners: make(map[string][]*eventListener), listeners: make(map[string][]*eventListener),
ctx: ctx,
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
} }
return result, nil return result, nil
@@ -80,15 +80,16 @@ func (e *Event) Start() error {
e.logger.Trace("Starting") e.logger.Trace("Starting")
e.running = true e.wg.Add(1)
// Spin off a go routine // Spin off a go routine
go func() { go func() {
for e.running { defer e.logger.Trace("Shutdown")
for {
select { select {
case <-e.quitChannel: case <-e.ctx.Done():
e.running = false e.wg.Done()
break return
case eventMessage := <-e.eventChannel: case eventMessage := <-e.eventChannel:
splitTopic := strings.Split(eventMessage.Topic(), ":") splitTopic := strings.Split(eventMessage.Topic(), ":")
eventType := splitTopic[1] eventType := splitTopic[1]
@@ -128,8 +129,6 @@ func (e *Event) Start() error {
} }
} }
// Call shutdown
e.shutdown()
}() }()
return nil return nil
@@ -190,7 +189,3 @@ func (e *Event) notifyListeners(eventName string, message *message.EventMessage)
// Unlock // Unlock
e.notifyLock.Unlock() e.notifyLock.Unlock()
} }
func (e *Event) shutdown() {
e.logger.Trace("Shutdown")
}

View File

@@ -1,8 +1,10 @@
package subsystem package subsystem
import ( import (
"context"
"strconv" "strconv"
"strings" "strings"
"sync"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/runtime" "github.com/wailsapp/wails/v2/internal/runtime"
@@ -12,15 +14,23 @@ import (
// Log is the Logging subsystem. It handles messages with topics starting // Log is the Logging subsystem. It handles messages with topics starting
// with "log:" // with "log:"
type Log struct { type Log struct {
logChannel <-chan *servicebus.Message logChannel <-chan *servicebus.Message
quitChannel <-chan *servicebus.Message
running bool // quit flag
shouldQuit bool
// Logger! // Logger!
logger *logger.Logger logger *logger.Logger
// Loglevel store // Loglevel store
logLevelStore *runtime.Store logLevelStore *runtime.Store
// Context for shutdown
ctx context.Context
cancel context.CancelFunc
// internal waitgroup
wg sync.WaitGroup
} }
// NewLog creates a new log subsystem // NewLog creates a new log subsystem
@@ -32,17 +42,14 @@ func NewLog(bus *servicebus.ServiceBus, logger *logger.Logger, logLevelStore *ru
return nil, err return nil, err
} }
// Subscribe to quit messages ctx, cancel := context.WithCancel(context.Background())
quitChannel, err := bus.Subscribe("quit")
if err != nil {
return nil, err
}
result := &Log{ result := &Log{
logChannel: logChannel, logChannel: logChannel,
quitChannel: quitChannel,
logger: logger, logger: logger,
logLevelStore: logLevelStore, logLevelStore: logLevelStore,
ctx: ctx,
cancel: cancel,
} }
return result, nil return result, nil
@@ -51,15 +58,17 @@ func NewLog(bus *servicebus.ServiceBus, logger *logger.Logger, logLevelStore *ru
// Start the subsystem // Start the subsystem
func (l *Log) Start() error { func (l *Log) Start() error {
l.running = true l.wg.Add(1)
// Spin off a go routine // Spin off a go routine
go func() { go func() {
for l.running { defer l.logger.Trace("Logger Shutdown")
for l.shouldQuit == false {
select { select {
case <-l.quitChannel: case <-l.ctx.Done():
l.running = false l.wg.Done()
break return
case logMessage := <-l.logChannel: case logMessage := <-l.logChannel:
logType := strings.TrimPrefix(logMessage.Topic(), "log:") logType := strings.TrimPrefix(logMessage.Topic(), "log:")
switch logType { switch logType {
@@ -98,8 +107,12 @@ func (l *Log) Start() error {
} }
} }
} }
l.logger.Trace("Logger Shutdown")
}() }()
return nil return nil
} }
func (l *Log) Close() {
l.cancel()
l.wg.Wait()
}

View File

@@ -1,9 +1,12 @@
package subsystem package subsystem
import ( import (
"context"
"encoding/json" "encoding/json"
"github.com/wailsapp/wails/v2/pkg/menu"
"strings" "strings"
"sync"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/menumanager" "github.com/wailsapp/wails/v2/internal/menumanager"
@@ -13,9 +16,10 @@ import (
// Menu is the subsystem that handles the operation of menus. It manages all service bus messages // Menu is the subsystem that handles the operation of menus. It manages all service bus messages
// starting with "menu". // starting with "menu".
type Menu struct { type Menu struct {
quitChannel <-chan *servicebus.Message
menuChannel <-chan *servicebus.Message menuChannel <-chan *servicebus.Message
running bool
// shutdown flag
shouldQuit bool
// logger // logger
logger logger.CustomLogger logger logger.CustomLogger
@@ -25,16 +29,16 @@ type Menu struct {
// Menu Manager // Menu Manager
menuManager *menumanager.Manager menuManager *menumanager.Manager
// ctx
ctx context.Context
// parent waitgroup
wg *sync.WaitGroup
} }
// NewMenu creates a new menu subsystem // NewMenu creates a new menu subsystem
func NewMenu(bus *servicebus.ServiceBus, logger *logger.Logger, menuManager *menumanager.Manager) (*Menu, error) { func NewMenu(ctx context.Context, 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
}
// Subscribe to menu messages // Subscribe to menu messages
menuChannel, err := bus.Subscribe("menu:") menuChannel, err := bus.Subscribe("menu:")
@@ -43,11 +47,12 @@ func NewMenu(bus *servicebus.ServiceBus, logger *logger.Logger, menuManager *men
} }
result := &Menu{ result := &Menu{
quitChannel: quitChannel,
menuChannel: menuChannel, menuChannel: menuChannel,
logger: logger.CustomLogger("Menu Subsystem"), logger: logger.CustomLogger("Menu Subsystem"),
bus: bus, bus: bus,
menuManager: menuManager, menuManager: menuManager,
ctx: ctx,
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
} }
return result, nil return result, nil
@@ -58,15 +63,16 @@ func (m *Menu) Start() error {
m.logger.Trace("Starting") m.logger.Trace("Starting")
m.running = true m.wg.Add(1)
// Spin off a go routine // Spin off a go routine
go func() { go func() {
for m.running { defer m.logger.Trace("Shutdown")
for {
select { select {
case <-m.quitChannel: case <-m.ctx.Done():
m.running = false m.wg.Done()
break return
case menuMessage := <-m.menuChannel: case menuMessage := <-m.menuChannel:
splitTopic := strings.Split(menuMessage.Topic(), ":") splitTopic := strings.Split(menuMessage.Topic(), ":")
menuMessageType := splitTopic[1] menuMessageType := splitTopic[1]
@@ -147,14 +153,7 @@ func (m *Menu) Start() error {
} }
} }
} }
// Call shutdown
m.shutdown()
}() }()
return nil return nil
} }
func (m *Menu) shutdown() {
m.logger.Trace("Shutdown")
}

View File

@@ -1,6 +1,7 @@
package subsystem package subsystem
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
@@ -12,7 +13,6 @@ import (
// Runtime is the Runtime subsystem. It handles messages with topics starting // Runtime is the Runtime subsystem. It handles messages with topics starting
// with "runtime:" // with "runtime:"
type Runtime struct { type Runtime struct {
quitChannel <-chan *servicebus.Message
runtimeChannel <-chan *servicebus.Message runtimeChannel <-chan *servicebus.Message
// The hooks channel allows us to hook into frontend startup // The hooks channel allows us to hook into frontend startup
@@ -20,22 +20,20 @@ type Runtime struct {
startupCallback func(*runtime.Runtime) startupCallback func(*runtime.Runtime)
shutdownCallback func() shutdownCallback func()
running bool // quit flag
shouldQuit bool
logger logger.CustomLogger logger logger.CustomLogger
// Runtime library // Runtime library
runtime *runtime.Runtime runtime *runtime.Runtime
//ctx
ctx context.Context
} }
// NewRuntime creates a new runtime subsystem // NewRuntime creates a new runtime subsystem
func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, startupCallback func(*runtime.Runtime), shutdownCallback func()) (*Runtime, error) { func NewRuntime(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger, startupCallback func(*runtime.Runtime), shutdownCallback func()) (*Runtime, error) {
// Register quit channel
quitChannel, err := bus.Subscribe("quit")
if err != nil {
return nil, err
}
// Subscribe to log messages // Subscribe to log messages
runtimeChannel, err := bus.Subscribe("runtime:") runtimeChannel, err := bus.Subscribe("runtime:")
@@ -50,13 +48,13 @@ func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, startupCallba
} }
result := &Runtime{ result := &Runtime{
quitChannel: quitChannel,
runtimeChannel: runtimeChannel, runtimeChannel: runtimeChannel,
hooksChannel: hooksChannel, hooksChannel: hooksChannel,
logger: logger.CustomLogger("Runtime Subsystem"), logger: logger.CustomLogger("Runtime Subsystem"),
runtime: runtime.New(bus), runtime: runtime.New(bus, shutdownCallback),
startupCallback: startupCallback, startupCallback: startupCallback,
shutdownCallback: shutdownCallback, shutdownCallback: shutdownCallback,
ctx: ctx,
} }
return result, nil return result, nil
@@ -65,15 +63,11 @@ func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, startupCallba
// Start the subsystem // Start the subsystem
func (r *Runtime) Start() error { func (r *Runtime) Start() error {
r.running = true
// Spin off a go routine // Spin off a go routine
go func() { go func() {
for r.running { defer r.logger.Trace("Shutdown")
for {
select { select {
case <-r.quitChannel:
r.running = false
break
case hooksMessage := <-r.hooksChannel: case hooksMessage := <-r.hooksChannel:
r.logger.Trace(fmt.Sprintf("Received hooksmessage: %+v", hooksMessage)) r.logger.Trace(fmt.Sprintf("Received hooksmessage: %+v", hooksMessage))
messageSlice := strings.Split(hooksMessage.Topic(), ":") messageSlice := strings.Split(hooksMessage.Topic(), ":")
@@ -83,7 +77,7 @@ func (r *Runtime) Start() error {
if r.startupCallback != nil { if r.startupCallback != nil {
go r.startupCallback(r.runtime) go r.startupCallback(r.runtime)
} else { } else {
r.logger.Error("no startup callback registered!") r.logger.Warning("no startup callback registered!")
} }
default: default:
r.logger.Error("unknown hook message: %+v", hooksMessage) r.logger.Error("unknown hook message: %+v", hooksMessage)
@@ -113,11 +107,10 @@ func (r *Runtime) Start() error {
if err != nil { if err != nil {
r.logger.Error(err.Error()) r.logger.Error(err.Error())
} }
case <-r.ctx.Done():
return
} }
} }
// Call shutdown
r.shutdown()
}() }()
return nil return nil
@@ -128,13 +121,6 @@ func (r *Runtime) GoRuntime() *runtime.Runtime {
return r.runtime return r.runtime
} }
func (r *Runtime) shutdown() {
if r.shutdownCallback != nil {
go r.shutdownCallback()
}
r.logger.Trace("Shutdown")
}
func (r *Runtime) processBrowserMessage(method string, data interface{}) error { func (r *Runtime) processBrowserMessage(method string, data interface{}) error {
switch method { switch method {
case "open": case "open":

View File

@@ -2,14 +2,21 @@ package main
import ( import (
"github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2"
"log"
) )
func main() { func main() {
// Create application with options // Create application with options
app := wails.CreateApp("{{.ProjectName}}", 1024, 768) app, err := wails.CreateApp("{{.ProjectName}}", 1024, 768)
if err != nil {
log.Fatal(err)
}
app.Bind(newBasic()) app.Bind(newBasic())
app.Run() err = app.Run()
if err != nil {
log.Fatal(err)
}
} }

View File

@@ -20,6 +20,14 @@ type WebClient struct {
running bool running bool
} }
func (wc *WebClient) SetTrayMenu(trayMenuJSON string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) UpdateTrayMenuLabel(trayMenuJSON string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) { func (wc *WebClient) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
wc.logger.Info("Not implemented in server build") wc.logger.Info("Not implemented in server build")
} }

View File

@@ -104,7 +104,7 @@ func Build(options *Options) (string, error) {
// return "", err // return "", err
// } // }
if !options.IgnoreFrontend { if !options.IgnoreFrontend {
outputLogger.Println(" - Building Wails Frontend") outputLogger.Println(" - Building Project Frontend")
err = builder.BuildFrontend(outputLogger) err = builder.BuildFrontend(outputLogger)
if err != nil { if err != nil {
return "", err return "", err

View File

@@ -1,11 +1,12 @@
package options package options
import ( import (
wailsruntime "github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/pkg/menu"
"log" "log"
"runtime" "runtime"
wailsruntime "github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/imdario/mergo" "github.com/imdario/mergo"
"github.com/wailsapp/wails/v2/pkg/logger" "github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/options/mac" "github.com/wailsapp/wails/v2/pkg/options/mac"
@@ -13,26 +14,28 @@ import (
// App contains options for creating the App // App contains options for creating the App
type App struct { type App struct {
Title string Title string
Width int Width int
Height int Height int
DisableResize bool DisableResize bool
Fullscreen bool Fullscreen bool
MinWidth int MinWidth int
MinHeight int MinHeight int
MaxWidth int MaxWidth int
MaxHeight int MaxHeight int
StartHidden bool StartHidden bool
DevTools bool HideWindowOnClose bool
RGBA int DevTools bool
ContextMenus []*menu.ContextMenu RGBA int
TrayMenus []*menu.TrayMenu ContextMenus []*menu.ContextMenu
Menu *menu.Menu TrayMenus []*menu.TrayMenu
Mac *mac.Options Menu *menu.Menu
Logger logger.Logger `json:"-"` Mac *mac.Options
LogLevel logger.LogLevel Logger logger.Logger `json:"-"`
Startup func(*wailsruntime.Runtime) `json:"-"` LogLevel logger.LogLevel
Shutdown func() `json:"-"` Startup func(*wailsruntime.Runtime) `json:"-"`
Shutdown func() `json:"-"`
Bind []interface{}
} }
// MergeDefaults will set the minimum default values for an application // MergeDefaults will set the minimum default values for an application

View File

@@ -14,19 +14,12 @@ type Runtime = runtime.Runtime
// Store is an alias for the Store object // Store is an alias for the Store object
type Store = runtime.Store type Store = runtime.Store
// CreateAppWithOptions creates an application based on the given config // Run creates an application based on the given config and executes it
func CreateAppWithOptions(options *options.App) (*app.App, error) { func Run(options *options.App) error {
return app.CreateApp(options) app, err := app.CreateApp(options)
} if err != nil {
return err
// CreateApp creates an application based on the given title, width and height
func CreateApp(title string, width int, height int) (*app.App, error) {
options := &options.App{
Title: title,
Width: width,
Height: height,
} }
return app.CreateApp(options) return app.Run()
} }