mirror of
https://github.com/taigrr/wails.git
synced 2026-04-03 05:38:56 -07:00
Compare commits
58 Commits
feature/v2
...
v2.0.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55e6a0f312 | ||
|
|
81e83fdf18 | ||
|
|
f9b79d24f8 | ||
|
|
0599a47bfe | ||
|
|
817c55d318 | ||
|
|
14146c8c0c | ||
|
|
18adac20d4 | ||
|
|
eb4bff89da | ||
|
|
c66dc777f3 | ||
|
|
9003462457 | ||
|
|
e124f0a220 | ||
|
|
c6d3f57712 | ||
|
|
b4c669ff86 | ||
|
|
2d1b2c0947 | ||
|
|
4a0c5aa785 | ||
|
|
f48d7f8f60 | ||
|
|
651f24f641 | ||
|
|
8fd77148ca | ||
|
|
0dc0762fdf | ||
|
|
1a92550709 | ||
|
|
bffc15bc14 | ||
|
|
198d206c46 | ||
|
|
bb8e848ef6 | ||
|
|
bac3e9e5c1 | ||
|
|
bc5eddeb66 | ||
|
|
8e7258d812 | ||
|
|
7118762cec | ||
|
|
6af92cf0a4 | ||
|
|
1bb91634f7 | ||
|
|
f71ce7913f | ||
|
|
53db687a26 | ||
|
|
13939d3d6b | ||
|
|
552c6b8711 | ||
|
|
feee2b3db2 | ||
|
|
9889c2bdbb | ||
|
|
2432fccf71 | ||
|
|
70510fd180 | ||
|
|
17c6201469 | ||
|
|
0f209c8900 | ||
|
|
cbf043585c | ||
|
|
5ae621ceaa | ||
|
|
1231b59443 | ||
|
|
b18d4fbf41 | ||
|
|
9ec5605e63 | ||
|
|
98a4de8878 | ||
|
|
5fe709f558 | ||
|
|
5231a6893b | ||
|
|
74f3ce990f | ||
|
|
998a913853 | ||
|
|
964844835c | ||
|
|
4e152bb849 | ||
|
|
51133d098c | ||
|
|
d4191e7d1b | ||
|
|
9c273bc745 | ||
|
|
c6f6ad6beb | ||
|
|
4362a14459 | ||
|
|
0080e9e311 | ||
|
|
83d9297cac |
@@ -57,6 +57,11 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
||||
keepAssets := false
|
||||
command.BoolFlag("k", "Keep generated assets", &keepAssets)
|
||||
|
||||
appleIdentity := ""
|
||||
if runtime.GOOS == "darwin" {
|
||||
command.StringFlag("sign", "Signs your app with the given identity.", &appleIdentity)
|
||||
}
|
||||
|
||||
command.Action(func() error {
|
||||
|
||||
// Create logger
|
||||
@@ -72,6 +77,11 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
||||
app.PrintBanner()
|
||||
}
|
||||
|
||||
// Ensure package is used with apple identity
|
||||
if appleIdentity != "" && pack == false {
|
||||
return fmt.Errorf("must use `-package` flag when using `-sign`")
|
||||
}
|
||||
|
||||
task := fmt.Sprintf("Building %s Application", strings.Title(outputType))
|
||||
logger.Println(task)
|
||||
logger.Println(strings.Repeat("-", len(task)))
|
||||
@@ -84,14 +94,15 @@ func AddBuildSubcommand(app *clir.Cli, w io.Writer) {
|
||||
|
||||
// Create BuildOptions
|
||||
buildOptions := &build.Options{
|
||||
Logger: logger,
|
||||
OutputType: outputType,
|
||||
Mode: mode,
|
||||
Pack: pack,
|
||||
Platform: platform,
|
||||
LDFlags: ldflags,
|
||||
Compiler: compilerCommand,
|
||||
KeepAssets: keepAssets,
|
||||
Logger: logger,
|
||||
OutputType: outputType,
|
||||
Mode: mode,
|
||||
Pack: pack,
|
||||
Platform: platform,
|
||||
LDFlags: ldflags,
|
||||
Compiler: compilerCommand,
|
||||
KeepAssets: keepAssets,
|
||||
AppleIdentity: appleIdentity,
|
||||
}
|
||||
|
||||
return doBuild(buildOptions)
|
||||
|
||||
@@ -5,12 +5,13 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/internal/colour"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/leaanthony/clir"
|
||||
@@ -20,6 +21,21 @@ import (
|
||||
"github.com/wailsapp/wails/v2/pkg/commands/build"
|
||||
)
|
||||
|
||||
func LogGreen(message string, args ...interface{}) {
|
||||
text := fmt.Sprintf(message, args...)
|
||||
println(colour.Green(text))
|
||||
}
|
||||
|
||||
func LogRed(message string, args ...interface{}) {
|
||||
text := fmt.Sprintf(message, args...)
|
||||
println(colour.Red(text))
|
||||
}
|
||||
|
||||
func LogDarkYellow(message string, args ...interface{}) {
|
||||
text := fmt.Sprintf(message, args...)
|
||||
println(colour.DarkYellow(text))
|
||||
}
|
||||
|
||||
// AddSubcommand adds the `dev` command for the Wails application
|
||||
func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
|
||||
@@ -37,6 +53,13 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
extensions := "go"
|
||||
command.StringFlag("e", "Extensions to trigger rebuilds (comma separated) eg go,js,css,html", &extensions)
|
||||
|
||||
// extensions to trigger rebuilds
|
||||
showWarnings := false
|
||||
command.BoolFlag("w", "Show warnings", &showWarnings)
|
||||
|
||||
loglevel := ""
|
||||
command.StringFlag("loglevel", "Loglevel to use - Trace, Debug, Info, Warning, Error", &loglevel)
|
||||
|
||||
command.Action(func() error {
|
||||
|
||||
// Create logger
|
||||
@@ -52,7 +75,6 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
defer watcher.Close()
|
||||
|
||||
var debugBinaryProcess *process.Process = nil
|
||||
var buildFrontend bool = false
|
||||
var extensionsThatTriggerARebuild = strings.Split(extensions, ",")
|
||||
|
||||
// Setup signal handler
|
||||
@@ -63,7 +85,10 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
|
||||
// Do initial build
|
||||
logger.Println("Building application for development...")
|
||||
debugBinaryProcess, err = restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess)
|
||||
newProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess, loglevel)
|
||||
if newProcess != nil {
|
||||
debugBinaryProcess = newProcess
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -79,7 +104,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
if err != nil {
|
||||
logger.Fatal("%s", err.Error())
|
||||
}
|
||||
logger.Println("Watching directory: %s", event.Name)
|
||||
LogGreen("[New Directory] Watching new directory: %s", event.Name)
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -88,33 +113,29 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
// Check for file writes
|
||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||
|
||||
logger.Println("modified file: %s", event.Name)
|
||||
var rebuild bool = false
|
||||
|
||||
// Iterate all file patterns
|
||||
for _, pattern := range extensionsThatTriggerARebuild {
|
||||
rebuild = strings.HasSuffix(event.Name, pattern)
|
||||
if err != nil {
|
||||
logger.Fatal(err.Error())
|
||||
}
|
||||
if rebuild {
|
||||
// Only build frontend when the file isn't a Go file
|
||||
buildFrontend = !strings.HasSuffix(event.Name, "go")
|
||||
if strings.HasSuffix(event.Name, pattern) {
|
||||
rebuild = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !rebuild {
|
||||
logger.Println("Filename change: %s did not match extension list (%s)", event.Name, extensions)
|
||||
if showWarnings {
|
||||
LogDarkYellow("[File change] %s did not match extension list (%s)", event.Name, extensions)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
logger.Println("Partial build triggered: %s updated", event.Name)
|
||||
LogGreen("[Attempting rebuild] %s updated", event.Name)
|
||||
|
||||
// Do a rebuild
|
||||
|
||||
// Try and build the app
|
||||
newBinaryProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess)
|
||||
newBinaryProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess, loglevel)
|
||||
if err != nil {
|
||||
fmt.Printf("Error during build: %s", err.Error())
|
||||
return
|
||||
@@ -128,23 +149,28 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
})
|
||||
|
||||
// Get project dir
|
||||
dir, err := os.Getwd()
|
||||
projectDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get all subdirectories
|
||||
dirs, err := fs.GetSubdirectories(dir)
|
||||
dirs, err := fs.GetSubdirectories(projectDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
LogGreen("Watching (sub)/directory: %s", projectDir)
|
||||
|
||||
// Setup a watcher for non-node_modules directories
|
||||
dirs.Each(func(dir string) {
|
||||
if strings.Contains(dir, "node_modules") {
|
||||
return
|
||||
}
|
||||
logger.Println("Watching directory: %s", dir)
|
||||
// Ignore build directory
|
||||
if strings.HasPrefix(dir, filepath.Join(projectDir, "build")) {
|
||||
return
|
||||
}
|
||||
err = watcher.Add(dir)
|
||||
if err != nil {
|
||||
logger.Fatal(err.Error())
|
||||
@@ -156,7 +182,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
for quit == false {
|
||||
select {
|
||||
case <-quitChannel:
|
||||
println("Caught quit")
|
||||
LogGreen("\nCaught quit")
|
||||
// Notify debouncer to quit
|
||||
debounceQuit <- true
|
||||
quit = true
|
||||
@@ -171,7 +197,7 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
}
|
||||
}
|
||||
|
||||
logger.Println("Development mode exited")
|
||||
LogGreen("Development mode exited")
|
||||
|
||||
return nil
|
||||
})
|
||||
@@ -198,15 +224,15 @@ exit:
|
||||
}
|
||||
}
|
||||
|
||||
func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, debugBinaryProcess *process.Process) (*process.Process, error) {
|
||||
func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, debugBinaryProcess *process.Process, loglevel string) (*process.Process, error) {
|
||||
|
||||
appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand)
|
||||
println()
|
||||
if err != nil {
|
||||
logger.Fatal(err.Error())
|
||||
return nil, errors.Wrap(err, "Build Failed:")
|
||||
LogRed("Build error - continuing to run current version")
|
||||
LogDarkYellow(err.Error())
|
||||
return nil, nil
|
||||
}
|
||||
logger.Println("Build new binary: %s", appBinary)
|
||||
|
||||
// Kill existing binary if need be
|
||||
if debugBinaryProcess != nil {
|
||||
@@ -222,7 +248,7 @@ func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string,
|
||||
// TODO: Generate `backend.js`
|
||||
|
||||
// Start up new binary
|
||||
newProcess := process.NewProcess(logger, appBinary)
|
||||
newProcess := process.NewProcess(logger, appBinary, "-loglevel", loglevel)
|
||||
err = newProcess.Start()
|
||||
if err != nil {
|
||||
// Remove binary
|
||||
@@ -239,7 +265,7 @@ func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string,
|
||||
func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string) (string, error) {
|
||||
|
||||
// Create random output file
|
||||
outputFile := fmt.Sprintf("debug-%d", time.Now().Unix())
|
||||
outputFile := fmt.Sprintf("dev-%d", time.Now().Unix())
|
||||
|
||||
// Create BuildOptions
|
||||
buildOptions := &build.Options{
|
||||
|
||||
@@ -18,14 +18,14 @@ import (
|
||||
func AddSubcommand(app *clir.Cli, w io.Writer, currentVersion string) error {
|
||||
|
||||
command := app.NewSubCommand("update", "Update the Wails CLI")
|
||||
command.LongDescription(`This command allows you to update your version of Wails.`)
|
||||
command.LongDescription(`This command allows you to update your version of the Wails CLI.`)
|
||||
|
||||
// Setup flags
|
||||
var prereleaseRequired bool
|
||||
command.BoolFlag("pre", "Update to latest Prerelease", &prereleaseRequired)
|
||||
command.BoolFlag("pre", "Update CLI to latest Prerelease", &prereleaseRequired)
|
||||
|
||||
var specificVersion string
|
||||
command.StringFlag("version", "Install a specific version (Overrides other flags)", &specificVersion)
|
||||
command.StringFlag("version", "Install a specific version (Overrides other flags) of the CLI", &specificVersion)
|
||||
|
||||
command.Action(func() error {
|
||||
|
||||
@@ -143,7 +143,7 @@ func updateToVersion(logger *clilogger.CLILogger, targetVersion *github.Semantic
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
logger.Print("Installing Wails " + desiredVersion + "...")
|
||||
logger.Print("Installing Wails CLI " + desiredVersion + "...")
|
||||
|
||||
// Run command in non module directory
|
||||
homeDir, err := os.UserHomeDir()
|
||||
@@ -158,7 +158,7 @@ func updateToVersion(logger *clilogger.CLILogger, targetVersion *github.Semantic
|
||||
return err
|
||||
}
|
||||
fmt.Println()
|
||||
logger.Println("Wails updated to " + desiredVersion)
|
||||
logger.Println("Wails CLI updated to " + desiredVersion)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/colour"
|
||||
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/update"
|
||||
|
||||
"github.com/leaanthony/clir"
|
||||
@@ -19,12 +22,18 @@ func fatal(message string) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func banner(_ *clir.Cli) string {
|
||||
return fmt.Sprintf("%s %s - Go/HTML Application Framework", colour.Yellow("Wails"), colour.DarkRed(version))
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
var err error
|
||||
|
||||
app := clir.NewCli("Wails", "Go/HTML Application Framework", version)
|
||||
|
||||
app.SetBannerFunction(banner)
|
||||
|
||||
build.AddBuildSubcommand(app, os.Stdout)
|
||||
err = initialise.AddSubcommand(app, os.Stdout)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v2.0.0-alpha.26"
|
||||
var version = "v2.0.0-alpha.47"
|
||||
|
||||
@@ -20,9 +20,9 @@ require (
|
||||
github.com/tdewolff/minify v2.3.6+incompatible
|
||||
github.com/tdewolff/parse v2.3.4+incompatible // indirect
|
||||
github.com/tdewolff/test v1.0.6 // indirect
|
||||
github.com/wzshiming/ctc v1.2.3
|
||||
github.com/xyproto/xpm v1.2.1
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
|
||||
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82
|
||||
nhooyr.io/websocket v1.8.6
|
||||
|
||||
@@ -78,6 +78,10 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/wzshiming/ctc v1.2.3 h1:q+hW3IQNsjIlOFBTGZZZeIXTElFM4grF4spW/errh/c=
|
||||
github.com/wzshiming/ctc v1.2.3/go.mod h1:2tVAtIY7SUyraSk0JxvwmONNPFL4ARavPuEsg5+KA28=
|
||||
github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae h1:tpXvBXC3hpQBDCc9OojJZCQMVRAbT3TTdUMP8WguXkY=
|
||||
github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae/go.mod h1:VTAq37rkGeV+WOybvZwjXiJOicICdpLCN8ifpISjK20=
|
||||
github.com/xyproto/xpm v1.2.1 h1:trdvGjjWBsOOKzBBUPT6JvaIQM3acJEEYfbxN7M96wg=
|
||||
github.com/xyproto/xpm v1.2.1/go.mod h1:cMnesLsD0PBXLgjDfTDEaKr8XyTFsnP1QycSqRw7BiY=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -92,11 +96,10 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
|
||||
|
||||
@@ -4,8 +4,9 @@ package app
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
"strings"
|
||||
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
)
|
||||
|
||||
// Init initialises the application for a debug environment
|
||||
@@ -19,10 +20,10 @@ func (a *App) Init() error {
|
||||
}
|
||||
|
||||
// Set log levels
|
||||
greeting := flag.String("loglevel", "debug", "Loglevel to use - Trace, Debug, Info, Warning, Error")
|
||||
loglevel := flag.String("loglevel", "debug", "Loglevel to use - Trace, Debug, Info, Warning, Error")
|
||||
flag.Parse()
|
||||
if len(*greeting) > 0 {
|
||||
switch strings.ToLower(*greeting) {
|
||||
if len(*loglevel) > 0 {
|
||||
switch strings.ToLower(*loglevel) {
|
||||
case "trace":
|
||||
a.logger.SetLogLevel(logger.TRACE)
|
||||
case "info":
|
||||
|
||||
@@ -35,6 +35,7 @@ type App struct {
|
||||
//binding *subsystem.Binding
|
||||
call *subsystem.Call
|
||||
menu *subsystem.Menu
|
||||
url *subsystem.URL
|
||||
dispatcher *messagedispatcher.Dispatcher
|
||||
|
||||
menuManager *menumanager.Manager
|
||||
@@ -117,14 +118,6 @@ func (a *App) Run() error {
|
||||
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
|
||||
ctx, cancel := context.WithCancel(parentContext)
|
||||
|
||||
// Setup signal handler
|
||||
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger, a.shutdownCallback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.signal = signalsubsystem
|
||||
a.signal.Start()
|
||||
|
||||
// Start the service bus
|
||||
a.servicebus.Debug()
|
||||
err = a.servicebus.Start()
|
||||
@@ -132,7 +125,7 @@ func (a *App) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
|
||||
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -168,6 +161,19 @@ func (a *App) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if a.options.Mac.URLHandlers != nil {
|
||||
// Start the url handler subsystem
|
||||
url, err := subsystem.NewURL(a.servicebus, a.logger, a.options.Mac.URLHandlers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.url = url
|
||||
err = a.url.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Start the eventing subsystem
|
||||
eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
@@ -207,6 +213,14 @@ func (a *App) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Setup signal handler
|
||||
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.signal = signalsubsystem
|
||||
a.signal.Start()
|
||||
|
||||
err = a.window.Run(dispatcher, bindingDump, a.debug)
|
||||
a.logger.Trace("Ffenestri.Run() exited")
|
||||
if err != nil {
|
||||
@@ -231,5 +245,10 @@ func (a *App) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Shutdown callback
|
||||
if a.shutdownCallback != nil {
|
||||
a.shutdownCallback()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -66,7 +66,6 @@ func CreateApp(appoptions *options.App) (*App, error) {
|
||||
|
||||
// Set up logger
|
||||
myLogger := logger.New(appoptions.Logger)
|
||||
myLogger.SetLogLevel(appoptions.LogLevel)
|
||||
|
||||
// Create the menu manager
|
||||
menuManager := menumanager.NewManager()
|
||||
@@ -119,14 +118,6 @@ func (a *App) Run() error {
|
||||
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
|
||||
ctx, cancel := context.WithCancel(parentContext)
|
||||
|
||||
// Setup signal handler
|
||||
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger, a.shutdownCallback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.signal = signalsubsystem
|
||||
a.signal.Start()
|
||||
|
||||
// Start the service bus
|
||||
a.servicebus.Debug()
|
||||
err = a.servicebus.Start()
|
||||
@@ -134,7 +125,7 @@ func (a *App) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
|
||||
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -212,6 +203,14 @@ func (a *App) Run() error {
|
||||
// Generate backend.js
|
||||
a.bindings.GenerateBackendJS()
|
||||
|
||||
// Setup signal handler
|
||||
signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.signal = signalsubsystem
|
||||
a.signal.Start()
|
||||
|
||||
err = a.bridge.Run(dispatcher, a.menuManager, bindingDump, a.debug)
|
||||
a.logger.Trace("Bridge.Run() exited")
|
||||
if err != nil {
|
||||
@@ -236,6 +235,10 @@ func (a *App) Run() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Shutdown callback
|
||||
if a.shutdownCallback != nil {
|
||||
a.shutdownCallback()
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ const backend = {`)
|
||||
sortedPackageNames.Add(packageName)
|
||||
}
|
||||
sortedPackageNames.Sort()
|
||||
for _, packageName := range sortedPackageNames.AsSlice() {
|
||||
sortedPackageNames.Each(func(packageName string) {
|
||||
packages := store[packageName]
|
||||
output.WriteString(fmt.Sprintf(" \"%s\": {", packageName))
|
||||
output.WriteString("\n")
|
||||
@@ -67,7 +67,8 @@ const backend = {`)
|
||||
sortedStructNames.Add(structName)
|
||||
}
|
||||
sortedStructNames.Sort()
|
||||
for _, structName := range sortedStructNames.AsSlice() {
|
||||
|
||||
sortedStructNames.Each(func(structName string) {
|
||||
structs := packages[structName]
|
||||
output.WriteString(fmt.Sprintf(" \"%s\": {", structName))
|
||||
output.WriteString("\n")
|
||||
@@ -78,7 +79,7 @@ const backend = {`)
|
||||
}
|
||||
sortedMethodNames.Sort()
|
||||
|
||||
for _, methodName := range sortedMethodNames.AsSlice() {
|
||||
sortedMethodNames.Each(func(methodName string) {
|
||||
methodDetails := structs[methodName]
|
||||
output.WriteString(" /**\n")
|
||||
output.WriteString(" * " + methodName + "\n")
|
||||
@@ -109,13 +110,16 @@ const backend = {`)
|
||||
output.WriteString("\n")
|
||||
output.WriteString(fmt.Sprintf(" },"))
|
||||
output.WriteString("\n")
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
output.WriteString(fmt.Sprintf(" }"))
|
||||
output.WriteString("\n")
|
||||
}
|
||||
})
|
||||
|
||||
output.WriteString(fmt.Sprintf(" }\n"))
|
||||
output.WriteString("\n")
|
||||
}
|
||||
})
|
||||
|
||||
output.WriteString(`};
|
||||
export default backend;`)
|
||||
|
||||
@@ -11,6 +11,10 @@ type BridgeClient struct {
|
||||
messageCache chan string
|
||||
}
|
||||
|
||||
func (b BridgeClient) DeleteTrayMenuByID(id string) {
|
||||
b.session.sendMessage("TD" + id)
|
||||
}
|
||||
|
||||
func NewBridgeClient() *BridgeClient {
|
||||
return &BridgeClient{
|
||||
messageCache: make(chan string, 100),
|
||||
|
||||
@@ -19,6 +19,9 @@ type DialogClient struct {
|
||||
log *logger.Logger
|
||||
}
|
||||
|
||||
func (d *DialogClient) DeleteTrayMenuByID(id string) {
|
||||
}
|
||||
|
||||
func NewDialogClient(log *logger.Logger) *DialogClient {
|
||||
return &DialogClient{
|
||||
log: log,
|
||||
|
||||
89
v2/internal/colour/colour.go
Normal file
89
v2/internal/colour/colour.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package colour
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/wzshiming/ctc"
|
||||
)
|
||||
|
||||
func Col(col ctc.Color, text string) string {
|
||||
return fmt.Sprintf("%s%s%s", col, text, ctc.Reset)
|
||||
}
|
||||
|
||||
func Yellow(text string) string {
|
||||
return Col(ctc.ForegroundBrightYellow, text)
|
||||
}
|
||||
|
||||
func Red(text string) string {
|
||||
return Col(ctc.ForegroundBrightRed, text)
|
||||
}
|
||||
|
||||
func Blue(text string) string {
|
||||
return Col(ctc.ForegroundBrightBlue, text)
|
||||
}
|
||||
|
||||
func Green(text string) string {
|
||||
return Col(ctc.ForegroundBrightGreen, text)
|
||||
}
|
||||
|
||||
func Cyan(text string) string {
|
||||
return Col(ctc.ForegroundBrightCyan, text)
|
||||
}
|
||||
|
||||
func Magenta(text string) string {
|
||||
return Col(ctc.ForegroundBrightMagenta, text)
|
||||
}
|
||||
|
||||
func White(text string) string {
|
||||
return Col(ctc.ForegroundBrightWhite, text)
|
||||
}
|
||||
|
||||
func Black(text string) string {
|
||||
return Col(ctc.ForegroundBrightBlack, text)
|
||||
}
|
||||
|
||||
func DarkYellow(text string) string {
|
||||
return Col(ctc.ForegroundYellow, text)
|
||||
}
|
||||
|
||||
func DarkRed(text string) string {
|
||||
return Col(ctc.ForegroundRed, text)
|
||||
}
|
||||
|
||||
func DarkBlue(text string) string {
|
||||
return Col(ctc.ForegroundBlue, text)
|
||||
}
|
||||
|
||||
func DarkGreen(text string) string {
|
||||
return Col(ctc.ForegroundGreen, text)
|
||||
}
|
||||
|
||||
func DarkCyan(text string) string {
|
||||
return Col(ctc.ForegroundCyan, text)
|
||||
}
|
||||
|
||||
func DarkMagenta(text string) string {
|
||||
return Col(ctc.ForegroundMagenta, text)
|
||||
}
|
||||
|
||||
func DarkWhite(text string) string {
|
||||
return Col(ctc.ForegroundWhite, text)
|
||||
}
|
||||
|
||||
func DarkBlack(text string) string {
|
||||
return Col(ctc.ForegroundBlack, text)
|
||||
}
|
||||
|
||||
var rainbowCols = []func(string) string{Red, Yellow, Green, Cyan, Blue, Magenta}
|
||||
|
||||
func Rainbow(text string) string {
|
||||
var builder strings.Builder
|
||||
|
||||
for i := 0; i < len(text); i++ {
|
||||
fn := rainbowCols[i%len(rainbowCols)]
|
||||
builder.WriteString(fn(text[i : i+1]))
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
||||
@@ -37,6 +37,7 @@ extern void DarkModeEnabled(struct Application*, char *callbackID);
|
||||
extern void SetApplicationMenu(struct Application*, const char *);
|
||||
extern void AddTrayMenu(struct Application*, const char *menuTrayJSON);
|
||||
extern void SetTrayMenu(struct Application*, const char *menuTrayJSON);
|
||||
extern void DeleteTrayMenuByID(struct Application*, const char *id);
|
||||
extern void UpdateTrayMenuLabel(struct Application*, const char* JSON);
|
||||
extern void AddContextMenu(struct Application*, char *contextMenuJSON);
|
||||
extern void UpdateContextMenu(struct Application*, char *contextMenuJSON);
|
||||
|
||||
@@ -208,3 +208,7 @@ func (c *Client) UpdateTrayMenuLabel(JSON string) {
|
||||
func (c *Client) UpdateContextMenu(contextMenuJSON string) {
|
||||
C.UpdateContextMenu(c.app.app, c.app.string2CString(contextMenuJSON))
|
||||
}
|
||||
|
||||
func (c *Client) DeleteTrayMenuByID(id string) {
|
||||
C.DeleteTrayMenuByID(c.app.app, c.app.string2CString(id))
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ struct hashmap_s dialogIconCache;
|
||||
// Dispatch Method
|
||||
typedef void (^dispatchMethod)(void);
|
||||
|
||||
TrayMenuStore *TrayMenuStoreSingleton;
|
||||
|
||||
// dispatch will execute the given `func` pointer
|
||||
void dispatch(dispatchMethod func) {
|
||||
dispatch_async(dispatch_get_main_queue(), func);
|
||||
@@ -46,6 +48,18 @@ int hashmap_log(void *const context, struct hashmap_element_s *const e) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void filelog(const char *message) {
|
||||
FILE *fp = fopen("/tmp/wailslog.txt", "ab");
|
||||
if (fp != NULL)
|
||||
{
|
||||
fputs(message, fp);
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
// The delegate class for tray menus
|
||||
Class trayMenuDelegateClass;
|
||||
|
||||
// Utility function to visualise a hashmap
|
||||
void dumpHashmap(const char *name, struct hashmap_s *hashmap) {
|
||||
printf("%s = { ", name);
|
||||
@@ -79,6 +93,7 @@ struct Application {
|
||||
id mouseEvent;
|
||||
id mouseDownMonitor;
|
||||
id mouseUpMonitor;
|
||||
int activationPolicy;
|
||||
|
||||
// Window Data
|
||||
const char *title;
|
||||
@@ -112,13 +127,11 @@ struct Application {
|
||||
int useToolBar;
|
||||
int hideToolbarSeparator;
|
||||
int windowBackgroundIsTranslucent;
|
||||
int hasURLHandlers;
|
||||
|
||||
// Menu
|
||||
Menu *applicationMenu;
|
||||
|
||||
// Tray
|
||||
TrayMenuStore* trayMenuStore;
|
||||
|
||||
// Context Menus
|
||||
ContextMenuStore *contextMenuStore;
|
||||
|
||||
@@ -253,7 +266,7 @@ void Hide(struct Application *app) {
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
msg(app->application, s("hide:"))
|
||||
msg(app->application, s("hide:"));
|
||||
);
|
||||
}
|
||||
|
||||
@@ -466,7 +479,7 @@ void DestroyApplication(struct Application *app) {
|
||||
}
|
||||
|
||||
// Delete the tray menu store
|
||||
DeleteTrayMenuStore(app->trayMenuStore);
|
||||
DeleteTrayMenuStore(TrayMenuStoreSingleton);
|
||||
|
||||
// Delete the context menu store
|
||||
DeleteContextMenuStore(app->contextMenuStore);
|
||||
@@ -498,31 +511,6 @@ void DestroyApplication(struct Application *app) {
|
||||
Debug(app, "Finished Destroying Application");
|
||||
}
|
||||
|
||||
// Quit will stop the cocoa application and free up all the memory
|
||||
// used by the application
|
||||
void Quit(struct Application *app) {
|
||||
Debug(app, "Quit Called");
|
||||
ON_MAIN_THREAD (
|
||||
// Terminate app
|
||||
msg(app->application, s("stop:"), NULL);
|
||||
id fakeevent = msg(c("NSEvent"),
|
||||
s("otherEventWithType:location:modifierFlags:timestamp:windowNumber:context:subtype:data1:data2:"),
|
||||
15, // Type
|
||||
msg(c("CGPoint"), s("init:x:y:"), 0, 0), // location
|
||||
0, // flags
|
||||
0, // timestamp
|
||||
0, // window
|
||||
NULL, // context
|
||||
0, // subtype
|
||||
0, // data1
|
||||
0 // data2
|
||||
);
|
||||
msg(c("NSApp"), s("postEvent:atStart:"), fakeevent, true);
|
||||
// msg(c(app->mainWindow), s("performClose:"))
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
// SetTitle sets the main window title to the given string
|
||||
void SetTitle(struct Application *app, const char *title) {
|
||||
// Guard against calling during shutdown
|
||||
@@ -1058,7 +1046,7 @@ void AddTrayMenu(struct Application *app, const char *trayMenuJSON) {
|
||||
// Guard against calling during shutdown
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
AddTrayMenuToStore(app->trayMenuStore, trayMenuJSON);
|
||||
AddTrayMenuToStore(TrayMenuStoreSingleton, trayMenuJSON);
|
||||
}
|
||||
|
||||
void SetTrayMenu(struct Application *app, const char* trayMenuJSON) {
|
||||
@@ -1067,7 +1055,13 @@ void SetTrayMenu(struct Application *app, const char* trayMenuJSON) {
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
UpdateTrayMenuInStore(app->trayMenuStore, trayMenuJSON);
|
||||
UpdateTrayMenuInStore(TrayMenuStoreSingleton, trayMenuJSON);
|
||||
);
|
||||
}
|
||||
|
||||
void DeleteTrayMenuByID(struct Application *app, const char *id) {
|
||||
ON_MAIN_THREAD(
|
||||
DeleteTrayMenuInStore(TrayMenuStoreSingleton, id);
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1076,7 +1070,7 @@ void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
|
||||
if( app->shuttingDown ) return;
|
||||
|
||||
ON_MAIN_THREAD(
|
||||
UpdateTrayMenuLabelInStore(app->trayMenuStore, JSON);
|
||||
UpdateTrayMenuLabelInStore(TrayMenuStoreSingleton, JSON);
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1137,7 +1131,7 @@ void processDecorations(struct Application *app) {
|
||||
void createApplication(struct Application *app) {
|
||||
id application = msg(c("NSApplication"), s("sharedApplication"));
|
||||
app->application = application;
|
||||
msg(application, s("setActivationPolicy:"), 0);
|
||||
msg(application, s("setActivationPolicy:"), app->activationPolicy);
|
||||
}
|
||||
|
||||
void DarkModeEnabled(struct Application *app, const char *callbackID) {
|
||||
@@ -1161,17 +1155,31 @@ void DarkModeEnabled(struct Application *app, const char *callbackID) {
|
||||
);
|
||||
}
|
||||
|
||||
void getURL(id self, SEL selector, id event, id replyEvent) {
|
||||
id desc = msg(event, s("paramDescriptorForKeyword:"), keyDirectObject);
|
||||
id url = msg(desc, s("stringValue"));
|
||||
const char* curl = cstr(url);
|
||||
const char* message = concat("UC", curl);
|
||||
messageFromWindowCallback(message);
|
||||
MEMFREE(message);
|
||||
}
|
||||
|
||||
|
||||
void createDelegate(struct Application *app) {
|
||||
// Define delegate
|
||||
// Define delegate
|
||||
Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "AppDelegate", 0);
|
||||
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSApplicationDelegate"));
|
||||
class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) no, "c@:@");
|
||||
// class_addMethod(delegateClass, s("applicationWillTerminate:"), (IMP) closeWindow, "v@:@");
|
||||
class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
|
||||
|
||||
// All Menu Items use a common callback
|
||||
class_addMethod(delegateClass, s("menuItemCallback:"), (IMP)menuItemCallback, "v@:@");
|
||||
|
||||
// If there are URL Handlers, register the callback method
|
||||
if( app->hasURLHandlers ) {
|
||||
class_addMethod(delegateClass, s("getUrl:withReplyEvent:"), (IMP) getURL, "i@:@@");
|
||||
}
|
||||
|
||||
// Script handler
|
||||
class_addMethod(delegateClass, s("userContentController:didReceiveScriptMessage:"), (IMP) messageHandler, "v@:@@");
|
||||
objc_registerClassPair(delegateClass);
|
||||
@@ -1180,6 +1188,12 @@ void createDelegate(struct Application *app) {
|
||||
id delegate = msg((id)delegateClass, s("new"));
|
||||
objc_setAssociatedObject(delegate, "application", (id)app, OBJC_ASSOCIATION_ASSIGN);
|
||||
|
||||
// If there are URL Handlers, register a listener for them
|
||||
if( app->hasURLHandlers ) {
|
||||
id eventManager = msg(c("NSAppleEventManager"), s("sharedAppleEventManager"));
|
||||
msg(eventManager, s("setEventHandler:andSelector:forEventClass:andEventID:"), delegate, s("getUrl:withReplyEvent:"), kInternetEventClass, kAEGetURL);
|
||||
}
|
||||
|
||||
// Theme change listener
|
||||
class_addMethod(delegateClass, s("themeChanged:"), (IMP) themeChanged, "v@:@@");
|
||||
|
||||
@@ -1195,13 +1209,19 @@ void createDelegate(struct Application *app) {
|
||||
bool windowShouldClose(id self, SEL cmd, id sender) {
|
||||
msg(sender, s("orderBack:"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool windowShouldExit(id self, SEL cmd, id sender) {
|
||||
msg(sender, s("orderBack:"));
|
||||
messageFromWindowCallback("WC");
|
||||
return false;
|
||||
}
|
||||
|
||||
void createMainWindow(struct Application *app) {
|
||||
// Create main window
|
||||
id mainWindow = ALLOC("NSWindow");
|
||||
mainWindow = msg(mainWindow, s("initWithContentRect:styleMask:backing:defer:"),
|
||||
CGRectMake(0, 0, app->width, app->height), app->decorations, NSBackingStoreBuffered, NO);
|
||||
CGRectMake(0, 0, app->width, app->height), app->decorations, NSBackingStoreBuffered, NO);
|
||||
msg(mainWindow, s("autorelease"));
|
||||
|
||||
// Set Appearance
|
||||
@@ -1215,14 +1235,16 @@ void createMainWindow(struct Application *app) {
|
||||
msg(mainWindow, s("setTitlebarAppearsTransparent:"), app->titlebarAppearsTransparent ? YES : NO);
|
||||
msg(mainWindow, s("setTitleVisibility:"), app->hideTitle);
|
||||
|
||||
if( app->hideWindowOnClose ) {
|
||||
// Create window delegate to override windowShouldClose
|
||||
Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "WindowDelegate", 0);
|
||||
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSWindowDelegate"));
|
||||
// Create window delegate to override windowShouldClose
|
||||
Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "WindowDelegate", 0);
|
||||
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSWindowDelegate"));
|
||||
if( app->hideWindowOnClose ) {
|
||||
class_replaceMethod(delegateClass, s("windowShouldClose:"), (IMP) windowShouldClose, "v@:@");
|
||||
app->windowDelegate = msg((id)delegateClass, s("new"));
|
||||
msg(mainWindow, s("setDelegate:"), app->windowDelegate);
|
||||
}
|
||||
} else {
|
||||
class_replaceMethod(delegateClass, s("windowShouldClose:"), (IMP) windowShouldExit, "v@:@");
|
||||
}
|
||||
app->windowDelegate = msg((id)delegateClass, s("new"));
|
||||
msg(mainWindow, s("setDelegate:"), app->windowDelegate);
|
||||
|
||||
app->mainWindow = mainWindow;
|
||||
}
|
||||
@@ -1641,6 +1663,35 @@ void processUserDialogIcons(struct Application *app) {
|
||||
|
||||
}
|
||||
|
||||
void TrayMenuWillOpen(id self, SEL selector, id menu) {
|
||||
// Extract tray menu id from menu
|
||||
id trayMenuIDStr = objc_getAssociatedObject(menu, "trayMenuID");
|
||||
const char* trayMenuID = cstr(trayMenuIDStr);
|
||||
const char *message = concat("Mo", trayMenuID);
|
||||
messageFromWindowCallback(message);
|
||||
MEMFREE(message);
|
||||
}
|
||||
|
||||
void TrayMenuDidClose(id self, SEL selector, id menu) {
|
||||
// Extract tray menu id from menu
|
||||
id trayMenuIDStr = objc_getAssociatedObject(menu, "trayMenuID");
|
||||
const char* trayMenuID = cstr(trayMenuIDStr);
|
||||
const char *message = concat("Mc", trayMenuID);
|
||||
messageFromWindowCallback(message);
|
||||
MEMFREE(message);
|
||||
}
|
||||
|
||||
void createTrayMenuDelegate() {
|
||||
// Define delegate
|
||||
trayMenuDelegateClass = objc_allocateClassPair((Class) c("NSObject"), "MenuDelegate", 0);
|
||||
class_addProtocol(trayMenuDelegateClass, objc_getProtocol("NSMenuDelegate"));
|
||||
class_addMethod(trayMenuDelegateClass, s("menuWillOpen:"), (IMP) TrayMenuWillOpen, "v@:@");
|
||||
class_addMethod(trayMenuDelegateClass, s("menuDidClose:"), (IMP) TrayMenuDidClose, "v@:@");
|
||||
|
||||
// Script handler
|
||||
objc_registerClassPair(trayMenuDelegateClass);
|
||||
}
|
||||
|
||||
|
||||
void Run(struct Application *app, int argc, char **argv) {
|
||||
|
||||
@@ -1653,6 +1704,9 @@ void Run(struct Application *app, int argc, char **argv) {
|
||||
// Define delegate
|
||||
createDelegate(app);
|
||||
|
||||
// Define tray delegate
|
||||
createTrayMenuDelegate();
|
||||
|
||||
// Create the main window
|
||||
createMainWindow(app);
|
||||
|
||||
@@ -1823,7 +1877,7 @@ void Run(struct Application *app, int argc, char **argv) {
|
||||
}
|
||||
|
||||
// Setup initial trays
|
||||
ShowTrayMenusInStore(app->trayMenuStore);
|
||||
ShowTrayMenusInStore(TrayMenuStoreSingleton);
|
||||
|
||||
// Process dialog icons
|
||||
processUserDialogIcons(app);
|
||||
@@ -1837,6 +1891,23 @@ void Run(struct Application *app, int argc, char **argv) {
|
||||
MEMFREE(internalCode);
|
||||
}
|
||||
|
||||
void SetActivationPolicy(struct Application* app, int policy) {
|
||||
app->activationPolicy = policy;
|
||||
}
|
||||
|
||||
void HasURLHandlers(struct Application* app) {
|
||||
app->hasURLHandlers = 1;
|
||||
}
|
||||
|
||||
// Quit will stop the cocoa application and free up all the memory
|
||||
// used by the application
|
||||
void Quit(struct Application *app) {
|
||||
Debug(app, "Quit Called");
|
||||
msg(app->application, s("stop:"), NULL);
|
||||
SetSize(app, 0, 0);
|
||||
Show(app);
|
||||
Hide(app);
|
||||
}
|
||||
|
||||
void* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose) {
|
||||
|
||||
@@ -1883,7 +1954,7 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
|
||||
result->applicationMenu = NULL;
|
||||
|
||||
// Tray
|
||||
result->trayMenuStore = NewTrayMenuStore();
|
||||
TrayMenuStoreSingleton = NewTrayMenuStore();
|
||||
|
||||
// Context Menus
|
||||
result->contextMenuStore = NewContextMenuStore();
|
||||
@@ -1899,6 +1970,10 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
|
||||
|
||||
result->shuttingDown = false;
|
||||
|
||||
result->activationPolicy = NSApplicationActivationPolicyRegular;
|
||||
|
||||
result->hasURLHandlers = 0;
|
||||
|
||||
return (void*) result;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,9 @@ func (a *Application) processPlatformSettings() error {
|
||||
C.SetAppearance(a.app, a.string2CString(string(mac.Appearance)))
|
||||
}
|
||||
|
||||
// Set activation policy
|
||||
C.SetActivationPolicy(a.app, C.int(mac.ActivationPolicy))
|
||||
|
||||
// Check if the webview should be transparent
|
||||
if mac.WebviewIsTransparent {
|
||||
C.WebviewIsTransparent(a.app)
|
||||
@@ -87,5 +90,10 @@ func (a *Application) processPlatformSettings() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Process URL Handlers
|
||||
if a.config.Mac.URLHandlers != nil {
|
||||
C.HasURLHandlers(a.app)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
// Macros to make it slightly more sane
|
||||
#define msg objc_msgSend
|
||||
|
||||
#define kInternetEventClass 'GURL'
|
||||
#define kAEGetURL 'GURL'
|
||||
#define keyDirectObject '----'
|
||||
|
||||
#define c(str) (id)objc_getClass(str)
|
||||
#define s(str) sel_registerName(str)
|
||||
#define u(str) sel_getUid(str)
|
||||
@@ -66,6 +70,10 @@
|
||||
#define NSControlStateValueOff 0
|
||||
#define NSControlStateValueOn 1
|
||||
|
||||
#define NSApplicationActivationPolicyRegular 0
|
||||
#define NSApplicationActivationPolicyAccessory 1
|
||||
#define NSApplicationActivationPolicyProhibited 2
|
||||
|
||||
// Unbelievably, if the user swaps their button preference
|
||||
// then right buttons are reported as left buttons
|
||||
#define NSEventMaskLeftMouseDown 1 << 1
|
||||
@@ -110,6 +118,10 @@ void SetTray(struct Application* app, const char *, const char *, const char *);
|
||||
//void SetContextMenus(struct Application* app, const char *);
|
||||
void AddTrayMenu(struct Application* app, const char *);
|
||||
|
||||
void SetActivationPolicy(struct Application* app, int policy);
|
||||
|
||||
void* lookupStringConstant(id constantName);
|
||||
|
||||
void HasURLHandlers(struct Application* app);
|
||||
|
||||
#endif
|
||||
@@ -180,151 +180,151 @@ id processAcceleratorKey(const char *key) {
|
||||
return str("");
|
||||
}
|
||||
|
||||
if( STREQ(key, "Backspace") ) {
|
||||
if( STREQ(key, "backspace") ) {
|
||||
return strunicode(0x0008);
|
||||
}
|
||||
if( STREQ(key, "Tab") ) {
|
||||
if( STREQ(key, "tab") ) {
|
||||
return strunicode(0x0009);
|
||||
}
|
||||
if( STREQ(key, "Return") ) {
|
||||
if( STREQ(key, "return") ) {
|
||||
return strunicode(0x000d);
|
||||
}
|
||||
if( STREQ(key, "Escape") ) {
|
||||
if( STREQ(key, "escape") ) {
|
||||
return strunicode(0x001b);
|
||||
}
|
||||
if( STREQ(key, "Left") ) {
|
||||
if( STREQ(key, "left") ) {
|
||||
return strunicode(0x001c);
|
||||
}
|
||||
if( STREQ(key, "Right") ) {
|
||||
if( STREQ(key, "right") ) {
|
||||
return strunicode(0x001d);
|
||||
}
|
||||
if( STREQ(key, "Up") ) {
|
||||
if( STREQ(key, "up") ) {
|
||||
return strunicode(0x001e);
|
||||
}
|
||||
if( STREQ(key, "Down") ) {
|
||||
if( STREQ(key, "down") ) {
|
||||
return strunicode(0x001f);
|
||||
}
|
||||
if( STREQ(key, "Space") ) {
|
||||
if( STREQ(key, "space") ) {
|
||||
return strunicode(0x0020);
|
||||
}
|
||||
if( STREQ(key, "Delete") ) {
|
||||
if( STREQ(key, "delete") ) {
|
||||
return strunicode(0x007f);
|
||||
}
|
||||
if( STREQ(key, "Home") ) {
|
||||
if( STREQ(key, "home") ) {
|
||||
return strunicode(0x2196);
|
||||
}
|
||||
if( STREQ(key, "End") ) {
|
||||
if( STREQ(key, "end") ) {
|
||||
return strunicode(0x2198);
|
||||
}
|
||||
if( STREQ(key, "Page Up") ) {
|
||||
if( STREQ(key, "page up") ) {
|
||||
return strunicode(0x21de);
|
||||
}
|
||||
if( STREQ(key, "Page Down") ) {
|
||||
if( STREQ(key, "page down") ) {
|
||||
return strunicode(0x21df);
|
||||
}
|
||||
if( STREQ(key, "F1") ) {
|
||||
if( STREQ(key, "f1") ) {
|
||||
return strunicode(0xf704);
|
||||
}
|
||||
if( STREQ(key, "F2") ) {
|
||||
if( STREQ(key, "f2") ) {
|
||||
return strunicode(0xf705);
|
||||
}
|
||||
if( STREQ(key, "F3") ) {
|
||||
if( STREQ(key, "f3") ) {
|
||||
return strunicode(0xf706);
|
||||
}
|
||||
if( STREQ(key, "F4") ) {
|
||||
if( STREQ(key, "f4") ) {
|
||||
return strunicode(0xf707);
|
||||
}
|
||||
if( STREQ(key, "F5") ) {
|
||||
if( STREQ(key, "f5") ) {
|
||||
return strunicode(0xf708);
|
||||
}
|
||||
if( STREQ(key, "F6") ) {
|
||||
if( STREQ(key, "f6") ) {
|
||||
return strunicode(0xf709);
|
||||
}
|
||||
if( STREQ(key, "F7") ) {
|
||||
if( STREQ(key, "f7") ) {
|
||||
return strunicode(0xf70a);
|
||||
}
|
||||
if( STREQ(key, "F8") ) {
|
||||
if( STREQ(key, "f8") ) {
|
||||
return strunicode(0xf70b);
|
||||
}
|
||||
if( STREQ(key, "F9") ) {
|
||||
if( STREQ(key, "f9") ) {
|
||||
return strunicode(0xf70c);
|
||||
}
|
||||
if( STREQ(key, "F10") ) {
|
||||
if( STREQ(key, "f10") ) {
|
||||
return strunicode(0xf70d);
|
||||
}
|
||||
if( STREQ(key, "F11") ) {
|
||||
if( STREQ(key, "f11") ) {
|
||||
return strunicode(0xf70e);
|
||||
}
|
||||
if( STREQ(key, "F12") ) {
|
||||
if( STREQ(key, "f12") ) {
|
||||
return strunicode(0xf70f);
|
||||
}
|
||||
if( STREQ(key, "F13") ) {
|
||||
if( STREQ(key, "f13") ) {
|
||||
return strunicode(0xf710);
|
||||
}
|
||||
if( STREQ(key, "F14") ) {
|
||||
if( STREQ(key, "f14") ) {
|
||||
return strunicode(0xf711);
|
||||
}
|
||||
if( STREQ(key, "F15") ) {
|
||||
if( STREQ(key, "f15") ) {
|
||||
return strunicode(0xf712);
|
||||
}
|
||||
if( STREQ(key, "F16") ) {
|
||||
if( STREQ(key, "f16") ) {
|
||||
return strunicode(0xf713);
|
||||
}
|
||||
if( STREQ(key, "F17") ) {
|
||||
if( STREQ(key, "f17") ) {
|
||||
return strunicode(0xf714);
|
||||
}
|
||||
if( STREQ(key, "F18") ) {
|
||||
if( STREQ(key, "f18") ) {
|
||||
return strunicode(0xf715);
|
||||
}
|
||||
if( STREQ(key, "F19") ) {
|
||||
if( STREQ(key, "f19") ) {
|
||||
return strunicode(0xf716);
|
||||
}
|
||||
if( STREQ(key, "F20") ) {
|
||||
if( STREQ(key, "f20") ) {
|
||||
return strunicode(0xf717);
|
||||
}
|
||||
if( STREQ(key, "F21") ) {
|
||||
if( STREQ(key, "f21") ) {
|
||||
return strunicode(0xf718);
|
||||
}
|
||||
if( STREQ(key, "F22") ) {
|
||||
if( STREQ(key, "f22") ) {
|
||||
return strunicode(0xf719);
|
||||
}
|
||||
if( STREQ(key, "F23") ) {
|
||||
if( STREQ(key, "f23") ) {
|
||||
return strunicode(0xf71a);
|
||||
}
|
||||
if( STREQ(key, "F24") ) {
|
||||
if( STREQ(key, "f24") ) {
|
||||
return strunicode(0xf71b);
|
||||
}
|
||||
if( STREQ(key, "F25") ) {
|
||||
if( STREQ(key, "f25") ) {
|
||||
return strunicode(0xf71c);
|
||||
}
|
||||
if( STREQ(key, "F26") ) {
|
||||
if( STREQ(key, "f26") ) {
|
||||
return strunicode(0xf71d);
|
||||
}
|
||||
if( STREQ(key, "F27") ) {
|
||||
if( STREQ(key, "f27") ) {
|
||||
return strunicode(0xf71e);
|
||||
}
|
||||
if( STREQ(key, "F28") ) {
|
||||
if( STREQ(key, "f28") ) {
|
||||
return strunicode(0xf71f);
|
||||
}
|
||||
if( STREQ(key, "F29") ) {
|
||||
if( STREQ(key, "f29") ) {
|
||||
return strunicode(0xf720);
|
||||
}
|
||||
if( STREQ(key, "F30") ) {
|
||||
if( STREQ(key, "f30") ) {
|
||||
return strunicode(0xf721);
|
||||
}
|
||||
if( STREQ(key, "F31") ) {
|
||||
if( STREQ(key, "f31") ) {
|
||||
return strunicode(0xf722);
|
||||
}
|
||||
if( STREQ(key, "F32") ) {
|
||||
if( STREQ(key, "f32") ) {
|
||||
return strunicode(0xf723);
|
||||
}
|
||||
if( STREQ(key, "F33") ) {
|
||||
if( STREQ(key, "f33") ) {
|
||||
return strunicode(0xf724);
|
||||
}
|
||||
if( STREQ(key, "F34") ) {
|
||||
if( STREQ(key, "f34") ) {
|
||||
return strunicode(0xf725);
|
||||
}
|
||||
if( STREQ(key, "F35") ) {
|
||||
if( STREQ(key, "f35") ) {
|
||||
return strunicode(0xf726);
|
||||
}
|
||||
// if( STREQ(key, "Insert") ) {
|
||||
@@ -336,7 +336,7 @@ id processAcceleratorKey(const char *key) {
|
||||
// if( STREQ(key, "ScrollLock") ) {
|
||||
// return strunicode(0xf72f);
|
||||
// }
|
||||
if( STREQ(key, "NumLock") ) {
|
||||
if( STREQ(key, "numLock") ) {
|
||||
return strunicode(0xf739);
|
||||
}
|
||||
|
||||
@@ -508,20 +508,21 @@ unsigned long parseModifiers(const char **modifiers) {
|
||||
const char *thisModifier = modifiers[0];
|
||||
int count = 0;
|
||||
while( thisModifier != NULL ) {
|
||||
|
||||
// Determine flags
|
||||
if( STREQ(thisModifier, "CmdOrCtrl") ) {
|
||||
if( STREQ(thisModifier, "cmdorctrl") ) {
|
||||
result |= NSEventModifierFlagCommand;
|
||||
}
|
||||
if( STREQ(thisModifier, "OptionOrAlt") ) {
|
||||
if( STREQ(thisModifier, "optionoralt") ) {
|
||||
result |= NSEventModifierFlagOption;
|
||||
}
|
||||
if( STREQ(thisModifier, "Shift") ) {
|
||||
if( STREQ(thisModifier, "shift") ) {
|
||||
result |= NSEventModifierFlagShift;
|
||||
}
|
||||
if( STREQ(thisModifier, "Super") ) {
|
||||
if( STREQ(thisModifier, "super") ) {
|
||||
result |= NSEventModifierFlagCommand;
|
||||
}
|
||||
if( STREQ(thisModifier, "Control") ) {
|
||||
if( STREQ(thisModifier, "ctrl") ) {
|
||||
result |= NSEventModifierFlagControl;
|
||||
}
|
||||
count++;
|
||||
@@ -575,7 +576,7 @@ id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const c
|
||||
return item;
|
||||
}
|
||||
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage) {
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate) {
|
||||
id item = ALLOC("NSMenuItem");
|
||||
|
||||
// Create a MenuItemCallbackData
|
||||
@@ -584,9 +585,13 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
|
||||
id wrappedId = msg(c("NSValue"), s("valueWithPointer:"), callback);
|
||||
msg(item, s("setRepresentedObject:"), wrappedId);
|
||||
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
||||
s("menuItemCallback:"), key);
|
||||
if( !alternate ) {
|
||||
id key = processAcceleratorKey(acceleratorkey);
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title),
|
||||
s("menuItemCallback:"), key);
|
||||
} else {
|
||||
msg(item, s("initWithTitle:action:keyEquivalent:"), str(title), s("menuItemCallback:"), str(""));
|
||||
}
|
||||
|
||||
if( tooltip != NULL ) {
|
||||
msg(item, s("setToolTip:"), str(tooltip));
|
||||
@@ -662,10 +667,16 @@ id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char
|
||||
msg(item, s("autorelease"));
|
||||
|
||||
// Process modifiers
|
||||
if( modifiers != NULL ) {
|
||||
if( modifiers != NULL && !alternate) {
|
||||
unsigned long modifierFlags = parseModifiers(modifiers);
|
||||
msg(item, s("setKeyEquivalentModifierMask:"), modifierFlags);
|
||||
}
|
||||
|
||||
// alternate
|
||||
if( alternate ) {
|
||||
msg(item, s("setAlternate:"), true);
|
||||
msg(item, s("setKeyEquivalentModifierMask:"), NSEventModifierFlagOption);
|
||||
}
|
||||
msg(parentMenu, s("addItem:"), item);
|
||||
|
||||
return item;
|
||||
@@ -726,6 +737,11 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
label = "(empty)";
|
||||
}
|
||||
|
||||
|
||||
// Is this an alternate menu item?
|
||||
bool alternate = false;
|
||||
getJSONBool(item, "MacAlternate", &alternate);
|
||||
|
||||
const char *menuid = getJSONString(item, "ID");
|
||||
if ( menuid == NULL) {
|
||||
menuid = "";
|
||||
@@ -781,7 +797,7 @@ void processMenuItem(Menu *menu, id parentMenu, JsonNode *item) {
|
||||
if( type != NULL ) {
|
||||
|
||||
if( STREQ(type->string_, "Text")) {
|
||||
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage);
|
||||
processTextMenuItem(menu, parentMenu, label, menuid, disabled, acceleratorkey, modifiers, tooltip, image, fontName, fontSize, RGBA, templateImage, alternate);
|
||||
}
|
||||
else if ( STREQ(type->string_, "Separator")) {
|
||||
addSeparator(parentMenu);
|
||||
|
||||
@@ -105,7 +105,7 @@ id processRadioMenuItem(Menu *menu, id parentmenu, const char *title, const char
|
||||
|
||||
id processCheckboxMenuItem(Menu *menu, id parentmenu, const char *title, const char *menuid, bool disabled, bool checked, const char *key);
|
||||
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage);
|
||||
id processTextMenuItem(Menu *menu, id parentMenu, const char *title, const char *menuid, bool disabled, const char *acceleratorkey, const char **modifiers, const char* tooltip, const char* image, const char* fontName, int fontSize, const char* RGBA, bool templateImage, bool alternate);
|
||||
void processMenuItem(Menu *menu, id parentMenu, JsonNode *item);
|
||||
void processMenuData(Menu *menu, JsonNode *menuData);
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "traymenu_darwin.h"
|
||||
#include "trayicons.h"
|
||||
|
||||
extern Class trayMenuDelegateClass;
|
||||
|
||||
// A cache for all our tray menu icons
|
||||
// Global because it's a singleton
|
||||
struct hashmap_s trayIconCache;
|
||||
@@ -35,6 +37,8 @@ TrayMenu* NewTrayMenu(const char* menuJSON) {
|
||||
// Create the menu
|
||||
result->menu = NewMenu(processedMenu);
|
||||
|
||||
result->delegate = NULL;
|
||||
|
||||
// Init tray status bar item
|
||||
result->statusbaritem = NULL;
|
||||
|
||||
@@ -78,12 +82,20 @@ void UpdateTrayIcon(TrayMenu *trayMenu) {
|
||||
}
|
||||
|
||||
id trayImage = hashmap_get(&trayIconCache, trayMenu->icon, strlen(trayMenu->icon));
|
||||
|
||||
// If we don't have the image in the icon cache then assume it's base64 encoded image data
|
||||
if (trayImage == NULL) {
|
||||
id data = ALLOC("NSData");
|
||||
id imageData = msg(data, s("initWithBase64EncodedString:options:"), str(trayMenu->icon), 0);
|
||||
trayImage = ALLOC("NSImage");
|
||||
msg(trayImage, s("initWithData:"), imageData);
|
||||
}
|
||||
|
||||
msg(statusBarButton, s("setImagePosition:"), trayMenu->trayIconPosition);
|
||||
msg(statusBarButton, s("setImage:"), trayImage);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ShowTrayMenu(TrayMenu* trayMenu) {
|
||||
|
||||
// Create a status bar item if we don't have one
|
||||
@@ -91,7 +103,6 @@ void ShowTrayMenu(TrayMenu* trayMenu) {
|
||||
id statusBar = msg( c("NSStatusBar"), s("systemStatusBar") );
|
||||
trayMenu->statusbaritem = msg(statusBar, s("statusItemWithLength:"), NSVariableStatusItemLength);
|
||||
msg(trayMenu->statusbaritem, s("retain"));
|
||||
|
||||
}
|
||||
|
||||
id statusBarButton = msg(trayMenu->statusbaritem, s("button"));
|
||||
@@ -105,6 +116,16 @@ void ShowTrayMenu(TrayMenu* trayMenu) {
|
||||
|
||||
// Update the menu
|
||||
id menu = GetMenu(trayMenu->menu);
|
||||
objc_setAssociatedObject(menu, "trayMenuID", str(trayMenu->ID), OBJC_ASSOCIATION_ASSIGN);
|
||||
|
||||
// Create delegate
|
||||
id trayMenuDelegate = msg((id)trayMenuDelegateClass, s("new"));
|
||||
msg(menu, s("setDelegate:"), trayMenuDelegate);
|
||||
objc_setAssociatedObject(trayMenuDelegate, "menu", menu, OBJC_ASSOCIATION_ASSIGN);
|
||||
|
||||
// Create menu delegate
|
||||
trayMenu->delegate = trayMenuDelegate;
|
||||
|
||||
msg(trayMenu->statusbaritem, s("setMenu:"), menu);
|
||||
}
|
||||
|
||||
@@ -153,6 +174,10 @@ void DeleteTrayMenu(TrayMenu* trayMenu) {
|
||||
trayMenu->statusbaritem = NULL;
|
||||
}
|
||||
|
||||
if ( trayMenu->delegate != NULL ) {
|
||||
msg(trayMenu->delegate, s("release"));
|
||||
}
|
||||
|
||||
// Free the tray menu memory
|
||||
MEMFREE(trayMenu);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ typedef struct {
|
||||
|
||||
JsonNode* processedJSON;
|
||||
|
||||
id delegate;
|
||||
|
||||
} TrayMenu;
|
||||
|
||||
TrayMenu* NewTrayMenu(const char *trayJSON);
|
||||
|
||||
@@ -16,6 +16,11 @@ TrayMenuStore* NewTrayMenuStore() {
|
||||
ABORT("[NewTrayMenuStore] Not enough memory to allocate trayMenuMap!");
|
||||
}
|
||||
|
||||
if (pthread_mutex_init(&result->lock, NULL) != 0) {
|
||||
printf("\n mutex init has failed\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -25,15 +30,19 @@ int dumpTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
||||
}
|
||||
|
||||
void DumpTrayMenuStore(TrayMenuStore* store) {
|
||||
pthread_mutex_lock(&store->lock);
|
||||
hashmap_iterate_pairs(&store->trayMenuMap, dumpTrayMenu, NULL);
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
}
|
||||
|
||||
void AddTrayMenuToStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
|
||||
TrayMenu* newMenu = NewTrayMenu(menuJSON);
|
||||
|
||||
pthread_mutex_lock(&store->lock);
|
||||
//TODO: check if there is already an entry for this menu
|
||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
}
|
||||
|
||||
int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
||||
@@ -43,12 +52,13 @@ int showTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
||||
}
|
||||
|
||||
void ShowTrayMenusInStore(TrayMenuStore* store) {
|
||||
pthread_mutex_lock(&store->lock);
|
||||
if( hashmap_num_entries(&store->trayMenuMap) > 0 ) {
|
||||
hashmap_iterate_pairs(&store->trayMenuMap, showTrayMenu, NULL);
|
||||
}
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
}
|
||||
|
||||
|
||||
int freeTrayMenu(void *const context, struct hashmap_element_s *const e) {
|
||||
DeleteTrayMenu(e->data);
|
||||
return -1;
|
||||
@@ -65,22 +75,39 @@ void DeleteTrayMenuStore(TrayMenuStore *store) {
|
||||
|
||||
// Destroy tray menu map
|
||||
hashmap_destroy(&store->trayMenuMap);
|
||||
|
||||
pthread_mutex_destroy(&store->lock);
|
||||
}
|
||||
|
||||
TrayMenu* GetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
||||
// Get the current menu
|
||||
return hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
|
||||
pthread_mutex_lock(&store->lock);
|
||||
TrayMenu* result = hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
return result;
|
||||
}
|
||||
|
||||
TrayMenu* MustGetTrayMenuFromStore(TrayMenuStore* store, const char* menuID) {
|
||||
// Get the current menu
|
||||
pthread_mutex_lock(&store->lock);
|
||||
TrayMenu* result = hashmap_get(&store->trayMenuMap, menuID, strlen(menuID));
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
|
||||
if (result == NULL ) {
|
||||
ABORT("Unable to find TrayMenu with ID '%s' in the TrayMenuStore!", menuID);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void DeleteTrayMenuInStore(TrayMenuStore* store, const char* ID) {
|
||||
|
||||
TrayMenu *menu = MustGetTrayMenuFromStore(store, ID);
|
||||
pthread_mutex_lock(&store->lock);
|
||||
hashmap_remove(&store->trayMenuMap, ID, strlen(ID));
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
DeleteTrayMenu(menu);
|
||||
}
|
||||
|
||||
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON) {
|
||||
// Parse the JSON
|
||||
JsonNode *parsedUpdate = mustParseJSON(JSON);
|
||||
@@ -105,7 +132,9 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
// If we don't have a menu, we create one
|
||||
if ( currentMenu == NULL ) {
|
||||
// Store the new menu
|
||||
pthread_mutex_lock(&store->lock);
|
||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
|
||||
// Show it
|
||||
ShowTrayMenu(newMenu);
|
||||
@@ -116,7 +145,9 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
// Save the status bar reference
|
||||
newMenu->statusbaritem = currentMenu->statusbaritem;
|
||||
|
||||
pthread_mutex_lock(&store->lock);
|
||||
hashmap_remove(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID));
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
|
||||
// Delete the current menu
|
||||
DeleteMenu(currentMenu->menu);
|
||||
@@ -125,9 +156,10 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON) {
|
||||
// Free the tray menu memory
|
||||
MEMFREE(currentMenu);
|
||||
|
||||
pthread_mutex_lock(&store->lock);
|
||||
hashmap_put(&store->trayMenuMap, newMenu->ID, strlen(newMenu->ID), newMenu);
|
||||
pthread_mutex_unlock(&store->lock);
|
||||
|
||||
// Show the updated menu
|
||||
ShowTrayMenu(newMenu);
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
#ifndef TRAYMENUSTORE_DARWIN_H
|
||||
#define TRAYMENUSTORE_DARWIN_H
|
||||
|
||||
#include "traymenu_darwin.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
typedef struct {
|
||||
|
||||
int dummy;
|
||||
@@ -13,6 +17,8 @@ typedef struct {
|
||||
// It maps tray IDs to TrayMenu*
|
||||
struct hashmap_s trayMenuMap;
|
||||
|
||||
pthread_mutex_t lock;
|
||||
|
||||
} TrayMenuStore;
|
||||
|
||||
TrayMenuStore* NewTrayMenuStore();
|
||||
@@ -22,6 +28,9 @@ void UpdateTrayMenuInStore(TrayMenuStore* store, const char* menuJSON);
|
||||
void ShowTrayMenusInStore(TrayMenuStore* store);
|
||||
void DeleteTrayMenuStore(TrayMenuStore* store);
|
||||
|
||||
TrayMenu* GetTrayMenuByID(TrayMenuStore* store, const char* menuID);
|
||||
|
||||
void UpdateTrayMenuLabelInStore(TrayMenuStore* store, const char* JSON);
|
||||
void DeleteTrayMenuInStore(TrayMenuStore* store, const char* id);
|
||||
|
||||
#endif //TRAYMENUSTORE_DARWIN_H
|
||||
|
||||
@@ -40,7 +40,7 @@ func Mkdir(dirname string) error {
|
||||
// Returns error on failure
|
||||
func MkDirs(fullPath string, mode ...os.FileMode) error {
|
||||
var perms os.FileMode
|
||||
perms = 0700
|
||||
perms = 0755
|
||||
if len(mode) == 1 {
|
||||
perms = mode[0]
|
||||
}
|
||||
@@ -243,7 +243,7 @@ func CopyDir(src string, dst string) (err error) {
|
||||
return fmt.Errorf("destination already exists")
|
||||
}
|
||||
|
||||
err = os.MkdirAll(dst, si.Mode())
|
||||
err = MkDirs(dst)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ type ProcessedMenuItem struct {
|
||||
// Image - base64 image data
|
||||
Image string `json:",omitempty"`
|
||||
MacTemplateImage bool `json:", omitempty"`
|
||||
MacAlternate bool `json:", omitempty"`
|
||||
|
||||
// Tooltip
|
||||
Tooltip string `json:",omitempty"`
|
||||
@@ -60,6 +61,7 @@ func NewProcessedMenuItem(menuItemMap *MenuItemMap, menuItem *menu.MenuItem) *Pr
|
||||
FontName: menuItem.FontName,
|
||||
Image: menuItem.Image,
|
||||
MacTemplateImage: menuItem.MacTemplateImage,
|
||||
MacAlternate: menuItem.MacAlternate,
|
||||
Tooltip: menuItem.Tooltip,
|
||||
}
|
||||
|
||||
|
||||
@@ -3,20 +3,23 @@ package menumanager
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var trayMenuID int
|
||||
var trayMenuIDMutex sync.Mutex
|
||||
|
||||
func generateTrayID() string {
|
||||
var idStr string
|
||||
trayMenuIDMutex.Lock()
|
||||
result := fmt.Sprintf("%d", trayMenuID)
|
||||
idStr = strconv.Itoa(trayMenuID)
|
||||
trayMenuID++
|
||||
trayMenuIDMutex.Unlock()
|
||||
return result
|
||||
return idStr
|
||||
}
|
||||
|
||||
type TrayMenu struct {
|
||||
@@ -26,6 +29,7 @@ type TrayMenu struct {
|
||||
menuItemMap *MenuItemMap
|
||||
menu *menu.Menu
|
||||
ProcessedMenu *WailsMenu
|
||||
trayMenu *menu.TrayMenu
|
||||
}
|
||||
|
||||
func (t *TrayMenu) AsJSON() (string, error) {
|
||||
@@ -43,6 +47,7 @@ func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||
Icon: trayMenu.Icon,
|
||||
menu: trayMenu.Menu,
|
||||
menuItemMap: NewMenuItemMap(),
|
||||
trayMenu: trayMenu,
|
||||
}
|
||||
|
||||
result.menuItemMap.AddMenu(trayMenu.Menu)
|
||||
@@ -51,6 +56,28 @@ func NewTrayMenu(trayMenu *menu.TrayMenu) *TrayMenu {
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *Manager) OnTrayMenuOpen(id string) {
|
||||
trayMenu, ok := m.trayMenus[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if trayMenu.trayMenu.OnOpen == nil {
|
||||
return
|
||||
}
|
||||
go trayMenu.trayMenu.OnOpen()
|
||||
}
|
||||
|
||||
func (m *Manager) OnTrayMenuClose(id string) {
|
||||
trayMenu, ok := m.trayMenus[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if trayMenu.trayMenu.OnClose == nil {
|
||||
return
|
||||
}
|
||||
go trayMenu.trayMenu.OnClose()
|
||||
}
|
||||
|
||||
func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||
newTrayMenu := NewTrayMenu(trayMenu)
|
||||
|
||||
@@ -65,6 +92,14 @@ func (m *Manager) AddTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||
return newTrayMenu.AsJSON()
|
||||
}
|
||||
|
||||
func (m *Manager) GetTrayID(trayMenu *menu.TrayMenu) (string, error) {
|
||||
trayID, exists := m.trayMenuPointers[trayMenu]
|
||||
if !exists {
|
||||
return "", fmt.Errorf("Unable to find menu ID for tray menu!")
|
||||
}
|
||||
return trayID, nil
|
||||
}
|
||||
|
||||
// SetTrayMenu updates or creates a menu
|
||||
func (m *Manager) SetTrayMenu(trayMenu *menu.TrayMenu) (string, error) {
|
||||
trayID, trayMenuKnown := m.trayMenuPointers[trayMenu]
|
||||
|
||||
@@ -37,6 +37,7 @@ type Client interface {
|
||||
SetTrayMenu(trayMenuJSON string)
|
||||
UpdateTrayMenuLabel(JSON string)
|
||||
UpdateContextMenu(contextMenuJSON string)
|
||||
DeleteTrayMenuByID(id string)
|
||||
}
|
||||
|
||||
// DispatchClient is what the frontends use to interface with the
|
||||
|
||||
@@ -32,6 +32,14 @@ func menuMessageParser(message string) (*parsedMessage, error) {
|
||||
callbackid := message[2:]
|
||||
topic = "menu:clicked"
|
||||
data = callbackid
|
||||
case 'o':
|
||||
callbackid := message[2:]
|
||||
topic = "menu:ontrayopen"
|
||||
data = callbackid
|
||||
case 'c':
|
||||
callbackid := message[2:]
|
||||
topic = "menu:ontrayclose"
|
||||
data = callbackid
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid menu message: %s", message)
|
||||
}
|
||||
|
||||
@@ -21,13 +21,14 @@ var messageParsers = map[byte]func(string) (*parsedMessage, error){
|
||||
'M': menuMessageParser,
|
||||
'T': trayMessageParser,
|
||||
'X': contextMenusMessageParser,
|
||||
'U': urlMessageParser,
|
||||
}
|
||||
|
||||
// Parse will attempt to parse the given message
|
||||
func Parse(message string) (*parsedMessage, error) {
|
||||
|
||||
if len(message) == 0 {
|
||||
return nil, fmt.Errorf("MessageParser received blank message");
|
||||
return nil, fmt.Errorf("MessageParser received blank message")
|
||||
}
|
||||
|
||||
parseMethod := messageParsers[message[0]]
|
||||
|
||||
20
v2/internal/messagedispatcher/message/url.go
Normal file
20
v2/internal/messagedispatcher/message/url.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package message
|
||||
|
||||
import "fmt"
|
||||
|
||||
// urlMessageParser does what it says on the tin!
|
||||
func urlMessageParser(message string) (*parsedMessage, error) {
|
||||
|
||||
// Sanity check: URL messages must be at least 2 bytes
|
||||
if len(message) < 2 {
|
||||
return nil, fmt.Errorf("log message was an invalid length")
|
||||
}
|
||||
|
||||
// Switch on the log type
|
||||
switch message[1] {
|
||||
case 'C':
|
||||
return &parsedMessage{Topic: "url:handler", Data: message[2:]}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("url message type '%c' invalid", message[1])
|
||||
}
|
||||
}
|
||||
@@ -527,6 +527,17 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
|
||||
for _, client := range d.clients {
|
||||
client.frontend.UpdateTrayMenuLabel(updatedTrayMenuLabel)
|
||||
}
|
||||
case "deletetraymenu":
|
||||
traymenuid, ok := result.Data().(string)
|
||||
if !ok {
|
||||
d.logger.Error("Invalid data for 'menufrontend:updatetraymenulabel' : %#v",
|
||||
result.Data())
|
||||
return
|
||||
}
|
||||
|
||||
for _, client := range d.clients {
|
||||
client.frontend.DeleteTrayMenuByID(traymenuid)
|
||||
}
|
||||
|
||||
default:
|
||||
d.logger.Error("Unknown menufrontend command: %s", command)
|
||||
|
||||
@@ -39,7 +39,12 @@ func (p *Process) Start() error {
|
||||
|
||||
go func(cmd *exec.Cmd, running *bool, logger *clilogger.CLILogger, exitChannel chan bool) {
|
||||
logger.Println("Starting process (PID: %d)", cmd.Process.Pid)
|
||||
cmd.Wait()
|
||||
err := cmd.Wait()
|
||||
if err != nil {
|
||||
if err.Error() != "signal: killed" {
|
||||
logger.Fatal("Fatal error from app: " + err.Error())
|
||||
}
|
||||
}
|
||||
logger.Println("Exiting process (PID: %d)", cmd.Process.Pid)
|
||||
*running = false
|
||||
exitChannel <- true
|
||||
|
||||
@@ -620,7 +620,7 @@
|
||||
}
|
||||
|
||||
/** Menubar **/
|
||||
const menuVisible = writable(true);
|
||||
const menuVisible = writable(false);
|
||||
|
||||
/** Trays **/
|
||||
|
||||
@@ -649,6 +649,18 @@
|
||||
});
|
||||
}
|
||||
|
||||
function deleteTrayMenu(id) {
|
||||
trays.update((current) => {
|
||||
// Remove existing if it exists, else add
|
||||
const index = current.findIndex(item => item.ID === id);
|
||||
if ( index === -1 ) {
|
||||
return log("ERROR: Attempted to delete tray index ")
|
||||
}
|
||||
current.splice(index, 1);
|
||||
return current;
|
||||
});
|
||||
}
|
||||
|
||||
let selectedMenu = writable(null);
|
||||
|
||||
function fade(node, { delay = 0, duration = 400, easing = identity } = {}) {
|
||||
@@ -1220,11 +1232,11 @@
|
||||
|
||||
function get_each_context$1(ctx, list, i) {
|
||||
const child_ctx = ctx.slice();
|
||||
child_ctx[8] = list[i];
|
||||
child_ctx[9] = list[i];
|
||||
return child_ctx;
|
||||
}
|
||||
|
||||
// (29:0) {#if $menuVisible }
|
||||
// (38:0) {#if $menuVisible }
|
||||
function create_if_block$3(ctx) {
|
||||
let div;
|
||||
let span1;
|
||||
@@ -1336,11 +1348,11 @@
|
||||
};
|
||||
}
|
||||
|
||||
// (32:4) {#each $trays as tray}
|
||||
// (41:4) {#each $trays as tray}
|
||||
function create_each_block$1(ctx) {
|
||||
let traymenu;
|
||||
let current;
|
||||
traymenu = new TrayMenu({ props: { tray: /*tray*/ ctx[8] } });
|
||||
traymenu = new TrayMenu({ props: { tray: /*tray*/ ctx[9] } });
|
||||
|
||||
return {
|
||||
c() {
|
||||
@@ -1352,7 +1364,7 @@
|
||||
},
|
||||
p(ctx, dirty) {
|
||||
const traymenu_changes = {};
|
||||
if (dirty & /*$trays*/ 4) traymenu_changes.tray = /*tray*/ ctx[8];
|
||||
if (dirty & /*$trays*/ 4) traymenu_changes.tray = /*tray*/ ctx[9];
|
||||
traymenu.$set(traymenu_changes);
|
||||
},
|
||||
i(local) {
|
||||
@@ -1373,6 +1385,8 @@
|
||||
function create_fragment$3(ctx) {
|
||||
let if_block_anchor;
|
||||
let current;
|
||||
let mounted;
|
||||
let dispose;
|
||||
let if_block = /*$menuVisible*/ ctx[1] && create_if_block$3(ctx);
|
||||
|
||||
return {
|
||||
@@ -1384,6 +1398,11 @@
|
||||
if (if_block) if_block.m(target, anchor);
|
||||
insert(target, if_block_anchor, anchor);
|
||||
current = true;
|
||||
|
||||
if (!mounted) {
|
||||
dispose = listen(window, "keydown", /*handleKeydown*/ ctx[3]);
|
||||
mounted = true;
|
||||
}
|
||||
},
|
||||
p(ctx, [dirty]) {
|
||||
if (/*$menuVisible*/ ctx[1]) {
|
||||
@@ -1421,6 +1440,8 @@
|
||||
d(detaching) {
|
||||
if (if_block) if_block.d(detaching);
|
||||
if (detaching) detach(if_block_anchor);
|
||||
mounted = false;
|
||||
dispose();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1440,7 +1461,7 @@
|
||||
onMount(() => {
|
||||
const interval = setInterval(
|
||||
() => {
|
||||
$$invalidate(3, time = new Date());
|
||||
$$invalidate(4, time = new Date());
|
||||
},
|
||||
1000
|
||||
);
|
||||
@@ -1450,33 +1471,52 @@
|
||||
};
|
||||
});
|
||||
|
||||
function handleKeydown(e) {
|
||||
// Backtick toggle
|
||||
if (e.keyCode == 192) {
|
||||
menuVisible.update(current => {
|
||||
return !current;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$$self.$$.update = () => {
|
||||
if ($$self.$$.dirty & /*time*/ 8) {
|
||||
$$invalidate(4, day = time.toLocaleString("default", { weekday: "short" }));
|
||||
if ($$self.$$.dirty & /*time*/ 16) {
|
||||
$$invalidate(5, day = time.toLocaleString("default", { weekday: "short" }));
|
||||
}
|
||||
|
||||
if ($$self.$$.dirty & /*time*/ 8) {
|
||||
$$invalidate(5, dom = time.getDate());
|
||||
if ($$self.$$.dirty & /*time*/ 16) {
|
||||
$$invalidate(6, dom = time.getDate());
|
||||
}
|
||||
|
||||
if ($$self.$$.dirty & /*time*/ 8) {
|
||||
$$invalidate(6, mon = time.toLocaleString("default", { month: "short" }));
|
||||
if ($$self.$$.dirty & /*time*/ 16) {
|
||||
$$invalidate(7, mon = time.toLocaleString("default", { month: "short" }));
|
||||
}
|
||||
|
||||
if ($$self.$$.dirty & /*time*/ 8) {
|
||||
$$invalidate(7, currentTime = time.toLocaleString("en-US", {
|
||||
if ($$self.$$.dirty & /*time*/ 16) {
|
||||
$$invalidate(8, currentTime = time.toLocaleString("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
hour12: true
|
||||
}).toLowerCase());
|
||||
}
|
||||
|
||||
if ($$self.$$.dirty & /*day, dom, mon, currentTime*/ 240) {
|
||||
if ($$self.$$.dirty & /*day, dom, mon, currentTime*/ 480) {
|
||||
$$invalidate(0, dateTimeString = `${day} ${dom} ${mon} ${currentTime}`);
|
||||
}
|
||||
};
|
||||
|
||||
return [dateTimeString, $menuVisible, $trays, time, day, dom, mon, currentTime];
|
||||
return [
|
||||
dateTimeString,
|
||||
$menuVisible,
|
||||
$trays,
|
||||
handleKeydown,
|
||||
time,
|
||||
day,
|
||||
dom,
|
||||
mon,
|
||||
currentTime
|
||||
];
|
||||
}
|
||||
|
||||
class Menubar extends SvelteComponent {
|
||||
@@ -1638,6 +1678,11 @@
|
||||
let trayLabelData = JSON.parse(updateTrayLabelJSON);
|
||||
updateTrayLabel(trayLabelData);
|
||||
break
|
||||
case 'D':
|
||||
// Delete Tray Menu
|
||||
const id = trayMessage.slice(1);
|
||||
deleteTrayMenu(id);
|
||||
break
|
||||
default:
|
||||
log('Unknown tray message: ' + message.data);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wails/runtime",
|
||||
"version": "1.3.9",
|
||||
"version": "1.3.12",
|
||||
"description": "Wails V2 Javascript runtime library",
|
||||
"main": "main.js",
|
||||
"types": "runtime.d.ts",
|
||||
|
||||
@@ -24,6 +24,15 @@
|
||||
};
|
||||
});
|
||||
|
||||
function handleKeydown(e) {
|
||||
// Backtick toggle
|
||||
if( e.keyCode == 192 ) {
|
||||
menuVisible.update( (current) => {
|
||||
return !current;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#if $menuVisible }
|
||||
@@ -37,6 +46,8 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<svelte:window on:keydown={handleKeydown}/>
|
||||
|
||||
<style>
|
||||
|
||||
.tray-menus {
|
||||
|
||||
@@ -13,7 +13,7 @@ export function hideOverlay() {
|
||||
}
|
||||
|
||||
/** Menubar **/
|
||||
export const menuVisible = writable(true);
|
||||
export const menuVisible = writable(false);
|
||||
|
||||
export function showMenuBar() {
|
||||
menuVisible.set(true);
|
||||
@@ -49,4 +49,16 @@ export function updateTrayLabel(tray) {
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteTrayMenu(id) {
|
||||
trays.update((current) => {
|
||||
// Remove existing if it exists, else add
|
||||
const index = current.findIndex(item => item.ID === id);
|
||||
if ( index === -1 ) {
|
||||
return log("ERROR: Attempted to delete tray index ", id, "but it doesn't exist")
|
||||
}
|
||||
current.splice(index, 1);
|
||||
return current;
|
||||
})
|
||||
}
|
||||
|
||||
export let selectedMenu = writable(null);
|
||||
@@ -10,7 +10,7 @@ The lightweight framework for web-like apps
|
||||
/* jshint esversion: 6 */
|
||||
|
||||
|
||||
import {setTray, hideOverlay, showOverlay, updateTrayLabel} from "./store";
|
||||
import {setTray, hideOverlay, showOverlay, updateTrayLabel, deleteTrayMenu} from "./store";
|
||||
import {log} from "./log";
|
||||
|
||||
let websocket = null;
|
||||
@@ -154,6 +154,11 @@ function handleMessage(message) {
|
||||
let trayLabelData = JSON.parse(updateTrayLabelJSON)
|
||||
updateTrayLabel(trayLabelData)
|
||||
break
|
||||
case 'D':
|
||||
// Delete Tray Menu
|
||||
const id = trayMessage.slice(1);
|
||||
deleteTrayMenu(id)
|
||||
break
|
||||
default:
|
||||
log('Unknown tray message: ' + message.data);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ type Menu interface {
|
||||
UpdateApplicationMenu()
|
||||
UpdateContextMenu(contextMenu *menu.ContextMenu)
|
||||
SetTrayMenu(trayMenu *menu.TrayMenu)
|
||||
DeleteTrayMenu(trayMenu *menu.TrayMenu)
|
||||
UpdateTrayMenuLabel(trayMenu *menu.TrayMenu)
|
||||
}
|
||||
|
||||
@@ -39,3 +40,7 @@ func (m *menuRuntime) SetTrayMenu(trayMenu *menu.TrayMenu) {
|
||||
func (m *menuRuntime) UpdateTrayMenuLabel(trayMenu *menu.TrayMenu) {
|
||||
m.bus.Publish("menu:updatetraymenulabel", trayMenu)
|
||||
}
|
||||
|
||||
func (m *menuRuntime) DeleteTrayMenu(trayMenu *menu.TrayMenu) {
|
||||
m.bus.Publish("menu:deletetraymenu", trayMenu)
|
||||
}
|
||||
|
||||
@@ -6,20 +6,19 @@ import (
|
||||
|
||||
// Runtime is a means for the user to interact with the application at runtime
|
||||
type Runtime struct {
|
||||
Browser Browser
|
||||
Events Events
|
||||
Window Window
|
||||
Dialog Dialog
|
||||
System System
|
||||
Menu Menu
|
||||
Store *StoreProvider
|
||||
Log Log
|
||||
bus *servicebus.ServiceBus
|
||||
shutdownCallback func()
|
||||
Browser Browser
|
||||
Events Events
|
||||
Window Window
|
||||
Dialog Dialog
|
||||
System System
|
||||
Menu Menu
|
||||
Store *StoreProvider
|
||||
Log Log
|
||||
bus *servicebus.ServiceBus
|
||||
}
|
||||
|
||||
// New creates a new runtime
|
||||
func New(serviceBus *servicebus.ServiceBus, shutdownCallback func()) *Runtime {
|
||||
func New(serviceBus *servicebus.ServiceBus) *Runtime {
|
||||
result := &Runtime{
|
||||
Browser: newBrowser(),
|
||||
Events: newEvents(serviceBus),
|
||||
@@ -36,11 +35,6 @@ func New(serviceBus *servicebus.ServiceBus, shutdownCallback func()) *Runtime {
|
||||
|
||||
// Quit the application
|
||||
func (r *Runtime) Quit() {
|
||||
// Call back to user's shutdown method if defined
|
||||
if r.shutdownCallback != nil {
|
||||
r.shutdownCallback()
|
||||
}
|
||||
|
||||
// Start shutdown of Wails
|
||||
r.bus.Publish("quit", "runtime.Quit()")
|
||||
}
|
||||
|
||||
@@ -26,25 +26,20 @@ type Manager struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
// The shutdown callback to notify the user's app that a shutdown
|
||||
// has started
|
||||
shutdownCallback func()
|
||||
|
||||
// Parent waitgroup
|
||||
wg *sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewManager creates a new signal manager
|
||||
func NewManager(ctx context.Context, cancel context.CancelFunc, bus *servicebus.ServiceBus, logger *logger.Logger, shutdownCallback func()) (*Manager, error) {
|
||||
func NewManager(ctx context.Context, cancel context.CancelFunc, bus *servicebus.ServiceBus, logger *logger.Logger) (*Manager, error) {
|
||||
|
||||
result := &Manager{
|
||||
bus: bus,
|
||||
logger: logger.CustomLogger("Event Manager"),
|
||||
signalchannel: make(chan os.Signal, 2),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
shutdownCallback: shutdownCallback,
|
||||
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
|
||||
bus: bus,
|
||||
logger: logger.CustomLogger("Event Manager"),
|
||||
signalchannel: make(chan os.Signal, 2),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
|
||||
}
|
||||
|
||||
return result, nil
|
||||
@@ -67,11 +62,6 @@ func (m *Manager) Start() {
|
||||
m.logger.Trace("Ctrl+C detected. Shutting down...")
|
||||
m.bus.Publish("quit", "ctrl-c pressed")
|
||||
|
||||
// Shutdown app first
|
||||
if m.shutdownCallback != nil {
|
||||
m.shutdownCallback()
|
||||
}
|
||||
|
||||
// Start shutdown of Wails
|
||||
m.cancel()
|
||||
|
||||
|
||||
@@ -77,6 +77,12 @@ func (m *Menu) Start() error {
|
||||
splitTopic := strings.Split(menuMessage.Topic(), ":")
|
||||
menuMessageType := splitTopic[1]
|
||||
switch menuMessageType {
|
||||
case "ontrayopen":
|
||||
trayID := menuMessage.Data().(string)
|
||||
m.menuManager.OnTrayMenuOpen(trayID)
|
||||
case "ontrayclose":
|
||||
trayID := menuMessage.Data().(string)
|
||||
m.menuManager.OnTrayMenuClose(trayID)
|
||||
case "clicked":
|
||||
if len(splitTopic) != 2 {
|
||||
m.logger.Error("Received clicked message with invalid topic format. Expected 2 sections in topic, got %s", splitTopic)
|
||||
@@ -137,6 +143,17 @@ func (m *Menu) Start() error {
|
||||
// Notify frontend of menu change
|
||||
m.bus.Publish("menufrontend:settraymenu", updatedMenu)
|
||||
|
||||
case "deletetraymenu":
|
||||
trayMenu := menuMessage.Data().(*menu.TrayMenu)
|
||||
trayID, err := m.menuManager.GetTrayID(trayMenu)
|
||||
if err != nil {
|
||||
m.logger.Trace("%s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Notify frontend of menu change
|
||||
m.bus.Publish("menufrontend:deletetraymenu", trayID)
|
||||
|
||||
case "updatetraymenulabel":
|
||||
trayMenu := menuMessage.Data().(*menu.TrayMenu)
|
||||
updatedLabel, err := m.menuManager.UpdateTrayMenuLabel(trayMenu)
|
||||
|
||||
@@ -37,7 +37,7 @@ type Runtime struct {
|
||||
}
|
||||
|
||||
// NewRuntime creates a new runtime subsystem
|
||||
func NewRuntime(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger, startupCallback func(*runtime.Runtime), shutdownCallback func()) (*Runtime, error) {
|
||||
func NewRuntime(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger, startupCallback func(*runtime.Runtime)) (*Runtime, error) {
|
||||
|
||||
// Subscribe to log messages
|
||||
runtimeChannel, err := bus.Subscribe("runtime:")
|
||||
@@ -52,13 +52,12 @@ func NewRuntime(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.
|
||||
}
|
||||
|
||||
result := &Runtime{
|
||||
runtimeChannel: runtimeChannel,
|
||||
hooksChannel: hooksChannel,
|
||||
logger: logger.CustomLogger("Runtime Subsystem"),
|
||||
runtime: runtime.New(bus, shutdownCallback),
|
||||
startupCallback: startupCallback,
|
||||
shutdownCallback: shutdownCallback,
|
||||
ctx: ctx,
|
||||
runtimeChannel: runtimeChannel,
|
||||
hooksChannel: hooksChannel,
|
||||
logger: logger.CustomLogger("Runtime Subsystem"),
|
||||
runtime: runtime.New(bus),
|
||||
startupCallback: startupCallback,
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
||||
98
v2/internal/subsystem/url.go
Normal file
98
v2/internal/subsystem/url.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package subsystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/logger"
|
||||
"github.com/wailsapp/wails/v2/internal/servicebus"
|
||||
)
|
||||
|
||||
// URL is the URL Handler subsystem. It handles messages with topics starting
|
||||
// with "url:"
|
||||
type URL struct {
|
||||
urlChannel <-chan *servicebus.Message
|
||||
|
||||
// quit flag
|
||||
shouldQuit bool
|
||||
|
||||
// Logger!
|
||||
logger *logger.Logger
|
||||
|
||||
// Context for shutdown
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
// internal waitgroup
|
||||
wg sync.WaitGroup
|
||||
|
||||
// Handlers
|
||||
handlers map[string]func(string)
|
||||
}
|
||||
|
||||
// NewURL creates a new log subsystem
|
||||
func NewURL(bus *servicebus.ServiceBus, logger *logger.Logger, handlers map[string]func(string)) (*URL, error) {
|
||||
|
||||
// Subscribe to log messages
|
||||
urlChannel, err := bus.Subscribe("url")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
result := &URL{
|
||||
urlChannel: urlChannel,
|
||||
logger: logger,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
handlers: handlers,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Start the subsystem
|
||||
func (u *URL) Start() error {
|
||||
|
||||
u.wg.Add(1)
|
||||
|
||||
// Spin off a go routine
|
||||
go func() {
|
||||
defer u.logger.Trace("URL Shutdown")
|
||||
|
||||
for u.shouldQuit == false {
|
||||
select {
|
||||
case <-u.ctx.Done():
|
||||
u.wg.Done()
|
||||
return
|
||||
case urlMessage := <-u.urlChannel:
|
||||
// Guard against nil messages
|
||||
if urlMessage == nil {
|
||||
continue
|
||||
}
|
||||
messageType := strings.TrimPrefix(urlMessage.Topic(), "url:")
|
||||
switch messageType {
|
||||
case "handler":
|
||||
url := urlMessage.Data().(string)
|
||||
splitURL := strings.Split(url, ":")
|
||||
protocol := splitURL[0]
|
||||
callback, ok := u.handlers[protocol]
|
||||
if ok {
|
||||
go callback(url)
|
||||
}
|
||||
default:
|
||||
u.logger.Error("unknown url message: %+v", urlMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *URL) Close() {
|
||||
u.cancel()
|
||||
u.wg.Wait()
|
||||
}
|
||||
@@ -12,20 +12,19 @@ type Basic struct {
|
||||
}
|
||||
|
||||
// newBasic creates a new Basic application struct
|
||||
func newBasic() *Basic {
|
||||
func NewBasic() *Basic {
|
||||
return &Basic{}
|
||||
}
|
||||
|
||||
// WailsInit is called at application startup
|
||||
func (b *Basic) WailsInit(runtime *wails.Runtime) error {
|
||||
// startup is called at application startup
|
||||
func (b *Basic) startup(runtime *wails.Runtime) {
|
||||
// Perform your setup here
|
||||
b.runtime = runtime
|
||||
runtime.Window.SetTitle("{{.ProjectName}}")
|
||||
return nil
|
||||
}
|
||||
|
||||
// WailsShutdown is called at application termination
|
||||
func (b *Basic) WailsShutdown() {
|
||||
// shutdown is called at application termination
|
||||
func (b *Basic) shutdown() {
|
||||
// Perform your teardown here
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<link rel="stylesheet" href="/main.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body data-wails-drag>
|
||||
<div id="logo"></div>
|
||||
<div id="input">
|
||||
<input id="name" type="text"></input>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,21 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"log"
|
||||
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/logger"
|
||||
"github.com/wailsapp/wails/v2/pkg/menu"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// Create application with options
|
||||
app, err := wails.CreateApp("{{.ProjectName}}", 1024, 768)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
app := NewBasic()
|
||||
|
||||
app.Bind(newBasic())
|
||||
|
||||
err = app.Run()
|
||||
err := wails.Run(&options.App{
|
||||
Title: "{{.ProjectName}}",
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
DisableResize: true,
|
||||
Mac: &mac.Options{
|
||||
WebviewIsTransparent: true,
|
||||
WindowBackgroundIsTranslucent: true,
|
||||
TitleBar: mac.TitleBarHiddenInset(),
|
||||
Menu: menu.DefaultMacMenu(),
|
||||
},
|
||||
LogLevel: logger.DEBUG,
|
||||
Startup: app.startup,
|
||||
Shutdown: app.shutdown,
|
||||
Bind: []interface{}{
|
||||
app,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/wailsapp/wails/v2/internal/colour"
|
||||
)
|
||||
|
||||
// CLILogger is used by the cli
|
||||
@@ -51,9 +53,9 @@ func (c *CLILogger) Println(message string, args ...interface{}) {
|
||||
// Fatal prints the given message then aborts
|
||||
func (c *CLILogger) Fatal(message string, args ...interface{}) {
|
||||
temp := fmt.Sprintf(message, args...)
|
||||
_, err := fmt.Fprintln(c.Writer, "FATAL: "+temp)
|
||||
_, err := fmt.Fprintln(c.Writer, colour.Red("FATAL: "+temp))
|
||||
if err != nil {
|
||||
println("FATAL: ", err)
|
||||
println(colour.Red("FATAL: " + err.Error()))
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -145,6 +145,13 @@ func (b *BaseBuilder) CleanUp() {
|
||||
// CompileProject compiles the project
|
||||
func (b *BaseBuilder) CompileProject(options *Options) error {
|
||||
|
||||
// Run go mod tidy first
|
||||
cmd := exec.Command(options.Compiler, "mod", "tidy")
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Default go build command
|
||||
commands := slicer.String([]string{"build"})
|
||||
|
||||
@@ -188,7 +195,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
||||
|
||||
// Get application build directory
|
||||
appDir := options.BuildDirectory
|
||||
err := cleanBuildDirectory(options)
|
||||
err = cleanBuildDirectory(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -211,7 +218,7 @@ func (b *BaseBuilder) CompileProject(options *Options) error {
|
||||
options.CompiledBinary = compiledBinary
|
||||
|
||||
// Create the command
|
||||
cmd := exec.Command(options.Compiler, commands.AsSlice()...)
|
||||
cmd = exec.Command(options.Compiler, commands.AsSlice()...)
|
||||
|
||||
// Set the directory
|
||||
cmd.Dir = b.projectData.Path
|
||||
|
||||
@@ -38,6 +38,7 @@ type Options struct {
|
||||
BuildDirectory string // Directory to use for building the application
|
||||
CompiledBinary string // Fully qualified path to the compiled binary
|
||||
KeepAssets bool // /Keep the generated assets/files
|
||||
AppleIdentity string
|
||||
}
|
||||
|
||||
// GetModeAsString returns the current mode as a string
|
||||
|
||||
@@ -2,9 +2,11 @@ package build
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -52,6 +54,14 @@ func packageApplication(options *Options) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Sign app if needed
|
||||
if options.AppleIdentity != "" {
|
||||
err = signApplication(options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -176,3 +186,21 @@ func processApplicationIcon(resourceDir string, iconsDir string) (err error) {
|
||||
}()
|
||||
return icns.Encode(dest, srcImg)
|
||||
}
|
||||
|
||||
func signApplication(options *Options) error {
|
||||
bundlename := filepath.Join(options.BuildDirectory, options.ProjectData.Name+".app")
|
||||
identity := fmt.Sprintf(`"%s"`, options.AppleIdentity)
|
||||
cmd := exec.Command("codesign", "--sign", identity, "--deep", "--force", "--verbose", "--timestamp", "--options", "runtime", bundlename)
|
||||
var stdo, stde bytes.Buffer
|
||||
cmd.Stdout = &stdo
|
||||
cmd.Stderr = &stde
|
||||
|
||||
// Run command
|
||||
err := cmd.Run()
|
||||
|
||||
// Format error if we have one
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s\n%s", err, string(stde.Bytes()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,21 +1,44 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Modifier is actually a string
|
||||
type Modifier string
|
||||
|
||||
const (
|
||||
// CmdOrCtrlKey represents Command on Mac and Control on other platforms
|
||||
CmdOrCtrlKey Modifier = "CmdOrCtrl"
|
||||
CmdOrCtrlKey Modifier = "cmdorctrl"
|
||||
// OptionOrAltKey represents Option on Mac and Alt on other platforms
|
||||
OptionOrAltKey Modifier = "OptionOrAlt"
|
||||
OptionOrAltKey Modifier = "optionoralt"
|
||||
// ShiftKey represents the shift key on all systems
|
||||
ShiftKey Modifier = "Shift"
|
||||
ShiftKey Modifier = "shift"
|
||||
// SuperKey represents Command on Mac and the Windows key on the other platforms
|
||||
SuperKey Modifier = "Super"
|
||||
SuperKey Modifier = "super"
|
||||
// ControlKey represents the control key on all systems
|
||||
ControlKey Modifier = "Control"
|
||||
ControlKey Modifier = "ctrl"
|
||||
)
|
||||
|
||||
var modifierMap = map[string]Modifier{
|
||||
"cmdorctrl": CmdOrCtrlKey,
|
||||
"optionoralt": OptionOrAltKey,
|
||||
"shift": ShiftKey,
|
||||
"super": SuperKey,
|
||||
"ctrl": ControlKey,
|
||||
}
|
||||
|
||||
func parseModifier(text string) (*Modifier, error) {
|
||||
lowertext := strings.ToLower(text)
|
||||
result, valid := modifierMap[lowertext]
|
||||
if !valid {
|
||||
return nil, fmt.Errorf("'%s' is not a valid modifier", text)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// Accelerator holds the keyboard shortcut for a menu item
|
||||
type Accelerator struct {
|
||||
Key string
|
||||
@@ -25,14 +48,14 @@ type Accelerator struct {
|
||||
// Key creates a standard key Accelerator
|
||||
func Key(key string) *Accelerator {
|
||||
return &Accelerator{
|
||||
Key: key,
|
||||
Key: strings.ToLower(key),
|
||||
}
|
||||
}
|
||||
|
||||
// CmdOrCtrl creates a 'CmdOrCtrl' Accelerator
|
||||
func CmdOrCtrl(key string) *Accelerator {
|
||||
return &Accelerator{
|
||||
Key: key,
|
||||
Key: strings.ToLower(key),
|
||||
Modifiers: []Modifier{CmdOrCtrlKey},
|
||||
}
|
||||
}
|
||||
@@ -40,7 +63,7 @@ func CmdOrCtrl(key string) *Accelerator {
|
||||
// OptionOrAlt creates a 'OptionOrAlt' Accelerator
|
||||
func OptionOrAlt(key string) *Accelerator {
|
||||
return &Accelerator{
|
||||
Key: key,
|
||||
Key: strings.ToLower(key),
|
||||
Modifiers: []Modifier{OptionOrAltKey},
|
||||
}
|
||||
}
|
||||
@@ -48,7 +71,7 @@ func OptionOrAlt(key string) *Accelerator {
|
||||
// Shift creates a 'Shift' Accelerator
|
||||
func Shift(key string) *Accelerator {
|
||||
return &Accelerator{
|
||||
Key: key,
|
||||
Key: strings.ToLower(key),
|
||||
Modifiers: []Modifier{ShiftKey},
|
||||
}
|
||||
}
|
||||
@@ -56,7 +79,7 @@ func Shift(key string) *Accelerator {
|
||||
// Control creates a 'Control' Accelerator
|
||||
func Control(key string) *Accelerator {
|
||||
return &Accelerator{
|
||||
Key: key,
|
||||
Key: strings.ToLower(key),
|
||||
Modifiers: []Modifier{ControlKey},
|
||||
}
|
||||
}
|
||||
@@ -64,7 +87,7 @@ func Control(key string) *Accelerator {
|
||||
// Super creates a 'Super' Accelerator
|
||||
func Super(key string) *Accelerator {
|
||||
return &Accelerator{
|
||||
Key: key,
|
||||
Key: strings.ToLower(key),
|
||||
Modifiers: []Modifier{SuperKey},
|
||||
}
|
||||
}
|
||||
|
||||
90
v2/pkg/menu/keys/parser.go
Normal file
90
v2/pkg/menu/keys/parser.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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"})
|
||||
|
||||
func parseKey(key string) (string, bool) {
|
||||
|
||||
// Lowercase!
|
||||
key = strings.ToLower(key)
|
||||
|
||||
// Check special case
|
||||
if key == "plus" {
|
||||
return "+", true
|
||||
}
|
||||
|
||||
// Handle named keys
|
||||
if namedKeys.Contains(key) {
|
||||
return key, true
|
||||
}
|
||||
|
||||
// Check we only have a single character
|
||||
if len(key) != 1 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
runeKey := rune(key[0])
|
||||
|
||||
// This may be too inclusive
|
||||
if strconv.IsPrint(runeKey) {
|
||||
return key, true
|
||||
}
|
||||
|
||||
return "", false
|
||||
|
||||
}
|
||||
|
||||
func Parse(shortcut string) (*Accelerator, error) {
|
||||
|
||||
var result Accelerator
|
||||
|
||||
// Split the shortcut by +
|
||||
components := strings.Split(shortcut, "+")
|
||||
|
||||
// If we only have one it should be a key
|
||||
// We require components
|
||||
if len(components) == 0 {
|
||||
return nil, fmt.Errorf("no components given to validateComponents")
|
||||
}
|
||||
|
||||
// Keep track of modifiers we have processed
|
||||
var modifiersProcessed slicer.StringSlicer
|
||||
|
||||
// Check components
|
||||
for index, component := range components {
|
||||
|
||||
// If last component
|
||||
if index == len(components)-1 {
|
||||
processedkey, validKey := parseKey(component)
|
||||
if !validKey {
|
||||
return nil, fmt.Errorf("'%s' is not a valid key", component)
|
||||
}
|
||||
result.Key = processedkey
|
||||
continue
|
||||
}
|
||||
|
||||
// Not last component - needs to be modifier
|
||||
lowercaseComponent := strings.ToLower(component)
|
||||
thisModifier, valid := modifierMap[lowercaseComponent]
|
||||
if !valid {
|
||||
return nil, fmt.Errorf("'%s' is not a valid modifier", component)
|
||||
}
|
||||
// Needs to be unique
|
||||
if modifiersProcessed.Contains(lowercaseComponent) {
|
||||
return nil, fmt.Errorf("Modifier '%s' is defined twice for shortcut: %s", component, shortcut)
|
||||
}
|
||||
|
||||
// Save this data
|
||||
result.Modifiers = append(result.Modifiers, thisModifier)
|
||||
modifiersProcessed.Add(lowercaseComponent)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
38
v2/pkg/menu/keys/parser_test.go
Normal file
38
v2/pkg/menu/keys/parser_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package keys
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/matryer/is"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
|
||||
i := is.New(t)
|
||||
|
||||
type args struct {
|
||||
Input string
|
||||
Expected *Accelerator
|
||||
}
|
||||
|
||||
gooddata := []args{
|
||||
{"CmdOrCtrl+A", CmdOrCtrl("A")},
|
||||
{"SHIFT+.", Shift(".")},
|
||||
{"CTRL+plus", Control("+")},
|
||||
{"CTRL+SHIFT+escApe", Combo("escape", ControlKey, ShiftKey)},
|
||||
{";", Key(";")},
|
||||
{"Super+Tab", Super("tab")},
|
||||
{"OptionOrAlt+Page Down", OptionOrAlt("Page Down")},
|
||||
}
|
||||
for _, tt := range gooddata {
|
||||
result, err := Parse(tt.Input)
|
||||
i.NoErr(err)
|
||||
i.Equal(result, tt.Expected)
|
||||
}
|
||||
baddata := []string{"CmdOrCrl+A", "SHIT+.", "CTL+plus", "CTRL+SHIF+esApe", "escap", "Sper+Tab", "OptionOrAlt"}
|
||||
for _, d := range baddata {
|
||||
result, err := Parse(d)
|
||||
i.True(err != nil)
|
||||
i.Equal(result, "")
|
||||
}
|
||||
}
|
||||
@@ -39,9 +39,12 @@ type MenuItem struct {
|
||||
// Image - base64 image data
|
||||
Image string
|
||||
|
||||
// MacTemplateImage indicates that on a mac, this image is a template image
|
||||
// MacTemplateImage indicates that on a Mac, this image is a template image
|
||||
MacTemplateImage bool
|
||||
|
||||
// MacAlternate indicates that this item is an alternative to the previous menu item
|
||||
MacAlternate bool
|
||||
|
||||
// Tooltip
|
||||
Tooltip string
|
||||
|
||||
|
||||
@@ -14,4 +14,10 @@ type TrayMenu struct {
|
||||
|
||||
// Menu is the initial menu we wish to use for the tray
|
||||
Menu *Menu
|
||||
|
||||
// OnOpen is called when the Menu is opened
|
||||
OnOpen func()
|
||||
|
||||
// OnClose is called when the Menu is closed
|
||||
OnClose func()
|
||||
}
|
||||
|
||||
@@ -2,6 +2,14 @@ package mac
|
||||
|
||||
import "github.com/wailsapp/wails/v2/pkg/menu"
|
||||
|
||||
type ActivationPolicy int
|
||||
|
||||
const (
|
||||
NSApplicationActivationPolicyRegular ActivationPolicy = 0
|
||||
NSApplicationActivationPolicyAccessory ActivationPolicy = 1
|
||||
NSApplicationActivationPolicyProhibited ActivationPolicy = 2
|
||||
)
|
||||
|
||||
// Options are options specific to Mac
|
||||
type Options struct {
|
||||
TitleBar *TitleBar
|
||||
@@ -11,4 +19,6 @@ type Options struct {
|
||||
Menu *menu.Menu
|
||||
TrayMenus []*menu.TrayMenu
|
||||
ContextMenus []*menu.ContextMenu
|
||||
ActivationPolicy ActivationPolicy
|
||||
URLHandlers map[string]func(string)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user