mirror of
https://github.com/taigrr/wails.git
synced 2026-04-16 19:55:05 -07:00
Compare commits
70 Commits
v2.0.0-alp
...
v2-alpha-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2f233abfb | ||
|
|
2dd8833e67 | ||
|
|
3c30924493 | ||
|
|
4ce3e1d1bf | ||
|
|
e5f2746810 | ||
|
|
92ebf506dd | ||
|
|
9ab06152c5 | ||
|
|
bf36b6a59d | ||
|
|
4b9f6c4fb1 | ||
|
|
b1a42c8dea | ||
|
|
cbd98b5a1a | ||
|
|
c8e0aea69c | ||
|
|
7c0b236eb0 | ||
|
|
16debbd109 | ||
|
|
39bfa5d910 | ||
|
|
6424579a9e | ||
|
|
a962ae6f63 | ||
|
|
c7dee158ba | ||
|
|
237d25089d | ||
|
|
265328d648 | ||
|
|
6f40e85a6e | ||
|
|
96d8509da3 | ||
|
|
598445ab0f | ||
|
|
24788e2fd3 | ||
|
|
bbf4dde43f | ||
|
|
0afd27ab45 | ||
|
|
d498423ec2 | ||
|
|
66ce84973c | ||
|
|
55e6a0f312 | ||
|
|
81e83fdf18 | ||
|
|
f9b79d24f8 | ||
|
|
0599a47bfe | ||
|
|
817c55d318 | ||
|
|
14146c8c0c | ||
|
|
18adac20d4 | ||
|
|
eb4bff89da | ||
|
|
c66dc777f3 | ||
|
|
9003462457 | ||
|
|
e124f0a220 | ||
|
|
c6d3f57712 | ||
|
|
b4c669ff86 | ||
|
|
2d1b2c0947 | ||
|
|
4a0c5aa785 | ||
|
|
f48d7f8f60 | ||
|
|
651f24f641 | ||
|
|
8fd77148ca | ||
|
|
0dc0762fdf | ||
|
|
1a92550709 | ||
|
|
bffc15bc14 | ||
|
|
198d206c46 | ||
|
|
bb8e848ef6 | ||
|
|
bac3e9e5c1 | ||
|
|
bc5eddeb66 | ||
|
|
8e7258d812 | ||
|
|
7118762cec | ||
|
|
6af92cf0a4 | ||
|
|
1bb91634f7 | ||
|
|
f71ce7913f | ||
|
|
53db687a26 | ||
|
|
13939d3d6b | ||
|
|
552c6b8711 | ||
|
|
feee2b3db2 | ||
|
|
9889c2bdbb | ||
|
|
2432fccf71 | ||
|
|
70510fd180 | ||
|
|
17c6201469 | ||
|
|
0f209c8900 | ||
|
|
cbf043585c | ||
|
|
5ae621ceaa | ||
|
|
1231b59443 |
@@ -40,4 +40,5 @@ Wails is what it is because of the time and effort given by these great people.
|
|||||||
* [Balakrishna Prasad Ganne](https://github.com/aayush420)
|
* [Balakrishna Prasad Ganne](https://github.com/aayush420)
|
||||||
* [Charaf Rezrazi](https://github.com/Rezrazi)
|
* [Charaf Rezrazi](https://github.com/Rezrazi)
|
||||||
* [misitebao](https://github.com/misitebao)
|
* [misitebao](https://github.com/misitebao)
|
||||||
* [Elie Grenon](https://github.com/DrunkenPoney)
|
* [Elie Grenon](https://github.com/DrunkenPoney)
|
||||||
|
* [Amaury Tobias Quiroz](https://github.com/amaury-tobias)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
echo "**** Checking if Wails passes unit tests ****"
|
echo "**** Checking if Wails passes unit tests ****"
|
||||||
if ! go test ./...
|
if ! go test ./lib/... ./runtime/... ./cmd/...
|
||||||
then
|
then
|
||||||
echo ""
|
echo ""
|
||||||
echo "ERROR: Unit tests failed!"
|
echo "ERROR: Unit tests failed!"
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ package build
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/leaanthony/clir"
|
"github.com/leaanthony/clir"
|
||||||
@@ -23,8 +25,8 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
|||||||
command := app.NewSubCommand("build", "Builds the application")
|
command := app.NewSubCommand("build", "Builds the application")
|
||||||
|
|
||||||
// Setup target type flag
|
// Setup target type flag
|
||||||
description := "Type of application to build. Valid types: " + validTargetTypes.Join(",")
|
//description := "Type of application to build. Valid types: " + validTargetTypes.Join(",")
|
||||||
command.StringFlag("t", description, &outputType)
|
//command.StringFlag("t", description, &outputType)
|
||||||
|
|
||||||
// Setup production flag
|
// Setup production flag
|
||||||
production := false
|
production := false
|
||||||
@@ -41,24 +43,35 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
|||||||
platform := runtime.GOOS
|
platform := runtime.GOOS
|
||||||
command.StringFlag("platform", "Platform to target", &platform)
|
command.StringFlag("platform", "Platform to target", &platform)
|
||||||
|
|
||||||
// Quiet Build
|
// Verbosity
|
||||||
quiet := false
|
verbosity := 1
|
||||||
command.BoolFlag("q", "Suppress output to console", &quiet)
|
command.IntFlag("v", "Verbosity level (0 - silent, 1 - default, 2 - verbose)", &verbosity)
|
||||||
|
|
||||||
// ldflags to pass to `go`
|
// ldflags to pass to `go`
|
||||||
ldflags := ""
|
ldflags := ""
|
||||||
command.StringFlag("ldflags", "optional ldflags", &ldflags)
|
command.StringFlag("ldflags", "optional ldflags", &ldflags)
|
||||||
|
|
||||||
// Log to file
|
// Log to file
|
||||||
logFile := ""
|
//logFile := ""
|
||||||
command.StringFlag("l", "Log to file", &logFile)
|
//command.StringFlag("l", "Log to file", &logFile)
|
||||||
|
|
||||||
// Retain assets
|
// Retain assets
|
||||||
keepAssets := false
|
keepAssets := false
|
||||||
command.BoolFlag("k", "Keep generated assets", &keepAssets)
|
command.BoolFlag("k", "Keep generated assets", &keepAssets)
|
||||||
|
|
||||||
|
// Retain assets
|
||||||
|
outputFilename := ""
|
||||||
|
command.StringFlag("o", "Output filename", &outputFilename)
|
||||||
|
|
||||||
|
appleIdentity := ""
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
command.StringFlag("sign", "Signs your app with the given identity.", &appleIdentity)
|
||||||
|
}
|
||||||
|
|
||||||
command.Action(func() error {
|
command.Action(func() error {
|
||||||
|
|
||||||
|
quiet := verbosity == 0
|
||||||
|
|
||||||
// Create logger
|
// Create logger
|
||||||
logger := clilogger.New(w)
|
logger := clilogger.New(w)
|
||||||
logger.Mute(quiet)
|
logger.Mute(quiet)
|
||||||
@@ -72,9 +85,10 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
|||||||
app.PrintBanner()
|
app.PrintBanner()
|
||||||
}
|
}
|
||||||
|
|
||||||
task := fmt.Sprintf("Building %s Application", strings.Title(outputType))
|
// Ensure package is used with apple identity
|
||||||
logger.Println(task)
|
if appleIdentity != "" && pack == false {
|
||||||
logger.Println(strings.Repeat("-", len(task)))
|
return fmt.Errorf("must use `-package` flag when using `-sign`")
|
||||||
|
}
|
||||||
|
|
||||||
// Setup mode
|
// Setup mode
|
||||||
mode := build.Debug
|
mode := build.Debug
|
||||||
@@ -82,18 +96,66 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
|||||||
mode = build.Production
|
mode = build.Production
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check platform
|
||||||
|
validPlatformArch := slicer.String([]string{
|
||||||
|
"darwin",
|
||||||
|
"darwin/amd64",
|
||||||
|
"darwin/arm64",
|
||||||
|
"darwin/universal",
|
||||||
|
//"linux/amd64",
|
||||||
|
//"linux/arm-7",
|
||||||
|
//"windows/amd64",
|
||||||
|
})
|
||||||
|
if !validPlatformArch.Contains(platform) {
|
||||||
|
return fmt.Errorf("platform %s is not supported", platform)
|
||||||
|
}
|
||||||
|
|
||||||
// Create BuildOptions
|
// Create BuildOptions
|
||||||
buildOptions := &build.Options{
|
buildOptions := &build.Options{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
OutputType: outputType,
|
OutputType: outputType,
|
||||||
Mode: mode,
|
OutputFile: outputFilename,
|
||||||
Pack: pack,
|
Mode: mode,
|
||||||
Platform: platform,
|
Pack: pack,
|
||||||
LDFlags: ldflags,
|
LDFlags: ldflags,
|
||||||
Compiler: compilerCommand,
|
Compiler: compilerCommand,
|
||||||
KeepAssets: keepAssets,
|
KeepAssets: keepAssets,
|
||||||
|
AppleIdentity: appleIdentity,
|
||||||
|
Verbosity: verbosity,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate platform and arch
|
||||||
|
platformSplit := strings.Split(platform, "/")
|
||||||
|
buildOptions.Platform = platformSplit[0]
|
||||||
|
buildOptions.Arch = runtime.GOARCH
|
||||||
|
if len(platformSplit) == 2 {
|
||||||
|
buildOptions.Arch = platformSplit[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start a new tabwriter
|
||||||
|
w := new(tabwriter.Writer)
|
||||||
|
w.Init(os.Stdout, 8, 8, 0, '\t', 0)
|
||||||
|
|
||||||
|
buildModeText := "debug"
|
||||||
|
if production {
|
||||||
|
buildModeText = "production"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write out the system information
|
||||||
|
fmt.Fprintf(w, "App Type: \t%s\n", buildOptions.OutputType)
|
||||||
|
fmt.Fprintf(w, "Platform: \t%s\n", buildOptions.Platform)
|
||||||
|
fmt.Fprintf(w, "Arch: \t%s\n", buildOptions.Arch)
|
||||||
|
fmt.Fprintf(w, "Compiler: \t%s\n", buildOptions.Compiler)
|
||||||
|
fmt.Fprintf(w, "Build Mode: \t%s\n", buildModeText)
|
||||||
|
fmt.Fprintf(w, "Package: \t%t\n", buildOptions.Pack)
|
||||||
|
fmt.Fprintf(w, "KeepAssets: \t%t\n", buildOptions.KeepAssets)
|
||||||
|
fmt.Fprintf(w, "LDFlags: \t\"%s\"\n", buildOptions.LDFlags)
|
||||||
|
if len(buildOptions.OutputFile) > 0 {
|
||||||
|
fmt.Fprintf(w, "Output File: \t%s\n", buildOptions.OutputFile)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "\n")
|
||||||
|
w.Flush()
|
||||||
|
|
||||||
return doBuild(buildOptions)
|
return doBuild(buildOptions)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ func fatal(message string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func banner(_ *clir.Cli) string {
|
func banner(_ *clir.Cli) string {
|
||||||
return fmt.Sprintf("%s %s - Go/HTML Application Framework", colour.Yellow("Wails"), colour.DarkRed(version))
|
return fmt.Sprintf("%s %s", colour.Yellow("Wails CLI"), colour.DarkRed(version))
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
app := clir.NewCli("Wails", "Go/HTML Application Framework", version)
|
app := clir.NewCli("Wails", "Go/HTML Appkit", version)
|
||||||
|
|
||||||
app.SetBannerFunction(banner)
|
app.SetBannerFunction(banner)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
var version = "v2.0.0-alpha.32"
|
var version = "v2.0.0-alpha.55"
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ type App struct {
|
|||||||
//binding *subsystem.Binding
|
//binding *subsystem.Binding
|
||||||
call *subsystem.Call
|
call *subsystem.Call
|
||||||
menu *subsystem.Menu
|
menu *subsystem.Menu
|
||||||
|
url *subsystem.URL
|
||||||
dispatcher *messagedispatcher.Dispatcher
|
dispatcher *messagedispatcher.Dispatcher
|
||||||
|
|
||||||
menuManager *menumanager.Manager
|
menuManager *menumanager.Manager
|
||||||
@@ -117,14 +118,6 @@ func (a *App) Run() error {
|
|||||||
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
|
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
|
||||||
ctx, cancel := context.WithCancel(parentContext)
|
ctx, cancel := context.WithCancel(parentContext)
|
||||||
|
|
||||||
// Setup signal handler
|
|
||||||
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger, a.shutdownCallback)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.signal = signalsubsystem
|
|
||||||
a.signal.Start()
|
|
||||||
|
|
||||||
// Start the service bus
|
// Start the service bus
|
||||||
a.servicebus.Debug()
|
a.servicebus.Debug()
|
||||||
err = a.servicebus.Start()
|
err = a.servicebus.Start()
|
||||||
@@ -132,7 +125,7 @@ func (a *App) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
|
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -168,6 +161,19 @@ func (a *App) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.options.Mac.URLHandlers != nil {
|
||||||
|
// Start the url handler subsystem
|
||||||
|
url, err := subsystem.NewURL(a.servicebus, a.logger, a.options.Mac.URLHandlers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.url = url
|
||||||
|
err = a.url.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Start the eventing subsystem
|
// Start the eventing subsystem
|
||||||
eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger)
|
eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -207,6 +213,14 @@ func (a *App) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup signal handler
|
||||||
|
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.signal = signalsubsystem
|
||||||
|
a.signal.Start()
|
||||||
|
|
||||||
err = a.window.Run(dispatcher, bindingDump, a.debug)
|
err = a.window.Run(dispatcher, bindingDump, a.debug)
|
||||||
a.logger.Trace("Ffenestri.Run() exited")
|
a.logger.Trace("Ffenestri.Run() exited")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -231,5 +245,10 @@ func (a *App) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shutdown callback
|
||||||
|
if a.shutdownCallback != nil {
|
||||||
|
a.shutdownCallback()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,14 +118,6 @@ func (a *App) Run() error {
|
|||||||
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
|
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
|
||||||
ctx, cancel := context.WithCancel(parentContext)
|
ctx, cancel := context.WithCancel(parentContext)
|
||||||
|
|
||||||
// Setup signal handler
|
|
||||||
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger, a.shutdownCallback)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.signal = signalsubsystem
|
|
||||||
a.signal.Start()
|
|
||||||
|
|
||||||
// Start the service bus
|
// Start the service bus
|
||||||
a.servicebus.Debug()
|
a.servicebus.Debug()
|
||||||
err = a.servicebus.Start()
|
err = a.servicebus.Start()
|
||||||
@@ -133,7 +125,7 @@ func (a *App) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
|
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -211,6 +203,14 @@ func (a *App) Run() error {
|
|||||||
// Generate backend.js
|
// Generate backend.js
|
||||||
a.bindings.GenerateBackendJS()
|
a.bindings.GenerateBackendJS()
|
||||||
|
|
||||||
|
// Setup signal handler
|
||||||
|
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
a.signal = signalsubsystem
|
||||||
|
a.signal.Start()
|
||||||
|
|
||||||
err = a.bridge.Run(dispatcher, a.menuManager, bindingDump, a.debug)
|
err = a.bridge.Run(dispatcher, a.menuManager, bindingDump, a.debug)
|
||||||
a.logger.Trace("Bridge.Run() exited")
|
a.logger.Trace("Bridge.Run() exited")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -235,6 +235,10 @@ func (a *App) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shutdown callback
|
||||||
|
if a.shutdownCallback != nil {
|
||||||
|
a.shutdownCallback()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ type BridgeClient struct {
|
|||||||
messageCache chan string
|
messageCache chan string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b BridgeClient) DeleteTrayMenuByID(id string) {
|
||||||
|
b.session.sendMessage("TD" + id)
|
||||||
|
}
|
||||||
|
|
||||||
func NewBridgeClient() *BridgeClient {
|
func NewBridgeClient() *BridgeClient {
|
||||||
return &BridgeClient{
|
return &BridgeClient{
|
||||||
messageCache: make(chan string, 100),
|
messageCache: make(chan string, 100),
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ type DialogClient struct {
|
|||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DialogClient) DeleteTrayMenuByID(id string) {
|
||||||
|
}
|
||||||
|
|
||||||
func NewDialogClient(log *logger.Logger) *DialogClient {
|
func NewDialogClient(log *logger.Logger) *DialogClient {
|
||||||
return &DialogClient{
|
return &DialogClient{
|
||||||
log: log,
|
log: log,
|
||||||
|
|||||||
@@ -5,10 +5,6 @@
|
|||||||
#ifndef COMMON_H
|
#ifndef COMMON_H
|
||||||
#define COMMON_H
|
#define COMMON_H
|
||||||
|
|
||||||
#define OBJC_OLD_DISPATCH_PROTOTYPES 1
|
|
||||||
#include <objc/objc-runtime.h>
|
|
||||||
#include <CoreGraphics/CoreGraphics.h>
|
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include "string.h"
|
#include "string.h"
|
||||||
|
|||||||
@@ -82,10 +82,10 @@ void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *context
|
|||||||
FREE_AND_SET(contextMenu->contextMenuData, contextMenuData);
|
FREE_AND_SET(contextMenu->contextMenuData, contextMenuData);
|
||||||
|
|
||||||
// Grab the content view and show the menu
|
// Grab the content view and show the menu
|
||||||
id contentView = msg(mainWindow, s("contentView"));
|
id contentView = msg_reg(mainWindow, s("contentView"));
|
||||||
|
|
||||||
// Get the triggering event
|
// Get the triggering event
|
||||||
id menuEvent = msg(mainWindow, s("currentEvent"));
|
id menuEvent = msg_reg(mainWindow, s("currentEvent"));
|
||||||
|
|
||||||
if( contextMenu->nsmenu == NULL ) {
|
if( contextMenu->nsmenu == NULL ) {
|
||||||
// GetMenu creates the NSMenu
|
// GetMenu creates the NSMenu
|
||||||
@@ -93,7 +93,7 @@ void ShowContextMenu(ContextMenuStore* store, id mainWindow, const char *context
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Show popup
|
// Show popup
|
||||||
msg(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu->nsmenu, menuEvent, contentView);
|
((id(*)(id, SEL, id, id, id))objc_msgSend)(c("NSMenu"), s("popUpContextMenu:withEvent:forView:"), contextMenu->nsmenu, menuEvent, contentView);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ extern void DarkModeEnabled(struct Application*, char *callbackID);
|
|||||||
extern void SetApplicationMenu(struct Application*, const char *);
|
extern void SetApplicationMenu(struct Application*, const char *);
|
||||||
extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
|
extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
|
||||||
extern void SetTrayMenu(struct Application*, const char *menuTrayJSON);
|
extern void SetTrayMenu(struct Application*, const char *menuTrayJSON);
|
||||||
|
extern void DeleteTrayMenuByID(struct Application*, const char *id);
|
||||||
extern void UpdateTrayMenuLabel(struct Application*, const char* JSON);
|
extern void UpdateTrayMenuLabel(struct Application*, const char* JSON);
|
||||||
extern void AddContextMenu(struct Application*, char *contextMenuJSON);
|
extern void AddContextMenu(struct Application*, char *contextMenuJSON);
|
||||||
extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);
|
extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);
|
||||||
|
|||||||
@@ -208,3 +208,7 @@ func (c *Client) UpdateTrayMenuLabel(JSON string) {
|
|||||||
func (c *Client) UpdateContextMenu(contextMenuJSON string) {
|
func (c *Client) UpdateContextMenu(contextMenuJSON string) {
|
||||||
C.UpdateContextMenu(c.app.app, c.app.string2CString(contextMenuJSON))
|
C.UpdateContextMenu(c.app.app, c.app.string2CString(contextMenuJSON))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeleteTrayMenuByID(id string) {
|
||||||
|
C.DeleteTrayMenuByID(c.app.app, c.app.string2CString(id))
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -48,6 +48,9 @@ func (a *Application) processPlatformSettings() error {
|
|||||||
C.SetAppearance(a.app, a.string2CString(string(mac.Appearance)))
|
C.SetAppearance(a.app, a.string2CString(string(mac.Appearance)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set activation policy
|
||||||
|
C.SetActivationPolicy(a.app, C.int(mac.ActivationPolicy))
|
||||||
|
|
||||||
// Check if the webview should be transparent
|
// Check if the webview should be transparent
|
||||||
if mac.WebviewIsTransparent {
|
if mac.WebviewIsTransparent {
|
||||||
C.WebviewIsTransparent(a.app)
|
C.WebviewIsTransparent(a.app)
|
||||||
@@ -87,5 +90,10 @@ func (a *Application) processPlatformSettings() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process URL Handlers
|
||||||
|
if a.config.Mac.URLHandlers != nil {
|
||||||
|
C.HasURLHandlers(a.app)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,22 +13,41 @@
|
|||||||
|
|
||||||
// Macros to make it slightly more sane
|
// Macros to make it slightly more sane
|
||||||
#define msg objc_msgSend
|
#define msg objc_msgSend
|
||||||
|
#define msg_reg ((id(*)(id, SEL))objc_msgSend)
|
||||||
|
#define msg_id ((id(*)(id, SEL, id))objc_msgSend)
|
||||||
|
#define msg_id_id ((id(*)(id, SEL, id, id))objc_msgSend)
|
||||||
|
#define msg_bool ((id(*)(id, SEL, BOOL))objc_msgSend)
|
||||||
|
#define msg_int ((id(*)(id, SEL, int))objc_msgSend)
|
||||||
|
#define msg_uint ((id(*)(id, SEL, unsigned int))objc_msgSend)
|
||||||
|
#define msg_float ((id(*)(id, SEL, float))objc_msgSend)
|
||||||
|
#define kInternetEventClass 'GURL'
|
||||||
|
#define kAEGetURL 'GURL'
|
||||||
|
#define keyDirectObject '----'
|
||||||
|
|
||||||
#define c(str) (id)objc_getClass(str)
|
#define c(str) (id)objc_getClass(str)
|
||||||
#define s(str) sel_registerName(str)
|
#define s(str) sel_registerName(str)
|
||||||
#define u(str) sel_getUid(str)
|
#define u(str) sel_getUid(str)
|
||||||
#define str(input) msg(c("NSString"), s("stringWithUTF8String:"), input)
|
#define str(input) ((id(*)(id, SEL, const char *))objc_msgSend)(c("NSString"), s("stringWithUTF8String:"), input)
|
||||||
#define strunicode(input) msg(c("NSString"), s("stringWithFormat:"), str("%C"), (unsigned short)input)
|
#define strunicode(input) ((id(*)(id, SEL, id, unsigned short))objc_msgSend)(c("NSString"), s("stringWithFormat:"), str("%C"), (unsigned short)input)
|
||||||
#define cstr(input) (const char *)msg(input, s("UTF8String"))
|
#define cstr(input) (const char *)msg_reg(input, s("UTF8String"))
|
||||||
#define url(input) msg(c("NSURL"), s("fileURLWithPath:"), str(input))
|
#define url(input) msg_id(c("NSURL"), s("fileURLWithPath:"), str(input))
|
||||||
#define ALLOC(classname) msg(c(classname), s("alloc"))
|
#define ALLOC(classname) msg_reg(c(classname), s("alloc"))
|
||||||
#define ALLOC_INIT(classname) msg(msg(c(classname), s("alloc")), s("init"))
|
#define ALLOC_INIT(classname) msg_reg(msg_reg(c(classname), s("alloc")), s("init"))
|
||||||
|
|
||||||
|
#if defined (__aarch64__)
|
||||||
|
#define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend)(receiver, s("frame"))
|
||||||
|
#define GET_BOUNDS(receiver) ((CGRect(*)(id, SEL))objc_msgSend)(receiver, s("bounds"))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined (__x86_64__)
|
||||||
#define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("frame"))
|
#define GET_FRAME(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("frame"))
|
||||||
#define GET_BOUNDS(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("bounds"))
|
#define GET_BOUNDS(receiver) ((CGRect(*)(id, SEL))objc_msgSend_stret)(receiver, s("bounds"))
|
||||||
#define GET_BACKINGSCALEFACTOR(receiver) ((CGFloat(*)(id, SEL))msg)(receiver, s("backingScaleFactor"))
|
#endif
|
||||||
|
|
||||||
|
#define GET_BACKINGSCALEFACTOR(receiver) ((CGFloat(*)(id, SEL))objc_msgSend)(receiver, s("backingScaleFactor"))
|
||||||
|
|
||||||
#define ON_MAIN_THREAD(str) dispatch( ^{ str; } )
|
#define ON_MAIN_THREAD(str) dispatch( ^{ str; } )
|
||||||
#define MAIN_WINDOW_CALL(str) msg(app->mainWindow, s((str)))
|
#define MAIN_WINDOW_CALL(str) msg_reg(app->mainWindow, s((str)))
|
||||||
|
|
||||||
#define NSBackingStoreBuffered 2
|
#define NSBackingStoreBuffered 2
|
||||||
|
|
||||||
@@ -66,6 +85,10 @@
|
|||||||
#define NSControlStateValueOff 0
|
#define NSControlStateValueOff 0
|
||||||
#define NSControlStateValueOn 1
|
#define NSControlStateValueOn 1
|
||||||
|
|
||||||
|
#define NSApplicationActivationPolicyRegular 0
|
||||||
|
#define NSApplicationActivationPolicyAccessory 1
|
||||||
|
#define NSApplicationActivationPolicyProhibited 2
|
||||||
|
|
||||||
// Unbelievably, if the user swaps their button preference
|
// Unbelievably, if the user swaps their button preference
|
||||||
// then right buttons are reported as left buttons
|
// then right buttons are reported as left buttons
|
||||||
#define NSEventMaskLeftMouseDown 1 << 1
|
#define NSEventMaskLeftMouseDown 1 << 1
|
||||||
@@ -110,6 +133,10 @@ void SetTray(struct Application* app, const char *, const char *, const char *);
|
|||||||
//void SetContextMenus(struct Application* app, const char *);
|
//void SetContextMenus(struct Application* app, const char *);
|
||||||
void AddTrayMenu(struct Application* app, const char *);
|
void AddTrayMenu(struct Application* app, const char *);
|
||||||
|
|
||||||
|
void SetActivationPolicy(struct Application* app, int policy);
|
||||||
|
|
||||||
void* lookupStringConstant(id constantName);
|
void* lookupStringConstant(id constantName);
|
||||||
|
|
||||||
|
void HasURLHandlers(struct Application* app);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
78
v2/internal/ffenestri/ffenestri_windows.c
Normal file
78
v2/internal/ffenestri/ffenestri_windows.c
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
|
||||||
|
typedef struct {
|
||||||
|
} Application;
|
||||||
|
|
||||||
|
struct Application *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose) {
|
||||||
|
}
|
||||||
|
void SetMinWindowSize(struct Application* app, int minWidth, int minHeight) {
|
||||||
|
}
|
||||||
|
void SetMaxWindowSize(struct Application* app, int maxWidth, int maxHeight) {
|
||||||
|
}
|
||||||
|
void Run(struct Application* app, int argc, char **argv) {
|
||||||
|
}
|
||||||
|
void DestroyApplication(struct Application* app) {
|
||||||
|
}
|
||||||
|
void SetDebug(struct Application* app, int flag) {
|
||||||
|
}
|
||||||
|
void SetBindings(struct Application* app, const char *bindings) {
|
||||||
|
}
|
||||||
|
void ExecJS(struct Application* app, const char *script) {
|
||||||
|
}
|
||||||
|
void Hide(struct Application* app) {
|
||||||
|
}
|
||||||
|
void Show(struct Application* app) {
|
||||||
|
}
|
||||||
|
void Center(struct Application* app) {
|
||||||
|
}
|
||||||
|
void Maximise(struct Application* app) {
|
||||||
|
}
|
||||||
|
void Unmaximise(struct Application* app) {
|
||||||
|
}
|
||||||
|
void ToggleMaximise(struct Application* app) {
|
||||||
|
}
|
||||||
|
void Minimise(struct Application* app) {
|
||||||
|
}
|
||||||
|
void Unminimise(struct Application* app) {
|
||||||
|
}
|
||||||
|
void ToggleMinimise(struct Application* app) {
|
||||||
|
}
|
||||||
|
void SetColour(struct Application* app, int red, int green, int blue, int alpha) {
|
||||||
|
}
|
||||||
|
void SetSize(struct Application* app, int width, int height) {
|
||||||
|
}
|
||||||
|
void SetPosition(struct Application* app, int x, int y) {
|
||||||
|
}
|
||||||
|
void Quit(struct Application* app) {
|
||||||
|
}
|
||||||
|
void SetTitle(struct Application* app, const char *title) {
|
||||||
|
}
|
||||||
|
void Fullscreen(struct Application* app) {
|
||||||
|
}
|
||||||
|
void UnFullscreen(struct Application* app) {
|
||||||
|
}
|
||||||
|
void ToggleFullscreen(struct Application* app) {
|
||||||
|
}
|
||||||
|
void DisableFrame(struct Application* app) {
|
||||||
|
}
|
||||||
|
void OpenDialog(struct Application* app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) {
|
||||||
|
}
|
||||||
|
void SaveDialog(struct Application* app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) {
|
||||||
|
}
|
||||||
|
void MessageDialog(struct Application* app, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton) {
|
||||||
|
}
|
||||||
|
void DarkModeEnabled(struct Application* app, char *callbackID) {
|
||||||
|
}
|
||||||
|
void SetApplicationMenu(struct Application* app, const char *applicationMenuJSON) {
|
||||||
|
}
|
||||||
|
void AddTrayMenu(struct Application* app, const char *menuTrayJSON) {
|
||||||
|
}
|
||||||
|
void SetTrayMenu(struct Application* app, const char *menuTrayJSON) {
|
||||||
|
}
|
||||||
|
void DeleteTrayMenuByID(struct Application* app, const char *id) {
|
||||||
|
}
|
||||||
|
void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
|
||||||
|
}
|
||||||
|
void AddContextMenu(struct Application* app, char *contextMenuJSON) {
|
||||||
|
}
|
||||||
|
void UpdateContextMenu(struct Application* app, char *contextMenuJSON) {
|
||||||
|
}
|
||||||
14
v2/internal/ffenestri/ffenestri_windows.go
Normal file
14
v2/internal/ffenestri/ffenestri_windows.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package ffenestri
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
#include "ffenestri.h"
|
||||||
|
#include "ffenestri_windows.h"
|
||||||
|
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
func (a *Application) processPlatformSettings() error {
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
5
v2/internal/ffenestri/ffenestri_windows.h
Normal file
5
v2/internal/ffenestri/ffenestri_windows.h
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
#ifndef _FFENESTRI_WINDOWS_
|
||||||
|
#define _FFENESTRI_WINDOWS_
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -90,7 +90,7 @@ void DeleteMenu(Menu *menu) {
|
|||||||
|
|
||||||
// Free nsmenu if we have it
|
// Free nsmenu if we have it
|
||||||
if ( menu->menu != NULL ) {
|
if ( menu->menu != NULL ) {
|
||||||
msg(menu->menu, s("release"));
|
msg_reg(menu->menu, s("release"));
|
||||||
}
|
}
|
||||||
|
|
||||||
free(menu);
|
free(menu);
|
||||||
@@ -120,17 +120,17 @@ const char* createMenuClickedMessage(const char *menuItemID, const char *data, e
|
|||||||
|
|
||||||
// Callback for text menu items
|
// Callback for text menu items
|
||||||
void menuItemCallback(id self, SEL cmd, id sender) {
|
void menuItemCallback(id self, SEL cmd, id sender) {
|
||||||
MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg(msg(sender, s("representedObject")), s("pointerValue"));
|
MenuItemCallbackData *callbackData = (MenuItemCallbackData *)msg_reg(msg_reg(sender, s("representedObject")), s("pointerValue"));
|
||||||
const char *message;
|
const char *message;
|
||||||
|
|
||||||
// Update checkbox / radio item
|
// Update checkbox / radio item
|
||||||
if( callbackData->menuItemType == Checkbox) {
|
if( callbackData->menuItemType == Checkbox) {
|
||||||
// Toggle state
|
// Toggle state
|
||||||
bool state = msg(callbackData->menuItem, s("state"));
|
bool state = msg_reg(callbackData->menuItem, s("state"));
|
||||||
msg(callbackData->menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
|
msg_int(callbackData->menuItem, s("setState:"), (state? NSControlStateValueOff : NSControlStateValueOn));
|
||||||
} else if( callbackData->menuItemType == Radio ) {
|
} else if( callbackData->menuItemType == Radio ) {
|
||||||
// Check the menu items' current state
|
// Check the menu items' current state
|
||||||
bool selected = msg(callbackData->menuItem, s("state"));
|
bool selected = (bool)msg_reg(callbackData->menuItem, s("state"));
|
||||||
|
|
||||||
// If it's already selected, exit early
|
// If it's already selected, exit early
|
||||||
if (selected) return;
|
if (selected) return;
|
||||||
@@ -142,13 +142,13 @@ void menuItemCallback(id self, SEL cmd, id sender) {
|
|||||||
id thisMember = members[0];
|
id thisMember = members[0];
|
||||||
int count = 0;
|
int count = 0;
|
||||||
while(thisMember != NULL) {
|
while(thisMember != NULL) {
|
||||||
msg(thisMember, s("setState:"), NSControlStateValueOff);
|
msg_int(thisMember, s("setState:"), NSControlStateValueOff);
|
||||||
count = count + 1;
|
count = count + 1;
|
||||||
thisMember = members[count];
|
thisMember = members[count];
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the selected menu item
|
// check the selected menu item
|
||||||
msg(callbackData->menuItem, s("setState:"), NSControlStateValueOn);
|
msg_int(callbackData->menuItem, s("setState:"), NSControlStateValueOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *menuID = callbackData->menuID;
|
const char *menuID = callbackData->menuID;
|
||||||
@@ -345,61 +345,61 @@ id processAcceleratorKey(const char *key) {
|
|||||||
|
|
||||||
|
|
||||||
void addSeparator(id menu) {
|
void addSeparator(id menu) {
|
||||||
id item = msg(c("NSMenuItem"), s("separatorItem"));
|
id item = msg_reg(c("NSMenuItem"), s("separatorItem"));
|
||||||
msg(menu, s("addItem:"), item);
|
msg_id(menu, s("addItem:"), item);
|
||||||
}
|
}
|
||||||
|
|
||||||
id createMenuItemNoAutorelease( id title, const char *action, const char *key) {
|
id createMenuItemNoAutorelease( id title, const char *action, const char *key) {
|
||||||
id item = ALLOC("NSMenuItem");
|
id item = ALLOC("NSMenuItem");
|
||||||
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
|
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
id createMenuItem(id title, const char *action, const char *key) {
|
id createMenuItem(id title, const char *action, const char *key) {
|
||||||
id item = ALLOC("NSMenuItem");
|
id item = ALLOC("NSMenuItem");
|
||||||
msg(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
|
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), title, s(action), str(key));
|
||||||
msg(item, s("autorelease"));
|
msg_reg(item, s("autorelease"));
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled) {
|
id addMenuItem(id menu, const char *title, const char *action, const char *key, bool disabled) {
|
||||||
id item = createMenuItem(str(title), action, key);
|
id item = createMenuItem(str(title), action, key);
|
||||||
msg(item, s("setEnabled:"), !disabled);
|
msg_bool(item, s("setEnabled:"), !disabled);
|
||||||
msg(menu, s("addItem:"), item);
|
msg_id(menu, s("addItem:"), item);
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
id createMenu(id title) {
|
id createMenu(id title) {
|
||||||
id menu = ALLOC("NSMenu");
|
id menu = ALLOC("NSMenu");
|
||||||
msg(menu, s("initWithTitle:"), title);
|
msg_id(menu, s("initWithTitle:"), title);
|
||||||
msg(menu, s("setAutoenablesItems:"), NO);
|
msg_bool(menu, s("setAutoenablesItems:"), NO);
|
||||||
// msg(menu, s("autorelease"));
|
// msg(menu, s("autorelease"));
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
void createDefaultAppMenu(id parentMenu) {
|
void createDefaultAppMenu(id parentMenu) {
|
||||||
// App Menu
|
// App Menu
|
||||||
id appName = msg(msg(c("NSProcessInfo"), s("processInfo")), s("processName"));
|
id appName = msg_reg(msg_reg(c("NSProcessInfo"), s("processInfo")), s("processName"));
|
||||||
id appMenuItem = createMenuItemNoAutorelease(appName, NULL, "");
|
id appMenuItem = createMenuItemNoAutorelease(appName, NULL, "");
|
||||||
id appMenu = createMenu(appName);
|
id appMenu = createMenu(appName);
|
||||||
|
|
||||||
msg(appMenuItem, s("setSubmenu:"), appMenu);
|
msg_id(appMenuItem, s("setSubmenu:"), appMenu);
|
||||||
msg(parentMenu, s("addItem:"), appMenuItem);
|
msg_id(parentMenu, s("addItem:"), appMenuItem);
|
||||||
|
|
||||||
id title = msg(str("Hide "), s("stringByAppendingString:"), appName);
|
id title = msg_id(str("Hide "), s("stringByAppendingString:"), appName);
|
||||||
id item = createMenuItem(title, "hide:", "h");
|
id item = createMenuItem(title, "hide:", "h");
|
||||||
msg(appMenu, s("addItem:"), item);
|
msg_id(appMenu, s("addItem:"), item);
|
||||||
|
|
||||||
id hideOthers = addMenuItem(appMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
|
id hideOthers = addMenuItem(appMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
|
||||||
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
msg_int(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
||||||
|
|
||||||
addMenuItem(appMenu, "Show All", "unhideAllApplications:", "", FALSE);
|
addMenuItem(appMenu, "Show All", "unhideAllApplications:", "", FALSE);
|
||||||
|
|
||||||
addSeparator(appMenu);
|
addSeparator(appMenu);
|
||||||
|
|
||||||
title = msg(str("Quit "), s("stringByAppendingString:"), appName);
|
title = msg_id(str("Quit "), s("stringByAppendingString:"), appName);
|
||||||
item = createMenuItem(title, "terminate:", "q");
|
item = createMenuItem(title, "terminate:", "q");
|
||||||
msg(appMenu, s("addItem:"), item);
|
msg_id(appMenu, s("addItem:"), item);
|
||||||
}
|
}
|
||||||
|
|
||||||
void createDefaultEditMenu(id parentMenu) {
|
void createDefaultEditMenu(id parentMenu) {
|
||||||
@@ -407,8 +407,8 @@ void createDefaultEditMenu(id parentMenu) {
|
|||||||
id editMenuItem = createMenuItemNoAutorelease(str("Edit"), NULL, "");
|
id editMenuItem = createMenuItemNoAutorelease(str("Edit"), NULL, "");
|
||||||
id editMenu = createMenu(str("Edit"));
|
id editMenu = createMenu(str("Edit"));
|
||||||
|
|
||||||
msg(editMenuItem, s("setSubmenu:"), editMenu);
|
msg_id(editMenuItem, s("setSubmenu:"), editMenu);
|
||||||
msg(parentMenu, s("addItem:"), editMenuItem);
|
msg_id(parentMenu, s("addItem:"), editMenuItem);
|
||||||
|
|
||||||
addMenuItem(editMenu, "Undo", "undo:", "z", FALSE);
|
addMenuItem(editMenu, "Undo", "undo:", "z", FALSE);
|
||||||
addMenuItem(editMenu, "Redo", "redo:", "y", FALSE);
|
addMenuItem(editMenu, "Redo", "redo:", "y", FALSE);
|
||||||
@@ -436,7 +436,7 @@ void processMenuRole(Menu *menu, id parentMenu, JsonNode *item) {
|
|||||||
}
|
}
|
||||||
if ( STREQ(roleName, "hideothers")) {
|
if ( STREQ(roleName, "hideothers")) {
|
||||||
id hideOthers = addMenuItem(parentMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
|
id hideOthers = addMenuItem(parentMenu, "Hide Others", "hideOtherApplications:", "h", FALSE);
|
||||||
msg(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
msg_int(hideOthers, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagCommand));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ( STREQ(roleName, "unhide")) {
|
if ( STREQ(roleName, "unhide")) {
|
||||||
@@ -473,7 +473,7 @@ void processMenuRole(Menu *menu, id parentMenu, JsonNode *item) {
|
|||||||
}
|
}
|
||||||
if( STREQ(roleName, "pasteandmatchstyle")) {
|
if( STREQ(roleName, "pasteandmatchstyle")) {
|
||||||
id pasteandmatchstyle = addMenuItem(parentMenu, "Paste and Match Style", "pasteandmatchstyle:", "v", FALSE);
|
id pasteandmatchstyle = addMenuItem(parentMenu, "Paste and Match Style", "pasteandmatchstyle:", "v", FALSE);
|
||||||
msg(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
|
msg_int(pasteandmatchstyle, s("setKeyEquivalentModifierMask:"), (NSEventModifierFlagOption | NSEventModifierFlagShift | NSEventModifierFlagCommand));
|
||||||
}
|
}
|
||||||
if ( STREQ(roleName, "selectall")) {
|
if ( STREQ(roleName, "selectall")) {
|
||||||
addMenuItem(parentMenu, "Select All", "selectAll:", "a", FALSE);
|
addMenuItem(parentMenu, "Select All", "selectAll:", "a", FALSE);
|
||||||
@@ -508,6 +508,7 @@ unsigned long parseModifiers(const char **modifiers) {
|
|||||||
const char *thisModifier = modifiers[0];
|
const char *thisModifier = modifiers[0];
|
||||||
int count = 0;
|
int count = 0;
|
||||||
while( thisModifier != NULL ) {
|
while( thisModifier != NULL ) {
|
||||||
|
|
||||||
// Determine flags
|
// Determine flags
|
||||||
if( STREQ(thisModifier, "cmdorctrl") ) {
|
if( STREQ(thisModifier, "cmdorctrl") ) {
|
||||||
result |= NSEventModifierFlagCommand;
|
result |= NSEventModifierFlagCommand;
|
||||||
@@ -521,7 +522,7 @@ unsigned long parseModifiers(const char **modifiers) {
|
|||||||
if( STREQ(thisModifier, "super") ) {
|
if( STREQ(thisModifier, "super") ) {
|
||||||
result |= NSEventModifierFlagCommand;
|
result |= NSEventModifierFlagCommand;
|
||||||
}
|
}
|
||||||
if( STREQ(thisModifier, "control") ) {
|
if( STREQ(thisModifier, "ctrl") ) {
|
||||||
result |= NSEventModifierFlagControl;
|
result |= NSEventModifierFlagControl;
|
||||||
}
|
}
|
||||||
count++;
|
count++;
|
||||||
@@ -539,18 +540,18 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char
|
|||||||
// Create a MenuItemCallbackData
|
// Create a MenuItemCallbackData
|
||||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Radio);
|
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Radio);
|
||||||
|
|
||||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
id wrappedId = msg_id(c("NSValue"), s("valueWithPointer:"), (id)callback);
|
||||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
msg_id(item, s("setRepresentedObject:"), wrappedId);
|
||||||
|
|
||||||
id key = processAcceleratorKey(acceleratorkey);
|
id key = processAcceleratorKey(acceleratorkey);
|
||||||
|
|
||||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), key);
|
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), key);
|
||||||
|
|
||||||
msg(item, s("setEnabled:"), !disabled);
|
msg_bool(item, s("setEnabled:"), !disabled);
|
||||||
msg(item, s("autorelease"));
|
msg_reg(item, s("autorelease"));
|
||||||
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
msg_int(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
||||||
|
|
||||||
msg(parentmenu, s("addItem:"), item);
|
msg_id(parentmenu, s("addItem:"), item);
|
||||||
return item;
|
return item;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -565,74 +566,42 @@ id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const c
|
|||||||
// Create a MenuItemCallbackData
|
// Create a MenuItemCallbackData
|
||||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Checkbox);
|
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Checkbox);
|
||||||
|
|
||||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
id wrappedId = msg_id(c("NSValue"), s("valueWithPointer:"), (id)callback);
|
||||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
msg_id(item, s("setRepresentedObject:"), wrappedId);
|
||||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(key));
|
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(key));
|
||||||
msg(item, s("setEnabled:"), !disabled);
|
msg_bool(item, s("setEnabled:"), !disabled);
|
||||||
msg(item, s("autorelease"));
|
msg_reg(item, s("autorelease"));
|
||||||
msg(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
msg_int(item, s("setState:"), (checked ? NSControlStateValueOn : NSControlStateValueOff));
|
||||||
msg(parentmenu, s("addItem:"), item);
|
msg_id(parentmenu, s("addItem:"), item);
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage) {
|
id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA) {
|
||||||
id item = ALLOC("NSMenuItem");
|
|
||||||
|
|
||||||
// Create a MenuItemCallbackData
|
|
||||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
|
|
||||||
|
|
||||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
|
||||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
|
||||||
|
|
||||||
id key = processAcceleratorKey(acceleratorkey);
|
|
||||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
|
||||||
s("menuItemCallback:"), key);
|
|
||||||
|
|
||||||
if( tooltip != NULL ) {
|
|
||||||
msg(item, s("setToolTip:"), str(tooltip));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process image
|
|
||||||
if( image != NULL && strlen(image) > 0) {
|
|
||||||
id data = ALLOC("NSData");
|
|
||||||
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(image), 0);
|
|
||||||
id nsimage = ALLOC("NSImage");
|
|
||||||
msg(nsimage, s("initWithData:"), imageData);
|
|
||||||
if( templateImage ) {
|
|
||||||
msg(nsimage, s("template"), YES);
|
|
||||||
}
|
|
||||||
msg(item, s("setImage:"), nsimage);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process Menu Item attributes
|
// Process Menu Item attributes
|
||||||
id dictionary = ALLOC_INIT("NSMutableDictionary");
|
id dictionary = ALLOC_INIT("NSMutableDictionary");
|
||||||
|
|
||||||
// Process font
|
// Process font
|
||||||
id font;
|
|
||||||
CGFloat fontSizeFloat = (CGFloat)fontSize;
|
CGFloat fontSizeFloat = (CGFloat)fontSize;
|
||||||
|
|
||||||
// Check if valid
|
// Check if valid
|
||||||
id fontNameAsNSString = str(fontName);
|
id fontNameAsNSString = str(fontName);
|
||||||
id fontsOnSystem = msg(msg(c("NSFontManager"), s("sharedFontManager")), s("availableFonts"));
|
id font = ((id(*)(id, SEL, id, CGFloat))objc_msgSend)(c("NSFont"), s("fontWithName:size:"), fontNameAsNSString, fontSizeFloat);
|
||||||
bool valid = msg(fontsOnSystem, s("containsObject:"), fontNameAsNSString);
|
if( font == NULL ) {
|
||||||
if( valid ) {
|
bool supportsMonospacedDigitSystemFont = (bool) ((id(*)(id, SEL, SEL))objc_msgSend)(c("NSFont"), s("respondsToSelector:"), s("monospacedDigitSystemFontOfSize:weight:"));
|
||||||
font = msg(c("NSFont"), s("fontWithName:size:"), fontNameAsNSString, fontSizeFloat);
|
|
||||||
} else {
|
|
||||||
bool supportsMonospacedDigitSystemFont = (bool) msg(c("NSFont"), s("respondsToSelector:"), s("monospacedDigitSystemFontOfSize:weight:"));
|
|
||||||
if( supportsMonospacedDigitSystemFont ) {
|
if( supportsMonospacedDigitSystemFont ) {
|
||||||
font = msg(c("NSFont"), s("monospacedDigitSystemFontOfSize:weight:"), fontSizeFloat, NSFontWeightRegular);
|
font = ((id(*)(id, SEL, CGFloat, CGFloat))objc_msgSend)(c("NSFont"), s("monospacedDigitSystemFontOfSize:weight:"), fontSizeFloat, (CGFloat)NSFontWeightRegular);
|
||||||
} else {
|
} else {
|
||||||
font = msg(c("NSFont"), s("menuFontOfSize:"), fontSizeFloat);
|
font = ((id(*)(id, SEL, CGFloat))objc_msgSend)(c("NSFont"), s("menuFontOfSize:"), fontSizeFloat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add font to dictionary
|
// Add font to dictionary
|
||||||
msg(dictionary, s("setObject:forKey:"), font, lookupStringConstant(str("NSFontAttributeName")));
|
id fan = lookupStringConstant(str("NSFontAttributeName"));
|
||||||
|
msg_id_id(dictionary, s("setObject:forKey:"), font, fan);
|
||||||
// Add offset to dictionary
|
id offset = msg_float(c("NSNumber"), s("numberWithFloat:"), (float)0.0);
|
||||||
id offset = msg(c("NSNumber"), s("numberWithFloat:"), 0.0);
|
id offsetAttrName = lookupStringConstant(str("NSBaselineOffsetAttributeName"));
|
||||||
msg(dictionary, s("setObject:forKey:"), offset, lookupStringConstant(str("NSBaselineOffsetAttributeName")));
|
msg_id_id(dictionary, s("setObject:forKey:"), offset, offsetAttrName);
|
||||||
|
|
||||||
// RGBA
|
// RGBA
|
||||||
if( RGBA != NULL && strlen(RGBA) > 0) {
|
if( RGBA != NULL && strlen(RGBA) > 0) {
|
||||||
unsigned short r, g, b, a;
|
unsigned short r, g, b, a;
|
||||||
@@ -641,32 +610,77 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
|
|||||||
r = g = b = a = 255;
|
r = g = b = a = 255;
|
||||||
int count = sscanf(RGBA, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a);
|
int count = sscanf(RGBA, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a);
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
id colour = msg(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
|
id colour = ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend)(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
|
||||||
(float)r / 255.0,
|
(CGFloat)r / (CGFloat)255.0,
|
||||||
(float)g / 255.0,
|
(CGFloat)g / (CGFloat)255.0,
|
||||||
(float)b / 255.0,
|
(CGFloat)b / (CGFloat)255.0,
|
||||||
(float)a / 255.0);
|
(CGFloat)a / (CGFloat)255.0);
|
||||||
msg(dictionary, s("setObject:forKey:"), colour, lookupStringConstant(str("NSForegroundColorAttributeName")));
|
id NSForegroundColorAttributeName = lookupStringConstant(str("NSForegroundColorAttributeName"));
|
||||||
msg(colour, s("release"));
|
msg_id_id(dictionary, s("setObject:forKey:"), colour, NSForegroundColorAttributeName);
|
||||||
|
msg_reg(colour, s("autorelease"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
id attributedString = ALLOC("NSMutableAttributedString");
|
id attributedString = ALLOC("NSMutableAttributedString");
|
||||||
msg(attributedString, s("initWithString:attributes:"), str(title), dictionary);
|
msg_id_id(attributedString, s("initWithString:attributes:"), str(title), dictionary);
|
||||||
msg(dictionary, s("release"));
|
msg_reg(attributedString, s("autorelease"));
|
||||||
|
msg_reg(dictionary, s("release"));
|
||||||
|
return attributedString;
|
||||||
|
}
|
||||||
|
|
||||||
msg(item, s("setAttributedTitle:"), attributedString);
|
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate) {
|
||||||
msg(attributedString, s("autorelease"));
|
id item = ALLOC("NSMenuItem");
|
||||||
|
|
||||||
msg(item, s("setEnabled:"), !disabled);
|
// Create a MenuItemCallbackData
|
||||||
msg(item, s("autorelease"));
|
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
|
||||||
|
|
||||||
|
id wrappedId = msg_id(c("NSValue"), s("valueWithPointer:"), (id)callback);
|
||||||
|
msg_id(item, s("setRepresentedObject:"), wrappedId);
|
||||||
|
|
||||||
|
if( !alternate ) {
|
||||||
|
id key = processAcceleratorKey(acceleratorkey);
|
||||||
|
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
||||||
|
s("menuItemCallback:"), key);
|
||||||
|
} else {
|
||||||
|
((id(*)(id, SEL, id, SEL, id))objc_msgSend)(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
if( tooltip != NULL ) {
|
||||||
|
msg_id(item, s("setToolTip:"), str(tooltip));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process image
|
||||||
|
if( image != NULL && strlen(image) > 0) {
|
||||||
|
id data = ALLOC("NSData");
|
||||||
|
id imageData = ((id(*)(id, SEL, id, int))objc_msgSend)(data, s("initWithBase64EncodedString:options:"), str(image), 0);
|
||||||
|
id nsimage = ALLOC("NSImage");
|
||||||
|
msg_id(nsimage, s("initWithData:"), imageData);
|
||||||
|
if( templateImage ) {
|
||||||
|
msg_bool(nsimage, s("setTemplate:"), YES);
|
||||||
|
}
|
||||||
|
msg_id(item, s("setImage:"), nsimage);
|
||||||
|
}
|
||||||
|
|
||||||
|
id attributedString = createAttributedString(title, fontName, fontSize, RGBA);
|
||||||
|
msg_id(item, s("setAttributedTitle:"), attributedString);
|
||||||
|
|
||||||
|
//msg_id(item, s("setTitle:"), str(title));
|
||||||
|
|
||||||
|
msg_bool(item, s("setEnabled:"), !disabled);
|
||||||
|
msg_reg(item, s("autorelease"));
|
||||||
|
|
||||||
// Process modifiers
|
// Process modifiers
|
||||||
if( modifiers != NULL ) {
|
if( modifiers != NULL && !alternate) {
|
||||||
unsigned long modifierFlags = parseModifiers(modifiers);
|
unsigned long modifierFlags = parseModifiers(modifiers);
|
||||||
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
|
((id(*)(id, SEL, unsigned long))objc_msgSend)(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
|
||||||
}
|
}
|
||||||
msg(parentMenu, s("addItem:"), item);
|
|
||||||
|
// alternate
|
||||||
|
if( alternate ) {
|
||||||
|
msg_bool(item, s("setAlternate:"), true);
|
||||||
|
msg_int(item, s("setKeyEquivalentModifierMask:"), NSEventModifierFlagOption);
|
||||||
|
}
|
||||||
|
msg_id(parentMenu, s("addItem:"), item);
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@@ -700,8 +714,8 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
|||||||
id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, "");
|
id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, "");
|
||||||
id thisMenu = createMenu(str(name));
|
id thisMenu = createMenu(str(name));
|
||||||
|
|
||||||
msg(thisMenuItem, s("setSubmenu:"), thisMenu);
|
msg_id(thisMenuItem, s("setSubmenu:"), thisMenu);
|
||||||
msg(parentMenu, s("addItem:"), thisMenuItem);
|
msg_id(parentMenu, s("addItem:"), thisMenuItem);
|
||||||
|
|
||||||
JsonNode *submenuItems = json_find_member(submenu, "Items");
|
JsonNode *submenuItems = json_find_member(submenu, "Items");
|
||||||
// If we have no items, just return
|
// If we have no items, just return
|
||||||
@@ -726,6 +740,11 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
|||||||
label = "(empty)";
|
label = "(empty)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Is this an alternate menu item?
|
||||||
|
bool alternate = false;
|
||||||
|
getJSONBool(item, "MacAlternate", &alternate);
|
||||||
|
|
||||||
const char *menuid = getJSONString(item, "ID");
|
const char *menuid = getJSONString(item, "ID");
|
||||||
if ( menuid == NULL) {
|
if ( menuid == NULL) {
|
||||||
menuid = "";
|
menuid = "";
|
||||||
@@ -746,7 +765,7 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
|||||||
bool templateImage = false;
|
bool templateImage = false;
|
||||||
getJSONBool(item, "MacTemplateImage", &templateImage);
|
getJSONBool(item, "MacTemplateImage", &templateImage);
|
||||||
|
|
||||||
int fontSize = 12;
|
int fontSize = 0;
|
||||||
getJSONInt(item, "FontSize", &fontSize);
|
getJSONInt(item, "FontSize", &fontSize);
|
||||||
|
|
||||||
// If we have an accelerator
|
// If we have an accelerator
|
||||||
@@ -781,7 +800,7 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
|||||||
if( type != NULL ) {
|
if( type != NULL ) {
|
||||||
|
|
||||||
if( STREQ(type->string_, "Text")) {
|
if( STREQ(type->string_, "Text")) {
|
||||||
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage);
|
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage, alternate);
|
||||||
}
|
}
|
||||||
else if ( STREQ(type->string_, "Separator")) {
|
else if ( STREQ(type->string_, "Separator")) {
|
||||||
addSeparator(parentMenu);
|
addSeparator(parentMenu);
|
||||||
|
|||||||
@@ -105,10 +105,12 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char
|
|||||||
|
|
||||||
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key);
|
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key);
|
||||||
|
|
||||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage);
|
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate);
|
||||||
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
|
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
|
||||||
void processMenuData(Menu *menu, JsonNode *menuData);
|
void processMenuData(Menu *menu, JsonNode *menuData);
|
||||||
|
|
||||||
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup) ;
|
void processRadioGroupJSON(Menu *menu, JsonNode *radioGroup);
|
||||||
id GetMenu(Menu *menu);
|
id GetMenu(Menu *menu);
|
||||||
|
id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA);
|
||||||
|
|
||||||
#endif //ASSETS_C_MENU_DARWIN_H
|
#endif //ASSETS_C_MENU_DARWIN_H
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
#include "traymenu_darwin.h"
|
#include "traymenu_darwin.h"
|
||||||
#include "trayicons.h"
|
#include "trayicons.h"
|
||||||
|
|
||||||
|
extern Class trayMenuDelegateClass;
|
||||||
|
|
||||||
// A cache for all our tray menu icons
|
// A cache for all our tray menu icons
|
||||||
// Global because it's a singleton
|
// Global because it's a singleton
|
||||||
struct hashmap_s trayIconCache;
|
struct hashmap_s trayIconCache;
|
||||||
@@ -29,12 +31,23 @@ TrayMenu* NewTrayMenu(const char* menuJSON) {
|
|||||||
|
|
||||||
result->ID = mustJSONString(processedJSON, "ID");
|
result->ID = mustJSONString(processedJSON, "ID");
|
||||||
result->label = mustJSONString(processedJSON, "Label");
|
result->label = mustJSONString(processedJSON, "Label");
|
||||||
result->icon = mustJSONString(processedJSON, "Icon");
|
result->icon = mustJSONString(processedJSON, "Image");
|
||||||
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
|
result->fontName = getJSONString(processedJSON, "FontName");
|
||||||
|
result->RGBA = getJSONString(processedJSON, "RGBA");
|
||||||
|
getJSONBool(processedJSON, "MacTemplateImage", &result->templateImage);
|
||||||
|
result->fontSize = 0;
|
||||||
|
getJSONInt(processedJSON, "FontSize", &result->fontSize);
|
||||||
|
result->tooltip = NULL;
|
||||||
|
result->tooltip = getJSONString(processedJSON, "Tooltip");
|
||||||
|
result->disabled = false;
|
||||||
|
getJSONBool(processedJSON, "Disabled", &result->disabled);
|
||||||
|
|
||||||
// Create the menu
|
// Create the menu
|
||||||
|
JsonNode* processedMenu = mustJSONObject(processedJSON, "ProcessedMenu");
|
||||||
result->menu = NewMenu(processedMenu);
|
result->menu = NewMenu(processedMenu);
|
||||||
|
|
||||||
|
result->delegate = NULL;
|
||||||
|
|
||||||
// Init tray status bar item
|
// Init tray status bar item
|
||||||
result->statusbaritem = NULL;
|
result->statusbaritem = NULL;
|
||||||
|
|
||||||
@@ -50,15 +63,23 @@ void DumpTrayMenu(TrayMenu* trayMenu) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label) {
|
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled) {
|
||||||
|
|
||||||
// Exit early if NULL
|
// Exit early if NULL
|
||||||
if( trayMenu->label == NULL ) {
|
if( trayMenu->label == NULL ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Update button label
|
// Update button label
|
||||||
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
|
id statusBarButton = msg_reg(trayMenu->statusbaritem, s("button"));
|
||||||
msg(statusBarButton, s("setTitle:"), str(label));
|
id attributedString = createAttributedString(label, fontName, fontSize, RGBA);
|
||||||
|
|
||||||
|
if( tooltip != NULL ) {
|
||||||
|
msg_id(statusBarButton, s("setToolTip:"), str(tooltip));
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_bool(statusBarButton, s("setEnabled:"), !disabled);
|
||||||
|
|
||||||
|
msg_id(statusBarButton, s("setAttributedTitle:"), attributedString);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateTrayIcon(TrayMenu *trayMenu) {
|
void UpdateTrayIcon(TrayMenu *trayMenu) {
|
||||||
@@ -68,44 +89,64 @@ void UpdateTrayIcon(TrayMenu *trayMenu) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
|
id statusBarButton = msg_reg(trayMenu->statusbaritem, s("button"));
|
||||||
|
|
||||||
// Empty icon means remove it
|
// Empty icon means remove it
|
||||||
if( STREMPTY(trayMenu->icon) ) {
|
if( STREMPTY(trayMenu->icon) ) {
|
||||||
// Remove image
|
// Remove image
|
||||||
msg(statusBarButton, s("setImage:"), NULL);
|
msg_id(statusBarButton, s("setImage:"), NULL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
id trayImage = hashmap_get(&trayIconCache, trayMenu->icon, strlen(trayMenu->icon));
|
id trayImage = hashmap_get(&trayIconCache, trayMenu->icon, strlen(trayMenu->icon));
|
||||||
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
|
|
||||||
msg(statusBarButton, s("setImage:"), trayImage);
|
// If we don't have the image in the icon cache then assume it's base64 encoded image data
|
||||||
|
if (trayImage == NULL) {
|
||||||
|
id data = ALLOC("NSData");
|
||||||
|
id imageData = ((id(*)(id, SEL, id, int))objc_msgSend)(data, s("initWithBase64EncodedString:options:"), str(trayMenu->icon), 0);
|
||||||
|
trayImage = ALLOC("NSImage");
|
||||||
|
msg_id(trayImage, s("initWithData:"), imageData);
|
||||||
|
|
||||||
|
if( trayMenu->templateImage ) {
|
||||||
|
msg_bool(trayImage, s("setTemplate:"), YES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_int(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
|
||||||
|
msg_id(statusBarButton, s("setImage:"), trayImage);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void ShowTrayMenu(TrayMenu* trayMenu) {
|
void ShowTrayMenu(TrayMenu* trayMenu) {
|
||||||
|
|
||||||
// Create a status bar item if we don't have one
|
// Create a status bar item if we don't have one
|
||||||
if( trayMenu->statusbaritem == NULL ) {
|
if( trayMenu->statusbaritem == NULL ) {
|
||||||
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
|
id statusBar = msg_reg( c("NSStatusBar"), s("systemStatusBar") );
|
||||||
trayMenu->statusbaritem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
|
trayMenu->statusbaritem = ((id(*)(id, SEL, CGFloat))objc_msgSend)(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
|
||||||
msg(trayMenu->statusbaritem, s("retain"));
|
msg_reg(trayMenu->statusbaritem, s("retain"));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
|
id statusBarButton = msg_reg(trayMenu->statusbaritem, s("button"));
|
||||||
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
|
msg_uint(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
|
||||||
|
|
||||||
// Update the icon if needed
|
// Update the icon if needed
|
||||||
UpdateTrayIcon(trayMenu);
|
UpdateTrayIcon(trayMenu);
|
||||||
|
|
||||||
// Update the label if needed
|
// Update the label if needed
|
||||||
UpdateTrayLabel(trayMenu, trayMenu->label);
|
UpdateTrayLabel(trayMenu, trayMenu->label, trayMenu->fontName, trayMenu->fontSize, trayMenu->RGBA, trayMenu->tooltip, trayMenu->disabled);
|
||||||
|
|
||||||
// Update the menu
|
// Update the menu
|
||||||
id menu = GetMenu(trayMenu->menu);
|
id menu = GetMenu(trayMenu->menu);
|
||||||
msg(trayMenu->statusbaritem, s("setMenu:"), menu);
|
objc_setAssociatedObject(menu, "trayMenuID", str(trayMenu->ID), OBJC_ASSOCIATION_ASSIGN);
|
||||||
|
|
||||||
|
// Create delegate
|
||||||
|
id trayMenuDelegate = msg_reg((id)trayMenuDelegateClass, s("new"));
|
||||||
|
msg_id(menu, s("setDelegate:"), trayMenuDelegate);
|
||||||
|
objc_setAssociatedObject(trayMenuDelegate, "menu", menu, OBJC_ASSOCIATION_ASSIGN);
|
||||||
|
|
||||||
|
// Create menu delegate
|
||||||
|
trayMenu->delegate = trayMenuDelegate;
|
||||||
|
|
||||||
|
msg_id(trayMenu->statusbaritem, s("setMenu:"), menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateTrayMenuInPlace receives 2 menus. The current menu gets
|
// UpdateTrayMenuInPlace receives 2 menus. The current menu gets
|
||||||
@@ -147,12 +188,16 @@ void DeleteTrayMenu(TrayMenu* trayMenu) {
|
|||||||
|
|
||||||
// Free the status item
|
// Free the status item
|
||||||
if ( trayMenu->statusbaritem != NULL ) {
|
if ( trayMenu->statusbaritem != NULL ) {
|
||||||
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
|
id statusBar = msg_reg( c("NSStatusBar"), s("systemStatusBar") );
|
||||||
msg(statusBar, s("removeStatusItem:"), trayMenu->statusbaritem);
|
msg_id(statusBar, s("removeStatusItem:"), trayMenu->statusbaritem);
|
||||||
msg(trayMenu->statusbaritem, s("release"));
|
msg_reg(trayMenu->statusbaritem, s("release"));
|
||||||
trayMenu->statusbaritem = NULL;
|
trayMenu->statusbaritem = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( trayMenu->delegate != NULL ) {
|
||||||
|
msg_reg(trayMenu->delegate, s("release"));
|
||||||
|
}
|
||||||
|
|
||||||
// Free the tray menu memory
|
// Free the tray menu memory
|
||||||
MEMFREE(trayMenu);
|
MEMFREE(trayMenu);
|
||||||
}
|
}
|
||||||
@@ -182,9 +227,9 @@ void LoadTrayIcons() {
|
|||||||
int length = atoi((const char *)lengthAsString);
|
int length = atoi((const char *)lengthAsString);
|
||||||
|
|
||||||
// Create the icon and add to the hashmap
|
// Create the icon and add to the hashmap
|
||||||
id imageData = msg(c("NSData"), s("dataWithBytes:length:"), data, length);
|
id imageData = ((id(*)(id, SEL, id, int))objc_msgSend)(c("NSData"), s("dataWithBytes:length:"), data, length);
|
||||||
id trayImage = ALLOC("NSImage");
|
id trayImage = ALLOC("NSImage");
|
||||||
msg(trayImage, s("initWithData:"), imageData);
|
msg_id(trayImage, s("initWithData:"), imageData);
|
||||||
hashmap_put(&trayIconCache, (const char *)name, strlen((const char *)name), trayImage);
|
hashmap_put(&trayIconCache, (const char *)name, strlen((const char *)name), trayImage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,14 +13,24 @@ typedef struct {
|
|||||||
const char *label;
|
const char *label;
|
||||||
const char *icon;
|
const char *icon;
|
||||||
const char *ID;
|
const char *ID;
|
||||||
|
const char *tooltip;
|
||||||
|
|
||||||
|
bool templateImage;
|
||||||
|
const char *fontName;
|
||||||
|
int fontSize;
|
||||||
|
const char *RGBA;
|
||||||
|
|
||||||
|
bool disabled;
|
||||||
|
|
||||||
Menu* menu;
|
Menu* menu;
|
||||||
|
|
||||||
id statusbaritem;
|
id statusbaritem;
|
||||||
int trayIconPosition;
|
unsigned int trayIconPosition;
|
||||||
|
|
||||||
JsonNode* processedJSON;
|
JsonNode* processedJSON;
|
||||||
|
|
||||||
|
id delegate;
|
||||||
|
|
||||||
} TrayMenu;
|
} TrayMenu;
|
||||||
|
|
||||||
TrayMenu* NewTrayMenu(const char *trayJSON);
|
TrayMenu* NewTrayMenu(const char *trayJSON);
|
||||||
@@ -28,7 +38,7 @@ void DumpTrayMenu(TrayMenu* trayMenu);
|
|||||||
void ShowTrayMenu(TrayMenu* trayMenu);
|
void ShowTrayMenu(TrayMenu* trayMenu);
|
||||||
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);
|
void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu);
|
||||||
void UpdateTrayIcon(TrayMenu *trayMenu);
|
void UpdateTrayIcon(TrayMenu *trayMenu);
|
||||||
void UpdateTrayLabel(TrayMenu *trayMenu, const char*);
|
void UpdateTrayLabel(TrayMenu *trayMenu, const char *label, const char *fontName, int fontSize, const char *RGBA, const char *tooltip, bool disabled);
|
||||||
|
|
||||||
void LoadTrayIcons();
|
void LoadTrayIcons();
|
||||||
void UnloadTrayIcons();
|
void UnloadTrayIcons();
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ TrayMenuStore* NewTrayMenuStore() {
|
|||||||
ABORT("[NewTrayMenuStore] Not enough memory to allocate trayMenuMap!");
|
ABORT("[NewTrayMenuStore] Not enough memory to allocate trayMenuMap!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pthread_mutex_init(&result->lock, NULL) != 0) {
|
||||||
|
printf("\n mutex init has failed\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,15 +30,19 @@ int dumpTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DumpTrayMenuStore(TrayMenuStore* store) {
|
void DumpTrayMenuStore(TrayMenuStore* store) {
|
||||||
|
pthread_mutex_lock(&store->lock);
|
||||||
hashmap_iterate_pairs(&store->trayMenuMap, dumpTrayMenu, NULL);
|
hashmap_iterate_pairs(&store->trayMenuMap, dumpTrayMenu, NULL);
|
||||||
|
pthread_mutex_unlock(&store->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON) {
|
void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON) {
|
||||||
|
|
||||||
TrayMenu* newMenu = NewTrayMenu(menuJSON);
|
TrayMenu* newMenu = NewTrayMenu(menuJSON);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&store->lock);
|
||||||
//TODO: check if there is already an entry for this menu
|
//TODO: check if there is already an entry for this menu
|
||||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||||
|
pthread_mutex_unlock(&store->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
||||||
@@ -43,12 +52,13 @@ int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ShowTrayMenusInStore(TrayMenuStore* store) {
|
void ShowTrayMenusInStore(TrayMenuStore* store) {
|
||||||
|
pthread_mutex_lock(&store->lock);
|
||||||
if( hashmap_num_entries(&store->trayMenuMap) > 0 ) {
|
if( hashmap_num_entries(&store->trayMenuMap) > 0 ) {
|
||||||
hashmap_iterate_pairs(&store->trayMenuMap, showTrayMenu, NULL);
|
hashmap_iterate_pairs(&store->trayMenuMap, showTrayMenu, NULL);
|
||||||
}
|
}
|
||||||
|
pthread_mutex_unlock(&store->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int freeTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
int freeTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
||||||
DeleteTrayMenu(e->data);
|
DeleteTrayMenu(e->data);
|
||||||
return -1;
|
return -1;
|
||||||
@@ -65,22 +75,39 @@ void DeleteTrayMenuStore(TrayMenuStore *store) {
|
|||||||
|
|
||||||
// Destroy tray menu map
|
// Destroy tray menu map
|
||||||
hashmap_destroy(&store->trayMenuMap);
|
hashmap_destroy(&store->trayMenuMap);
|
||||||
|
|
||||||
|
pthread_mutex_destroy(&store->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
||||||
// Get the current menu
|
// Get the current menu
|
||||||
return hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
|
pthread_mutex_lock(&store->lock);
|
||||||
|
TrayMenu* result = hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
|
||||||
|
pthread_mutex_unlock(&store->lock);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
TrayMenu* MustGetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
TrayMenu* MustGetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
||||||
// Get the current menu
|
// Get the current menu
|
||||||
|
pthread_mutex_lock(&store->lock);
|
||||||
TrayMenu* result = hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
|
TrayMenu* result = hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
|
||||||
|
pthread_mutex_unlock(&store->lock);
|
||||||
|
|
||||||
if (result == NULL ) {
|
if (result == NULL ) {
|
||||||
ABORT("Unable to find TrayMenu with ID '%s' in the TrayMenuStore!", menuID);
|
ABORT("Unable to find TrayMenu with ID '%s' in the TrayMenuStore!", menuID);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DeleteTrayMenuInStore(TrayMenuStore* store, const char* ID) {
|
||||||
|
|
||||||
|
TrayMenu *menu = MustGetTrayMenuFromStore(store, ID);
|
||||||
|
pthread_mutex_lock(&store->lock);
|
||||||
|
hashmap_remove(&store->trayMenuMap, ID, strlen(ID));
|
||||||
|
pthread_mutex_unlock(&store->lock);
|
||||||
|
DeleteTrayMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
|
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
|
||||||
// Parse the JSON
|
// Parse the JSON
|
||||||
JsonNode *parsedUpdate = mustParseJSON(JSON);
|
JsonNode *parsedUpdate = mustParseJSON(JSON);
|
||||||
@@ -91,7 +118,17 @@ void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
|
|||||||
|
|
||||||
// Check we have this menu
|
// Check we have this menu
|
||||||
TrayMenu *menu = MustGetTrayMenuFromStore(store, ID);
|
TrayMenu *menu = MustGetTrayMenuFromStore(store, ID);
|
||||||
UpdateTrayLabel(menu, Label);
|
|
||||||
|
const char *fontName = getJSONString(parsedUpdate, "FontName");
|
||||||
|
const char *RGBA = getJSONString(parsedUpdate, "RGBA");
|
||||||
|
int fontSize = 0;
|
||||||
|
getJSONInt(parsedUpdate, "FontSize", &fontSize);
|
||||||
|
const char *tooltip = getJSONString(parsedUpdate, "Tooltip");
|
||||||
|
bool disabled = false;
|
||||||
|
getJSONBool(parsedUpdate, "Disabled", &disabled);
|
||||||
|
|
||||||
|
UpdateTrayLabel(menu, Label, fontName, fontSize, RGBA, tooltip, disabled);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +142,9 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
|||||||
// If we don't have a menu, we create one
|
// If we don't have a menu, we create one
|
||||||
if ( currentMenu == NULL ) {
|
if ( currentMenu == NULL ) {
|
||||||
// Store the new menu
|
// Store the new menu
|
||||||
|
pthread_mutex_lock(&store->lock);
|
||||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||||
|
pthread_mutex_unlock(&store->lock);
|
||||||
|
|
||||||
// Show it
|
// Show it
|
||||||
ShowTrayMenu(newMenu);
|
ShowTrayMenu(newMenu);
|
||||||
@@ -116,7 +155,9 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
|||||||
// Save the status bar reference
|
// Save the status bar reference
|
||||||
newMenu->statusbaritem = currentMenu->statusbaritem;
|
newMenu->statusbaritem = currentMenu->statusbaritem;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&store->lock);
|
||||||
hashmap_remove(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID));
|
hashmap_remove(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID));
|
||||||
|
pthread_mutex_unlock(&store->lock);
|
||||||
|
|
||||||
// Delete the current menu
|
// Delete the current menu
|
||||||
DeleteMenu(currentMenu->menu);
|
DeleteMenu(currentMenu->menu);
|
||||||
@@ -125,9 +166,10 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
|||||||
// Free the tray menu memory
|
// Free the tray menu memory
|
||||||
MEMFREE(currentMenu);
|
MEMFREE(currentMenu);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&store->lock);
|
||||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||||
|
pthread_mutex_unlock(&store->lock);
|
||||||
|
|
||||||
// Show the updated menu
|
// Show the updated menu
|
||||||
ShowTrayMenu(newMenu);
|
ShowTrayMenu(newMenu);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
#ifndef TRAYMENUSTORE_DARWIN_H
|
#ifndef TRAYMENUSTORE_DARWIN_H
|
||||||
#define TRAYMENUSTORE_DARWIN_H
|
#define TRAYMENUSTORE_DARWIN_H
|
||||||
|
|
||||||
|
#include "traymenu_darwin.h"
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
||||||
int dummy;
|
int dummy;
|
||||||
@@ -13,6 +17,8 @@ typedef struct {
|
|||||||
// It maps tray IDs to TrayMenu*
|
// It maps tray IDs to TrayMenu*
|
||||||
struct hashmap_s trayMenuMap;
|
struct hashmap_s trayMenuMap;
|
||||||
|
|
||||||
|
pthread_mutex_t lock;
|
||||||
|
|
||||||
} TrayMenuStore;
|
} TrayMenuStore;
|
||||||
|
|
||||||
TrayMenuStore* NewTrayMenuStore();
|
TrayMenuStore* NewTrayMenuStore();
|
||||||
@@ -22,6 +28,9 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON);
|
|||||||
void ShowTrayMenusInStore(TrayMenuStore* store);
|
void ShowTrayMenusInStore(TrayMenuStore* store);
|
||||||
void DeleteTrayMenuStore(TrayMenuStore* store);
|
void DeleteTrayMenuStore(TrayMenuStore* store);
|
||||||
|
|
||||||
|
TrayMenu* GetTrayMenuByID(TrayMenuStore* store, const char* menuID);
|
||||||
|
|
||||||
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON);
|
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON);
|
||||||
|
void DeleteTrayMenuInStore(TrayMenuStore* store, const char* id);
|
||||||
|
|
||||||
#endif //TRAYMENUSTORE_DARWIN_H
|
#endif //TRAYMENUSTORE_DARWIN_H
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func Mkdir(dirname string) error {
|
|||||||
// Returns error on failure
|
// Returns error on failure
|
||||||
func MkDirs(fullPath string, mode ...os.FileMode) error {
|
func MkDirs(fullPath string, mode ...os.FileMode) error {
|
||||||
var perms os.FileMode
|
var perms os.FileMode
|
||||||
perms = 0700
|
perms = 0755
|
||||||
if len(mode) == 1 {
|
if len(mode) == 1 {
|
||||||
perms = mode[0]
|
perms = mode[0]
|
||||||
}
|
}
|
||||||
@@ -243,7 +243,7 @@ func CopyDir(src string, dst string) (err error) {
|
|||||||
return fmt.Errorf("destination already exists")
|
return fmt.Errorf("destination already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.MkdirAll(dst, si.Mode())
|
err = MkDirs(dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,6 +112,9 @@ func (a *AssetBundle) processHTML(htmldata string) error {
|
|||||||
if attr.Key == "as" && attr.Val == "script" {
|
if attr.Key == "as" && attr.Val == "script" {
|
||||||
asset.Type = AssetTypes.JS
|
asset.Type = AssetTypes.JS
|
||||||
}
|
}
|
||||||
|
if attr.Key == "rel" && attr.Val == "modulepreload" {
|
||||||
|
asset.Type = AssetTypes.JS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we don't include duplicates
|
// Ensure we don't include duplicates
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ type ProcessedMenuItem struct {
|
|||||||
// Image - base64 image data
|
// Image - base64 image data
|
||||||
Image string `json:",omitempty"`
|
Image string `json:",omitempty"`
|
||||||
MacTemplateImage bool `json:", omitempty"`
|
MacTemplateImage bool `json:", omitempty"`
|
||||||
|
MacAlternate bool `json:", omitempty"`
|
||||||
|
|
||||||
// Tooltip
|
// Tooltip
|
||||||
Tooltip string `json:",omitempty"`
|
Tooltip string `json:",omitempty"`
|
||||||
@@ -60,6 +61,7 @@ func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *Pr
|
|||||||
FontName: menuItem.FontName,
|
FontName: menuItem.FontName,
|
||||||
Image: menuItem.Image,
|
Image: menuItem.Image,
|
||||||
MacTemplateImage: menuItem.MacTemplateImage,
|
MacTemplateImage: menuItem.MacTemplateImage,
|
||||||
|
MacAlternate: menuItem.MacAlternate,
|
||||||
Tooltip: menuItem.Tooltip,
|
Tooltip: menuItem.Tooltip,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,29 +3,39 @@ package menumanager
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var trayMenuID int
|
var trayMenuID int
|
||||||
var trayMenuIDMutex sync.Mutex
|
var trayMenuIDMutex sync.Mutex
|
||||||
|
|
||||||
func generateTrayID() string {
|
func generateTrayID() string {
|
||||||
|
var idStr string
|
||||||
trayMenuIDMutex.Lock()
|
trayMenuIDMutex.Lock()
|
||||||
result := fmt.Sprintf("%d", trayMenuID)
|
idStr = strconv.Itoa(trayMenuID)
|
||||||
trayMenuID++
|
trayMenuID++
|
||||||
trayMenuIDMutex.Unlock()
|
trayMenuIDMutex.Unlock()
|
||||||
return result
|
return idStr
|
||||||
}
|
}
|
||||||
|
|
||||||
type TrayMenu struct {
|
type TrayMenu struct {
|
||||||
ID string
|
ID string
|
||||||
Label string
|
Label string
|
||||||
Icon string
|
FontSize int
|
||||||
menuItemMap *MenuItemMap
|
FontName string
|
||||||
menu *menu.Menu
|
Disabled bool
|
||||||
ProcessedMenu *WailsMenu
|
Tooltip string `json:",omitempty"`
|
||||||
|
Image string
|
||||||
|
MacTemplateImage bool
|
||||||
|
RGBA string
|
||||||
|
menuItemMap *MenuItemMap
|
||||||
|
menu *menu.Menu
|
||||||
|
ProcessedMenu *WailsMenu
|
||||||
|
trayMenu *menu.TrayMenu
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TrayMenu) AsJSON() (string, error) {
|
func (t *TrayMenu) AsJSON() (string, error) {
|
||||||
@@ -39,10 +49,17 @@ func (t *TrayMenu) AsJSON() (string, error) {
|
|||||||
func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||||
|
|
||||||
result := &TrayMenu{
|
result := &TrayMenu{
|
||||||
Label: trayMenu.Label,
|
Label: trayMenu.Label,
|
||||||
Icon: trayMenu.Icon,
|
FontName: trayMenu.FontName,
|
||||||
menu: trayMenu.Menu,
|
FontSize: trayMenu.FontSize,
|
||||||
menuItemMap: NewMenuItemMap(),
|
Disabled: trayMenu.Disabled,
|
||||||
|
Tooltip: trayMenu.Tooltip,
|
||||||
|
Image: trayMenu.Image,
|
||||||
|
MacTemplateImage: trayMenu.MacTemplateImage,
|
||||||
|
menu: trayMenu.Menu,
|
||||||
|
RGBA: trayMenu.RGBA,
|
||||||
|
menuItemMap: NewMenuItemMap(),
|
||||||
|
trayMenu: trayMenu,
|
||||||
}
|
}
|
||||||
|
|
||||||
result.menuItemMap.AddMenu(trayMenu.Menu)
|
result.menuItemMap.AddMenu(trayMenu.Menu)
|
||||||
@@ -51,6 +68,28 @@ func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) OnTrayMenuOpen(id string) {
|
||||||
|
trayMenu, ok := m.trayMenus[id]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if trayMenu.trayMenu.OnOpen == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go trayMenu.trayMenu.OnOpen()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) OnTrayMenuClose(id string) {
|
||||||
|
trayMenu, ok := m.trayMenus[id]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if trayMenu.trayMenu.OnClose == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go trayMenu.trayMenu.OnClose()
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||||
newTrayMenu := NewTrayMenu(trayMenu)
|
newTrayMenu := NewTrayMenu(trayMenu)
|
||||||
|
|
||||||
@@ -65,6 +104,14 @@ func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
|||||||
return newTrayMenu.AsJSON()
|
return newTrayMenu.AsJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) GetTrayID(trayMenu *menu.TrayMenu) (string, error) {
|
||||||
|
trayID, exists := m.trayMenuPointers[trayMenu]
|
||||||
|
if !exists {
|
||||||
|
return "", fmt.Errorf("Unable to find menu ID for tray menu!")
|
||||||
|
}
|
||||||
|
return trayID, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetTrayMenu updates or creates a menu
|
// SetTrayMenu updates or creates a menu
|
||||||
func (m *Manager) SetTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
func (m *Manager) SetTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||||
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
|
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
|
||||||
@@ -102,13 +149,27 @@ func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LabelUpdate struct {
|
type LabelUpdate struct {
|
||||||
ID string
|
ID string
|
||||||
Label string
|
Label string
|
||||||
|
FontName string
|
||||||
|
FontSize int
|
||||||
|
RGBA string
|
||||||
|
Disabled bool
|
||||||
|
Tooltip string
|
||||||
|
Image string
|
||||||
|
MacTemplateImage bool
|
||||||
}
|
}
|
||||||
|
|
||||||
update := &LabelUpdate{
|
update := &LabelUpdate{
|
||||||
ID: trayID,
|
ID: trayID,
|
||||||
Label: trayMenu.Label,
|
Label: trayMenu.Label,
|
||||||
|
FontName: trayMenu.FontName,
|
||||||
|
FontSize: trayMenu.FontSize,
|
||||||
|
Disabled: trayMenu.Disabled,
|
||||||
|
Tooltip: trayMenu.Tooltip,
|
||||||
|
Image: trayMenu.Image,
|
||||||
|
MacTemplateImage: trayMenu.MacTemplateImage,
|
||||||
|
RGBA: trayMenu.RGBA,
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(update)
|
data, err := json.Marshal(update)
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ type Client interface {
|
|||||||
SetTrayMenu(trayMenuJSON string)
|
SetTrayMenu(trayMenuJSON string)
|
||||||
UpdateTrayMenuLabel(JSON string)
|
UpdateTrayMenuLabel(JSON string)
|
||||||
UpdateContextMenu(contextMenuJSON string)
|
UpdateContextMenu(contextMenuJSON string)
|
||||||
|
DeleteTrayMenuByID(id string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DispatchClient is what the frontends use to interface with the
|
// DispatchClient is what the frontends use to interface with the
|
||||||
|
|||||||
@@ -32,6 +32,14 @@ func menuMessageParser(message string) (*parsedMessage, error) {
|
|||||||
callbackid := message[2:]
|
callbackid := message[2:]
|
||||||
topic = "menu:clicked"
|
topic = "menu:clicked"
|
||||||
data = callbackid
|
data = callbackid
|
||||||
|
case 'o':
|
||||||
|
callbackid := message[2:]
|
||||||
|
topic = "menu:ontrayopen"
|
||||||
|
data = callbackid
|
||||||
|
case 'c':
|
||||||
|
callbackid := message[2:]
|
||||||
|
topic = "menu:ontrayclose"
|
||||||
|
data = callbackid
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid menu message: %s", message)
|
return nil, fmt.Errorf("invalid menu message: %s", message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,14 @@ var messageParsers = map[byte]func(string) (*parsedMessage, error){
|
|||||||
'M': menuMessageParser,
|
'M': menuMessageParser,
|
||||||
'T': trayMessageParser,
|
'T': trayMessageParser,
|
||||||
'X': contextMenusMessageParser,
|
'X': contextMenusMessageParser,
|
||||||
|
'U': urlMessageParser,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse will attempt to parse the given message
|
// Parse will attempt to parse the given message
|
||||||
func Parse(message string) (*parsedMessage, error) {
|
func Parse(message string) (*parsedMessage, error) {
|
||||||
|
|
||||||
if len(message) == 0 {
|
if len(message) == 0 {
|
||||||
return nil, fmt.Errorf("MessageParser received blank message");
|
return nil, fmt.Errorf("MessageParser received blank message")
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMethod := messageParsers[message[0]]
|
parseMethod := messageParsers[message[0]]
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ func systemMessageParser(message string) (*parsedMessage, error) {
|
|||||||
// This is our startup hook - the frontend is now ready
|
// This is our startup hook - the frontend is now ready
|
||||||
case 'S':
|
case 'S':
|
||||||
topic := "hooks:startup"
|
topic := "hooks:startup"
|
||||||
responseMessage = &parsedMessage{Topic: topic, Data: nil}
|
startupURL := message[1:]
|
||||||
|
responseMessage = &parsedMessage{Topic: topic, Data: startupURL}
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Invalid message to systemMessageParser()")
|
return nil, fmt.Errorf("Invalid message to systemMessageParser()")
|
||||||
}
|
}
|
||||||
|
|||||||
20
v2/internal/messagedispatcher/message/url.go
Normal file
20
v2/internal/messagedispatcher/message/url.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package message
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// urlMessageParser does what it says on the tin!
|
||||||
|
func urlMessageParser(message string) (*parsedMessage, error) {
|
||||||
|
|
||||||
|
// Sanity check: URL messages must be at least 2 bytes
|
||||||
|
if len(message) < 2 {
|
||||||
|
return nil, fmt.Errorf("log message was an invalid length")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch on the log type
|
||||||
|
switch message[1] {
|
||||||
|
case 'C':
|
||||||
|
return &parsedMessage{Topic: "url:handler", Data: message[2:]}, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("url message type '%c' invalid", message[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -527,6 +527,17 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
|
|||||||
for _, client := range d.clients {
|
for _, client := range d.clients {
|
||||||
client.frontend.UpdateTrayMenuLabel(updatedTrayMenuLabel)
|
client.frontend.UpdateTrayMenuLabel(updatedTrayMenuLabel)
|
||||||
}
|
}
|
||||||
|
case "deletetraymenu":
|
||||||
|
traymenuid, ok := result.Data().(string)
|
||||||
|
if !ok {
|
||||||
|
d.logger.Error("Invalid data for 'menufrontend:updatetraymenulabel' : %#v",
|
||||||
|
result.Data())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, client := range d.clients {
|
||||||
|
client.frontend.DeleteTrayMenuByID(traymenuid)
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
d.logger.Error("Unknown menufrontend command: %s", command)
|
d.logger.Error("Unknown menufrontend command: %s", command)
|
||||||
|
|||||||
@@ -620,7 +620,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Menubar **/
|
/** Menubar **/
|
||||||
const menuVisible = writable(true);
|
const menuVisible = writable(false);
|
||||||
|
|
||||||
/** Trays **/
|
/** Trays **/
|
||||||
|
|
||||||
@@ -649,6 +649,18 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deleteTrayMenu(id) {
|
||||||
|
trays.update((current) => {
|
||||||
|
// Remove existing if it exists, else add
|
||||||
|
const index = current.findIndex(item => item.ID === id);
|
||||||
|
if ( index === -1 ) {
|
||||||
|
return log("ERROR: Attempted to delete tray index ")
|
||||||
|
}
|
||||||
|
current.splice(index, 1);
|
||||||
|
return current;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let selectedMenu = writable(null);
|
let selectedMenu = writable(null);
|
||||||
|
|
||||||
function fade(node, { delay = 0, duration = 400, easing = identity } = {}) {
|
function fade(node, { delay = 0, duration = 400, easing = identity } = {}) {
|
||||||
@@ -1220,11 +1232,11 @@
|
|||||||
|
|
||||||
function get_each_context$1(ctx, list, i) {
|
function get_each_context$1(ctx, list, i) {
|
||||||
const child_ctx = ctx.slice();
|
const child_ctx = ctx.slice();
|
||||||
child_ctx[8] = list[i];
|
child_ctx[9] = list[i];
|
||||||
return child_ctx;
|
return child_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// (29:0) {#if $menuVisible }
|
// (38:0) {#if $menuVisible }
|
||||||
function create_if_block$3(ctx) {
|
function create_if_block$3(ctx) {
|
||||||
let div;
|
let div;
|
||||||
let span1;
|
let span1;
|
||||||
@@ -1336,11 +1348,11 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// (32:4) {#each $trays as tray}
|
// (41:4) {#each $trays as tray}
|
||||||
function create_each_block$1(ctx) {
|
function create_each_block$1(ctx) {
|
||||||
let traymenu;
|
let traymenu;
|
||||||
let current;
|
let current;
|
||||||
traymenu = new TrayMenu({ props: { tray: /*tray*/ ctx[8] } });
|
traymenu = new TrayMenu({ props: { tray: /*tray*/ ctx[9] } });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
c() {
|
c() {
|
||||||
@@ -1352,7 +1364,7 @@
|
|||||||
},
|
},
|
||||||
p(ctx, dirty) {
|
p(ctx, dirty) {
|
||||||
const traymenu_changes = {};
|
const traymenu_changes = {};
|
||||||
if (dirty & /*$trays*/ 4) traymenu_changes.tray = /*tray*/ ctx[8];
|
if (dirty & /*$trays*/ 4) traymenu_changes.tray = /*tray*/ ctx[9];
|
||||||
traymenu.$set(traymenu_changes);
|
traymenu.$set(traymenu_changes);
|
||||||
},
|
},
|
||||||
i(local) {
|
i(local) {
|
||||||
@@ -1373,6 +1385,8 @@
|
|||||||
function create_fragment$3(ctx) {
|
function create_fragment$3(ctx) {
|
||||||
let if_block_anchor;
|
let if_block_anchor;
|
||||||
let current;
|
let current;
|
||||||
|
let mounted;
|
||||||
|
let dispose;
|
||||||
let if_block = /*$menuVisible*/ ctx[1] && create_if_block$3(ctx);
|
let if_block = /*$menuVisible*/ ctx[1] && create_if_block$3(ctx);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -1384,6 +1398,11 @@
|
|||||||
if (if_block) if_block.m(target, anchor);
|
if (if_block) if_block.m(target, anchor);
|
||||||
insert(target, if_block_anchor, anchor);
|
insert(target, if_block_anchor, anchor);
|
||||||
current = true;
|
current = true;
|
||||||
|
|
||||||
|
if (!mounted) {
|
||||||
|
dispose = listen(window, "keydown", /*handleKeydown*/ ctx[3]);
|
||||||
|
mounted = true;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
p(ctx, [dirty]) {
|
p(ctx, [dirty]) {
|
||||||
if (/*$menuVisible*/ ctx[1]) {
|
if (/*$menuVisible*/ ctx[1]) {
|
||||||
@@ -1421,6 +1440,8 @@
|
|||||||
d(detaching) {
|
d(detaching) {
|
||||||
if (if_block) if_block.d(detaching);
|
if (if_block) if_block.d(detaching);
|
||||||
if (detaching) detach(if_block_anchor);
|
if (detaching) detach(if_block_anchor);
|
||||||
|
mounted = false;
|
||||||
|
dispose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1440,7 +1461,7 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
const interval = setInterval(
|
const interval = setInterval(
|
||||||
() => {
|
() => {
|
||||||
$$invalidate(3, time = new Date());
|
$$invalidate(4, time = new Date());
|
||||||
},
|
},
|
||||||
1000
|
1000
|
||||||
);
|
);
|
||||||
@@ -1450,33 +1471,52 @@
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function handleKeydown(e) {
|
||||||
|
// Backtick toggle
|
||||||
|
if (e.keyCode == 192) {
|
||||||
|
menuVisible.update(current => {
|
||||||
|
return !current;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$$self.$$.update = () => {
|
$$self.$$.update = () => {
|
||||||
if ($$self.$$.dirty & /*time*/ 8) {
|
if ($$self.$$.dirty & /*time*/ 16) {
|
||||||
$$invalidate(4, day = time.toLocaleString("default", { weekday: "short" }));
|
$$invalidate(5, day = time.toLocaleString("default", { weekday: "short" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($$self.$$.dirty & /*time*/ 8) {
|
if ($$self.$$.dirty & /*time*/ 16) {
|
||||||
$$invalidate(5, dom = time.getDate());
|
$$invalidate(6, dom = time.getDate());
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($$self.$$.dirty & /*time*/ 8) {
|
if ($$self.$$.dirty & /*time*/ 16) {
|
||||||
$$invalidate(6, mon = time.toLocaleString("default", { month: "short" }));
|
$$invalidate(7, mon = time.toLocaleString("default", { month: "short" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($$self.$$.dirty & /*time*/ 8) {
|
if ($$self.$$.dirty & /*time*/ 16) {
|
||||||
$$invalidate(7, currentTime = time.toLocaleString("en-US", {
|
$$invalidate(8, currentTime = time.toLocaleString("en-US", {
|
||||||
hour: "numeric",
|
hour: "numeric",
|
||||||
minute: "numeric",
|
minute: "numeric",
|
||||||
hour12: true
|
hour12: true
|
||||||
}).toLowerCase());
|
}).toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($$self.$$.dirty & /*day, dom, mon, currentTime*/ 240) {
|
if ($$self.$$.dirty & /*day, dom, mon, currentTime*/ 480) {
|
||||||
$$invalidate(0, dateTimeString = `${day} ${dom} ${mon} ${currentTime}`);
|
$$invalidate(0, dateTimeString = `${day} ${dom} ${mon} ${currentTime}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return [dateTimeString, $menuVisible, $trays, time, day, dom, mon, currentTime];
|
return [
|
||||||
|
dateTimeString,
|
||||||
|
$menuVisible,
|
||||||
|
$trays,
|
||||||
|
handleKeydown,
|
||||||
|
time,
|
||||||
|
day,
|
||||||
|
dom,
|
||||||
|
mon,
|
||||||
|
currentTime
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
class Menubar extends SvelteComponent {
|
class Menubar extends SvelteComponent {
|
||||||
@@ -1638,6 +1678,11 @@
|
|||||||
let trayLabelData = JSON.parse(updateTrayLabelJSON);
|
let trayLabelData = JSON.parse(updateTrayLabelJSON);
|
||||||
updateTrayLabel(trayLabelData);
|
updateTrayLabel(trayLabelData);
|
||||||
break
|
break
|
||||||
|
case 'D':
|
||||||
|
// Delete Tray Menu
|
||||||
|
const id = trayMessage.slice(1);
|
||||||
|
deleteTrayMenu(id);
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
log('Unknown tray message: ' + message.data);
|
log('Unknown tray message: ' + message.data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wails/runtime",
|
"name": "@wails/runtime",
|
||||||
"version": "1.3.9",
|
"version": "1.3.12",
|
||||||
"description": "Wails V2 Javascript runtime library",
|
"description": "Wails V2 Javascript runtime library",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"types": "runtime.d.ts",
|
"types": "runtime.d.ts",
|
||||||
|
|||||||
@@ -24,6 +24,15 @@
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function handleKeydown(e) {
|
||||||
|
// Backtick toggle
|
||||||
|
if( e.keyCode == 192 ) {
|
||||||
|
menuVisible.update( (current) => {
|
||||||
|
return !current;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $menuVisible }
|
{#if $menuVisible }
|
||||||
@@ -37,6 +46,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<svelte:window on:keydown={handleKeydown}/>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
.tray-menus {
|
.tray-menus {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export function hideOverlay() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Menubar **/
|
/** Menubar **/
|
||||||
export const menuVisible = writable(true);
|
export const menuVisible = writable(false);
|
||||||
|
|
||||||
export function showMenuBar() {
|
export function showMenuBar() {
|
||||||
menuVisible.set(true);
|
menuVisible.set(true);
|
||||||
@@ -49,4 +49,16 @@ export function updateTrayLabel(tray) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deleteTrayMenu(id) {
|
||||||
|
trays.update((current) => {
|
||||||
|
// Remove existing if it exists, else add
|
||||||
|
const index = current.findIndex(item => item.ID === id);
|
||||||
|
if ( index === -1 ) {
|
||||||
|
return log("ERROR: Attempted to delete tray index ", id, "but it doesn't exist")
|
||||||
|
}
|
||||||
|
current.splice(index, 1);
|
||||||
|
return current;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export let selectedMenu = writable(null);
|
export let selectedMenu = writable(null);
|
||||||
@@ -10,7 +10,7 @@ The lightweight framework for web-like apps
|
|||||||
/* jshint esversion: 6 */
|
/* jshint esversion: 6 */
|
||||||
|
|
||||||
|
|
||||||
import {setTray, hideOverlay, showOverlay, updateTrayLabel} from "./store";
|
import {setTray, hideOverlay, showOverlay, updateTrayLabel, deleteTrayMenu} from "./store";
|
||||||
import {log} from "./log";
|
import {log} from "./log";
|
||||||
|
|
||||||
let websocket = null;
|
let websocket = null;
|
||||||
@@ -154,6 +154,11 @@ function handleMessage(message) {
|
|||||||
let trayLabelData = JSON.parse(updateTrayLabelJSON)
|
let trayLabelData = JSON.parse(updateTrayLabelJSON)
|
||||||
updateTrayLabel(trayLabelData)
|
updateTrayLabel(trayLabelData)
|
||||||
break
|
break
|
||||||
|
case 'D':
|
||||||
|
// Delete Tray Menu
|
||||||
|
const id = trayMessage.slice(1);
|
||||||
|
deleteTrayMenu(id)
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
log('Unknown tray message: ' + message.data);
|
log('Unknown tray message: ' + message.data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ type Menu interface {
|
|||||||
UpdateApplicationMenu()
|
UpdateApplicationMenu()
|
||||||
UpdateContextMenu(contextMenu *menu.ContextMenu)
|
UpdateContextMenu(contextMenu *menu.ContextMenu)
|
||||||
SetTrayMenu(trayMenu *menu.TrayMenu)
|
SetTrayMenu(trayMenu *menu.TrayMenu)
|
||||||
|
DeleteTrayMenu(trayMenu *menu.TrayMenu)
|
||||||
UpdateTrayMenuLabel(trayMenu *menu.TrayMenu)
|
UpdateTrayMenuLabel(trayMenu *menu.TrayMenu)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,3 +40,7 @@ func (m *menuRuntime) SetTrayMenu(trayMenu *menu.TrayMenu) {
|
|||||||
func (m *menuRuntime) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) {
|
func (m *menuRuntime) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) {
|
||||||
m.bus.Publish("menu:updatetraymenulabel", trayMenu)
|
m.bus.Publish("menu:updatetraymenulabel", trayMenu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *menuRuntime) DeleteTrayMenu(trayMenu *menu.TrayMenu) {
|
||||||
|
m.bus.Publish("menu:deletetraymenu", trayMenu)
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,20 +6,19 @@ import (
|
|||||||
|
|
||||||
// Runtime is a means for the user to interact with the application at runtime
|
// Runtime is a means for the user to interact with the application at runtime
|
||||||
type Runtime struct {
|
type Runtime struct {
|
||||||
Browser Browser
|
Browser Browser
|
||||||
Events Events
|
Events Events
|
||||||
Window Window
|
Window Window
|
||||||
Dialog Dialog
|
Dialog Dialog
|
||||||
System System
|
System System
|
||||||
Menu Menu
|
Menu Menu
|
||||||
Store *StoreProvider
|
Store *StoreProvider
|
||||||
Log Log
|
Log Log
|
||||||
bus *servicebus.ServiceBus
|
bus *servicebus.ServiceBus
|
||||||
shutdownCallback func()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new runtime
|
// New creates a new runtime
|
||||||
func New(serviceBus *servicebus.ServiceBus, shutdownCallback func()) *Runtime {
|
func New(serviceBus *servicebus.ServiceBus) *Runtime {
|
||||||
result := &Runtime{
|
result := &Runtime{
|
||||||
Browser: newBrowser(),
|
Browser: newBrowser(),
|
||||||
Events: newEvents(serviceBus),
|
Events: newEvents(serviceBus),
|
||||||
@@ -36,11 +35,6 @@ func New(serviceBus *servicebus.ServiceBus, shutdownCallback func()) *Runtime {
|
|||||||
|
|
||||||
// Quit the application
|
// Quit the application
|
||||||
func (r *Runtime) Quit() {
|
func (r *Runtime) Quit() {
|
||||||
// Call back to user's shutdown method if defined
|
|
||||||
if r.shutdownCallback != nil {
|
|
||||||
r.shutdownCallback()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start shutdown of Wails
|
// Start shutdown of Wails
|
||||||
r.bus.Publish("quit", "runtime.Quit()")
|
r.bus.Publish("quit", "runtime.Quit()")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,25 +26,20 @@ type Manager struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
|
||||||
// The shutdown callback to notify the user's app that a shutdown
|
|
||||||
// has started
|
|
||||||
shutdownCallback func()
|
|
||||||
|
|
||||||
// Parent waitgroup
|
// Parent waitgroup
|
||||||
wg *sync.WaitGroup
|
wg *sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewManager creates a new signal manager
|
// NewManager creates a new signal manager
|
||||||
func NewManager(ctx context.Context, cancel context.CancelFunc, bus *servicebus.ServiceBus, logger *logger.Logger, shutdownCallback func()) (*Manager, error) {
|
func NewManager(ctx context.Context, cancel context.CancelFunc, bus *servicebus.ServiceBus, logger *logger.Logger) (*Manager, error) {
|
||||||
|
|
||||||
result := &Manager{
|
result := &Manager{
|
||||||
bus: bus,
|
bus: bus,
|
||||||
logger: logger.CustomLogger("Event Manager"),
|
logger: logger.CustomLogger("Event Manager"),
|
||||||
signalchannel: make(chan os.Signal, 2),
|
signalchannel: make(chan os.Signal, 2),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
shutdownCallback: shutdownCallback,
|
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
|
||||||
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
@@ -67,11 +62,6 @@ func (m *Manager) Start() {
|
|||||||
m.logger.Trace("Ctrl+C detected. Shutting down...")
|
m.logger.Trace("Ctrl+C detected. Shutting down...")
|
||||||
m.bus.Publish("quit", "ctrl-c pressed")
|
m.bus.Publish("quit", "ctrl-c pressed")
|
||||||
|
|
||||||
// Shutdown app first
|
|
||||||
if m.shutdownCallback != nil {
|
|
||||||
m.shutdownCallback()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start shutdown of Wails
|
// Start shutdown of Wails
|
||||||
m.cancel()
|
m.cancel()
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,12 @@ func (m *Menu) Start() error {
|
|||||||
splitTopic := strings.Split(menuMessage.Topic(), ":")
|
splitTopic := strings.Split(menuMessage.Topic(), ":")
|
||||||
menuMessageType := splitTopic[1]
|
menuMessageType := splitTopic[1]
|
||||||
switch menuMessageType {
|
switch menuMessageType {
|
||||||
|
case "ontrayopen":
|
||||||
|
trayID := menuMessage.Data().(string)
|
||||||
|
m.menuManager.OnTrayMenuOpen(trayID)
|
||||||
|
case "ontrayclose":
|
||||||
|
trayID := menuMessage.Data().(string)
|
||||||
|
m.menuManager.OnTrayMenuClose(trayID)
|
||||||
case "clicked":
|
case "clicked":
|
||||||
if len(splitTopic) != 2 {
|
if len(splitTopic) != 2 {
|
||||||
m.logger.Error("Received clicked message with invalid topic format. Expected 2 sections in topic, got %s", splitTopic)
|
m.logger.Error("Received clicked message with invalid topic format. Expected 2 sections in topic, got %s", splitTopic)
|
||||||
@@ -137,6 +143,17 @@ func (m *Menu) Start() error {
|
|||||||
// Notify frontend of menu change
|
// Notify frontend of menu change
|
||||||
m.bus.Publish("menufrontend:settraymenu", updatedMenu)
|
m.bus.Publish("menufrontend:settraymenu", updatedMenu)
|
||||||
|
|
||||||
|
case "deletetraymenu":
|
||||||
|
trayMenu := menuMessage.Data().(*menu.TrayMenu)
|
||||||
|
trayID, err := m.menuManager.GetTrayID(trayMenu)
|
||||||
|
if err != nil {
|
||||||
|
m.logger.Trace("%s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify frontend of menu change
|
||||||
|
m.bus.Publish("menufrontend:deletetraymenu", trayID)
|
||||||
|
|
||||||
case "updatetraymenulabel":
|
case "updatetraymenulabel":
|
||||||
trayMenu := menuMessage.Data().(*menu.TrayMenu)
|
trayMenu := menuMessage.Data().(*menu.TrayMenu)
|
||||||
updatedLabel, err := m.menuManager.UpdateTrayMenuLabel(trayMenu)
|
updatedLabel, err := m.menuManager.UpdateTrayMenuLabel(trayMenu)
|
||||||
|
|||||||
@@ -34,10 +34,13 @@ type Runtime struct {
|
|||||||
|
|
||||||
// Startup Hook
|
// Startup Hook
|
||||||
startupOnce sync.Once
|
startupOnce sync.Once
|
||||||
|
|
||||||
|
// Service bus
|
||||||
|
bus *servicebus.ServiceBus
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRuntime creates a new runtime subsystem
|
// NewRuntime creates a new runtime subsystem
|
||||||
func NewRuntime(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger, startupCallback func(*runtime.Runtime), shutdownCallback func()) (*Runtime, error) {
|
func NewRuntime(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger, startupCallback func(*runtime.Runtime)) (*Runtime, error) {
|
||||||
|
|
||||||
// Subscribe to log messages
|
// Subscribe to log messages
|
||||||
runtimeChannel, err := bus.Subscribe("runtime:")
|
runtimeChannel, err := bus.Subscribe("runtime:")
|
||||||
@@ -52,13 +55,13 @@ func NewRuntime(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.
|
|||||||
}
|
}
|
||||||
|
|
||||||
result := &Runtime{
|
result := &Runtime{
|
||||||
runtimeChannel: runtimeChannel,
|
runtimeChannel: runtimeChannel,
|
||||||
hooksChannel: hooksChannel,
|
hooksChannel: hooksChannel,
|
||||||
logger: logger.CustomLogger("Runtime Subsystem"),
|
logger: logger.CustomLogger("Runtime Subsystem"),
|
||||||
runtime: runtime.New(bus, shutdownCallback),
|
runtime: runtime.New(bus),
|
||||||
startupCallback: startupCallback,
|
startupCallback: startupCallback,
|
||||||
shutdownCallback: shutdownCallback,
|
ctx: ctx,
|
||||||
ctx: ctx,
|
bus: bus,
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
@@ -80,7 +83,15 @@ func (r *Runtime) Start() error {
|
|||||||
case "startup":
|
case "startup":
|
||||||
if r.startupCallback != nil {
|
if r.startupCallback != nil {
|
||||||
r.startupOnce.Do(func() {
|
r.startupOnce.Do(func() {
|
||||||
go r.startupCallback(r.runtime)
|
go func() {
|
||||||
|
r.startupCallback(r.runtime)
|
||||||
|
|
||||||
|
// If we got a url, publish it now startup completed
|
||||||
|
url, ok := hooksMessage.Data().(string)
|
||||||
|
if ok && len(url) > 0 {
|
||||||
|
r.bus.Publish("url:handler", url)
|
||||||
|
}
|
||||||
|
}()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
r.logger.Warning("no startup callback registered!")
|
r.logger.Warning("no startup callback registered!")
|
||||||
|
|||||||
98
v2/internal/subsystem/url.go
Normal file
98
v2/internal/subsystem/url.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package subsystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/internal/logger"
|
||||||
|
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// URL is the URL Handler subsystem. It handles messages with topics starting
|
||||||
|
// with "url:"
|
||||||
|
type URL struct {
|
||||||
|
urlChannel <-chan *servicebus.Message
|
||||||
|
|
||||||
|
// quit flag
|
||||||
|
shouldQuit bool
|
||||||
|
|
||||||
|
// Logger!
|
||||||
|
logger *logger.Logger
|
||||||
|
|
||||||
|
// Context for shutdown
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
|
||||||
|
// internal waitgroup
|
||||||
|
wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Handlers
|
||||||
|
handlers map[string]func(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewURL creates a new log subsystem
|
||||||
|
func NewURL(bus *servicebus.ServiceBus, logger *logger.Logger, handlers map[string]func(string)) (*URL, error) {
|
||||||
|
|
||||||
|
// Subscribe to log messages
|
||||||
|
urlChannel, err := bus.Subscribe("url")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
result := &URL{
|
||||||
|
urlChannel: urlChannel,
|
||||||
|
logger: logger,
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
handlers: handlers,
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the subsystem
|
||||||
|
func (u *URL) Start() error {
|
||||||
|
|
||||||
|
u.wg.Add(1)
|
||||||
|
|
||||||
|
// Spin off a go routine
|
||||||
|
go func() {
|
||||||
|
defer u.logger.Trace("URL Shutdown")
|
||||||
|
|
||||||
|
for u.shouldQuit == false {
|
||||||
|
select {
|
||||||
|
case <-u.ctx.Done():
|
||||||
|
u.wg.Done()
|
||||||
|
return
|
||||||
|
case urlMessage := <-u.urlChannel:
|
||||||
|
// Guard against nil messages
|
||||||
|
if urlMessage == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
messageType := strings.TrimPrefix(urlMessage.Topic(), "url:")
|
||||||
|
switch messageType {
|
||||||
|
case "handler":
|
||||||
|
url := urlMessage.Data().(string)
|
||||||
|
splitURL := strings.Split(url, ":")
|
||||||
|
protocol := splitURL[0]
|
||||||
|
callback, ok := u.handlers[protocol]
|
||||||
|
if ok {
|
||||||
|
go callback(url)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
u.logger.Error("unknown url message: %+v", urlMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *URL) Close() {
|
||||||
|
u.cancel()
|
||||||
|
u.wg.Wait()
|
||||||
|
}
|
||||||
@@ -17,13 +17,14 @@ func platformInfo() (*OS, error) {
|
|||||||
// Ignore errors as it isn't a showstopper
|
// Ignore errors as it isn't a showstopper
|
||||||
key, _ := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
|
key, _ := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
|
||||||
|
|
||||||
defer key.Close()
|
|
||||||
|
|
||||||
fmt.Printf("%+v\n", key)
|
|
||||||
|
|
||||||
// Ignore errors as it isn't a showstopper
|
|
||||||
productName, _, _ := key.GetStringValue("ProductName")
|
productName, _, _ := key.GetStringValue("ProductName")
|
||||||
fmt.Println(productName)
|
currentBuild, _, _ := key.GetStringValue("CurrentBuildNumber")
|
||||||
|
displayVersion, _, _ := key.GetStringValue("DisplayVersion")
|
||||||
|
releaseId, _, _ := key.GetStringValue("ReleaseId")
|
||||||
|
|
||||||
return nil, nil
|
result.Name = productName
|
||||||
|
result.Version = fmt.Sprintf("%s (Build: %s)", releaseId, currentBuild)
|
||||||
|
result.ID = displayVersion
|
||||||
|
|
||||||
|
return &result, key.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,5 @@ func (i *Info) discover() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
i.OS = osinfo
|
i.OS = osinfo
|
||||||
|
|
||||||
// dll := syscall.MustLoadDLL("kernel32.dll")
|
|
||||||
// p := dll.MustFindProc("GetVersion")
|
|
||||||
// v, _, _ := p.Call()
|
|
||||||
// fmt.Printf("Windows version %d.%d (Build %d)\n", byte(v), uint8(v>>8), uint16(v>>16))
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,20 +12,19 @@ type Basic struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newBasic creates a new Basic application struct
|
// newBasic creates a new Basic application struct
|
||||||
func newBasic() *Basic {
|
func NewBasic() *Basic {
|
||||||
return &Basic{}
|
return &Basic{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WailsInit is called at application startup
|
// startup is called at application startup
|
||||||
func (b *Basic) WailsInit(runtime *wails.Runtime) error {
|
func (b *Basic) startup(runtime *wails.Runtime) {
|
||||||
// Perform your setup here
|
// Perform your setup here
|
||||||
b.runtime = runtime
|
b.runtime = runtime
|
||||||
runtime.Window.SetTitle("{{.ProjectName}}")
|
runtime.Window.SetTitle("{{.ProjectName}}")
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WailsShutdown is called at application termination
|
// shutdown is called at application termination
|
||||||
func (b *Basic) WailsShutdown() {
|
func (b *Basic) shutdown() {
|
||||||
// Perform your teardown here
|
// Perform your teardown here
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<link rel="stylesheet" href="/main.css">
|
<link rel="stylesheet" href="/main.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body data-wails-drag>
|
||||||
<div id="logo"></div>
|
<div id="logo"></div>
|
||||||
<div id="input">
|
<div id="input">
|
||||||
<input id="name" type="text"></input>
|
<input id="name" type="text"></input>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,21 +1,38 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/wailsapp/wails/v2"
|
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/options"
|
||||||
|
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
// Create application with options
|
// Create application with options
|
||||||
app, err := wails.CreateApp("{{.ProjectName}}", 1024, 768)
|
app := NewBasic()
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.Bind(newBasic())
|
err := wails.Run(&options.App{
|
||||||
|
Title: "{{.ProjectName}}",
|
||||||
err = app.Run()
|
Width: 800,
|
||||||
|
Height: 600,
|
||||||
|
DisableResize: true,
|
||||||
|
Mac: &mac.Options{
|
||||||
|
WebviewIsTransparent: true,
|
||||||
|
WindowBackgroundIsTranslucent: true,
|
||||||
|
TitleBar: mac.TitleBarHiddenInset(),
|
||||||
|
Menu: menu.DefaultMacMenu(),
|
||||||
|
},
|
||||||
|
LogLevel: logger.DEBUG,
|
||||||
|
Startup: app.startup,
|
||||||
|
Shutdown: app.shutdown,
|
||||||
|
Bind: []interface{}{
|
||||||
|
app,
|
||||||
|
},
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ import (
|
|||||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
VERBOSE int = 2
|
||||||
|
)
|
||||||
|
|
||||||
// BaseBuilder is the common builder struct
|
// BaseBuilder is the common builder struct
|
||||||
type BaseBuilder struct {
|
type BaseBuilder struct {
|
||||||
filesToDelete slicer.StringSlicer
|
filesToDelete slicer.StringSlicer
|
||||||
@@ -142,9 +146,28 @@ func (b *BaseBuilder) CleanUp() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BaseBuilder) OutputFilename(options *Options) string {
|
||||||
|
outputFile := options.OutputFile
|
||||||
|
if outputFile == "" {
|
||||||
|
outputFile = b.projectData.OutputFilename
|
||||||
|
}
|
||||||
|
return outputFile
|
||||||
|
}
|
||||||
|
|
||||||
// CompileProject compiles the project
|
// CompileProject compiles the project
|
||||||
func (b *BaseBuilder) CompileProject(options *Options) error {
|
func (b *BaseBuilder) CompileProject(options *Options) error {
|
||||||
|
|
||||||
|
// Run go mod tidy first
|
||||||
|
cmd := exec.Command(options.Compiler, "mod", "tidy")
|
||||||
|
if options.Verbosity == VERBOSE {
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
}
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Default go build command
|
// Default go build command
|
||||||
commands := slicer.String([]string{"build"})
|
commands := slicer.String([]string{"build"})
|
||||||
|
|
||||||
@@ -160,6 +183,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
|||||||
// potentially try and see if the assets have changed but will
|
// potentially try and see if the assets have changed but will
|
||||||
// this take as much time as a `-a` build?
|
// this take as much time as a `-a` build?
|
||||||
commands.Add("-a")
|
commands.Add("-a")
|
||||||
|
commands.Add("-x")
|
||||||
|
|
||||||
var tags slicer.StringSlicer
|
var tags slicer.StringSlicer
|
||||||
tags.Add(options.OutputType)
|
tags.Add(options.OutputType)
|
||||||
@@ -188,10 +212,10 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
|||||||
|
|
||||||
// Get application build directory
|
// Get application build directory
|
||||||
appDir := options.BuildDirectory
|
appDir := options.BuildDirectory
|
||||||
err := cleanBuildDirectory(options)
|
//err = cleanBuildDirectory(options)
|
||||||
if err != nil {
|
//if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
//}
|
||||||
|
|
||||||
if options.LDFlags != "" {
|
if options.LDFlags != "" {
|
||||||
commands.Add("-ldflags")
|
commands.Add("-ldflags")
|
||||||
@@ -199,10 +223,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set up output filename
|
// Set up output filename
|
||||||
outputFile := options.OutputFile
|
outputFile := b.OutputFilename(options)
|
||||||
if outputFile == "" {
|
|
||||||
outputFile = b.projectData.OutputFilename
|
|
||||||
}
|
|
||||||
compiledBinary := filepath.Join(appDir, outputFile)
|
compiledBinary := filepath.Join(appDir, outputFile)
|
||||||
commands.Add("-o")
|
commands.Add("-o")
|
||||||
commands.Add(compiledBinary)
|
commands.Add(compiledBinary)
|
||||||
@@ -211,14 +232,14 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
|||||||
options.CompiledBinary = compiledBinary
|
options.CompiledBinary = compiledBinary
|
||||||
|
|
||||||
// Create the command
|
// Create the command
|
||||||
cmd := exec.Command(options.Compiler, commands.AsSlice()...)
|
cmd = exec.Command(options.Compiler, commands.AsSlice()...)
|
||||||
|
if options.Verbosity == VERBOSE {
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
}
|
||||||
// Set the directory
|
// Set the directory
|
||||||
cmd.Dir = b.projectData.Path
|
cmd.Dir = b.projectData.Path
|
||||||
|
|
||||||
// Set GO111MODULE environment variable
|
|
||||||
cmd.Env = append(os.Environ(), "GO111MODULE=on")
|
|
||||||
|
|
||||||
// Add CGO flags
|
// Add CGO flags
|
||||||
// We use the project/build dir as a temporary place for our generated c headers
|
// We use the project/build dir as a temporary place for our generated c headers
|
||||||
buildBaseDir, err := fs.RelativeToCwd("build")
|
buildBaseDir, err := fs.RelativeToCwd("build")
|
||||||
@@ -226,31 +247,47 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Env = append(os.Environ(), fmt.Sprintf("CGO_CFLAGS=-I%s", buildBaseDir))
|
cmd.Env = os.Environ() // inherit env
|
||||||
|
|
||||||
// Setup buffers
|
// Use upsertEnv so we don't overwrite user's CGO_CFLAGS
|
||||||
var stdo, stde bytes.Buffer
|
cmd.Env = upsertEnv(cmd.Env, "CGO_CFLAGS", func(v string) string {
|
||||||
cmd.Stdout = &stdo
|
if v != "" {
|
||||||
cmd.Stderr = &stde
|
v += " "
|
||||||
|
}
|
||||||
|
v += "-I" + buildBaseDir
|
||||||
|
return v
|
||||||
|
})
|
||||||
|
|
||||||
|
cmd.Env = upsertEnv(cmd.Env, "GOOS", func(v string) string {
|
||||||
|
return options.Platform
|
||||||
|
})
|
||||||
|
|
||||||
|
cmd.Env = upsertEnv(cmd.Env, "GOARCH", func(v string) string {
|
||||||
|
return options.Arch
|
||||||
|
})
|
||||||
|
|
||||||
|
cmd.Env = upsertEnv(cmd.Env, "CGO_ENABLED", func(v string) string {
|
||||||
|
return "1"
|
||||||
|
})
|
||||||
|
|
||||||
// Run command
|
// Run command
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
|
|
||||||
// Format error if we have one
|
// Format error if we have one
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s\n%s", err, string(stde.Bytes()))
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NpmInstall runs "npm install" in the given directory
|
// NpmInstall runs "npm install" in the given directory
|
||||||
func (b *BaseBuilder) NpmInstall(sourceDir string) error {
|
func (b *BaseBuilder) NpmInstall(sourceDir string, verbose bool) error {
|
||||||
return b.NpmInstallUsingCommand(sourceDir, "npm install")
|
return b.NpmInstallUsingCommand(sourceDir, "npm install", verbose)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NpmInstallUsingCommand runs the given install command in the specified npm project directory
|
// NpmInstallUsingCommand runs the given install command in the specified npm project directory
|
||||||
func (b *BaseBuilder) NpmInstallUsingCommand(sourceDir string, installCommand string) error {
|
func (b *BaseBuilder) NpmInstallUsingCommand(sourceDir string, installCommand string, verbose bool) error {
|
||||||
|
|
||||||
packageJSON := filepath.Join(sourceDir, "package.json")
|
packageJSON := filepath.Join(sourceDir, "package.json")
|
||||||
|
|
||||||
@@ -292,7 +329,7 @@ func (b *BaseBuilder) NpmInstallUsingCommand(sourceDir string, installCommand st
|
|||||||
// Split up the InstallCommand and execute it
|
// Split up the InstallCommand and execute it
|
||||||
cmd := strings.Split(installCommand, " ")
|
cmd := strings.Split(installCommand, " ")
|
||||||
stdout, stderr, err := shell.RunCommand(sourceDir, cmd[0], cmd[1:]...)
|
stdout, stderr, err := shell.RunCommand(sourceDir, cmd[0], cmd[1:]...)
|
||||||
if err != nil {
|
if verbose || err != nil {
|
||||||
for _, l := range strings.Split(stdout, "\n") {
|
for _, l := range strings.Split(stdout, "\n") {
|
||||||
fmt.Printf(" %s\n", l)
|
fmt.Printf(" %s\n", l)
|
||||||
}
|
}
|
||||||
@@ -341,31 +378,40 @@ func (b *BaseBuilder) NpmRunWithEnvironment(projectDir, buildTarget string, verb
|
|||||||
func (b *BaseBuilder) BuildFrontend(outputLogger *clilogger.CLILogger) error {
|
func (b *BaseBuilder) BuildFrontend(outputLogger *clilogger.CLILogger) error {
|
||||||
|
|
||||||
// TODO: Fix this up from the CLI
|
// TODO: Fix this up from the CLI
|
||||||
verbose := false
|
verbose := b.options.Verbosity == VERBOSE
|
||||||
|
|
||||||
frontendDir := filepath.Join(b.projectData.Path, "frontend")
|
frontendDir := filepath.Join(b.projectData.Path, "frontend")
|
||||||
|
|
||||||
// Check there is an 'InstallCommand' provided in wails.json
|
// Check there is an 'InstallCommand' provided in wails.json
|
||||||
if b.projectData.InstallCommand == "" {
|
if b.projectData.InstallCommand == "" {
|
||||||
// No - don't install
|
// No - don't install
|
||||||
outputLogger.Println(" - No Install command. Skipping.")
|
outputLogger.Println("No Install command. Skipping.")
|
||||||
} else {
|
} else {
|
||||||
// Do install if needed
|
// Do install if needed
|
||||||
outputLogger.Println(" - Installing dependencies...")
|
outputLogger.Print("Installing frontend dependencies: ")
|
||||||
if err := b.NpmInstallUsingCommand(frontendDir, b.projectData.InstallCommand); err != nil {
|
if verbose {
|
||||||
|
outputLogger.Println("")
|
||||||
|
outputLogger.Println("\tCommand: " + b.projectData.InstallCommand)
|
||||||
|
}
|
||||||
|
if err := b.NpmInstallUsingCommand(frontendDir, b.projectData.InstallCommand, verbose); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
outputLogger.Println("Done.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there is a build command
|
// Check if there is a build command
|
||||||
if b.projectData.BuildCommand == "" {
|
if b.projectData.BuildCommand == "" {
|
||||||
outputLogger.Println(" - No Build command. Skipping.")
|
outputLogger.Println("No Build command. Skipping.")
|
||||||
// No - ignore
|
// No - ignore
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
outputLogger.Println(" - Compiling Frontend Project")
|
outputLogger.Print("Compiling frontend: ")
|
||||||
cmd := strings.Split(b.projectData.BuildCommand, " ")
|
cmd := strings.Split(b.projectData.BuildCommand, " ")
|
||||||
|
if verbose {
|
||||||
|
outputLogger.Println("")
|
||||||
|
outputLogger.Println("\tCommand: '" + strings.Join(cmd, " ") + "'")
|
||||||
|
}
|
||||||
stdout, stderr, err := shell.RunCommand(frontendDir, cmd[0], cmd[1:]...)
|
stdout, stderr, err := shell.RunCommand(frontendDir, cmd[0], cmd[1:]...)
|
||||||
if verbose || err != nil {
|
if verbose || err != nil {
|
||||||
for _, l := range strings.Split(stdout, "\n") {
|
for _, l := range strings.Split(stdout, "\n") {
|
||||||
@@ -375,7 +421,12 @@ func (b *BaseBuilder) BuildFrontend(outputLogger *clilogger.CLILogger) error {
|
|||||||
fmt.Printf(" %s\n", l)
|
fmt.Printf(" %s\n", l)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
outputLogger.Println("Done.")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractAssets gets the assets from the index.html file
|
// ExtractAssets gets the assets from the index.html file
|
||||||
@@ -384,3 +435,22 @@ func (b *BaseBuilder) ExtractAssets() (*html.AssetBundle, error) {
|
|||||||
// Read in html
|
// Read in html
|
||||||
return html.NewAssetBundle(b.projectData.HTML)
|
return html.NewAssetBundle(b.projectData.HTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func upsertEnv(env []string, key string, update func(v string) string) []string {
|
||||||
|
newEnv := make([]string, len(env), len(env)+1)
|
||||||
|
found := false
|
||||||
|
for i := range env {
|
||||||
|
if strings.HasPrefix(env[i], key+"=") {
|
||||||
|
eqIndex := strings.Index(env[i], "=")
|
||||||
|
val := env[i][eqIndex+1:]
|
||||||
|
newEnv[i] = fmt.Sprintf("%s=%v", key, update(val))
|
||||||
|
found = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
newEnv[i] = env[i]
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
newEnv = append(newEnv, fmt.Sprintf("%s=%v", key, update("")))
|
||||||
|
}
|
||||||
|
return newEnv
|
||||||
|
}
|
||||||
|
|||||||
31
v2/pkg/commands/build/base_test.go
Normal file
31
v2/pkg/commands/build/base_test.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestUpdateEnv(t *testing.T) {
|
||||||
|
|
||||||
|
env := []string{"one=1", "two=a=b", "three="}
|
||||||
|
newEnv := upsertEnv(env, "two", func(v string) string {
|
||||||
|
return v + "+added"
|
||||||
|
})
|
||||||
|
newEnv = upsertEnv(newEnv, "newVar", func(v string) string {
|
||||||
|
return "added"
|
||||||
|
})
|
||||||
|
newEnv = upsertEnv(newEnv, "three", func(v string) string {
|
||||||
|
return "3"
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(newEnv) != 4 {
|
||||||
|
t.Errorf("expected: 4, got: %d", len(newEnv))
|
||||||
|
}
|
||||||
|
if newEnv[1] != "two=a=b+added" {
|
||||||
|
t.Errorf("expected: \"two=a=b+added\", got: %q", newEnv[1])
|
||||||
|
}
|
||||||
|
if newEnv[2] != "three=3" {
|
||||||
|
t.Errorf("expected: \"three=3\", got: %q", newEnv[2])
|
||||||
|
}
|
||||||
|
if newEnv[3] != "newVar=added" {
|
||||||
|
t.Errorf("expected: \"newVar=added\", got: %q", newEnv[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,7 +6,10 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/leaanthony/slicer"
|
"github.com/wailsapp/wails/v2/internal/fs"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v2/internal/shell"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/internal/project"
|
"github.com/wailsapp/wails/v2/internal/project"
|
||||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
||||||
)
|
)
|
||||||
@@ -32,12 +35,15 @@ type Options struct {
|
|||||||
ProjectData *project.Project // The project data
|
ProjectData *project.Project // The project data
|
||||||
Pack bool // Create a package for the app after building
|
Pack bool // Create a package for the app after building
|
||||||
Platform string // The platform to build for
|
Platform string // The platform to build for
|
||||||
|
Arch string // The architecture to build for
|
||||||
Compiler string // The compiler command to use
|
Compiler string // The compiler command to use
|
||||||
IgnoreFrontend bool // Indicates if the frontend does not need building
|
IgnoreFrontend bool // Indicates if the frontend does not need building
|
||||||
OutputFile string // Override the output filename
|
OutputFile string // Override the output filename
|
||||||
BuildDirectory string // Directory to use for building the application
|
BuildDirectory string // Directory to use for building the application
|
||||||
CompiledBinary string // Fully qualified path to the compiled binary
|
CompiledBinary string // Fully qualified path to the compiled binary
|
||||||
KeepAssets bool // /Keep the generated assets/files
|
KeepAssets bool // /Keep the generated assets/files
|
||||||
|
Verbosity int // Verbosity level (0 - silent, 1 - default, 2 - verbose)
|
||||||
|
AppleIdentity string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetModeAsString returns the current mode as a string
|
// GetModeAsString returns the current mode as a string
|
||||||
@@ -57,12 +63,6 @@ func Build(options *Options) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check platform
|
|
||||||
validPlatforms := slicer.String([]string{"linux", "darwin"})
|
|
||||||
if !validPlatforms.Contains(options.Platform) {
|
|
||||||
return "", fmt.Errorf("platform %s not supported", options.Platform)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load project
|
// Load project
|
||||||
projectData, err := project.Load(cwd)
|
projectData, err := project.Load(cwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -70,7 +70,7 @@ func Build(options *Options) (string, error) {
|
|||||||
}
|
}
|
||||||
options.ProjectData = projectData
|
options.ProjectData = projectData
|
||||||
|
|
||||||
// Calculate build dir
|
// Set build directory
|
||||||
options.BuildDirectory = filepath.Join(options.ProjectData.Path, "build", options.Platform, options.OutputType)
|
options.BuildDirectory = filepath.Join(options.ProjectData.Path, "build", options.Platform, options.OutputType)
|
||||||
|
|
||||||
// Save the project type
|
// Save the project type
|
||||||
@@ -106,7 +106,6 @@ func Build(options *Options) (string, error) {
|
|||||||
// return "", err
|
// return "", err
|
||||||
// }
|
// }
|
||||||
if !options.IgnoreFrontend {
|
if !options.IgnoreFrontend {
|
||||||
outputLogger.Println(" - Building Project Frontend")
|
|
||||||
err = builder.BuildFrontend(outputLogger)
|
err = builder.BuildFrontend(outputLogger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -114,30 +113,62 @@ func Build(options *Options) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build the base assets
|
// Build the base assets
|
||||||
outputLogger.Println(" - Compiling Assets")
|
|
||||||
err = builder.BuildAssets(options)
|
err = builder.BuildAssets(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile the application
|
// Compile the application
|
||||||
outputLogger.Print(" - Compiling Application in " + GetModeAsString(options.Mode) + " mode...")
|
outputLogger.Print("Compiling application: ")
|
||||||
err = builder.CompileProject(options)
|
|
||||||
if err != nil {
|
if options.Platform == "darwin" && options.Arch == "universal" {
|
||||||
return "", err
|
outputFile := builder.OutputFilename(options)
|
||||||
|
amd64Filename := outputFile + "-amd64"
|
||||||
|
arm64Filename := outputFile + "-arm64"
|
||||||
|
|
||||||
|
// Build amd64 first
|
||||||
|
options.Arch = "amd64"
|
||||||
|
options.OutputFile = amd64Filename
|
||||||
|
err = builder.CompileProject(options)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Build arm64
|
||||||
|
options.Arch = "arm64"
|
||||||
|
options.OutputFile = arm64Filename
|
||||||
|
err = builder.CompileProject(options)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Run lipo
|
||||||
|
_, stderr, err := shell.RunCommand(options.BuildDirectory, "lipo", "-create", "-output", outputFile, amd64Filename, arm64Filename)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("%s - %s", err.Error(), stderr)
|
||||||
|
}
|
||||||
|
// Remove temp binaries
|
||||||
|
fs.DeleteFile(filepath.Join(options.BuildDirectory, amd64Filename))
|
||||||
|
fs.DeleteFile(filepath.Join(options.BuildDirectory, arm64Filename))
|
||||||
|
projectData.OutputFilename = outputFile
|
||||||
|
options.CompiledBinary = filepath.Join(options.BuildDirectory, outputFile)
|
||||||
|
} else {
|
||||||
|
err = builder.CompileProject(options)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
outputLogger.Println("done.")
|
outputLogger.Println("Done.")
|
||||||
|
|
||||||
// Do we need to pack the app?
|
// Do we need to pack the app?
|
||||||
if options.Pack {
|
if options.Pack {
|
||||||
|
|
||||||
outputLogger.Println(" - Packaging Application")
|
outputLogger.Print("Packaging application: ")
|
||||||
|
|
||||||
// TODO: Allow cross platform build
|
// TODO: Allow cross platform build
|
||||||
err = packageProject(options, runtime.GOOS)
|
err = packageProject(options, runtime.GOOS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
outputLogger.Println("Done.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return projectData.OutputFilename, nil
|
return projectData.OutputFilename, nil
|
||||||
|
|||||||
@@ -12,5 +12,6 @@ type Builder interface {
|
|||||||
BuildFrontend(*clilogger.CLILogger) error
|
BuildFrontend(*clilogger.CLILogger) error
|
||||||
BuildRuntime(*Options) error
|
BuildRuntime(*Options) error
|
||||||
CompileProject(*Options) error
|
CompileProject(*Options) error
|
||||||
|
OutputFilename(*Options) string
|
||||||
CleanUp()
|
CleanUp()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func (d *DesktopBuilder) BuildBaseAssets(assets *html.AssetBundle, options *Opti
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
outputLogger := options.Logger
|
outputLogger := options.Logger
|
||||||
outputLogger.Print(" - Embedding Assets...")
|
outputLogger.Print("Building assets: ")
|
||||||
|
|
||||||
// Get target asset directory
|
// Get target asset directory
|
||||||
assetDir, err := fs.RelativeToCwd("build")
|
assetDir, err := fs.RelativeToCwd("build")
|
||||||
@@ -96,7 +96,7 @@ func (d *DesktopBuilder) BuildBaseAssets(assets *html.AssetBundle, options *Opti
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
outputLogger.Println("done.")
|
outputLogger.Println("Done.")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -125,11 +125,11 @@ func (d *DesktopBuilder) BuildRuntime(options *Options) error {
|
|||||||
|
|
||||||
sourceDir := fs.RelativePath("../../../internal/runtime/js")
|
sourceDir := fs.RelativePath("../../../internal/runtime/js")
|
||||||
|
|
||||||
if err := d.NpmInstall(sourceDir); err != nil {
|
if err := d.NpmInstall(sourceDir, options.Verbosity == VERBOSE); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
outputLogger.Print(" - Embedding Runtime...")
|
outputLogger.Print("Embedding Runtime: ")
|
||||||
envvars := []string{"WAILSPLATFORM=" + options.Platform}
|
envvars := []string{"WAILSPLATFORM=" + options.Platform}
|
||||||
if err := d.NpmRunWithEnvironment(sourceDir, "build:desktop", false, envvars); err != nil {
|
if err := d.NpmRunWithEnvironment(sourceDir, "build:desktop", false, envvars); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package build
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -52,6 +54,14 @@ func packageApplication(options *Options) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sign app if needed
|
||||||
|
if options.AppleIdentity != "" {
|
||||||
|
err = signApplication(options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,3 +186,21 @@ func processApplicationIcon(resourceDir string, iconsDir string) (err error) {
|
|||||||
}()
|
}()
|
||||||
return icns.Encode(dest, srcImg)
|
return icns.Encode(dest, srcImg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func signApplication(options *Options) error {
|
||||||
|
bundlename := filepath.Join(options.BuildDirectory, options.ProjectData.Name+".app")
|
||||||
|
identity := fmt.Sprintf(`"%s"`, options.AppleIdentity)
|
||||||
|
cmd := exec.Command("codesign", "--sign", identity, "--deep", "--force", "--verbose", "--timestamp", "--options", "runtime", bundlename)
|
||||||
|
var stdo, stde bytes.Buffer
|
||||||
|
cmd.Stdout = &stdo
|
||||||
|
cmd.Stderr = &stde
|
||||||
|
|
||||||
|
// Run command
|
||||||
|
err := cmd.Run()
|
||||||
|
|
||||||
|
// Format error if we have one
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s\n%s", err, string(stde.Bytes()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ func (s *ServerBuilder) BuildBaseAssets(assets *html.AssetBundle) error {
|
|||||||
func (s *ServerBuilder) BuildRuntime(options *Options) error {
|
func (s *ServerBuilder) BuildRuntime(options *Options) error {
|
||||||
|
|
||||||
sourceDir := fs.RelativePath("../../../internal/runtime/js")
|
sourceDir := fs.RelativePath("../../../internal/runtime/js")
|
||||||
if err := s.NpmInstall(sourceDir); err != nil {
|
if err := s.NpmInstall(sourceDir, options.Verbosity == VERBOSE); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
66
v2/pkg/logger/filelogger.go
Normal file
66
v2/pkg/logger/filelogger.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileLogger is a utility to log messages to a number of destinations
|
||||||
|
type FileLogger struct {
|
||||||
|
filename string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileLogger creates a new Logger.
|
||||||
|
func NewFileLogger(filename string) Logger {
|
||||||
|
return &FileLogger{
|
||||||
|
filename: filename,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print works like Sprintf.
|
||||||
|
func (l *FileLogger) Print(message string) {
|
||||||
|
f, err := os.OpenFile(l.filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := f.WriteString(message); err != nil {
|
||||||
|
f.Close()
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *FileLogger) Println(message string) {
|
||||||
|
l.Print(message + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace level logging. Works like Sprintf.
|
||||||
|
func (l *FileLogger) Trace(message string) {
|
||||||
|
l.Println("TRACE | " + message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug level logging. Works like Sprintf.
|
||||||
|
func (l *FileLogger) Debug(message string) {
|
||||||
|
l.Println("DEBUG | " + message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info level logging. Works like Sprintf.
|
||||||
|
func (l *FileLogger) Info(message string) {
|
||||||
|
l.Println("INFO | " + message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning level logging. Works like Sprintf.
|
||||||
|
func (l *FileLogger) Warning(message string) {
|
||||||
|
l.Println("WARN | " + message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error level logging. Works like Sprintf.
|
||||||
|
func (l *FileLogger) Error(message string) {
|
||||||
|
l.Println("ERROR | " + message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal level logging. Works like Sprintf.
|
||||||
|
func (l *FileLogger) Fatal(message string) {
|
||||||
|
l.Println("FATAL | " + message)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
@@ -10,15 +10,15 @@ type Modifier string
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// CmdOrCtrlKey represents Command on Mac and Control on other platforms
|
// CmdOrCtrlKey represents Command on Mac and Control on other platforms
|
||||||
CmdOrCtrlKey Modifier = "CmdOrCtrl"
|
CmdOrCtrlKey Modifier = "cmdorctrl"
|
||||||
// OptionOrAltKey represents Option on Mac and Alt on other platforms
|
// OptionOrAltKey represents Option on Mac and Alt on other platforms
|
||||||
OptionOrAltKey Modifier = "OptionOrAlt"
|
OptionOrAltKey Modifier = "optionoralt"
|
||||||
// ShiftKey represents the shift key on all systems
|
// ShiftKey represents the shift key on all systems
|
||||||
ShiftKey Modifier = "Shift"
|
ShiftKey Modifier = "shift"
|
||||||
// SuperKey represents Command on Mac and the Windows key on the other platforms
|
// SuperKey represents Command on Mac and the Windows key on the other platforms
|
||||||
SuperKey Modifier = "Super"
|
SuperKey Modifier = "super"
|
||||||
// ControlKey represents the control key on all systems
|
// ControlKey represents the control key on all systems
|
||||||
ControlKey Modifier = "Control"
|
ControlKey Modifier = "ctrl"
|
||||||
)
|
)
|
||||||
|
|
||||||
var modifierMap = map[string]Modifier{
|
var modifierMap = map[string]Modifier{
|
||||||
@@ -30,7 +30,8 @@ var modifierMap = map[string]Modifier{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseModifier(text string) (*Modifier, error) {
|
func parseModifier(text string) (*Modifier, error) {
|
||||||
result, valid := modifierMap[text]
|
lowertext := strings.ToLower(text)
|
||||||
|
result, valid := modifierMap[lowertext]
|
||||||
if !valid {
|
if !valid {
|
||||||
return nil, fmt.Errorf("'%s' is not a valid modifier", text)
|
return nil, fmt.Errorf("'%s' is not a valid modifier", text)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ type MenuItem struct {
|
|||||||
// Callback function when menu clicked
|
// Callback function when menu clicked
|
||||||
Click Callback `json:"-"`
|
Click Callback `json:"-"`
|
||||||
|
|
||||||
// Colour
|
// Text Colour
|
||||||
RGBA string
|
RGBA string
|
||||||
|
|
||||||
// Font
|
// Font
|
||||||
@@ -39,9 +39,12 @@ type MenuItem struct {
|
|||||||
// Image - base64 image data
|
// Image - base64 image data
|
||||||
Image string
|
Image string
|
||||||
|
|
||||||
// MacTemplateImage indicates that on a mac, this image is a template image
|
// MacTemplateImage indicates that on a Mac, this image is a template image
|
||||||
MacTemplateImage bool
|
MacTemplateImage bool
|
||||||
|
|
||||||
|
// MacAlternate indicates that this item is an alternative to the previous menu item
|
||||||
|
MacAlternate bool
|
||||||
|
|
||||||
// Tooltip
|
// Tooltip
|
||||||
Tooltip string
|
Tooltip string
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,38 @@ type TrayMenu struct {
|
|||||||
// Label is the text we wish to display in the tray
|
// Label is the text we wish to display in the tray
|
||||||
Label string
|
Label string
|
||||||
|
|
||||||
// Icon is the name of the tray icon we wish to display.
|
// Image is the name of the tray icon we wish to display.
|
||||||
// These are read up during build from <projectdir>/trayicons and
|
// These are read up during build from <projectdir>/trayicons and
|
||||||
// the filenames are used as IDs, minus the extension
|
// the filenames are used as IDs, minus the extension
|
||||||
// EG: <projectdir>/trayicons/main.png can be referenced here with "main"
|
// EG: <projectdir>/trayicons/main.png can be referenced here with "main"
|
||||||
Icon string
|
// If the image is not a filename, it will be treated as base64 image data
|
||||||
|
Image string
|
||||||
|
|
||||||
|
// MacTemplateImage indicates that on a Mac, this image is a template image
|
||||||
|
MacTemplateImage bool
|
||||||
|
|
||||||
|
// Text Colour
|
||||||
|
RGBA string
|
||||||
|
|
||||||
|
// Font
|
||||||
|
FontSize int
|
||||||
|
FontName string
|
||||||
|
|
||||||
|
// Tooltip
|
||||||
|
Tooltip string
|
||||||
|
|
||||||
|
// Callback function when menu clicked
|
||||||
|
//Click Callback `json:"-"`
|
||||||
|
|
||||||
|
// Disabled makes the item unselectable
|
||||||
|
Disabled bool
|
||||||
|
|
||||||
// Menu is the initial menu we wish to use for the tray
|
// Menu is the initial menu we wish to use for the tray
|
||||||
Menu *Menu
|
Menu *Menu
|
||||||
|
|
||||||
|
// OnOpen is called when the Menu is opened
|
||||||
|
OnOpen func()
|
||||||
|
|
||||||
|
// OnClose is called when the Menu is closed
|
||||||
|
OnClose func()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,14 @@ package mac
|
|||||||
|
|
||||||
import "github.com/wailsapp/wails/v2/pkg/menu"
|
import "github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
|
|
||||||
|
type ActivationPolicy int
|
||||||
|
|
||||||
|
const (
|
||||||
|
NSApplicationActivationPolicyRegular ActivationPolicy = 0
|
||||||
|
NSApplicationActivationPolicyAccessory ActivationPolicy = 1
|
||||||
|
NSApplicationActivationPolicyProhibited ActivationPolicy = 2
|
||||||
|
)
|
||||||
|
|
||||||
// Options are options specific to Mac
|
// Options are options specific to Mac
|
||||||
type Options struct {
|
type Options struct {
|
||||||
TitleBar *TitleBar
|
TitleBar *TitleBar
|
||||||
@@ -11,4 +19,6 @@ type Options struct {
|
|||||||
Menu *menu.Menu
|
Menu *menu.Menu
|
||||||
TrayMenus []*menu.TrayMenu
|
TrayMenus []*menu.TrayMenu
|
||||||
ContextMenus []*menu.ContextMenu
|
ContextMenus []*menu.ContextMenu
|
||||||
|
ActivationPolicy ActivationPolicy
|
||||||
|
URLHandlers map[string]func(string)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user