mirror of
https://github.com/taigrr/wails.git
synced 2026-04-17 04:05:12 -07:00
Compare commits
67 Commits
v2.0.0-alp
...
v2.0.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5e76c50b0 | ||
|
|
e40226ff7a | ||
|
|
53a3638fa8 | ||
|
|
1344638c52 | ||
|
|
6fdc87454a | ||
|
|
c36f9501a9 | ||
|
|
c23b43c352 | ||
|
|
a76851463b | ||
|
|
e17b432c8f | ||
|
|
5d444cd6dd | ||
|
|
be43049fc6 | ||
|
|
2e01710412 | ||
|
|
1b0193161c | ||
|
|
1b377fb575 | ||
|
|
1203ae64b8 | ||
|
|
26ed8002b9 | ||
|
|
cf23bffc67 | ||
|
|
d70f6fffe7 | ||
|
|
54c99fc386 | ||
|
|
86c1ea5e6a | ||
|
|
b394c1914c | ||
|
|
91c2ddf155 | ||
|
|
712ad96d2a | ||
|
|
86b4a4f2f5 | ||
|
|
4b9786abc9 | ||
|
|
fd96ebc050 | ||
|
|
939e0f5975 | ||
|
|
6a7a288a0f | ||
|
|
0564d0aa98 | ||
|
|
3a136a73ca | ||
|
|
50c219307f | ||
|
|
de3038b302 | ||
|
|
6eb4b0a419 | ||
|
|
41d2158375 | ||
|
|
5d7f57e80b | ||
|
|
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 |
@@ -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
|
||||||
@@ -37,26 +39,41 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
|||||||
compilerCommand := "go"
|
compilerCommand := "go"
|
||||||
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
|
command.StringFlag("compiler", "Use a different go compiler to build, eg go1.15beta1", &compilerCommand)
|
||||||
|
|
||||||
|
compress := false
|
||||||
|
command.BoolFlag("compress", "Compress final binary", &compress)
|
||||||
|
|
||||||
// Setup Platform flag
|
// Setup Platform flag
|
||||||
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)
|
||||||
|
|
||||||
|
// tags to pass to `go`
|
||||||
|
tags := ""
|
||||||
|
command.StringFlag("tags", "tags to pass to Go compiler (quoted and space separated)", &tags)
|
||||||
|
|
||||||
// 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)
|
||||||
|
|
||||||
|
// Clean build directory
|
||||||
|
cleanBuildDirectory := false
|
||||||
|
command.BoolFlag("clean", "Clean the build directory before building", &cleanBuildDirectory)
|
||||||
|
|
||||||
appleIdentity := ""
|
appleIdentity := ""
|
||||||
if runtime.GOOS == "darwin" {
|
if runtime.GOOS == "darwin" {
|
||||||
command.StringFlag("sign", "Signs your app with the given identity.", &appleIdentity)
|
command.StringFlag("sign", "Signs your app with the given identity.", &appleIdentity)
|
||||||
@@ -64,6 +81,8 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
|||||||
|
|
||||||
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)
|
||||||
@@ -82,29 +101,93 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
|||||||
return fmt.Errorf("must use `-package` flag when using `-sign`")
|
return fmt.Errorf("must use `-package` flag when using `-sign`")
|
||||||
}
|
}
|
||||||
|
|
||||||
task := fmt.Sprintf("Building %s Application", strings.Title(outputType))
|
|
||||||
logger.Println(task)
|
|
||||||
logger.Println(strings.Repeat("-", len(task)))
|
|
||||||
|
|
||||||
// Setup mode
|
// Setup mode
|
||||||
mode := build.Debug
|
mode := build.Debug
|
||||||
if production {
|
if production {
|
||||||
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if compress && platform == "darwin/universal" {
|
||||||
|
println("Warning: compress flag unsupported for universal binaries. Ignoring.")
|
||||||
|
compress = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tags
|
||||||
|
userTags := []string{}
|
||||||
|
for _, tag := range strings.Split(tags, " ") {
|
||||||
|
thisTag := strings.TrimSpace(tag)
|
||||||
|
if thisTag != "" {
|
||||||
|
userTags = append(userTags, thisTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create BuildOptions
|
// Create BuildOptions
|
||||||
buildOptions := &build.Options{
|
buildOptions := &build.Options{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
OutputType: outputType,
|
OutputType: outputType,
|
||||||
Mode: mode,
|
OutputFile: outputFilename,
|
||||||
Pack: pack,
|
CleanBuildDirectory: cleanBuildDirectory,
|
||||||
Platform: platform,
|
Mode: mode,
|
||||||
LDFlags: ldflags,
|
Pack: pack,
|
||||||
Compiler: compilerCommand,
|
LDFlags: ldflags,
|
||||||
KeepAssets: keepAssets,
|
Compiler: compilerCommand,
|
||||||
AppleIdentity: appleIdentity,
|
KeepAssets: keepAssets,
|
||||||
|
AppleIdentity: appleIdentity,
|
||||||
|
Verbosity: verbosity,
|
||||||
|
Compress: compress,
|
||||||
|
UserTags: userTags,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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, "\n")
|
||||||
|
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, "Compress: \t%t\n", buildOptions.Compress)
|
||||||
|
fmt.Fprintf(w, "Build Mode: \t%s\n", buildModeText)
|
||||||
|
fmt.Fprintf(w, "Package: \t%t\n", buildOptions.Pack)
|
||||||
|
fmt.Fprintf(w, "Clean Build Dir: \t%t\n", buildOptions.CleanBuildDirectory)
|
||||||
|
fmt.Fprintf(w, "KeepAssets: \t%t\n", buildOptions.KeepAssets)
|
||||||
|
fmt.Fprintf(w, "LDFlags: \t\"%s\"\n", buildOptions.LDFlags)
|
||||||
|
fmt.Fprintf(w, "Tags: \t[%s]\n", strings.Join(buildOptions.UserTags, ","))
|
||||||
|
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)
|
||||||
|
|
||||||
@@ -67,5 +67,6 @@ func main() {
|
|||||||
err = app.Run()
|
err = app.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("\n\nERROR: " + err.Error())
|
println("\n\nERROR: " + err.Error())
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
var version = "v2.0.0-alpha.44"
|
var version = "v2.0.0-alpha.64"
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ go 1.16
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Masterminds/semver v1.5.0
|
github.com/Masterminds/semver v1.5.0
|
||||||
github.com/davecgh/go-spew v1.1.1
|
|
||||||
github.com/fatih/structtag v1.2.0
|
github.com/fatih/structtag v1.2.0
|
||||||
github.com/fsnotify/fsnotify v1.4.9
|
github.com/fsnotify/fsnotify v1.4.9
|
||||||
github.com/gorilla/websocket v1.4.1
|
github.com/gorilla/websocket v1.4.1
|
||||||
github.com/imdario/mergo v0.3.11
|
github.com/imdario/mergo v0.3.11
|
||||||
github.com/jackmordaunt/icns v1.0.0
|
github.com/jackmordaunt/icns v1.0.0
|
||||||
github.com/leaanthony/clir v1.0.4
|
github.com/leaanthony/clir v1.0.4
|
||||||
|
github.com/leaanthony/go-ansi-parser v1.0.1
|
||||||
github.com/leaanthony/gosod v0.0.4
|
github.com/leaanthony/gosod v0.0.4
|
||||||
github.com/leaanthony/slicer v1.5.0
|
github.com/leaanthony/slicer v1.5.0
|
||||||
github.com/matryer/is v1.4.0
|
github.com/matryer/is v1.4.0
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
||||||
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||||
@@ -42,6 +41,8 @@ github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eT
|
|||||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU=
|
github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU=
|
||||||
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
|
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
|
||||||
|
github.com/leaanthony/go-ansi-parser v1.0.1 h1:97v6c5kYppVsbScf4r/VZdXyQ21KQIfeQOk2DgKxGG4=
|
||||||
|
github.com/leaanthony/go-ansi-parser v1.0.1/go.mod h1:7arTzgVI47srICYhvgUV4CGd063sGEeoSlych5yeSPM=
|
||||||
github.com/leaanthony/gosod v0.0.4 h1:v4hepo4IyL8E8c9qzDsvYcA0KGh7Npf8As74K5ibQpI=
|
github.com/leaanthony/gosod v0.0.4 h1:v4hepo4IyL8E8c9qzDsvYcA0KGh7Npf8As74K5ibQpI=
|
||||||
github.com/leaanthony/gosod v0.0.4/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
|
github.com/leaanthony/gosod v0.0.4/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
|
||||||
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
|
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -11,9 +11,21 @@
|
|||||||
#include "hashmap.h"
|
#include "hashmap.h"
|
||||||
#include "stdlib.h"
|
#include "stdlib.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
long maj;
|
||||||
|
long min;
|
||||||
|
long patch;
|
||||||
|
} OSVersion;
|
||||||
|
|
||||||
// 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 kInternetEventClass 'GURL'
|
||||||
#define kAEGetURL 'GURL'
|
#define kAEGetURL 'GURL'
|
||||||
#define keyDirectObject '----'
|
#define keyDirectObject '----'
|
||||||
@@ -21,18 +33,29 @@
|
|||||||
#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"))
|
||||||
|
#define GET_OSVERSION(receiver) ((OSVersion(*)(id, SEL))objc_msgSend)(processInfo, s("operatingSystemVersion"));
|
||||||
|
#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"))
|
#define GET_OSVERSION(receiver) ((OSVersion(*)(id, SEL))objc_msgSend_stret)(processInfo, s("operatingSystemVersion"));
|
||||||
|
#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
|
||||||
|
|
||||||
@@ -102,6 +125,8 @@
|
|||||||
#define NSAlertSecondButtonReturn 1001
|
#define NSAlertSecondButtonReturn 1001
|
||||||
#define NSAlertThirdButtonReturn 1002
|
#define NSAlertThirdButtonReturn 1002
|
||||||
|
|
||||||
|
#define BrokenImage "iVBORw0KGgoAAAANSUhEUgAAABAAAAASCAMAAABl5a5YAAABj1BMVEWopan///+koqSWk5P9/v3///////////+AgACMiovz8/PB0fG9z+3i4+WysbGBfX1Erh80rACLiYqBxolEsDhHlDEbqQDDx+CNho7W1tj4+/bw+O3P5Mn4/f/W1tbK6sX////b2dn////////////8/Pz6+vro6Ojj4+P////G1PL////EzNydmp2cmZnd3eDF1PHs8v/o8P/Q3vrS3vfE0vCdmpqZkpr19/3N2vXI1vPH1fOgnqDg6frP3PbCytvHx8irqq6HhIZtuGtjnlZetU1Xs0NWskBNsi7w9v/d6P7w9P3S4Pzr8Pvl7PrY5PrU4PjQ3fjD1Ozo6Om30NjGzNi7ubm34K+UxKmbnaWXlJeUjpSPi4tppF1TtjxSsTf2+f7L2PTr7e3H2+3V7+q+0uXg4OPg4eLR1uG7z+Hg4ODGzODV2N7V1trP5dmxzs65vcfFxMWq0cKxxr+/vr+0s7apxbWaxrCv2qao05+dlp2Uuo2Dn4F8vIB6xnyAoHmAym9zqGpctENLryNFsgoblJpnAAAAKnRSTlP+hP7+5ZRmYgL+/f39/f39/f38/Pz8/Pv69+7j083My8GocnBPTTMWEgjxeITOAAABEklEQVQY0y3KZXuCYBiG4ceYuu7u3nyVAaKOMBBQ7O5Yd3f3fvheDnd9u8/jBkGwNxP6sjOWVQvY/ftrzfT6bd3yEhCnYZqiaYoKiwX/gXkFiHySTcUTLJMsZ9v8nQvgssWYOEKedKpcOO6CUXD5IlGEY5hLUbyDAAZ6HRf1bnkoavOsFQibg+Q4nuNYL+ON5PHD5nBaraRVyxnzGf6BJzUi2QQCQgMyk8tleL7dg1owpJ17D5IkvV100EingeOopPyo6vfAuXF+9hbDTknZCIaUoeK4efKwG4iT6xDewd7imGlid7gGwv37b6Oh9jwaTdOf/Tc1qH7UZVmuP6G5qZfBr9cAGNy4KiDd4tXIs7tS+QO9aUKvPAIKuQAAAABJRU5ErkJggg=="
|
||||||
|
|
||||||
struct Application;
|
struct Application;
|
||||||
int releaseNSObject(void *const context, struct hashmap_element_s *const e);
|
int releaseNSObject(void *const context, struct hashmap_element_s *const e);
|
||||||
void TitlebarAppearsTransparent(struct Application* app);
|
void TitlebarAppearsTransparent(struct Application* app);
|
||||||
@@ -124,4 +149,6 @@ void* lookupStringConstant(id constantName);
|
|||||||
|
|
||||||
void HasURLHandlers(struct Application* app);
|
void HasURLHandlers(struct Application* app);
|
||||||
|
|
||||||
|
id createImageFromBase64Data(const char *data, bool isTemplateImage);
|
||||||
|
|
||||||
#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;
|
||||||
@@ -189,6 +189,9 @@ id processAcceleratorKey(const char *key) {
|
|||||||
if( STREQ(key, "return") ) {
|
if( STREQ(key, "return") ) {
|
||||||
return strunicode(0x000d);
|
return strunicode(0x000d);
|
||||||
}
|
}
|
||||||
|
if( STREQ(key, "enter") ) {
|
||||||
|
return strunicode(0x000d);
|
||||||
|
}
|
||||||
if( STREQ(key, "escape") ) {
|
if( STREQ(key, "escape") ) {
|
||||||
return strunicode(0x001b);
|
return strunicode(0x001b);
|
||||||
}
|
}
|
||||||
@@ -345,61 +348,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 +410,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 +439,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 +476,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);
|
||||||
@@ -540,18 +543,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;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -566,77 +569,136 @@ 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, bool alternate) {
|
// getColour returns the colour from a styledLabel based on the key
|
||||||
id item = ALLOC("NSMenuItem");
|
const char* getColour(JsonNode *styledLabelEntry, const char* key) {
|
||||||
|
JsonNode* colEntry = getJSONObject(styledLabelEntry, key);
|
||||||
// Create a MenuItemCallbackData
|
if( colEntry == NULL ) {
|
||||||
MenuItemCallbackData *callback = CreateMenuItemCallbackData(menu, item, menuid, Text);
|
return NULL;
|
||||||
|
|
||||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
|
||||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
|
||||||
|
|
||||||
if( !alternate ) {
|
|
||||||
id key = processAcceleratorKey(acceleratorkey);
|
|
||||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
|
||||||
s("menuItemCallback:"), key);
|
|
||||||
} else {
|
|
||||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(""));
|
|
||||||
}
|
}
|
||||||
|
return getJSONString(colEntry, "hex");
|
||||||
|
}
|
||||||
|
|
||||||
if( tooltip != NULL ) {
|
id createAttributedStringFromStyledLabel(JsonNode *styledLabel, const char* fontName, int fontSize) {
|
||||||
msg(item, s("setToolTip:"), str(tooltip));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process image
|
// Create result
|
||||||
if( image != NULL && strlen(image) > 0) {
|
id attributedString = ALLOC_INIT("NSMutableAttributedString");
|
||||||
id data = ALLOC("NSData");
|
msg_reg(attributedString, s("autorelease"));
|
||||||
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
|
// Create new Dictionary
|
||||||
id dictionary = ALLOC_INIT("NSMutableDictionary");
|
id dictionary = ALLOC_INIT("NSMutableDictionary");
|
||||||
|
msg_reg(dictionary, s("autorelease"));
|
||||||
|
|
||||||
// Process font
|
// Use default font
|
||||||
id font;
|
CGFloat fontSizeFloat = (CGFloat)fontSize;
|
||||||
|
id font = ((id(*)(id, SEL, CGFloat))objc_msgSend)(c("NSFont"), s("menuBarFontOfSize:"), fontSizeFloat);
|
||||||
|
|
||||||
|
// Check user supplied font
|
||||||
|
if( STR_HAS_CHARS(fontName) ) {
|
||||||
|
id fontNameAsNSString = str(fontName);
|
||||||
|
id userFont = ((id(*)(id, SEL, id, CGFloat))objc_msgSend)(c("NSFont"), s("fontWithName:size:"), fontNameAsNSString, fontSizeFloat);
|
||||||
|
if( userFont != NULL ) {
|
||||||
|
font = userFont;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
id fan = lookupStringConstant(str("NSFontAttributeName"));
|
||||||
|
id NSForegroundColorAttributeName = lookupStringConstant(str("NSForegroundColorAttributeName"));
|
||||||
|
id NSBackgroundColorAttributeName = lookupStringConstant(str("NSBackgroundColorAttributeName"));
|
||||||
|
|
||||||
|
// Loop over styled text creating NSAttributedText and appending to result
|
||||||
|
JsonNode *styledLabelEntry;
|
||||||
|
json_foreach(styledLabelEntry, styledLabel) {
|
||||||
|
|
||||||
|
// Clear dictionary
|
||||||
|
msg_reg(dictionary, s("removeAllObjects"));
|
||||||
|
|
||||||
|
// Add font to dictionary
|
||||||
|
msg_id_id(dictionary, s("setObject:forKey:"), font, fan);
|
||||||
|
|
||||||
|
// Get Text
|
||||||
|
const char* thisLabel = mustJSONString(styledLabelEntry, "Label");
|
||||||
|
|
||||||
|
// Get foreground colour
|
||||||
|
const char *hexColour = getColour(styledLabelEntry, "FgCol");
|
||||||
|
if( hexColour != NULL) {
|
||||||
|
unsigned short r, g, b, a;
|
||||||
|
|
||||||
|
// white by default
|
||||||
|
r = g = b = a = 255;
|
||||||
|
int count = sscanf(hexColour, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a);
|
||||||
|
if (count > 0) {
|
||||||
|
id colour = ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend)(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
|
||||||
|
(CGFloat)r / (CGFloat)255.0,
|
||||||
|
(CGFloat)g / (CGFloat)255.0,
|
||||||
|
(CGFloat)b / (CGFloat)255.0,
|
||||||
|
(CGFloat)a / (CGFloat)255.0);
|
||||||
|
msg_id_id(dictionary, s("setObject:forKey:"), colour, NSForegroundColorAttributeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get background colour
|
||||||
|
hexColour = getColour(styledLabelEntry, "BgCol");
|
||||||
|
if( hexColour != NULL) {
|
||||||
|
unsigned short r, g, b, a;
|
||||||
|
|
||||||
|
// white by default
|
||||||
|
r = g = b = a = 255;
|
||||||
|
int count = sscanf(hexColour, "#%02hx%02hx%02hx%02hx", &r, &g, &b, &a);
|
||||||
|
if (count > 0) {
|
||||||
|
id colour = ((id(*)(id, SEL, CGFloat, CGFloat, CGFloat, CGFloat))objc_msgSend)(c("NSColor"), s("colorWithCalibratedRed:green:blue:alpha:"),
|
||||||
|
(CGFloat)r / (CGFloat)255.0,
|
||||||
|
(CGFloat)g / (CGFloat)255.0,
|
||||||
|
(CGFloat)b / (CGFloat)255.0,
|
||||||
|
(CGFloat)a / (CGFloat)255.0);
|
||||||
|
msg_id_id(dictionary, s("setObject:forKey:"), colour, NSForegroundColorAttributeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create AttributedText
|
||||||
|
id thisString = ALLOC("NSMutableAttributedString");
|
||||||
|
msg_reg(thisString, s("autorelease"));
|
||||||
|
msg_id_id(thisString, s("initWithString:attributes:"), str(thisLabel), dictionary);
|
||||||
|
|
||||||
|
// Append text to result
|
||||||
|
msg_id(attributedString, s("appendAttributedString:"), thisString);
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributedString;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
id createAttributedString(const char* title, const char* fontName, int fontSize, const char* RGBA) {
|
||||||
|
|
||||||
|
// Create new Dictionary
|
||||||
|
id dictionary = ALLOC_INIT("NSMutableDictionary");
|
||||||
CGFloat fontSizeFloat = (CGFloat)fontSize;
|
CGFloat fontSizeFloat = (CGFloat)fontSize;
|
||||||
|
|
||||||
// Check if valid
|
// Use default font
|
||||||
id fontNameAsNSString = str(fontName);
|
id font = ((id(*)(id, SEL, CGFloat))objc_msgSend)(c("NSFont"), s("menuBarFontOfSize:"), fontSizeFloat);
|
||||||
id fontsOnSystem = msg(msg(c("NSFontManager"), s("sharedFontManager")), s("availableFonts"));
|
|
||||||
bool valid = msg(fontsOnSystem, s("containsObject:"), fontNameAsNSString);
|
// Check user supplied font
|
||||||
if( valid ) {
|
if( STR_HAS_CHARS(fontName) ) {
|
||||||
font = msg(c("NSFont"), s("fontWithName:size:"), fontNameAsNSString, fontSizeFloat);
|
id fontNameAsNSString = str(fontName);
|
||||||
} else {
|
id userFont = ((id(*)(id, SEL, id, CGFloat))objc_msgSend)(c("NSFont"), s("fontWithName:size:"), fontNameAsNSString, fontSizeFloat);
|
||||||
bool supportsMonospacedDigitSystemFont = (bool) msg(c("NSFont"), s("respondsToSelector:"), s("monospacedDigitSystemFontOfSize:weight:"));
|
if( userFont != NULL ) {
|
||||||
if( supportsMonospacedDigitSystemFont ) {
|
font = userFont;
|
||||||
font = msg(c("NSFont"), s("monospacedDigitSystemFontOfSize:weight:"), fontSizeFloat, NSFontWeightRegular);
|
|
||||||
} else {
|
|
||||||
font = msg(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(c("NSNumber"), s("numberWithFloat:"), 0.0);
|
|
||||||
msg(dictionary, s("setObject:forKey:"), offset, lookupStringConstant(str("NSBaselineOffsetAttributeName")));
|
|
||||||
|
|
||||||
// RGBA
|
// RGBA
|
||||||
if( RGBA != NULL && strlen(RGBA) > 0) {
|
if( RGBA != NULL && strlen(RGBA) > 0) {
|
||||||
@@ -646,38 +708,75 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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("autorelease"));
|
||||||
|
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, JsonNode* styledLabel) {
|
||||||
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 nsimage = createImageFromBase64Data(image, templateImage);
|
||||||
|
msg_id(item, s("setImage:"), nsimage);
|
||||||
|
}
|
||||||
|
|
||||||
|
id attributedString = NULL;
|
||||||
|
if( styledLabel != NULL) {
|
||||||
|
attributedString = createAttributedStringFromStyledLabel(styledLabel, fontName, fontSize);
|
||||||
|
} else {
|
||||||
|
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 && !alternate) {
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// alternate
|
// alternate
|
||||||
if( alternate ) {
|
if( alternate ) {
|
||||||
msg(item, s("setAlternate:"), true);
|
msg_bool(item, s("setAlternate:"), true);
|
||||||
msg(item, s("setKeyEquivalentModifierMask:"), NSEventModifierFlagOption);
|
msg_int(item, s("setKeyEquivalentModifierMask:"), NSEventModifierFlagOption);
|
||||||
}
|
}
|
||||||
msg(parentMenu, s("addItem:"), item);
|
msg_id(parentMenu, s("addItem:"), item);
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@@ -698,38 +797,6 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is a submenu
|
|
||||||
JsonNode *submenu = json_find_member(item, "SubMenu");
|
|
||||||
if( submenu != NULL ) {
|
|
||||||
// Get the label
|
|
||||||
JsonNode *menuNameNode = json_find_member(item, "Label");
|
|
||||||
const char *name = "";
|
|
||||||
if ( menuNameNode != NULL) {
|
|
||||||
name = menuNameNode->string_;
|
|
||||||
}
|
|
||||||
|
|
||||||
id thisMenuItem = createMenuItemNoAutorelease(str(name), NULL, "");
|
|
||||||
id thisMenu = createMenu(str(name));
|
|
||||||
|
|
||||||
msg(thisMenuItem, s("setSubmenu:"), thisMenu);
|
|
||||||
msg(parentMenu, s("addItem:"), thisMenuItem);
|
|
||||||
|
|
||||||
JsonNode *submenuItems = json_find_member(submenu, "Items");
|
|
||||||
// If we have no items, just return
|
|
||||||
if ( submenuItems == NULL ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop over submenu items
|
|
||||||
JsonNode *item;
|
|
||||||
json_foreach(item, submenuItems) {
|
|
||||||
// Get item label
|
|
||||||
processMenuItem(menu, thisMenu, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a user menu. Get the common data
|
// This is a user menu. Get the common data
|
||||||
// Get the label
|
// Get the label
|
||||||
const char *label = getJSONString(item, "Label");
|
const char *label = getJSONString(item, "Label");
|
||||||
@@ -737,6 +804,8 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
|||||||
label = "(empty)";
|
label = "(empty)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for a styled label
|
||||||
|
JsonNode *styledLabel = getJSONObject(item, "StyledLabel");
|
||||||
|
|
||||||
// Is this an alternate menu item?
|
// Is this an alternate menu item?
|
||||||
bool alternate = false;
|
bool alternate = false;
|
||||||
@@ -762,7 +831,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
|
||||||
@@ -795,9 +864,36 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
|||||||
// Get the Type
|
// Get the Type
|
||||||
JsonNode *type = json_find_member(item, "Type");
|
JsonNode *type = json_find_member(item, "Type");
|
||||||
if( type != NULL ) {
|
if( type != NULL ) {
|
||||||
|
if( STREQ(type->string_, "Text") || STREQ(type->string_, "Submenu")) {
|
||||||
|
id thisMenuItem = processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage, alternate, styledLabel);
|
||||||
|
|
||||||
if( STREQ(type->string_, "Text")) {
|
// Check if this node has a submenu
|
||||||
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage, alternate);
|
JsonNode *submenu = json_find_member(item, "SubMenu");
|
||||||
|
if( submenu != NULL ) {
|
||||||
|
// Get the label
|
||||||
|
JsonNode *menuNameNode = json_find_member(item, "Label");
|
||||||
|
const char *name = "";
|
||||||
|
if ( menuNameNode != NULL) {
|
||||||
|
name = menuNameNode->string_;
|
||||||
|
}
|
||||||
|
|
||||||
|
id thisMenu = createMenu(str(name));
|
||||||
|
|
||||||
|
msg_id(thisMenuItem, s("setSubmenu:"), thisMenu);
|
||||||
|
|
||||||
|
JsonNode *submenuItems = json_find_member(submenu, "Items");
|
||||||
|
// If we have no items, just return
|
||||||
|
if ( submenuItems == NULL ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop over submenu items
|
||||||
|
JsonNode *item;
|
||||||
|
json_foreach(item, submenuItems) {
|
||||||
|
// Get item label
|
||||||
|
processMenuItem(menu, thisMenu, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if ( STREQ(type->string_, "Separator")) {
|
else if ( STREQ(type->string_, "Separator")) {
|
||||||
addSeparator(parentMenu);
|
addSeparator(parentMenu);
|
||||||
@@ -816,7 +912,6 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
|||||||
|
|
||||||
processRadioMenuItem(menu, parentMenu, label, menuid, disabled, checked, "");
|
processRadioMenuItem(menu, parentMenu, label, menuid, disabled, checked, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( modifiers != NULL ) {
|
if ( modifiers != NULL ) {
|
||||||
|
|||||||
@@ -105,10 +105,13 @@ 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, bool alternate);
|
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, JsonNode* styledLabel);
|
||||||
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);
|
||||||
|
id createAttributedStringFromStyledLabel(JsonNode *styledLabel, const char* fontName, int fontSize);
|
||||||
|
|
||||||
#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,25 @@ 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);
|
||||||
|
|
||||||
|
result->styledLabel = getJSONObject(processedJSON, "StyledLabel");
|
||||||
|
|
||||||
// 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 +65,28 @@ 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, JsonNode *styledLabel) {
|
||||||
|
|
||||||
// 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 = NULL;
|
||||||
|
if( styledLabel != NULL) {
|
||||||
|
attributedString = createAttributedStringFromStyledLabel(styledLabel, fontName, fontSize);
|
||||||
|
} else {
|
||||||
|
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 +96,57 @@ 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) {
|
||||||
|
trayImage = createImageFromBase64Data(trayMenu->icon, trayMenu->templateImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
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, trayMenu->styledLabel);
|
||||||
|
|
||||||
// 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
|
||||||
@@ -127,6 +168,7 @@ void UpdateTrayMenuInPlace(TrayMenu* currentMenu, TrayMenu* newMenu) {
|
|||||||
// Copy the other data
|
// Copy the other data
|
||||||
currentMenu->ID = newMenu->ID;
|
currentMenu->ID = newMenu->ID;
|
||||||
currentMenu->label = newMenu->label;
|
currentMenu->label = newMenu->label;
|
||||||
|
currentMenu->styledLabel = newMenu->styledLabel;
|
||||||
currentMenu->trayIconPosition = newMenu->trayIconPosition;
|
currentMenu->trayIconPosition = newMenu->trayIconPosition;
|
||||||
currentMenu->icon = newMenu->icon;
|
currentMenu->icon = newMenu->icon;
|
||||||
|
|
||||||
@@ -147,12 +189,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 +228,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:"), (id)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,26 @@ 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;
|
||||||
|
|
||||||
|
JsonNode* styledLabel;
|
||||||
|
|
||||||
|
id delegate;
|
||||||
|
|
||||||
} TrayMenu;
|
} TrayMenu;
|
||||||
|
|
||||||
TrayMenu* NewTrayMenu(const char *trayJSON);
|
TrayMenu* NewTrayMenu(const char *trayJSON);
|
||||||
@@ -28,7 +40,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, JsonNode *styledLabel);
|
||||||
|
|
||||||
void LoadTrayIcons();
|
void LoadTrayIcons();
|
||||||
void UnloadTrayIcons();
|
void UnloadTrayIcons();
|
||||||
|
|||||||
@@ -118,7 +118,19 @@ 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);
|
||||||
|
|
||||||
|
JsonNode *styledLabel = getJSONObject(parsedUpdate, "StyledLabel");
|
||||||
|
|
||||||
|
UpdateTrayLabel(menu, Label, fontName, fontSize, RGBA, tooltip, disabled, styledLabel);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#ifndef TRAYMENUSTORE_DARWIN_H
|
#ifndef TRAYMENUSTORE_DARWIN_H
|
||||||
#define TRAYMENUSTORE_DARWIN_H
|
#define TRAYMENUSTORE_DARWIN_H
|
||||||
|
|
||||||
|
#include "traymenu_darwin.h"
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -26,6 +28,8 @@ 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);
|
void DeleteTrayMenuInStore(TrayMenuStore* store, const char* id);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package menumanager
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/leaanthony/go-ansi-parser"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
"github.com/wailsapp/wails/v2/pkg/menu/keys"
|
||||||
@@ -41,11 +44,25 @@ type ProcessedMenuItem struct {
|
|||||||
|
|
||||||
// Tooltip
|
// Tooltip
|
||||||
Tooltip string `json:",omitempty"`
|
Tooltip string `json:",omitempty"`
|
||||||
|
|
||||||
|
// Styled label
|
||||||
|
StyledLabel []*ansi.StyledText `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *ProcessedMenuItem {
|
func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *ProcessedMenuItem {
|
||||||
|
|
||||||
ID := menuItemMap.menuItemToIDMap[menuItem]
|
ID := menuItemMap.menuItemToIDMap[menuItem]
|
||||||
|
|
||||||
|
// Parse ANSI text
|
||||||
|
var styledLabel []*ansi.StyledText
|
||||||
|
tempLabel := menuItem.Label
|
||||||
|
if strings.Contains(tempLabel, "\033[") {
|
||||||
|
parsedLabel, err := ansi.Parse(menuItem.Label)
|
||||||
|
if err == nil {
|
||||||
|
styledLabel = parsedLabel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result := &ProcessedMenuItem{
|
result := &ProcessedMenuItem{
|
||||||
ID: ID,
|
ID: ID,
|
||||||
Label: menuItem.Label,
|
Label: menuItem.Label,
|
||||||
@@ -63,6 +80,7 @@ func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *Pr
|
|||||||
MacTemplateImage: menuItem.MacTemplateImage,
|
MacTemplateImage: menuItem.MacTemplateImage,
|
||||||
MacAlternate: menuItem.MacAlternate,
|
MacAlternate: menuItem.MacAlternate,
|
||||||
Tooltip: menuItem.Tooltip,
|
Tooltip: menuItem.Tooltip,
|
||||||
|
StyledLabel: styledLabel,
|
||||||
}
|
}
|
||||||
|
|
||||||
if menuItem.SubMenu != nil {
|
if menuItem.SubMenu != nil {
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/leaanthony/go-ansi-parser"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||||
)
|
)
|
||||||
@@ -23,12 +26,20 @@ func generateTrayID() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
StyledLabel []*ansi.StyledText `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TrayMenu) AsJSON() (string, error) {
|
func (t *TrayMenu) AsJSON() (string, error) {
|
||||||
@@ -41,11 +52,29 @@ func (t *TrayMenu) AsJSON() (string, error) {
|
|||||||
|
|
||||||
func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||||
|
|
||||||
|
// Parse ANSI text
|
||||||
|
var styledLabel []*ansi.StyledText
|
||||||
|
tempLabel := trayMenu.Label
|
||||||
|
if strings.Contains(tempLabel, "\033[") {
|
||||||
|
parsedLabel, err := ansi.Parse(tempLabel)
|
||||||
|
if err == nil {
|
||||||
|
styledLabel = parsedLabel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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,
|
||||||
|
StyledLabel: styledLabel,
|
||||||
}
|
}
|
||||||
|
|
||||||
result.menuItemMap.AddMenu(trayMenu.Menu)
|
result.menuItemMap.AddMenu(trayMenu.Menu)
|
||||||
@@ -54,6 +83,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)
|
||||||
|
|
||||||
@@ -113,13 +164,39 @@ func (m *Manager) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LabelUpdate struct {
|
type LabelUpdate struct {
|
||||||
ID string
|
ID string
|
||||||
Label string
|
Label string `json:",omitempty"`
|
||||||
|
FontName string `json:",omitempty"`
|
||||||
|
FontSize int
|
||||||
|
RGBA string `json:",omitempty"`
|
||||||
|
Disabled bool
|
||||||
|
Tooltip string `json:",omitempty"`
|
||||||
|
Image string `json:",omitempty"`
|
||||||
|
MacTemplateImage bool
|
||||||
|
StyledLabel []*ansi.StyledText `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse ANSI text
|
||||||
|
var styledLabel []*ansi.StyledText
|
||||||
|
tempLabel := trayMenu.Label
|
||||||
|
if strings.Contains(tempLabel, "\033[") {
|
||||||
|
parsedLabel, err := ansi.Parse(tempLabel)
|
||||||
|
if err == nil {
|
||||||
|
styledLabel = parsedLabel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
|
StyledLabel: styledLabel,
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(update)
|
data, err := json.Marshal(update)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ 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
|
||||||
@@ -58,6 +61,7 @@ func NewRuntime(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.
|
|||||||
runtime: runtime.New(bus),
|
runtime: runtime.New(bus),
|
||||||
startupCallback: startupCallback,
|
startupCallback: startupCallback,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
bus: bus,
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
@@ -79,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!")
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ func (u *URL) Start() error {
|
|||||||
u.wg.Done()
|
u.wg.Done()
|
||||||
return
|
return
|
||||||
case urlMessage := <-u.urlChannel:
|
case urlMessage := <-u.urlChannel:
|
||||||
|
// Guard against nil messages
|
||||||
|
if urlMessage == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
messageType := strings.TrimPrefix(urlMessage.Topic(), "url:")
|
messageType := strings.TrimPrefix(urlMessage.Topic(), "url:")
|
||||||
switch messageType {
|
switch messageType {
|
||||||
case "handler":
|
case "handler":
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Angular",
|
|
||||||
"shortname": "angular",
|
|
||||||
"author": "bh90210 <ktc@pm.me>",
|
|
||||||
"description": "Angular projects w/ @angular/cli - Note: in order to reach the cli use npx like this: npx ng"
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "React JS",
|
|
||||||
"shortname": "react",
|
|
||||||
"author": "bh90210 <ktc@pm.me>",
|
|
||||||
"description": "Create React App v3 standar tooling"
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Basic application struct
|
|
||||||
type Basic struct {
|
|
||||||
runtime *wails.Runtime
|
|
||||||
}
|
|
||||||
|
|
||||||
// newBasic creates a new Basic application struct
|
|
||||||
func newBasic() *Basic {
|
|
||||||
return &Basic{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WailsInit is called at application startup
|
|
||||||
func (b *Basic) WailsInit(runtime *wails.Runtime) error {
|
|
||||||
// Perform your setup here
|
|
||||||
b.runtime = runtime
|
|
||||||
runtime.Window.SetTitle("{{.ProjectName}}")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WailsShutdown is called at application termination
|
|
||||||
func (b *Basic) WailsShutdown() {
|
|
||||||
// Perform your teardown here
|
|
||||||
}
|
|
||||||
|
|
||||||
// Greet returns a greeting for the given name
|
|
||||||
func (b *Basic) Greet(name string) string {
|
|
||||||
return fmt.Sprintf("Hello %s!", name)
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
/node_modules/
|
|
||||||
/public/build/
|
|
||||||
|
|
||||||
.DS_Store
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
*Looking for a shareable component template? Go here --> [sveltejs/component-template](https://github.com/sveltejs/component-template)*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# svelte app
|
|
||||||
|
|
||||||
This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template.
|
|
||||||
|
|
||||||
To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx degit sveltejs/template svelte-app
|
|
||||||
cd svelte-app
|
|
||||||
```
|
|
||||||
|
|
||||||
*Note that you will need to have [Node.js](https://nodejs.org) installed.*
|
|
||||||
|
|
||||||
|
|
||||||
## Get started
|
|
||||||
|
|
||||||
Install the dependencies...
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd svelte-app
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
...then start [Rollup](https://rollupjs.org):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
|
|
||||||
|
|
||||||
By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`.
|
|
||||||
|
|
||||||
|
|
||||||
## Building and running in production mode
|
|
||||||
|
|
||||||
To create an optimised version of the app:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com).
|
|
||||||
|
|
||||||
|
|
||||||
## Single-page app mode
|
|
||||||
|
|
||||||
By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere.
|
|
||||||
|
|
||||||
If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json:
|
|
||||||
|
|
||||||
```js
|
|
||||||
"start": "sirv public --single"
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Deploying to the web
|
|
||||||
|
|
||||||
### With [now](https://zeit.co/now)
|
|
||||||
|
|
||||||
Install `now` if you haven't already:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install -g now
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, from within your project folder:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd public
|
|
||||||
now deploy --name my-project
|
|
||||||
```
|
|
||||||
|
|
||||||
As an alternative, use the [Now desktop client](https://zeit.co/download) and simply drag the unzipped project folder to the taskbar icon.
|
|
||||||
|
|
||||||
### With [surge](https://surge.sh/)
|
|
||||||
|
|
||||||
Install `surge` if you haven't already:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install -g surge
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, from within your project folder:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
surge public my-project.surge.sh
|
|
||||||
```
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "svelte-app",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"scripts": {
|
|
||||||
"build": "npx rollup -c",
|
|
||||||
"dev": "npx rollup -c -w",
|
|
||||||
"start": "npx sirv public",
|
|
||||||
"start:dev": "npx sirv public --single --host 0.0.0.0 --dev"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@rollup/plugin-commonjs": "^17.0.0",
|
|
||||||
"@rollup/plugin-node-resolve": "^11.0.1",
|
|
||||||
"focus-visible": "^5.2.0",
|
|
||||||
"rollup": "^2.35.1",
|
|
||||||
"rollup-plugin-livereload": "^2.0.0",
|
|
||||||
"rollup-plugin-svelte": "^7.0.0",
|
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
|
||||||
"svelte": "^3.31.1",
|
|
||||||
"svelte-mui": "^0.3.3-5"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"sirv-cli": "^0.4.4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.9 KiB |
@@ -1,24 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
||||||
<meta name="mobile-web-app-capable" content="yes" />
|
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
|
||||||
<meta name="apple-mobile-web-app-title" content="svelte-mui" />
|
|
||||||
<meta name="application-name" content="svelte-mui" />
|
|
||||||
<meta name="theme-color" content="#212121" />
|
|
||||||
|
|
||||||
<title>Svelte app</title>
|
|
||||||
|
|
||||||
<link rel='icon' type='image/png' href='/favicon.png'>
|
|
||||||
<link rel='stylesheet' href='/bundle.css'>
|
|
||||||
|
|
||||||
<script defer src='/bundle.js'></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>Please enable JavaScript.</noscript>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import svelte from 'rollup-plugin-svelte';
|
|
||||||
import resolve from '@rollup/plugin-node-resolve';
|
|
||||||
import commonjs from '@rollup/plugin-commonjs';
|
|
||||||
import livereload from 'rollup-plugin-livereload';
|
|
||||||
import { terser } from 'rollup-plugin-terser';
|
|
||||||
|
|
||||||
const production = !process.env.ROLLUP_WATCH;
|
|
||||||
|
|
||||||
export default {
|
|
||||||
input: 'src/main.js',
|
|
||||||
output: {
|
|
||||||
sourcemap: true,
|
|
||||||
format: 'iife',
|
|
||||||
name: 'app',
|
|
||||||
file: 'public/bundle.js'
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
svelte({
|
|
||||||
// enable run-time checks when not in production
|
|
||||||
dev: !production,
|
|
||||||
// we'll extract any component CSS out into
|
|
||||||
// a separate file - better for performance
|
|
||||||
css: css => {
|
|
||||||
css.write('public/bundle.css');
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
// If you have external dependencies installed from
|
|
||||||
// npm, you'll most likely need these plugins. In
|
|
||||||
// some cases you'll need additional configuration -
|
|
||||||
// consult the documentation for details:
|
|
||||||
// https://github.com/rollup/plugins/tree/master/packages/commonjs
|
|
||||||
resolve({
|
|
||||||
browser: true,
|
|
||||||
dedupe: ['svelte']
|
|
||||||
}),
|
|
||||||
commonjs(),
|
|
||||||
|
|
||||||
// In dev mode, call `npm run start` once
|
|
||||||
// the bundle has been generated
|
|
||||||
!production && serve(),
|
|
||||||
|
|
||||||
// Watch the `public` directory and refresh the
|
|
||||||
// browser on changes when not in production
|
|
||||||
!production && livereload('public'),
|
|
||||||
|
|
||||||
// If we're building for production (npm run build
|
|
||||||
// instead of npm run dev), minify
|
|
||||||
production && terser()
|
|
||||||
],
|
|
||||||
watch: {
|
|
||||||
clearScreen: false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function serve() {
|
|
||||||
let started = false;
|
|
||||||
|
|
||||||
return {
|
|
||||||
writeBundle() {
|
|
||||||
if (!started) {
|
|
||||||
started = true;
|
|
||||||
|
|
||||||
require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
|
|
||||||
stdio: ['ignore', 'inherit', 'inherit'],
|
|
||||||
shell: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
<h1>Hello {name}!</h1>
|
|
||||||
|
|
||||||
<Snackbar bind:visible bg="#f44336">
|
|
||||||
{response}
|
|
||||||
<span slot="action">
|
|
||||||
<Button color="#ff0" on:click={() => (visible = false)}>Close</Button>
|
|
||||||
</span>
|
|
||||||
</Snackbar>
|
|
||||||
|
|
||||||
<Textfield
|
|
||||||
autocomplete="off"
|
|
||||||
label="Message"
|
|
||||||
required
|
|
||||||
bind:value
|
|
||||||
message=""
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
outlined
|
|
||||||
shaped
|
|
||||||
color="Red"
|
|
||||||
on:click={() => {
|
|
||||||
window.backend.main.Basic.Greet(value).then((result) => {
|
|
||||||
response = result;
|
|
||||||
visible = true;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Hello
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export let name;
|
|
||||||
let value = '';
|
|
||||||
let response = '';
|
|
||||||
|
|
||||||
// optional import focus-visible polyfill only once
|
|
||||||
import 'focus-visible';
|
|
||||||
// import any components
|
|
||||||
import { Button, Checkbox, Snackbar, Textfield } from 'svelte-mui';
|
|
||||||
|
|
||||||
let checked = true;
|
|
||||||
let visible = false;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
h1 {
|
|
||||||
color: purple;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import App from './App.svelte';
|
|
||||||
|
|
||||||
const app = new App({
|
|
||||||
target: document.body,
|
|
||||||
props: {
|
|
||||||
name: 'Wails User'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default app;
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
module test
|
|
||||||
|
|
||||||
go 1.13
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/wailsapp/wails/v2 v2.0.0-alpha
|
|
||||||
)
|
|
||||||
|
|
||||||
replace github.com/wailsapp/wails/v2 v2.0.0-alpha => {{.WailsDirectory}}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/wailsapp/wails/v2"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
// Create application with options
|
|
||||||
app, err := wails.CreateApp("{{.ProjectName}}", 1024, 768)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.Bind(newBasic())
|
|
||||||
|
|
||||||
err = app.Run()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Svelte + MUI3",
|
|
||||||
"shortname": "svelte-mui",
|
|
||||||
"author": "Travis McLane <travis.mclane@gmail.com>",
|
|
||||||
"description": "A simple template using Svelte + Mui3",
|
|
||||||
"helpurl": "https://wails.app/help/templates/svelte-mui3"
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "{{.ProjectName}}",
|
|
||||||
"outputfilename": "{{.BinaryName}}",
|
|
||||||
"html": "frontend/public/index.html",
|
|
||||||
"js": "frontend/public/bundle.js",
|
|
||||||
"css": "frontend/public/bundle.css",
|
|
||||||
"frontend:build": "npm run build",
|
|
||||||
"frontend:install": "npm install"
|
|
||||||
}
|
|
||||||
@@ -23,6 +23,7 @@ button {
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
outline: none;
|
outline: none;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
}
|
}
|
||||||
|
|
||||||
#logo {
|
#logo {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
// Get input + focus
|
// Get input + focus
|
||||||
var nameElement = document.getElementById("name");
|
let nameElement = document.getElementById("name");
|
||||||
nameElement.focus();
|
nameElement.focus();
|
||||||
|
|
||||||
// Stup the greet function
|
// Setup the greet function
|
||||||
window.greet = function () {
|
window.greet = function () {
|
||||||
|
|
||||||
// Get name
|
// Get name
|
||||||
var name = nameElement.value;
|
let name = nameElement.value;
|
||||||
|
|
||||||
// Call Basic.Greet(name)
|
// Call Basic.Greet(name)
|
||||||
window.backend.main.Basic.Greet(name).then((result) => {
|
window.backend.main.Basic.Greet(name).then((result) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module test
|
module test
|
||||||
|
|
||||||
go 1.13
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/wailsapp/wails/v2 v2.0.0-alpha
|
github.com/wailsapp/wails/v2 v2.0.0-alpha
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Vue2/Webpack Basic",
|
|
||||||
"shortname": "vue",
|
|
||||||
"author": "Lea Anthony<lea.anthony@gmail.com>",
|
|
||||||
"description": "A basic template using Vue 2 and bundled using Webpack 4"
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Vuetify1.5/Webpack Basic",
|
|
||||||
"shortname": "vuetify1",
|
|
||||||
"author": "Lea Anthony <lea.anthony@gmail.com>",
|
|
||||||
"description": "Basic template using Vuetify v1.5 and bundled using Webpack"
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Basic application struct
|
|
||||||
type Basic struct {
|
|
||||||
runtime *wails.Runtime
|
|
||||||
}
|
|
||||||
|
|
||||||
// newBasic creates a new Basic application struct
|
|
||||||
func newBasic() *Basic {
|
|
||||||
return &Basic{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WailsInit is called at application startup
|
|
||||||
func (b *Basic) WailsInit(runtime *wails.Runtime) error {
|
|
||||||
// Perform your setup here
|
|
||||||
b.runtime = runtime
|
|
||||||
runtime.Window.SetTitle("{{.ProjectName}}")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WailsShutdown is called at application termination
|
|
||||||
func (b *Basic) WailsShutdown() {
|
|
||||||
// Perform your teardown here
|
|
||||||
}
|
|
||||||
|
|
||||||
// Greet returns a greeting for the given name
|
|
||||||
func (b *Basic) Greet(name string) string {
|
|
||||||
return fmt.Sprintf("Hello %s!", name)
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
presets: [
|
|
||||||
[ '@vue/app', { useBuiltIns: 'entry' } ]
|
|
||||||
]
|
|
||||||
};
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "vuetify2",
|
|
||||||
"author": "{{.AuthorNameAndEmail}}",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"serve": "vue-cli-service serve",
|
|
||||||
"build": "vue-cli-service build",
|
|
||||||
"lint": "vue-cli-service lint"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"core-js": "^3.6.1",
|
|
||||||
"regenerator-runtime": "^0.13.3",
|
|
||||||
"vue": "^2.5.22",
|
|
||||||
"vuetify": "^2.2.17"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@mdi/font": "^4.3.95",
|
|
||||||
"@vue/cli-plugin-babel": "^3.4.0",
|
|
||||||
"@vue/cli-plugin-eslint": "^3.4.0",
|
|
||||||
"@vue/cli-service": "^3.4.0",
|
|
||||||
"babel-eslint": "^10.0.1",
|
|
||||||
"eslint": "^5.8.0",
|
|
||||||
"eslint-plugin-vue": "^5.0.0",
|
|
||||||
"eventsource-polyfill": "^0.9.6",
|
|
||||||
"vue-template-compiler": "^2.5.21",
|
|
||||||
"webpack-hot-middleware": "^2.24.3"
|
|
||||||
},
|
|
||||||
"eslintConfig": {
|
|
||||||
"root": true,
|
|
||||||
"env": {
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"plugin:vue/essential",
|
|
||||||
"eslint:recommended"
|
|
||||||
],
|
|
||||||
"rules": {},
|
|
||||||
"parserOptions": {
|
|
||||||
"parser": "babel-eslint"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"postcss": {
|
|
||||||
"plugins": {
|
|
||||||
"autoprefixer": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"browserslist": [
|
|
||||||
"> 1%",
|
|
||||||
"last 2 versions",
|
|
||||||
"not ie <= 8"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><title>Wails</title></head><body><div id=app></div></body></html>
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-app id="inspire">
|
|
||||||
<v-navigation-drawer v-model="drawer" clipped fixed app>
|
|
||||||
<v-list dense>
|
|
||||||
<v-list-item>
|
|
||||||
<v-list-item-icon>
|
|
||||||
<v-icon>mdi-view-dashboard</v-icon>
|
|
||||||
</v-list-item-icon>
|
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title>Dashboard</v-list-item-title>
|
|
||||||
</v-list-item-content>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item>
|
|
||||||
<v-list-item-icon>
|
|
||||||
<v-icon>mdi-settings</v-icon>
|
|
||||||
</v-list-item-icon>
|
|
||||||
<v-list-item-content>
|
|
||||||
<v-list-item-title>Settings</v-list-item-title>
|
|
||||||
</v-list-item-content>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-navigation-drawer>
|
|
||||||
<v-app-bar app fixed clipped-left>
|
|
||||||
<v-app-bar-nav-icon @click.stop="drawer = !drawer"></v-app-bar-nav-icon>
|
|
||||||
<v-toolbar-title>Application</v-toolbar-title>
|
|
||||||
</v-app-bar>
|
|
||||||
<v-content>
|
|
||||||
<v-container fluid class="px-0">
|
|
||||||
<v-layout justify-center align-center class="px-0">
|
|
||||||
<hello-world></hello-world>
|
|
||||||
</v-layout>
|
|
||||||
</v-container>
|
|
||||||
</v-content>
|
|
||||||
<v-footer app fixed>
|
|
||||||
<span style="margin-left:1em">© You</span>
|
|
||||||
</v-footer>
|
|
||||||
</v-app>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import HelloWorld from "./components/HelloWorld.vue"
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data: () => ({
|
|
||||||
drawer: false
|
|
||||||
}),
|
|
||||||
components: {
|
|
||||||
HelloWorld
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
source: String
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.logo {
|
|
||||||
width: 16em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 301 KiB |
@@ -1,85 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-container fluid class="px-0">
|
|
||||||
<v-layout>
|
|
||||||
<v-flex xs12 sm6 offset-sm3>
|
|
||||||
<v-card raised="raised" class="pa-4 ma-4">
|
|
||||||
<v-layout justify-center align-center class="pa-4 ma-4">
|
|
||||||
<v-img :src="require('../assets/images/logo.png')"></v-img>
|
|
||||||
</v-layout>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-layout justify-center align-center class="px-0">
|
|
||||||
<v-btn color="blue" @click="getMessage">Press Me</v-btn>
|
|
||||||
</v-layout>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</v-flex>
|
|
||||||
</v-layout>
|
|
||||||
<div class="text-xs-center">
|
|
||||||
<v-dialog v-model="dialog" width="500">
|
|
||||||
<v-card>
|
|
||||||
<v-card-title class="headline" primary-title>Message from Go</v-card-title>
|
|
||||||
<v-card-text>{{message}}</v-card-text>
|
|
||||||
<v-divider></v-divider>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn color="primary" text @click="dialog = false">Awesome</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</v-dialog>
|
|
||||||
</div>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
message: " ",
|
|
||||||
raised: true,
|
|
||||||
dialog: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getMessage: function () {
|
|
||||||
var self = this
|
|
||||||
window.backend.main.Basic.Greet("Hello from Go!").then(result => {
|
|
||||||
self.message = result
|
|
||||||
self.dialog = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
||||||
<style scoped>
|
|
||||||
h1 {
|
|
||||||
margin-top: 2em;
|
|
||||||
position: relative;
|
|
||||||
min-height: 5rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
font-size: 1.7em;
|
|
||||||
border-color: blue;
|
|
||||||
background-color: blue;
|
|
||||||
color: white;
|
|
||||||
border: 3px solid white;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 9px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: 500ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-size: 1.7em;
|
|
||||||
border-color: white;
|
|
||||||
background-color: #121212;
|
|
||||||
color: white;
|
|
||||||
border: 3px solid white;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 9px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import 'core-js/stable';
|
|
||||||
import 'regenerator-runtime/runtime';
|
|
||||||
import '@mdi/font/css/materialdesignicons.css';
|
|
||||||
import Vue from 'vue';
|
|
||||||
import Vuetify from 'vuetify';
|
|
||||||
import 'vuetify/dist/vuetify.min.css';
|
|
||||||
|
|
||||||
Vue.use(Vuetify);
|
|
||||||
|
|
||||||
import App from './App.vue';
|
|
||||||
|
|
||||||
Vue.config.productionTip = false;
|
|
||||||
Vue.config.devtools = true;
|
|
||||||
|
|
||||||
Wails.Init(() => {
|
|
||||||
new Vue({
|
|
||||||
vuetify: new Vuetify({
|
|
||||||
icons: {
|
|
||||||
iconfont: 'mdi'
|
|
||||||
},
|
|
||||||
theme: {
|
|
||||||
dark: true
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
render: h => h(App)
|
|
||||||
}).$mount('#app');
|
|
||||||
});
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
let cssConfig = {};
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV == 'production') {
|
|
||||||
cssConfig = {
|
|
||||||
extract: {
|
|
||||||
filename: '[name].css',
|
|
||||||
chunkFilename: '[name].css'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
indexPath: 'index.html',
|
|
||||||
publicPath: 'public',
|
|
||||||
// disable hashes in filenames
|
|
||||||
filenameHashing: false,
|
|
||||||
// delete HTML related webpack plugins
|
|
||||||
chainWebpack: config => {
|
|
||||||
config.plugins.delete('preload');
|
|
||||||
config.plugins.delete('prefetch');
|
|
||||||
config
|
|
||||||
.plugin('html')
|
|
||||||
.tap(args => {
|
|
||||||
args[0].template = 'public/index.html';
|
|
||||||
args[0].inject = false;
|
|
||||||
args[0].cache = false;
|
|
||||||
args[0].minify = false;
|
|
||||||
args[0].filename = 'index.html';
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
|
|
||||||
let limit = 9999999999999999;
|
|
||||||
config.module
|
|
||||||
.rule('images')
|
|
||||||
.test(/\.(png|gif|jpg)(\?.*)?$/i)
|
|
||||||
.use('url-loader')
|
|
||||||
.loader('url-loader')
|
|
||||||
.tap(options => Object.assign(options, { limit: limit }));
|
|
||||||
config.module
|
|
||||||
.rule('fonts')
|
|
||||||
.test(/\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/i)
|
|
||||||
.use('url-loader')
|
|
||||||
.loader('url-loader')
|
|
||||||
.options({
|
|
||||||
limit: limit
|
|
||||||
});
|
|
||||||
},
|
|
||||||
css: cssConfig,
|
|
||||||
configureWebpack: {
|
|
||||||
output: {
|
|
||||||
filename: '[name].js'
|
|
||||||
},
|
|
||||||
optimization: {
|
|
||||||
splitChunks: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
devServer: {
|
|
||||||
disableHostCheck: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
module test
|
|
||||||
|
|
||||||
go 1.13
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/wailsapp/wails/v2 v2.0.0-alpha
|
|
||||||
)
|
|
||||||
|
|
||||||
replace github.com/wailsapp/wails/v2 v2.0.0-alpha => {{.WailsDirectory}}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/wailsapp/wails/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
// Create application with options
|
|
||||||
app := wails.CreateApp("{{.ProjectName}}", 1024, 768)
|
|
||||||
|
|
||||||
app.Bind(newBasic())
|
|
||||||
|
|
||||||
app.Run()
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Vuetify 2 ",
|
|
||||||
"shortname": "vuetify2",
|
|
||||||
"author": "Michael Hipp <michael@redmule.com>",
|
|
||||||
"description": "A simple template using only HTML/CSS/JS",
|
|
||||||
"helpurl": "https://wails.app/help/templates/vuetify2"
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "{{.ProjectName}}",
|
|
||||||
"outputfilename": "{{.BinaryName}}",
|
|
||||||
"html": "frontend/dist/index.html",
|
|
||||||
"js": "frontend/dist/app.js",
|
|
||||||
"css": "frontend/dist/app.css",
|
|
||||||
"frontend:build": "npm run build",
|
|
||||||
"frontend:install": "npm install",
|
|
||||||
"author": {
|
|
||||||
"name": "{{.AuthorName}}",
|
|
||||||
"email": "{{.AuthorEmail}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,6 +10,8 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/leaanthony/slicer"
|
"github.com/leaanthony/slicer"
|
||||||
"github.com/wailsapp/wails/v2/internal/assetdb"
|
"github.com/wailsapp/wails/v2/internal/assetdb"
|
||||||
"github.com/wailsapp/wails/v2/internal/fs"
|
"github.com/wailsapp/wails/v2/internal/fs"
|
||||||
@@ -19,6 +21,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,11 +148,25 @@ 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 {
|
||||||
|
|
||||||
|
verbose := options.Verbosity == VERBOSE
|
||||||
// Run go mod tidy first
|
// Run go mod tidy first
|
||||||
cmd := exec.Command(options.Compiler, "mod", "tidy")
|
cmd := exec.Command(options.Compiler, "mod", "tidy")
|
||||||
|
if verbose {
|
||||||
|
println("")
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
}
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -170,10 +190,11 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
|||||||
|
|
||||||
var tags slicer.StringSlicer
|
var tags slicer.StringSlicer
|
||||||
tags.Add(options.OutputType)
|
tags.Add(options.OutputType)
|
||||||
|
tags.AddSlice(options.UserTags)
|
||||||
if options.Mode == Debug {
|
if options.Mode == Debug {
|
||||||
tags.Add("debug")
|
tags.Add("debug")
|
||||||
}
|
}
|
||||||
|
tags.Deduplicate()
|
||||||
|
|
||||||
// Add the output type build tag
|
// Add the output type build tag
|
||||||
commands.Add("-tags")
|
commands.Add("-tags")
|
||||||
@@ -195,9 +216,11 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
|||||||
|
|
||||||
// Get application build directory
|
// Get application build directory
|
||||||
appDir := options.BuildDirectory
|
appDir := options.BuildDirectory
|
||||||
err = cleanBuildDirectory(options)
|
if options.CleanBuildDirectory {
|
||||||
if err != nil {
|
err = cleanBuildDirectory(options)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.LDFlags != "" {
|
if options.LDFlags != "" {
|
||||||
@@ -206,10 +229,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)
|
||||||
@@ -219,13 +239,14 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
|||||||
|
|
||||||
// Create the command
|
// Create the command
|
||||||
cmd = exec.Command(options.Compiler, commands.AsSlice()...)
|
cmd = exec.Command(options.Compiler, commands.AsSlice()...)
|
||||||
|
if verbose {
|
||||||
|
println(" Build command:", commands.Join(" "))
|
||||||
|
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")
|
||||||
@@ -233,7 +254,32 @@ 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
|
||||||
|
|
||||||
|
// Use upsertEnv so we don't overwrite user's CGO_CFLAGS
|
||||||
|
cmd.Env = upsertEnv(cmd.Env, "CGO_CFLAGS", func(v string) string {
|
||||||
|
if v != "" {
|
||||||
|
v += " "
|
||||||
|
}
|
||||||
|
v += "-I" + buildBaseDir
|
||||||
|
return v
|
||||||
|
})
|
||||||
|
|
||||||
|
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"
|
||||||
|
})
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
println(" Environment:", strings.Join(cmd.Env, " "))
|
||||||
|
}
|
||||||
|
|
||||||
// Setup buffers
|
// Setup buffers
|
||||||
var stdo, stde bytes.Buffer
|
var stdo, stde bytes.Buffer
|
||||||
@@ -248,16 +294,37 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
|||||||
return fmt.Errorf("%s\n%s", err, string(stde.Bytes()))
|
return fmt.Errorf("%s\n%s", err, string(stde.Bytes()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !options.Compress {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do we have upx installed?
|
||||||
|
if !shell.CommandExists("upx") {
|
||||||
|
println("Warning: Cannot compress binary: upx not found")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
println(" Compressing with:", "upx", "--best", "--no-color", "--no-progress", options.CompiledBinary)
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := exec.Command(options.BuildDirectory, "upx", "--best", "--no-color", "--no-progress", options.CompiledBinary).Output()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "Error during compression:")
|
||||||
|
}
|
||||||
|
if verbose {
|
||||||
|
println(output)
|
||||||
|
}
|
||||||
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")
|
||||||
|
|
||||||
@@ -299,7 +366,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)
|
||||||
}
|
}
|
||||||
@@ -348,31 +415,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(" Install command: '" + 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(" Build command: '" + 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") {
|
||||||
@@ -382,7 +458,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
|
||||||
@@ -391,3 +472,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"
|
||||||
)
|
)
|
||||||
@@ -25,20 +28,25 @@ var modeMap = []string{"Debug", "Production"}
|
|||||||
|
|
||||||
// Options contains all the build options as well as the project data
|
// Options contains all the build options as well as the project data
|
||||||
type Options struct {
|
type Options struct {
|
||||||
LDFlags string // Optional flags to pass to linker
|
LDFlags string // Optional flags to pass to linker
|
||||||
Logger *clilogger.CLILogger // All output to the logger
|
UserTags []string // Tags to pass to the Go compiler
|
||||||
OutputType string // EG: desktop, server....
|
Logger *clilogger.CLILogger // All output to the logger
|
||||||
Mode Mode // release or debug
|
OutputType string // EG: desktop, server....
|
||||||
ProjectData *project.Project // The project data
|
Mode Mode // release or debug
|
||||||
Pack bool // Create a package for the app after building
|
ProjectData *project.Project // The project data
|
||||||
Platform string // The platform to build for
|
Pack bool // Create a package for the app after building
|
||||||
Compiler string // The compiler command to use
|
Platform string // The platform to build for
|
||||||
IgnoreFrontend bool // Indicates if the frontend does not need building
|
Arch string // The architecture to build for
|
||||||
OutputFile string // Override the output filename
|
Compiler string // The compiler command to use
|
||||||
BuildDirectory string // Directory to use for building the application
|
IgnoreFrontend bool // Indicates if the frontend does not need building
|
||||||
CompiledBinary string // Fully qualified path to the compiled binary
|
OutputFile string // Override the output filename
|
||||||
KeepAssets bool // /Keep the generated assets/files
|
BuildDirectory string // Directory to use for building the application
|
||||||
AppleIdentity string
|
CleanBuildDirectory bool // Indicates if the build directory should be cleaned before building
|
||||||
|
CompiledBinary string // Fully qualified path to the compiled binary
|
||||||
|
KeepAssets bool // Keep the generated assets/files
|
||||||
|
Verbosity int // Verbosity level (0 - silent, 1 - default, 2 - verbose)
|
||||||
|
Compress bool // Compress the final binary
|
||||||
|
AppleIdentity string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetModeAsString returns the current mode as a string
|
// GetModeAsString returns the current mode as a string
|
||||||
@@ -58,12 +66,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 {
|
||||||
@@ -71,7 +73,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
|
||||||
@@ -107,7 +109,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
|
||||||
@@ -115,30 +116,74 @@ 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
|
||||||
|
options.CleanBuildDirectory = false
|
||||||
|
if options.Verbosity == VERBOSE {
|
||||||
|
println()
|
||||||
|
println(" Building AMD64 Target:", filepath.Join(options.BuildDirectory, options.OutputFile))
|
||||||
|
}
|
||||||
|
err = builder.CompileProject(options)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Build arm64
|
||||||
|
options.Arch = "arm64"
|
||||||
|
options.OutputFile = arm64Filename
|
||||||
|
options.CleanBuildDirectory = false
|
||||||
|
if options.Verbosity == VERBOSE {
|
||||||
|
println(" Building ARM64 Target:", filepath.Join(options.BuildDirectory, options.OutputFile))
|
||||||
|
}
|
||||||
|
err = builder.CompileProject(options)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Run lipo
|
||||||
|
if options.Verbosity == VERBOSE {
|
||||||
|
println(" Running lipo: ", "lipo", "-create", "-output", outputFile, amd64Filename, arm64Filename)
|
||||||
|
}
|
||||||
|
_, 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
|
||||||
|
|||||||
@@ -49,4 +49,4 @@ Example:
|
|||||||
|
|
||||||
## Mac
|
## Mac
|
||||||
|
|
||||||
The `mac` directory holds files specific to Mac builds, such as `info.plist`. These may be edited and used as part of the build.
|
The `mac` directory holds files specific to Mac builds, such as `Info.plist`. These may be edited and used as part of the build.
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func packageApplication(options *Options) error {
|
|||||||
return errors.Wrap(err, "Cannot move file: "+options.ProjectData.OutputFilename)
|
return errors.Wrap(err, "Cannot move file: "+options.ProjectData.OutputFilename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate info.plist
|
// Generate Info.plist
|
||||||
err = processPList(options, contentsDirectory)
|
err = processPList(options, contentsDirectory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -68,7 +68,7 @@ func packageApplication(options *Options) error {
|
|||||||
func processPList(options *Options, contentsDirectory string) error {
|
func processPList(options *Options, contentsDirectory string) error {
|
||||||
|
|
||||||
// Check if plist already exists in project dir
|
// Check if plist already exists in project dir
|
||||||
plistFile := filepath.Join(options.ProjectData.AssetsDir, "mac", "info.plist")
|
plistFile := filepath.Join(options.ProjectData.AssetsDir, "mac", "Info.plist")
|
||||||
|
|
||||||
// If the file doesn't exist, generate it
|
// If the file doesn't exist, generate it
|
||||||
if !fs.FileExists(plistFile) {
|
if !fs.FileExists(plistFile) {
|
||||||
@@ -79,7 +79,7 @@ func processPList(options *Options, contentsDirectory string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Copy it to the contents directory
|
// Copy it to the contents directory
|
||||||
targetFile := filepath.Join(contentsDirectory, "info.plist")
|
targetFile := filepath.Join(contentsDirectory, "Info.plist")
|
||||||
return fs.CopyFile(plistFile, targetFile)
|
return fs.CopyFile(plistFile, targetFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,11 +88,11 @@ func generateDefaultPlist(options *Options, targetPlistFile string) error {
|
|||||||
exe := defaultString(options.OutputFile, name)
|
exe := defaultString(options.OutputFile, name)
|
||||||
version := "1.0.0"
|
version := "1.0.0"
|
||||||
author := defaultString(options.ProjectData.Author.Name, "Anonymous")
|
author := defaultString(options.ProjectData.Author.Name, "Anonymous")
|
||||||
packageID := strings.Join([]string{"wails", name, version}, ".")
|
packageID := strings.Join([]string{"wails", name}, ".")
|
||||||
plistData := newPlistData(name, exe, packageID, version, author)
|
plistData := newPlistData(name, exe, packageID, version, author)
|
||||||
|
|
||||||
tmpl := template.New("infoPlist")
|
tmpl := template.New("infoPlist")
|
||||||
plistTemplate := fs.RelativePath("./internal/packager/darwin/info.plist")
|
plistTemplate := fs.RelativePath("./internal/packager/darwin/Info.plist")
|
||||||
infoPlist, err := ioutil.ReadFile(plistTemplate)
|
infoPlist, err := ioutil.ReadFile(plistTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "Cannot open plist template")
|
return errors.Wrap(err, "Cannot open plist template")
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
60
v2/pkg/mac/login_darwin.go
Normal file
60
v2/pkg/mac/login_darwin.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// Package mac provides MacOS related utility functions for Wails applications
|
||||||
|
package mac
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/leaanthony/slicer"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/wailsapp/wails/v2/internal/shell"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StartAtLogin will either add or remove this application to/from the login
|
||||||
|
// items, depending on the given boolean flag. The limitation is that the
|
||||||
|
// currently running app must be in an app bundle.
|
||||||
|
func StartAtLogin(enabled bool) error {
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "Error running os.Executable:")
|
||||||
|
}
|
||||||
|
binName := filepath.Base(exe)
|
||||||
|
if !strings.HasSuffix(exe, "/Contents/MacOS/"+binName) {
|
||||||
|
return fmt.Errorf("app needs to be running as package.app file to start at login")
|
||||||
|
}
|
||||||
|
appPath := strings.TrimSuffix(exe, "/Contents/MacOS/"+binName)
|
||||||
|
var command string
|
||||||
|
if enabled {
|
||||||
|
command = fmt.Sprintf("tell application \"System Events\" to make login item at end with properties {name: \"%s\",path:\"%s\", hidden:false}", binName, appPath)
|
||||||
|
} else {
|
||||||
|
command = fmt.Sprintf("tell application \"System Events\" to delete login item \"%s\"", binName)
|
||||||
|
}
|
||||||
|
_, stde, err := shell.RunCommand("/tmp", "osascript", "-e", command)
|
||||||
|
if err != nil {
|
||||||
|
errors.Wrap(err, stde)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartsAtLogin will indicate if this application is in the login
|
||||||
|
// items. The limitation is that the currently running app must be
|
||||||
|
// in an app bundle.
|
||||||
|
func StartsAtLogin() (bool, error) {
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
binName := filepath.Base(exe)
|
||||||
|
if !strings.HasSuffix(exe, "/Contents/MacOS/"+binName) {
|
||||||
|
return false, fmt.Errorf("app needs to be running as package.app file to start at login")
|
||||||
|
}
|
||||||
|
results, stde, err := shell.RunCommand("/tmp", "osascript", "-e", `tell application "System Events" to get the name of every login item`)
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.Wrap(err, stde)
|
||||||
|
}
|
||||||
|
results = strings.TrimSpace(results)
|
||||||
|
startupApps := slicer.String(strings.Split(results, ", "))
|
||||||
|
return startupApps.Contains(binName), nil
|
||||||
|
}
|
||||||
30
v2/pkg/mac/notification_darwin.go
Normal file
30
v2/pkg/mac/notification_darwin.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// Package mac provides MacOS related utility functions for Wails applications
|
||||||
|
package mac
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/wailsapp/wails/v2/internal/shell"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StartAtLogin will either add or remove this application to/from the login
|
||||||
|
// items, depending on the given boolean flag. The limitation is that the
|
||||||
|
// currently running app must be in an app bundle.
|
||||||
|
func ShowNotification(title string, subtitle string, message string, sound string) error {
|
||||||
|
command := fmt.Sprintf("display notification \"%s\"", message)
|
||||||
|
if len(title) > 0 {
|
||||||
|
command += fmt.Sprintf(" with title \"%s\"", title)
|
||||||
|
}
|
||||||
|
if len(subtitle) > 0 {
|
||||||
|
command += fmt.Sprintf(" subtitle \"%s\"", subtitle)
|
||||||
|
}
|
||||||
|
if len(sound) > 0 {
|
||||||
|
command += fmt.Sprintf(" sound name \"%s\"", sound)
|
||||||
|
}
|
||||||
|
_, stde, err := shell.RunCommand("/tmp", "osascript", "-e", command)
|
||||||
|
if err != nil {
|
||||||
|
errors.Wrap(err, stde)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
34
v2/pkg/mac/notification_darwin_test.go
Normal file
34
v2/pkg/mac/notification_darwin_test.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package mac
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestShowNotification(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
title string
|
||||||
|
subtitle string
|
||||||
|
message string
|
||||||
|
sound string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
title string
|
||||||
|
subtitle string
|
||||||
|
message string
|
||||||
|
sound string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"No message", "", "", "", "", false},
|
||||||
|
{"Title only", "I am a Title", "", "", "", false},
|
||||||
|
{"SubTitle only", "", "I am a subtitle", "", "", false},
|
||||||
|
{"Message only", "", "", "I am a message!", "", false},
|
||||||
|
{"Sound only", "", "", "", "submarine.aiff", false},
|
||||||
|
{"Full", "Title", "Subtitle", "This is a long message to show that text gets wrapped in a notification", "submarine.aiff", false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := ShowNotification(tt.title, tt.subtitle, tt.message, tt.sound); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("ShowNotification() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/leaanthony/slicer"
|
"github.com/leaanthony/slicer"
|
||||||
)
|
)
|
||||||
|
|
||||||
var namedKeys = slicer.String([]string{"backspace", "tab", "return", "escape", "left", "right", "up", "down", "space", "delete", "home", "end", "page up", "page down", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23", "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31", "f32", "f33", "f34", "f35", "numlock"})
|
var namedKeys = slicer.String([]string{"backspace", "tab", "return", "enter", "escape", "left", "right", "up", "down", "space", "delete", "home", "end", "page up", "page down", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23", "f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31", "f32", "f33", "f34", "f35", "numlock"})
|
||||||
|
|
||||||
func parseKey(key string) (string, bool) {
|
func parseKey(key string) (string, bool) {
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"go/ast"
|
"go/ast"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
"github.com/fatih/structtag"
|
"github.com/fatih/structtag"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -225,7 +224,6 @@ func (p *Parser) parseField(file *ast.File, field *ast.Field, pkg *Package) ([]*
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
spew.Dump(t)
|
|
||||||
return nil, fmt.Errorf("unsupported field found in struct: %+v", t)
|
return nil, fmt.Errorf("unsupported field found in struct: %+v", t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
v2/test/kitchensink/.gitignore
vendored
2
v2/test/kitchensink/.gitignore
vendored
@@ -1 +1 @@
|
|||||||
info.plist
|
Info.plist
|
||||||
Reference in New Issue
Block a user