Compare commits

...

45 Commits

Author SHA1 Message Date
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
Lea Anthony
c6d87da4f0 v2.0.0-alpha.13 2021-01-23 20:39:43 +11:00
Lea Anthony
a9faebe51a MenuItem image support 2021-01-23 20:32:42 +11:00
Lea Anthony
d436f5d1be v2.0.0-alpha.12 2021-01-23 16:22:00 +11:00
Lea Anthony
f40899821f Support ToolTips 2021-01-23 16:14:48 +11:00
Lea Anthony
2a64ed19a3 v2.0.0-alpha.11 2021-01-22 14:57:50 +11:00
Lea Anthony
47bca0be88 Support updating tray labels in an efficient manner 2021-01-16 11:35:49 +11:00
Lea Anthony
7ac8cc6b8b Add Menu.Merge() to combine 2 menus 2021-01-15 11:53:55 +11:00
Lea Anthony
b435ec1217 v2.0.0-alpha.10 2021-01-14 20:43:26 +11:00
Lea Anthony
688d4fee6a UpdateTrayMenu -> SetTrayMenu (upsert). Support no menus on trays. 2021-01-14 16:13:59 +11:00
Lea Anthony
29ffeaa9f3 Misc tidy up 2021-01-14 13:55:20 +11:00
Lea Anthony
742e4ba2cb Remove WailsInit and WailsShutdown methodsr 2021-01-14 11:07:06 +11:00
79 changed files with 2659 additions and 517 deletions

View File

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

View File

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

View File

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

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

View File

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

View File

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

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

View File

@@ -2,11 +2,39 @@
package app
import (
"flag"
"github.com/wailsapp/wails/v2/pkg/logger"
"strings"
)
// Init initialises the application for a debug environment
func (a *App) Init() error {
// Indicate debug mode
a.debug = true
// Enable dev tools
a.options.DevTools = true
if a.appType == "desktop" {
// Enable dev tools
a.options.DevTools = true
}
// Set log levels
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
}

View File

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

View File

@@ -17,6 +17,8 @@ import (
// App defines a Wails application structure
type App struct {
appType string
window *ffenestri.Application
servicebus *servicebus.ServiceBus
logger *logger.Logger
@@ -43,6 +45,10 @@ type App struct {
// Application Stores
loglevelStore *runtime.Store
appconfigStore *runtime.Store
// Startup/Shutdown
startupCallback func(*runtime.Runtime)
shutdownCallback func()
}
// Create App
@@ -75,12 +81,18 @@ func CreateApp(appoptions *options.App) (*App, error) {
window := ffenestri.NewApplicationWithConfig(appoptions, myLogger, menuManager)
// Create binding exemptions - Ugly hack. There must be a better way
bindingExemptions := []interface{}{appoptions.Startup, appoptions.Shutdown}
result := &App{
window: window,
servicebus: servicebus.New(myLogger),
logger: myLogger,
bindings: binding.NewBindings(myLogger),
menuManager: menuManager,
appType: "desktop",
window: window,
servicebus: servicebus.New(myLogger),
logger: myLogger,
bindings: binding.NewBindings(myLogger, appoptions.Bind, bindingExemptions),
menuManager: menuManager,
startupCallback: appoptions.Startup,
shutdownCallback: appoptions.Shutdown,
}
result.options = appoptions
@@ -112,7 +124,7 @@ func (a *App) Run() error {
return err
}
runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger)
runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
if err != nil {
return err
}
@@ -207,14 +219,3 @@ func (a *App) Run() error {
return result
}
// Bind a struct to the application by passing in
// a pointer to it
func (a *App) Bind(structPtr interface{}) {
// Add the struct to the bindings
err := a.bindings.Add(structPtr)
if err != nil {
a.logger.Fatal("Error during binding: " + err.Error())
}
}

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"reflect"
"strings"
)
// BoundMethod defines all the data related to a Go method that is
@@ -17,58 +16,6 @@ type BoundMethod struct {
Method reflect.Value `json:"-"`
}
// IsWailsInit returns true if the method name is "WailsInit"
func (b *BoundMethod) IsWailsInit() bool {
return strings.HasSuffix(b.Name, "WailsInit")
}
// IsWailsShutdown returns true if the method name is "WailsShutdown"
func (b *BoundMethod) IsWailsShutdown() bool {
return strings.HasSuffix(b.Name, "WailsShutdown")
}
// VerifyWailsInit checks if the WailsInit signature is correct
func (b *BoundMethod) VerifyWailsInit() error {
// Must only have 1 input
if b.InputCount() != 1 {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Check input type
if !b.Inputs[0].IsType("*runtime.Runtime") {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Must only have 1 output
if b.OutputCount() != 1 {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Check output type
if !b.Outputs[0].IsError() {
return fmt.Errorf("invalid method signature for %s: expected `WailsInit(*wails.Runtime) error`", b.Name)
}
// Input must be of type Runtime
return nil
}
// VerifyWailsShutdown checks if the WailsShutdown signature is correct
func (b *BoundMethod) VerifyWailsShutdown() error {
// Must have no inputs
if b.InputCount() != 0 {
return fmt.Errorf("invalid method signature for WailsShutdown: expected `WailsShutdown()`")
}
// Must have no outputs
if b.OutputCount() != 0 {
return fmt.Errorf("invalid method signature for WailsShutdown: expected `WailsShutdown()`")
}
// Input must be of type Runtime
return nil
}
// InputCount returns the number of inputs this bound method has
func (b *BoundMethod) InputCount() int {
return len(b.Inputs)
@@ -83,6 +30,9 @@ func (b *BoundMethod) OutputCount() int {
func (b *BoundMethod) ParseArgs(args []json.RawMessage) ([]interface{}, error) {
result := make([]interface{}, b.InputCount())
if len(args) != b.InputCount() {
return nil, fmt.Errorf("received %d arguments to method '%s', expected %d", len(args), b.Name, b.InputCount())
}
for index, arg := range args {
typ := b.Inputs[index].reflectType
inputValue := reflect.New(typ).Interface()

View File

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

View File

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

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

@@ -86,3 +86,10 @@ bool getJSONInt(JsonNode *item, const char* key, int *result) {
return false;
}
JsonNode* mustParseJSON(const char* JSON) {
JsonNode* parsedUpdate = json_decode(JSON);
if ( parsedUpdate == NULL ) {
ABORT("Unable to decode JSON: %s\n", JSON);
}
return parsedUpdate;
}

View File

@@ -35,4 +35,6 @@ JsonNode* mustJSONObject(JsonNode *node, const char* key);
bool getJSONBool(JsonNode *item, const char* key, bool *result);
bool getJSONInt(JsonNode *item, const char* key, int *result);
JsonNode* mustParseJSON(const char* JSON);
#endif //ASSETS_C_COMMON_H

View File

@@ -36,7 +36,8 @@ extern void MessageDialog(struct Application*, char *callbackID, char *type, cha
extern void DarkModeEnabled(struct Application*, char *callbackID);
extern void SetApplicationMenu(struct Application*, const char *);
extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
extern void UpdateTrayMenu(struct Application*, const char *menuTrayJSON);
extern void SetTrayMenu(struct Application*, const char *menuTrayJSON);
extern void UpdateTrayMenuLabel(struct Application*, const char* JSON);
extern void AddContextMenu(struct Application*, char *contextMenuJSON);
extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);

View File

@@ -14,8 +14,9 @@ import "C"
import (
"strconv"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/pkg/options"
)
// Client is our implentation of messageDispatcher.Client
@@ -113,6 +114,14 @@ func (c *Client) WindowSize(width int, height int) {
C.SetSize(c.app.app, C.int(width), C.int(height))
}
func (c *Client) WindowSetMinSize(width int, height int) {
C.SetMinWindowSize(c.app.app, C.int(width), C.int(height))
}
func (c *Client) WindowSetMaxSize(width int, height int) {
C.SetMaxWindowSize(c.app.app, C.int(width), C.int(height))
}
// WindowSetColour sets the window colour
func (c *Client) WindowSetColour(colour int) {
r, g, b, a := intToColour(colour)
@@ -120,7 +129,7 @@ func (c *Client) WindowSetColour(colour int) {
}
// OpenDialog will open a dialog with the given title and filter
func (c *Client) OpenDialog(dialogOptions *options.OpenDialog, callbackID string) {
func (c *Client) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
C.OpenDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(dialogOptions.Title),
@@ -138,7 +147,7 @@ func (c *Client) OpenDialog(dialogOptions *options.OpenDialog, callbackID string
}
// SaveDialog will open a dialog with the given title and filter
func (c *Client) SaveDialog(dialogOptions *options.SaveDialog, callbackID string) {
func (c *Client) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
C.SaveDialog(c.app.app,
c.app.string2CString(callbackID),
c.app.string2CString(dialogOptions.Title),
@@ -152,7 +161,7 @@ func (c *Client) SaveDialog(dialogOptions *options.SaveDialog, callbackID string
}
// MessageDialog will open a message dialog with the given options
func (c *Client) MessageDialog(dialogOptions *options.MessageDialog, callbackID string) {
func (c *Client) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
// Sanity check button length
if len(dialogOptions.Buttons) > 4 {
@@ -188,8 +197,12 @@ func (c *Client) SetApplicationMenu(applicationMenuJSON string) {
C.SetApplicationMenu(c.app.app, c.app.string2CString(applicationMenuJSON))
}
func (c *Client) UpdateTrayMenu(trayMenuJSON string) {
C.UpdateTrayMenu(c.app.app, c.app.string2CString(trayMenuJSON))
func (c *Client) SetTrayMenu(trayMenuJSON string) {
C.SetTrayMenu(c.app.app, c.app.string2CString(trayMenuJSON))
}
func (c *Client) UpdateTrayMenuLabel(JSON string) {
C.UpdateTrayMenuLabel(c.app.app, c.app.string2CString(JSON))
}
func (c *Client) UpdateContextMenu(contextMenuJSON string) {

View File

@@ -21,10 +21,6 @@ int debug;
// A cache for all our dialog icons
struct hashmap_s dialogIconCache;
// Context menu data is given by the frontend when clicking a context menu.
// We send this to the backend when an item is selected;
const char *contextMenuData;
// Dispatch Method
typedef void (^dispatchMethod)(void);
@@ -144,6 +140,16 @@ void Debug(struct Application *app, const char *message, ... ) {
}
}
void Error(struct Application *app, const char *message, ... ) {
const char *temp = concat("LEFfenestri (C) | ", message);
va_list args;
va_start(args, message);
vsnprintf(logbuffer, MAXMESSAGE, temp, args);
app->sendMessageToBackend(&logbuffer[0]);
MEMFREE(temp);
va_end(args);
}
void Fatal(struct Application *app, const char *message, ... ) {
const char *temp = concat("LFFfenestri (C) | ", message);
va_list args;
@@ -154,6 +160,12 @@ void Fatal(struct Application *app, const char *message, ... ) {
va_end(args);
}
// Requires NSString input EG lookupStringConstant(str("NSFontAttributeName"))
void* lookupStringConstant(id constantName) {
void ** dataPtr = CFBundleGetDataPointerForName(CFBundleGetBundleWithIdentifier((CFStringRef)str("com.apple.AppKit")), (CFStringRef) constantName);
return (dataPtr ? *dataPtr : nil);
}
bool isRetina(struct Application *app) {
CGFloat scale = GET_BACKINGSCALEFACTOR(app->mainWindow);
if( (int)scale == 1 ) {
@@ -244,7 +256,10 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
struct Application *app = (struct Application *)objc_getAssociatedObject(
self, "application");
const char *name = (const char *)msg(msg(message, s("name")), s("UTF8String"));
if( strcmp(name, "completed") == 0) {
if( strcmp(name, "error") == 0 ) {
printf("There was a Javascript error. Please open the devtools for more information.\n");
Show(app);
} else if( strcmp(name, "completed") == 0) {
// Delete handler
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("completed"));
@@ -255,6 +270,10 @@ void messageHandler(id self, SEL cmd, id contentController, id message) {
// TODO: Check this actually does reduce flicker
msg(app->config, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 0), str("suppressesIncrementalRendering"));
// Notify backend we are ready (system startup)
app->sendMessageToBackend("SS");
} else if( strcmp(name, "windowDrag") == 0 ) {
// Guard against null events
if( app->mouseEvent != NULL ) {
@@ -444,6 +463,7 @@ void DestroyApplication(struct Application *app) {
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("contextMenu"));
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("windowDrag"));
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("external"));
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("error"));
// Close main window
msg(app->mainWindow, s("close"));
@@ -633,6 +653,8 @@ extern void MessageDialog(struct Application *app, char *callbackID, char *type,
dialogIcon = icon;
}
// TODO: move dialog icons + methods to own file
// Determine what dialog icon we are looking for
id dialogImage = NULL;
// Look for `name-theme2x` first
@@ -877,6 +899,20 @@ void setMinMaxSize(struct Application *app)
{
msg(app->mainWindow, s("setMinSize:"), CGSizeMake(app->minWidth, app->minHeight));
}
// Calculate if window needs resizing
int newWidth = app->width;
int newHeight = app->height;
if (app->maxWidth > 0 && app->width > app->maxWidth) newWidth = app->maxWidth;
if (app->minWidth > 0 && app->width < app->minWidth) newWidth = app->minWidth;
if (app->maxHeight > 0 && app->height > app->maxHeight ) newHeight = app->maxHeight;
if (app->minHeight > 0 && app->height < app->minHeight ) newHeight = app->minHeight;
// If we have any change, resize window
if ( newWidth != app->width || newHeight != app->height ) {
SetSize(app, newWidth, newHeight);
}
}
void SetMinWindowSize(struct Application *app, int minWidth, int minHeight)
@@ -911,7 +947,8 @@ void SetDebug(void *applicationPointer, int flag) {
}
// SetContextMenus sets the context menu map for this application
// AddContextMenu sets the context menu map for this application
void AddContextMenu(struct Application *app, const char *contextMenuJSON) {
AddContextMenuToStore(app->contextMenuStore, contextMenuJSON);
}
@@ -924,10 +961,19 @@ void AddTrayMenu(struct Application *app, const char *trayMenuJSON) {
AddTrayMenuToStore(app->trayMenuStore, trayMenuJSON);
}
void UpdateTrayMenu(struct Application *app, const char* trayMenuJSON) {
UpdateTrayMenuInStore(app->trayMenuStore, trayMenuJSON);
void SetTrayMenu(struct Application *app, const char* trayMenuJSON) {
ON_MAIN_THREAD(
UpdateTrayMenuInStore(app->trayMenuStore, trayMenuJSON);
);
}
void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
ON_MAIN_THREAD(
UpdateTrayMenuLabelInStore(app->trayMenuStore, JSON);
);
}
void SetBindings(struct Application *app, const char *bindings) {
const char* temp = concat("window.wailsbindings = \"", bindings);
const char* jscall = concat(temp, "\";");
@@ -1501,6 +1547,9 @@ void Run(struct Application *app, int argc, char **argv) {
makeWindowBackgroundTranslucent(app);
}
// We set it to be invisible by default. It will become visible when everything has initialised
msg(app->mainWindow, s("setIsVisible:"), NO);
// Setup webview
id config = msg(c("WKWebViewConfiguration"), s("new"));
msg(config, s("setValue:forKey:"), msg(c("NSNumber"), s("numberWithBool:"), 1), str("suppressesIncrementalRendering"));
@@ -1513,6 +1562,7 @@ void Run(struct Application *app, int argc, char **argv) {
id manager = msg(config, s("userContentController"));
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("external"));
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("completed"));
msg(manager, s("addScriptMessageHandler:name:"), app->delegate, str("error"));
app->manager = manager;
id wkwebview = msg(c("WKWebView"), s("alloc"));
@@ -1651,9 +1701,6 @@ void Run(struct Application *app, int argc, char **argv) {
// Process dialog icons
processUserDialogIcons(app);
// We set it to be invisible by default. It will become visible when everything has initialised
msg(app->mainWindow, s("setIsVisible:"), NO);
// Finally call run
Debug(app, "Run called");
msg(app->application, s("run"));

View File

@@ -2,7 +2,7 @@ package ffenestri
/*
#cgo darwin CFLAGS: -DFFENESTRI_DARWIN=1
#cgo darwin LDFLAGS: -framework WebKit -lobjc
#cgo darwin LDFLAGS: -framework WebKit -framework CoreFoundation -lobjc
#include "ffenestri.h"
#include "ffenestri_darwin.h"

View File

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

View File

@@ -5,6 +5,7 @@
#include "ffenestri_darwin.h"
#include "menu_darwin.h"
#include "contextmenus_darwin.h"
#include "common.h"
// NewMenu creates a new Menu struct, saving the given menu structure as JSON
Menu* NewMenu(JsonNode *menuData) {
@@ -63,8 +64,6 @@ MenuItemCallbackData* CreateMenuItemCallbackData(Menu *menu, id menuItem, const
return result;
}
void DeleteMenu(Menu *menu) {
// Free menu item hashmap
@@ -99,7 +98,11 @@ void DeleteMenu(Menu *menu) {
// Creates a JSON message for the given menuItemID and data
const char* createMenuClickedMessage(const char *menuItemID, const char *data, enum MenuType menuType, const char *parentID) {
JsonNode *jsonObject = json_mkobject();
if (menuItemID == NULL ) {
ABORT("Item ID NULL for menu!!\n");
}
json_append_member(jsonObject, "menuItemID", json_mkstring(menuItemID));
json_append_member(jsonObject, "menuType", json_mkstring(MenuTypeAsString[(int)menuType]));
if (data != NULL) {
@@ -572,7 +575,7 @@ id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const c
return item;
}
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers) {
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA) {
id item = ALLOC("NSMenuItem");
// Create a MenuItemCallbackData
@@ -585,6 +588,73 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
s("menuItemCallback:"), key);
if( tooltip != NULL ) {
msg(item, s("setToolTip:"), str(tooltip));
}
// Process image
if( image != NULL && strlen(image) > 0) {
id data = ALLOC("NSData");
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(image), 0);
id nsimage = ALLOC("NSImage");
msg(nsimage, s("initWithData:"), imageData);
msg(item, s("setImage:"), nsimage);
}
// 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("autorelease"));
@@ -666,6 +736,13 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
const char *acceleratorkey = NULL;
const char **modifiers = NULL;
const char *tooltip = getJSONString(item, "Tooltip");
const char *image = getJSONString(item, "Image");
const char *fontName = getJSONString(item, "FontName");
const char *RGBA = getJSONString(item, "RGBA");
int fontSize = 12;
getJSONInt(item, "FontSize", &fontSize);
// If we have an accelerator
if( accelerator != NULL ) {
// Get the key
@@ -698,7 +775,7 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
if( type != NULL ) {
if( STREQ(type->string_, "Text")) {
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers);
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA);
}
else if ( STREQ(type->string_, "Separator")) {
addSeparator(parentMenu);

View File

@@ -14,6 +14,21 @@ static const char *MenuTypeAsString[] = {
"ApplicationMenu", "ContextMenu", "TrayMenu",
};
typedef struct _NSRange {
unsigned long location;
unsigned long length;
} NSRange;
#define NSFontWeightUltraLight -0.8
#define NSFontWeightThin -0.6
#define NSFontWeightLight -0.4
#define NSFontWeightRegular 0
#define NSFontWeightMedium 0.23
#define NSFontWeightSemibold 0.3
#define NSFontWeightBold 0.4
#define NSFontWeightHeavy 0.56
#define NSFontWeightBlack 0.62
extern void messageFromWindowCallback(const char *);
typedef struct {
@@ -90,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 processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers);
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA);
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
void processMenuData(Menu *menu, JsonNode *menuData);

File diff suppressed because one or more lines are too long

View File

@@ -49,42 +49,19 @@ void DumpTrayMenu(TrayMenu* trayMenu) {
printf(" ['%s':%p] = { label: '%s', icon: '%s', menu: %p, statusbar: %p }\n", trayMenu->ID, trayMenu, trayMenu->label, trayMenu->icon, trayMenu->menu, trayMenu->statusbaritem );
}
void ShowTrayMenu(TrayMenu* trayMenu) {
// Create a status bar item if we don't have one
if( trayMenu->statusbaritem == NULL ) {
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
trayMenu->statusbaritem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
msg(trayMenu->statusbaritem, s("retain"));
}
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
// Update the icon if needed
UpdateTrayMenuIcon(trayMenu);
// Update the label if needed
UpdateTrayMenuLabel(trayMenu);
// Update the menu
id menu = GetMenu(trayMenu->menu);
msg(trayMenu->statusbaritem, s("setMenu:"), menu);
}
void UpdateTrayMenuLabel(TrayMenu *trayMenu) {
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) {
// Exit early if NULL
if( trayMenu->label == NULL ) {
return;
}
// We don't check for a
// Update button label
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
msg(statusBarButton, s("setTitle:"), str(trayMenu->label));
msg(statusBarButton, s("setTitle:"), str(label));
}
void UpdateTrayMenuIcon(TrayMenu *trayMenu) {
void UpdateTrayIcon(TrayMenu *trayMenu) {
// Exit early if NULL
if( trayMenu->icon == NULL ) {
@@ -105,6 +82,32 @@ void UpdateTrayMenuIcon(TrayMenu *trayMenu) {
msg(statusBarButton, s("setImage:"), trayImage);
}
void ShowTrayMenu(TrayMenu* trayMenu) {
// Create a status bar item if we don't have one
if( trayMenu->statusbaritem == NULL ) {
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
trayMenu->statusbaritem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
msg(trayMenu->statusbaritem, s("retain"));
}
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
// Update the icon if needed
UpdateTrayIcon(trayMenu);
// Update the label if needed
UpdateTrayLabel(trayMenu, trayMenu->label);
// Update the menu
id menu = GetMenu(trayMenu->menu);
msg(trayMenu->statusbaritem, s("setMenu:"), menu);
}
// UpdateTrayMenuInPlace receives 2 menus. The current menu gets
// updated with the data from the new menu.
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu) {

View File

@@ -27,8 +27,8 @@ TrayMenu* NewTrayMenu(const char *trayJSON);
void DumpTrayMenu(TrayMenu* trayMenu);
void ShowTrayMenu(TrayMenu* trayMenu);
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);
void UpdateTrayMenuIcon(TrayMenu *trayMenu);
void UpdateTrayMenuLabel(TrayMenu *trayMenu);
void UpdateTrayIcon(TrayMenu *trayMenu);
void UpdateTrayLabel(TrayMenu *trayMenu, const char*);
void LoadTrayIcons();
void UnloadTrayIcons();

View File

@@ -72,14 +72,46 @@ TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
return hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
}
TrayMenu* MustGetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
// Get the current menu
TrayMenu* result = hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
if (result == NULL ) {
ABORT("Unable to find TrayMenu with ID '%s' in the TrayMenuStore!", menuID);
}
return result;
}
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
// Parse the JSON
JsonNode *parsedUpdate = mustParseJSON(JSON);
// Get the data out
const char* ID = mustJSONString(parsedUpdate, "ID");
const char* Label = mustJSONString(parsedUpdate, "Label");
// Check we have this menu
TrayMenu *menu = MustGetTrayMenuFromStore(store, ID);
UpdateTrayLabel(menu, Label);
}
void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
TrayMenu* newMenu = NewTrayMenu(menuJSON);
// DumpTrayMenu(newMenu);
// Get the current menu
TrayMenu *currentMenu = GetTrayMenuFromStore(store, newMenu->ID);
// If we don't have a menu, we create one
if ( currentMenu == NULL ) {
ABORT("Attempted to update unknown tray menu with ID '%s'.", newMenu->ID);
// Store the new menu
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
// Show it
ShowTrayMenu(newMenu);
return;
}
// DumpTrayMenu(currentMenu);
// Save the status bar reference
newMenu->statusbaritem = currentMenu->statusbaritem;
@@ -90,12 +122,6 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
DeleteMenu(currentMenu->menu);
currentMenu->menu = NULL;
// Free JSON
if (currentMenu->processedJSON != NULL ) {
json_delete(currentMenu->processedJSON);
currentMenu->processedJSON = NULL;
}
// Free the tray menu memory
MEMFREE(currentMenu);

View File

@@ -22,4 +22,6 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON);
void ShowTrayMenusInStore(TrayMenuStore* store);
void DeleteTrayMenuStore(TrayMenuStore* store);
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON);
#endif //TRAYMENUSTORE_DARWIN_H

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

@@ -26,6 +26,9 @@ func NewMenuItemMap() *MenuItemMap {
}
func (m *MenuItemMap) AddMenu(menu *menu.Menu) {
if menu == nil {
return
}
for _, item := range menu.Items {
m.processMenuItem(item)
}

View File

@@ -9,28 +9,35 @@ import (
type ProcessedMenuItem struct {
ID string
// Label is what appears as the menu text
Label string
Label string `json:",omitempty"`
// Role is a predefined menu type
Role menu.Role `json:"Role,omitempty"`
Role menu.Role `json:",omitempty"`
// Accelerator holds a representation of a key binding
Accelerator *keys.Accelerator `json:"Accelerator,omitempty"`
Accelerator *keys.Accelerator `json:",omitempty"`
// Type of MenuItem, EG: Checkbox, Text, Separator, Radio, Submenu
Type menu.Type
// Disabled makes the item unselectable
Disabled bool
Disabled bool `json:",omitempty"`
// Hidden ensures that the item is not shown in the menu
Hidden bool
Hidden bool `json:",omitempty"`
// Checked indicates if the item is selected (used by Checkbox and Radio types only)
Checked bool
Checked bool `json:",omitempty"`
// Submenu contains a list of menu items that will be shown as a submenu
//SubMenu []*MenuItem `json:"SubMenu,omitempty"`
SubMenu *ProcessedMenu `json:"SubMenu,omitempty"`
SubMenu *ProcessedMenu `json:",omitempty"`
// Foreground colour in hex RGBA format EG: 0xFF0000FF = #FF0000FF = red
Foreground int
// Colour
RGBA string `json:",omitempty"`
// Background colour
Background int
// Font
FontSize int `json:",omitempty"`
FontName string `json:",omitempty"`
// Image - base64 image data
Image string `json:",omitempty"`
// Tooltip
Tooltip string `json:",omitempty"`
}
func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *ProcessedMenuItem {
@@ -45,8 +52,12 @@ func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *Pr
Disabled: menuItem.Disabled,
Hidden: menuItem.Hidden,
Checked: menuItem.Checked,
Foreground: menuItem.Foreground,
Background: menuItem.Background,
SubMenu: nil,
RGBA: menuItem.RGBA,
FontSize: menuItem.FontSize,
FontName: menuItem.FontName,
Image: menuItem.Image,
Tooltip: menuItem.Tooltip,
}
if menuItem.SubMenu != nil {
@@ -63,9 +74,11 @@ type ProcessedMenu struct {
func NewProcessedMenu(menuItemMap *MenuItemMap, menu *menu.Menu) *ProcessedMenu {
result := &ProcessedMenu{}
for _, item := range menu.Items {
processedMenuItem := NewProcessedMenuItem(menuItemMap, item)
result.Items = append(result.Items, processedMenuItem)
if menu != nil {
for _, item := range menu.Items {
processedMenuItem := NewProcessedMenuItem(menuItemMap, item)
result.Items = append(result.Items, processedMenuItem)
}
}
return result

View File

@@ -3,6 +3,7 @@ package menumanager
import (
"encoding/json"
"fmt"
"github.com/pkg/errors"
"github.com/wailsapp/wails/v2/pkg/menu"
"sync"
)
@@ -50,7 +51,7 @@ func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
return result
}
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) {
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
newTrayMenu := NewTrayMenu(trayMenu)
// Hook up a new ID
@@ -60,12 +61,15 @@ func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) {
// Save the references
m.trayMenus[trayID] = newTrayMenu
m.trayMenuPointers[trayMenu] = trayID
return newTrayMenu.AsJSON()
}
func (m *Manager) UpdateTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
// SetTrayMenu updates or creates a menu
func (m *Manager) SetTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
if !trayMenuKnown {
return "", fmt.Errorf("unknown Tray Menu '%s'. Please add the tray menu using AddTrayMenu()", trayMenu.Label)
return m.AddTrayMenu(trayMenu)
}
// Create the updated tray menu
@@ -91,6 +95,31 @@ func (m *Manager) GetTrayMenus() ([]string, error) {
return result, nil
}
func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) {
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
if !trayMenuKnown {
return "", fmt.Errorf("[UpdateTrayMenuLabel] unknown tray id for tray %s", trayMenu.Label)
}
type LabelUpdate struct {
ID string
Label string
}
update := &LabelUpdate{
ID: trayID,
Label: trayMenu.Label,
}
data, err := json.Marshal(update)
if err != nil {
return "", errors.Wrap(err, "[UpdateTrayMenuLabel] ")
}
return string(data), nil
}
func (m *Manager) GetContextMenus() ([]string, error) {
result := []string{}
for _, contextMenu := range m.contextMenus {

View File

@@ -2,10 +2,11 @@ package messagedispatcher
import (
"fmt"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
)
// Client defines what a frontend client can do
@@ -13,9 +14,9 @@ type Client interface {
Quit()
NotifyEvent(message string)
CallResult(message string)
OpenDialog(dialogOptions *options.OpenDialog, callbackID string)
SaveDialog(dialogOptions *options.SaveDialog, callbackID string)
MessageDialog(dialogOptions *options.MessageDialog, callbackID string)
OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string)
SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string)
MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string)
WindowSetTitle(title string)
WindowShow()
WindowHide()
@@ -26,12 +27,15 @@ type Client interface {
WindowUnminimise()
WindowPosition(x int, y int)
WindowSize(width int, height int)
WindowSetMinSize(width int, height int)
WindowSetMaxSize(width int, height int)
WindowFullscreen()
WindowUnFullscreen()
WindowSetColour(colour int)
DarkModeEnabled(callbackID string)
SetApplicationMenu(menuJSON string)
UpdateTrayMenu(trayMenuJSON string)
SetTrayMenu(trayMenuJSON string)
UpdateTrayMenuLabel(JSON string)
UpdateContextMenu(contextMenuJSON string)
}

View File

@@ -8,8 +8,8 @@ import (
// systemMessageParser does what it says on the tin!
func systemMessageParser(message string) (*parsedMessage, error) {
// Sanity check: system messages must be at least 4 bytes
if len(message) < 4 {
// Sanity check: system messages must be at least 2 bytes
if len(message) < 2 {
return nil, fmt.Errorf("system message was an invalid length")
}
@@ -23,6 +23,9 @@ func systemMessageParser(message string) (*parsedMessage, error) {
// Format of system response messages: S<command><callbackID>|<payload>
// DarkModeEnabled
case 'D':
if len(message) < 4 {
return nil, fmt.Errorf("system message was an invalid length")
}
message = message[1:]
idx := strings.IndexByte(message, '|')
if idx < 0 {
@@ -34,6 +37,10 @@ func systemMessageParser(message string) (*parsedMessage, error) {
topic := "systemresponse:" + callbackID
responseMessage = &parsedMessage{Topic: topic, Data: payloadData == "T"}
// This is our startup hook - the frontend is now ready
case 'S':
topic := "hooks:startup"
responseMessage = &parsedMessage{Topic: topic, Data: nil}
default:
return nil, fmt.Errorf("Invalid message to systemMessageParser()")
}

View File

@@ -6,11 +6,12 @@ import (
"strings"
"sync"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/crypto"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/pkg/options"
)
// Dispatcher translates messages received from the frontend
@@ -349,6 +350,38 @@ func (d *Dispatcher) processWindowMessage(result *servicebus.Message) {
for _, client := range d.clients {
client.frontend.WindowSize(w, h)
}
case "minsize":
// We need 2 arguments
if len(splitTopic) != 4 {
d.logger.Error("Invalid number of parameters for 'window:minsize' : %#v", result.Data())
return
}
w, err1 := strconv.Atoi(splitTopic[2])
h, err2 := strconv.Atoi(splitTopic[3])
if err1 != nil || err2 != nil {
d.logger.Error("Invalid integer parameters for 'window:minsize' : %#v", result.Data())
return
}
// Notifh clients
for _, client := range d.clients {
client.frontend.WindowSetMinSize(w, h)
}
case "maxsize":
// We need 2 arguments
if len(splitTopic) != 4 {
d.logger.Error("Invalid number of parameters for 'window:maxsize' : %#v", result.Data())
return
}
w, err1 := strconv.Atoi(splitTopic[2])
h, err2 := strconv.Atoi(splitTopic[3])
if err1 != nil || err2 != nil {
d.logger.Error("Invalid integer parameters for 'window:maxsize' : %#v", result.Data())
return
}
// Notifh clients
for _, client := range d.clients {
client.frontend.WindowSetMaxSize(w, h)
}
default:
d.logger.Error("Unknown window command: %s", command)
}
@@ -370,7 +403,7 @@ func (d *Dispatcher) processDialogMessage(result *servicebus.Message) {
dialogType := splitTopic[2]
switch dialogType {
case "open":
dialogOptions, ok := result.Data().(*options.OpenDialog)
dialogOptions, ok := result.Data().(*dialog.OpenDialog)
if !ok {
d.logger.Error("Invalid data for 'dialog:select:open' : %#v", result.Data())
return
@@ -384,7 +417,7 @@ func (d *Dispatcher) processDialogMessage(result *servicebus.Message) {
client.frontend.OpenDialog(dialogOptions, callbackID)
}
case "save":
dialogOptions, ok := result.Data().(*options.SaveDialog)
dialogOptions, ok := result.Data().(*dialog.SaveDialog)
if !ok {
d.logger.Error("Invalid data for 'dialog:select:save' : %#v", result.Data())
return
@@ -398,7 +431,7 @@ func (d *Dispatcher) processDialogMessage(result *servicebus.Message) {
client.frontend.SaveDialog(dialogOptions, callbackID)
}
case "message":
dialogOptions, ok := result.Data().(*options.MessageDialog)
dialogOptions, ok := result.Data().(*dialog.MessageDialog)
if !ok {
d.logger.Error("Invalid data for 'dialog:select:message' : %#v", result.Data())
return
@@ -445,10 +478,10 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
client.frontend.SetApplicationMenu(updatedMenu)
}
case "updatetraymenu":
updatedTrayMenu, ok := result.Data().(string)
case "settraymenu":
trayMenuJSON, ok := result.Data().(string)
if !ok {
d.logger.Error("Invalid data for 'menufrontend:updatetraymenu' : %#v",
d.logger.Error("Invalid data for 'menufrontend:settraymenu' : %#v",
result.Data())
return
}
@@ -456,8 +489,9 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
// TODO: Work out what we mean in a multi window environment...
// For now we will just pick the first one
for _, client := range d.clients {
client.frontend.UpdateTrayMenu(updatedTrayMenu)
client.frontend.SetTrayMenu(trayMenuJSON)
}
case "updatecontextmenu":
updatedContextMenu, ok := result.Data().(string)
if !ok {
@@ -472,6 +506,20 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
client.frontend.UpdateContextMenu(updatedContextMenu)
}
case "updatetraymenulabel":
updatedTrayMenuLabel, ok := result.Data().(string)
if !ok {
d.logger.Error("Invalid data for 'menufrontend:updatetraymenulabel' : %#v",
result.Data())
return
}
// TODO: Work out what we mean in a multi window environment...
// For now we will just pick the first one
for _, client := range d.clients {
client.frontend.UpdateTrayMenuLabel(updatedTrayMenuLabel)
}
default:
d.logger.Error("Unknown menufrontend command: %s", command)
}

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -5,14 +5,14 @@ import (
"github.com/wailsapp/wails/v2/internal/crypto"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/pkg/options"
dialogoptions "github.com/wailsapp/wails/v2/pkg/options/dialog"
)
// Dialog defines all Dialog related operations
type Dialog interface {
Open(dialogOptions *options.OpenDialog) []string
Save(dialogOptions *options.SaveDialog) string
Message(dialogOptions *options.MessageDialog) string
Open(dialogOptions *dialogoptions.OpenDialog) []string
Save(dialogOptions *dialogoptions.SaveDialog) string
Message(dialogOptions *dialogoptions.MessageDialog) string
}
// dialog exposes the Dialog interface
@@ -45,7 +45,7 @@ func (r *dialog) processTitleAndFilter(params ...string) (string, string) {
}
// Open prompts the user to select a file
func (r *dialog) Open(dialogOptions *options.OpenDialog) []string {
func (r *dialog) Open(dialogOptions *dialogoptions.OpenDialog) []string {
// Create unique dialog callback
uniqueCallback := crypto.RandomID()
@@ -70,7 +70,7 @@ func (r *dialog) Open(dialogOptions *options.OpenDialog) []string {
}
// Save prompts the user to select a file
func (r *dialog) Save(dialogOptions *options.SaveDialog) string {
func (r *dialog) Save(dialogOptions *dialogoptions.SaveDialog) string {
// Create unique dialog callback
uniqueCallback := crypto.RandomID()
@@ -95,7 +95,7 @@ func (r *dialog) Save(dialogOptions *options.SaveDialog) string {
}
// Message show a message to the user
func (r *dialog) Message(dialogOptions *options.MessageDialog) string {
func (r *dialog) Message(dialogOptions *dialogoptions.MessageDialog) string {
// Create unique dialog callback
uniqueCallback := crypto.RandomID()

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,8 @@ import (
type Menu interface {
UpdateApplicationMenu()
UpdateContextMenu(contextMenu *menu.ContextMenu)
UpdateTrayMenu(trayMenu *menu.TrayMenu)
SetTrayMenu(trayMenu *menu.TrayMenu)
UpdateTrayMenuLabel(trayMenu *menu.TrayMenu)
}
type menuRuntime struct {
@@ -31,6 +32,10 @@ func (m *menuRuntime) UpdateContextMenu(contextMenu *menu.ContextMenu) {
m.bus.Publish("menu:updatecontextmenu", contextMenu)
}
func (m *menuRuntime) UpdateTrayMenu(trayMenu *menu.TrayMenu) {
m.bus.Publish("menu:updatetraymenu", trayMenu)
func (m *menuRuntime) SetTrayMenu(trayMenu *menu.TrayMenu) {
m.bus.Publish("menu:settraymenu", trayMenu)
}
func (m *menuRuntime) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) {
m.bus.Publish("menu:updatetraymenulabel", trayMenu)
}

View File

@@ -3,12 +3,13 @@ package main
import (
"bytes"
"fmt"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/shell"
"io/ioutil"
"log"
"os"
"strings"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/shell"
)
func main() {
@@ -49,7 +50,7 @@ func main() {
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)
if err != nil {
log.Fatal(err)

View File

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

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

@@ -44,6 +44,7 @@ func (r *system) IsDarkMode() bool {
systemResponseChannel, err := r.bus.Subscribe(responseTopic)
if err != nil {
fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error())
return false
}
message := "system:isdarkmode:" + uniqueCallback

View File

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

View File

@@ -71,10 +71,11 @@ func (s *ServiceBus) Start() error {
}
// We run in a different thread
s.wg.Add(1)
go func() {
quit := false
s.wg.Add(1)
// Loop until we get a quit message
for !quit {

View File

@@ -5,8 +5,6 @@ import (
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/internal/servicebus"
"os"
"time"
)
// Binding is the Binding subsystem. It manages all service bus messages
@@ -26,13 +24,6 @@ type Binding struct {
runtime *runtime.Runtime
}
func showError(err error) {
// Add a slight delay so log buffer clears
time.Sleep(1 * time.Second)
println("\n\n\n\n\n\n")
println("Fatal Error in WailsInit(): " + err.Error())
}
// 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) {
@@ -56,16 +47,6 @@ func NewBinding(bus *servicebus.ServiceBus, logger *logger.Logger, bindings *bin
runtime: runtime,
}
// Call WailsInit methods once the frontend is loaded
runtime.Events.On("wails:loaded", func(...interface{}) {
result.logger.Trace("Calling WailsInit() methods")
err := result.CallWailsInit()
if err != nil {
showError(err)
os.Exit(1)
}
})
return result, nil
}
@@ -94,34 +75,6 @@ func (b *Binding) Start() error {
return nil
}
// CallWailsInit will callback to the registered WailsInit
// methods with the runtime object
func (b *Binding) CallWailsInit() error {
for _, wailsinit := range b.bindings.DB().WailsInitMethods() {
_, err := wailsinit.Call([]interface{}{b.runtime})
if err != nil {
return err
}
}
return nil
}
// CallWailsShutdown will callback to the registered WailsShutdown
// methods with the runtime object
func (b *Binding) CallWailsShutdown() error {
for _, wailsshutdown := range b.bindings.DB().WailsShutdownMethods() {
_, err := wailsshutdown.Call([]interface{}{})
if err != nil {
return err
}
}
return nil
}
func (b *Binding) shutdown() {
err := b.CallWailsShutdown()
if err != nil {
showError(err)
}
b.logger.Trace("Shutdown")
}

View File

@@ -3,6 +3,7 @@ package subsystem
import (
"encoding/json"
"fmt"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"strings"
"github.com/wailsapp/wails/v2/internal/binding"
@@ -10,7 +11,6 @@ import (
"github.com/wailsapp/wails/v2/internal/messagedispatcher/message"
"github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/internal/servicebus"
"github.com/wailsapp/wails/v2/pkg/options"
)
// Call is the Call subsystem. It manages all service bus messages
@@ -131,7 +131,7 @@ func (c *Call) processSystemCall(payload *message.CallMessage, clientID string)
darkModeEnabled := c.runtime.System.IsDarkMode()
c.sendResult(darkModeEnabled, payload, clientID)
case "Dialog.Open":
dialogOptions := new(options.OpenDialog)
dialogOptions := new(dialog.OpenDialog)
err := json.Unmarshal(payload.Args[0], dialogOptions)
if err != nil {
c.logger.Error("Error decoding: %s", err)
@@ -139,7 +139,7 @@ func (c *Call) processSystemCall(payload *message.CallMessage, clientID string)
result := c.runtime.Dialog.Open(dialogOptions)
c.sendResult(result, payload, clientID)
case "Dialog.Save":
dialogOptions := new(options.SaveDialog)
dialogOptions := new(dialog.SaveDialog)
err := json.Unmarshal(payload.Args[0], dialogOptions)
if err != nil {
c.logger.Error("Error decoding: %s", err)
@@ -147,7 +147,7 @@ func (c *Call) processSystemCall(payload *message.CallMessage, clientID string)
result := c.runtime.Dialog.Save(dialogOptions)
c.sendResult(result, payload, clientID)
case "Dialog.Message":
dialogOptions := new(options.MessageDialog)
dialogOptions := new(dialog.MessageDialog)
err := json.Unmarshal(payload.Args[0], dialogOptions)
if err != nil {
c.logger.Error("Error decoding: %s", err)

View File

@@ -120,16 +120,27 @@ func (m *Menu) Start() error {
// Notify frontend of menu change
m.bus.Publish("menufrontend:updatecontextmenu", updatedMenu)
case "updatetraymenu":
case "settraymenu":
trayMenu := menuMessage.Data().(*menu.TrayMenu)
updatedMenu, err := m.menuManager.UpdateTrayMenu(trayMenu)
updatedMenu, err := m.menuManager.SetTrayMenu(trayMenu)
if err != nil {
m.logger.Trace("%s", err.Error())
return
}
// Notify frontend of menu change
m.bus.Publish("menufrontend:updatetraymenu", updatedMenu)
m.bus.Publish("menufrontend:settraymenu", updatedMenu)
case "updatetraymenulabel":
trayMenu := menuMessage.Data().(*menu.TrayMenu)
updatedLabel, err := m.menuManager.UpdateTrayMenuLabel(trayMenu)
if err != nil {
m.logger.Trace("%s", err.Error())
return
}
// Notify frontend of menu change
m.bus.Publish("menufrontend:updatetraymenulabel", updatedLabel)
default:
m.logger.Error("unknown menu message: %+v", menuMessage)

View File

@@ -14,7 +14,13 @@ import (
type Runtime struct {
quitChannel <-chan *servicebus.Message
runtimeChannel <-chan *servicebus.Message
running bool
// The hooks channel allows us to hook into frontend startup
hooksChannel <-chan *servicebus.Message
startupCallback func(*runtime.Runtime)
shutdownCallback func()
running bool
logger logger.CustomLogger
@@ -23,7 +29,7 @@ type Runtime struct {
}
// NewRuntime creates a new runtime subsystem
func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger) (*Runtime, error) {
func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, startupCallback func(*runtime.Runtime), shutdownCallback func()) (*Runtime, error) {
// Register quit channel
quitChannel, err := bus.Subscribe("quit")
@@ -37,11 +43,20 @@ func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger) (*Runtime, er
return nil, err
}
// Subscribe to log messages
hooksChannel, err := bus.Subscribe("hooks:")
if err != nil {
return nil, err
}
result := &Runtime{
quitChannel: quitChannel,
runtimeChannel: runtimeChannel,
logger: logger.CustomLogger("Runtime Subsystem"),
runtime: runtime.New(bus),
quitChannel: quitChannel,
runtimeChannel: runtimeChannel,
hooksChannel: hooksChannel,
logger: logger.CustomLogger("Runtime Subsystem"),
runtime: runtime.New(bus),
startupCallback: startupCallback,
shutdownCallback: shutdownCallback,
}
return result, nil
@@ -59,6 +74,21 @@ func (r *Runtime) Start() error {
case <-r.quitChannel:
r.running = false
break
case hooksMessage := <-r.hooksChannel:
r.logger.Trace(fmt.Sprintf("Received hooksmessage: %+v", hooksMessage))
messageSlice := strings.Split(hooksMessage.Topic(), ":")
hook := messageSlice[1]
switch hook {
case "startup":
if r.startupCallback != nil {
go r.startupCallback(r.runtime)
} else {
r.logger.Warning("no startup callback registered!")
}
default:
r.logger.Error("unknown hook message: %+v", hooksMessage)
continue
}
case runtimeMessage := <-r.runtimeChannel:
r.logger.Trace(fmt.Sprintf("Received message: %+v", runtimeMessage))
// Topics have the format: "runtime:category:call"
@@ -99,6 +129,11 @@ func (r *Runtime) GoRuntime() *runtime.Runtime {
}
func (r *Runtime) shutdown() {
if r.shutdownCallback != nil {
go r.shutdownCallback()
} else {
r.logger.Warning("no shutdown callback registered!")
}
r.logger.Trace("Shutdown")
}

View File

@@ -2,14 +2,21 @@ package main
import (
"github.com/wailsapp/wails/v2"
"log"
)
func main() {
// 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.Run()
err = app.Run()
if err != nil {
log.Fatal(err)
}
}

View File

@@ -3,7 +3,7 @@ package webserver
import (
"context"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"net/http"
"strings"
@@ -20,11 +20,35 @@ type WebClient struct {
running bool
}
func (wc *WebClient) OpenDialog(dialogOptions *options.OpenDialog, callbackID string) {
func (wc *WebClient) SetTrayMenu(trayMenuJSON string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) SaveDialog(dialogOptions *options.SaveDialog, callbackID string) {
func (wc *WebClient) UpdateTrayMenuLabel(trayMenuJSON string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) SetApplicationMenu(menuJSON string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) UpdateTrayMenu(trayMenuJSON string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) UpdateContextMenu(contextMenuJSON string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) {
wc.logger.Info("Not implemented in server build")
}
@@ -84,10 +108,6 @@ func (wc *WebClient) UpdateTrayIcon(name string) {
wc.logger.Info("Not implemented in server build")
}
func (wc *WebClient) UpdateContextMenus(contextMenus *menu.ContextMenus) {
wc.logger.Info("Not implemented in server build")
}
// Quit terminates the webclient session
func (wc *WebClient) Quit() {
wc.running = false

View File

@@ -211,7 +211,6 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
options.CompiledBinary = compiledBinary
// Create the command
fmt.Printf("Compile command: %+v", commands.AsSlice())
cmd := exec.Command(options.Compiler, commands.AsSlice()...)
// Set the directory

View File

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

View File

@@ -12,6 +12,14 @@ func (m *Menu) Append(item *MenuItem) {
m.Items = append(m.Items, item)
}
// Merge will append the items in the given menu
// into this menu
func (m *Menu) Merge(menu *Menu) {
for _, item := range menu.Items {
m.Items = append(m.Items, item)
}
}
func (m *Menu) Prepend(item *MenuItem) {
m.Items = append([]*MenuItem{item}, m.Items...)
}

View File

@@ -28,11 +28,18 @@ type MenuItem struct {
// Callback function when menu clicked
Click Callback `json:"-"`
// Foreground colour in hex RGBA format EG: 0xFF0000FF = #FF0000FF = red
Foreground int
// Colour
RGBA string
// Background colour
Background int
// Font
FontSize int
FontName string
// Image - base64 image data
Image string
// Tooltip
Tooltip string
// This holds the menu item's parent.
parent *MenuItem

View File

@@ -1,4 +1,4 @@
package options
package dialog
// OpenDialog contains the options for the OpenDialog runtime method
type OpenDialog struct {

View File

@@ -1,10 +1,12 @@
package options
import (
"github.com/wailsapp/wails/v2/pkg/menu"
"log"
"runtime"
wailsruntime "github.com/wailsapp/wails/v2/internal/runtime"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/imdario/mergo"
"github.com/wailsapp/wails/v2/pkg/logger"
"github.com/wailsapp/wails/v2/pkg/options/mac"
@@ -30,6 +32,9 @@ type App struct {
Mac *mac.Options
Logger logger.Logger `json:"-"`
LogLevel logger.LogLevel
Startup func(*wailsruntime.Runtime) `json:"-"`
Shutdown func() `json:"-"`
Bind []interface{}
}
// MergeDefaults will set the minimum default values for an application

View File

@@ -44,11 +44,6 @@ func (p *Parser) parseStructMethods(boundStruct *Struct) error {
continue
}
// We want to ignore Internal functions
if funcDecl.Name.Name == "WailsInit" || funcDecl.Name.Name == "WailsShutdown" {
continue
}
// If this method is not Public, ignore
if string(funcDecl.Name.Name[0]) != strings.ToUpper((string(funcDecl.Name.Name[0]))) {
continue

10
v2/pkg/str/str.go Normal file
View File

@@ -0,0 +1,10 @@
package str
import (
"fmt"
"time"
)
func UnixNow() string {
return fmt.Sprintf("%+v", time.Now().Unix())
}

View File

@@ -2,7 +2,7 @@ package main
import (
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
)
// Dialog struct
@@ -18,16 +18,16 @@ func (l *Dialog) WailsInit(runtime *wails.Runtime) error {
}
// Open Dialog
func (l *Dialog) Open(options *options.OpenDialog) []string {
func (l *Dialog) Open(options *dialog.OpenDialog) []string {
return l.runtime.Dialog.Open(options)
}
// Save Dialog
func (l *Dialog) Save(options *options.SaveDialog) string {
func (l *Dialog) Save(options *dialog.SaveDialog) string {
return l.runtime.Dialog.Save(options)
}
// Message Dialog
func (l *Dialog) Message(options *options.MessageDialog) string {
func (l *Dialog) Message(options *dialog.MessageDialog) string {
return l.runtime.Dialog.Message(options)
}

View File

@@ -1,10 +1,10 @@
package main
import (
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"io/ioutil"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
)
type Notepad struct {
@@ -21,7 +21,7 @@ func (n *Notepad) WailsInit(runtime *wails.Runtime) error {
// successful save.
func (n *Notepad) SaveNotes(notes string) (bool, error) {
selectedFile := n.runtime.Dialog.Save(&options.SaveDialog{
selectedFile := n.runtime.Dialog.Save(&dialog.SaveDialog{
DefaultFilename: "notes.md",
Filters: "*.md",
})

View File

@@ -1,10 +1,10 @@
package main
import (
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"io/ioutil"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
)
type Notepad struct {
@@ -18,7 +18,7 @@ func (n *Notepad) WailsInit(runtime *wails.Runtime) error {
func (n *Notepad) LoadNotes() (string, error) {
selectedFiles := n.runtime.Dialog.Open(&options.OpenDialog{
selectedFiles := n.runtime.Dialog.Open(&dialog.OpenDialog{
DefaultFilename: "notes.md",
Filters: "*.md",
AllowFiles: true,

View File

@@ -1,10 +1,10 @@
package main
import (
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"io/ioutil"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
)
type Notepad struct {
@@ -21,7 +21,7 @@ func (n *Notepad) WailsInit(runtime *wails.Runtime) error {
// successful save.
func (n *Notepad) SaveNotes(notes string) (bool, error) {
selectedFile := n.runtime.Dialog.Save(&options.SaveDialog{
selectedFile := n.runtime.Dialog.Save(&dialog.SaveDialog{
DefaultFilename: "notes.md",
Filters: "*.md",
})

View File

@@ -2,8 +2,6 @@ module test
go 1.13
require (
github.com/wailsapp/wails/v2 v2.0.0-alpha
)
require github.com/wailsapp/wails/v2 v2.0.0-alpha
replace github.com/wailsapp/wails/v2 v2.0.0-alpha => ../../../v2

View File

@@ -1,112 +1,90 @@
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
github.com/abadojack/whatlanggo v1.0.1 h1:19N6YogDnf71CTHm3Mp2qhYfkRdyvbgwWdd2EPxJRG4=
github.com/abadojack/whatlanggo v1.0.1/go.mod h1:66WiQbSbJBIlOZMsvbKe5m6pzQovxCH9B/K8tQB2uoc=
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/colors v1.2.0/go.mod h1:miw1R2JIE19cclPxsXqNdzLZsk4DP4iF+m88bRc7kfM=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/jackmordaunt/icns v1.0.0/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU=
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
github.com/leaanthony/gosod v0.0.4/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
github.com/leaanthony/mewn v0.10.7/go.mod h1:CRkTx8unLiSSilu/Sd7i1LwrdaAL+3eQ3ses99qGMEQ=
github.com/leaanthony/slicer v1.4.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leaanthony/slicer v1.4.1/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leaanthony/spinner v0.5.3/go.mod h1:oHlrvWicr++CVV7ALWYi+qHk/XNA91D9IJ48IqmpVUo=
github.com/leaanthony/synx v0.1.0/go.mod h1:Iz7eybeeG8bdq640iR+CwYb8p+9EOsgMWghkSRyZcqs=
github.com/leaanthony/wincursor v0.1.0/go.mod h1:7TVwwrzSH/2Y9gLOGH+VhA+bZhoWXBRgbGNTMk+yimE=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba/go.mod h1:iLnlXG2Pakcii2CU0cbY07DRCSvpWNa7nFxtevhOChk=
github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs=
github.com/tdewolff/minify/v2 v2.9.5/go.mod h1:jshtBj/uUJH6JX1fuxTLnnHOA1RVJhF5MM+leJzDKb4=
github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ=
github.com/tdewolff/parse/v2 v2.5.3/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/wailsapp/wails v1.8.0 h1:gnQhpwoGM8s2GD5PZrgMKU1PO3pQ9cdKKJgwtkNz2f4=
github.com/wailsapp/wails v1.8.0/go.mod h1:XFZunea+USOCMMgBlz0A0JHLL3oWrRhnOl4baZlRpxo=
github.com/wailsapp/wails v1.9.1 h1:ez/TK8YpU9lvOZ9nkgzUXsWu+xOPFVO57zTy0n5w3hc=
github.com/xyproto/xpm v1.2.1/go.mod h1:cMnesLsD0PBXLgjDfTDEaKr8XyTFsnP1QycSqRw7BiY=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180606202747-9527bec2660b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -123,12 +101,14 @@ golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82/go.mod h1:Cj7w3i3Rnn0Xh82u
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/AlecAivazis/survey.v1 v1.8.4/go.mod h1:iBNOmqKz/NUbZx3bA+4hAGLRC7fSK7tgtVDT4tB22XA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=

View File

@@ -34,6 +34,8 @@ func main() {
TrayMenus: Tray.createTrayMenus(),
},
LogLevel: logger.TRACE,
Startup: Tray.start,
Shutdown: Tray.shutdown,
})
if err != nil {

View File

@@ -21,8 +21,8 @@ type Tray struct {
done bool
}
// WailsInit is called at application startup
func (t *Tray) WailsInit(runtime *wails.Runtime) error {
// This is called at application startup
func (t *Tray) start(runtime *wails.Runtime) {
// Perform your setup here
t.runtime = runtime
@@ -36,7 +36,6 @@ func (t *Tray) WailsInit(runtime *wails.Runtime) error {
// t.runtime.Tray.SetIcon("dark")
//})
return nil
}
func (t *Tray) showWindow(_ *menu.CallbackData) {
@@ -55,7 +54,7 @@ func (t *Tray) minimiseWindow(_ *menu.CallbackData) {
t.runtime.Window.Minimise()
}
func (t *Tray) WailsShutdown() {
func (t *Tray) shutdown() {
t.done = true
}
@@ -71,19 +70,19 @@ func (t *Tray) decrementcounter() int {
func (t *Tray) SvelteIcon(_ *menu.CallbackData) {
t.secondTrayMenu.Icon = "svelte"
t.runtime.Menu.UpdateTrayMenu(t.secondTrayMenu)
t.runtime.Menu.SetTrayMenu(t.secondTrayMenu)
}
func (t *Tray) NoIcon(_ *menu.CallbackData) {
t.secondTrayMenu.Icon = ""
t.runtime.Menu.UpdateTrayMenu(t.secondTrayMenu)
t.runtime.Menu.SetTrayMenu(t.secondTrayMenu)
}
func (t *Tray) LightIcon(_ *menu.CallbackData) {
t.secondTrayMenu.Icon = "light"
t.runtime.Menu.UpdateTrayMenu(t.secondTrayMenu)
t.runtime.Menu.SetTrayMenu(t.secondTrayMenu)
}
func (t *Tray) DarkIcon(_ *menu.CallbackData) {
t.secondTrayMenu.Icon = "dark"
t.runtime.Menu.UpdateTrayMenu(t.secondTrayMenu)
t.runtime.Menu.SetTrayMenu(t.secondTrayMenu)
}
//func (t *Tray) removeMenu(_ *menu.MenuItem) {
@@ -146,7 +145,7 @@ func (t *Tray) createTrayMenus() []*menu.TrayMenu {
counter := t.incrementcounter()
trayLabel := "Updated Label " + strconv.Itoa(counter)
secondTrayMenu.Label = trayLabel
t.runtime.Menu.UpdateTrayMenu(t.secondTrayMenu)
t.runtime.Menu.SetTrayMenu(t.secondTrayMenu)
}),
menu.SubMenu("Select Icon", menu.NewMenuFromItems(
menu.Text("Svelte", nil, t.SvelteIcon),

View File

@@ -1,10 +1,10 @@
package main
import (
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"time"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
)
// RuntimeTest to test the runtimes
@@ -75,7 +75,7 @@ func (r *RuntimeTest) SetColour(colour int) {
// OpenFileDialog will call the Runtime.Dialog.OpenDialog method requesting File selection
func (r *RuntimeTest) OpenFileDialog(title string, filter string) []string {
dialogOptions := &options.OpenDialog{
dialogOptions := &dialog.OpenDialog{
Title: title,
Filters: filter,
AllowFiles: true,
@@ -85,7 +85,7 @@ func (r *RuntimeTest) OpenFileDialog(title string, filter string) []string {
// OpenDirectoryDialog will call the Runtime.Dialog.OpenDialog method requesting File selection
func (r *RuntimeTest) OpenDirectoryDialog(title string, filter string) []string {
dialogOptions := &options.OpenDialog{
dialogOptions := &dialog.OpenDialog{
Title: title,
Filters: filter,
AllowDirectories: true,
@@ -95,7 +95,7 @@ func (r *RuntimeTest) OpenDirectoryDialog(title string, filter string) []string
// OpenDialog will call the Runtime.Dialog.OpenDialog method requesting both Files and Directories
func (r *RuntimeTest) OpenDialog(title string, filter string) []string {
dialogOptions := &options.OpenDialog{
dialogOptions := &dialog.OpenDialog{
Title: title,
Filters: filter,
AllowDirectories: true,
@@ -106,7 +106,7 @@ func (r *RuntimeTest) OpenDialog(title string, filter string) []string {
// OpenDialogMultiple will call the Runtime.Dialog.OpenDialog method allowing multiple selection
func (r *RuntimeTest) OpenDialogMultiple(title string, filter string) []string {
dialogOptions := &options.OpenDialog{
dialogOptions := &dialog.OpenDialog{
Title: title,
Filters: filter,
AllowDirectories: true,
@@ -118,7 +118,7 @@ func (r *RuntimeTest) OpenDialogMultiple(title string, filter string) []string {
// OpenDialogAllOptions will call the Runtime.Dialog.OpenDialog method allowing multiple selection
func (r *RuntimeTest) OpenDialogAllOptions(filter string, defaultDir string, defaultFilename string) []string {
dialogOptions := &options.OpenDialog{
dialogOptions := &dialog.OpenDialog{
DefaultDirectory: defaultDir,
DefaultFilename: defaultFilename,
Filters: filter,
@@ -134,7 +134,7 @@ func (r *RuntimeTest) OpenDialogAllOptions(filter string, defaultDir string, def
// SaveFileDialog will call the Runtime.Dialog.SaveDialog method requesting a File selection
func (r *RuntimeTest) SaveFileDialog(title string, filter string) string {
dialogOptions := &options.SaveDialog{
dialogOptions := &dialog.SaveDialog{
Title: title,
Filters: filter,
}
@@ -143,7 +143,7 @@ func (r *RuntimeTest) SaveFileDialog(title string, filter string) string {
// SaveDialogAllOptions will call the Runtime.Dialog.SaveDialog method allowing multiple selection
func (r *RuntimeTest) SaveDialogAllOptions(filter string, defaultDir string, defaultFilename string) string {
dialogOptions := &options.SaveDialog{
dialogOptions := &dialog.SaveDialog{
DefaultDirectory: defaultDir,
DefaultFilename: defaultFilename,
Filters: filter,

View File

@@ -14,19 +14,12 @@ type Runtime = runtime.Runtime
// Store is an alias for the Store object
type Store = runtime.Store
// CreateAppWithOptions creates an application based on the given config
func CreateAppWithOptions(options *options.App) (*app.App, error) {
return app.CreateApp(options)
}
// 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,
// Run creates an application based on the given config and executes it
func Run(options *options.App) error {
app, err := app.CreateApp(options)
if err != nil {
return err
}
return app.CreateApp(options)
return app.Run()
}