Compare commits

..

1 Commits

Author SHA1 Message Date
Lea Anthony
29715b2d57 [WIP] 2021-02-19 20:37:02 +11:00
24 changed files with 580 additions and 656 deletions

View File

@@ -1,41 +1,30 @@
package dev
import (
"context"
"fmt"
"io"
"os"
"os/signal"
"path/filepath"
"runtime"
"strings"
"syscall"
"time"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wailsapp/wails/v2/pkg/commands/build"
"github.com/wailsapp/wails/v2/internal/process"
"github.com/wzshiming/ctc"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/fsnotify/fsnotify"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/internal/fs"
"github.com/wailsapp/wails/v2/internal/process"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"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 {
@@ -53,13 +42,6 @@ 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
@@ -67,215 +49,262 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
app.PrintBanner()
// TODO: Check you are in a project directory
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
defer watcher.Close()
var debugBinaryProcess *process.Process = nil
var extensionsThatTriggerARebuild = strings.Split(extensions, ",")
// Setup signal handler
quitChannel := make(chan os.Signal, 1)
signal.Notify(quitChannel, os.Interrupt, os.Kill, syscall.SIGTERM)
debounceQuit := make(chan bool, 1)
// Do initial build
logger.Println("Building application for development...")
newProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess, loglevel)
if newProcess != nil {
debugBinaryProcess = newProcess
}
if err != nil {
return err
}
go debounce(100*time.Millisecond, watcher.Events, debounceQuit, func(event fsnotify.Event) {
// logger.Println("event: %+v", event)
// Check for new directories
if event.Op&fsnotify.Create == fsnotify.Create {
// If this is a folder, add it to our watch list
if fs.DirExists(event.Name) {
if !strings.Contains(event.Name, "node_modules") {
err := watcher.Add(event.Name)
if err != nil {
logger.Fatal("%s", err.Error())
}
LogGreen("[New Directory] Watching new directory: %s", event.Name)
}
}
return
}
// Check for file writes
if event.Op&fsnotify.Write == fsnotify.Write {
var rebuild bool = false
// Iterate all file patterns
for _, pattern := range extensionsThatTriggerARebuild {
if strings.HasSuffix(event.Name, pattern) {
rebuild = true
break
}
}
if !rebuild {
if showWarnings {
LogDarkYellow("[File change] %s did not match extension list (%s)", event.Name, extensions)
}
return
}
LogGreen("[Attempting rebuild] %s updated", event.Name)
// Do a rebuild
// Try and build the app
newBinaryProcess, err := restartApp(logger, "dev", ldflags, compilerCommand, debugBinaryProcess, loglevel)
if err != nil {
fmt.Printf("Error during build: %s", err.Error())
return
}
// If we have a new process, save it
if newBinaryProcess != nil {
debugBinaryProcess = newBinaryProcess
}
}
})
// Get project dir
projectDir, err := os.Getwd()
reloader, err := NewReloader(logger, extensionsThatTriggerARebuild, ldflags, compilerCommand)
if err != nil {
return err
}
// Get all subdirectories
dirs, err := fs.GetSubdirectories(projectDir)
// Start
err = reloader.Start()
if err != nil {
return err
println("ERRRRRRRRRR: %+v", err)
}
LogGreen("Watching (sub)/directory: %s", projectDir)
logger.Println("\nDevelopment mode exited")
// Setup a watcher for non-node_modules directories
dirs.Each(func(dir string) {
if strings.Contains(dir, "node_modules") {
return
}
// Ignore build directory
if strings.HasPrefix(dir, filepath.Join(projectDir, "build")) {
return
}
err = watcher.Add(dir)
if err != nil {
logger.Fatal(err.Error())
}
})
// Wait until we get a quit signal
quit := false
for quit == false {
select {
case <-quitChannel:
LogGreen("\nCaught quit")
// Notify debouncer to quit
debounceQuit <- true
quit = true
}
}
// Kill the current program if running
if debugBinaryProcess != nil {
err := debugBinaryProcess.Kill()
if err != nil {
return err
}
}
LogGreen("Development mode exited")
return nil
return err
})
return nil
}
// Credit: https://drailing.net/2018/01/debounce-function-for-golang/
func debounce(interval time.Duration, input chan fsnotify.Event, quitChannel chan bool, cb func(arg fsnotify.Event)) {
var item fsnotify.Event
timer := time.NewTimer(interval)
exit:
type Reloader struct {
// Main context
ctx context.Context
// Signal context
signalContext context.Context
// notify
watcher *fsnotify.Watcher
// Logger
logger *clilogger.CLILogger
// Extensions to listen for
extensionsThatTriggerARebuild []string
// The binary we are running
binary *process.Process
// options
ldflags string
compiler string
}
func NewReloader(logger *clilogger.CLILogger, extensionsThatTriggerARebuild []string, ldFlags string, compiler string) (*Reloader, error) {
var result Reloader
// Create context
result.ctx = context.Background()
// Signal context (we don't need cancel)
signalContext, _ := signal.NotifyContext(result.ctx, os.Interrupt, os.Kill, syscall.SIGTERM)
result.signalContext = signalContext
// Create watcher
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
result.watcher = watcher
// Logger
result.logger = logger
// Extensions
result.extensionsThatTriggerARebuild = extensionsThatTriggerARebuild
// Options
result.ldflags = ldFlags
result.compiler = compiler
return &result, nil
}
func (r *Reloader) Start() error {
err := r.rebuildBinary()
if err != nil {
return err
}
// Get project dir
dir, err := os.Getwd()
if err != nil {
return err
}
// Get all subdirectories
dirs, err := fs.GetSubdirectories(dir)
if err != nil {
return err
}
// Setup a watcher for non-node_modules directories
r.logger.Println("Watching (sub)directories: %s", dir)
dirs.Each(func(dir string) {
if strings.Contains(dir, "node_modules") {
return
}
err = r.watcher.Add(dir)
if err != nil {
r.logger.Fatal(err.Error())
}
})
// Main loop
for {
select {
case item = <-input:
timer.Reset(interval)
case <-timer.C:
if item.Name != "" {
cb(item)
case <-r.signalContext.Done():
if r.binary != nil {
println("Binary is not nil - kill")
return r.binary.Kill()
}
return nil
case event := <-r.watcher.Events:
err := r.processWatcherEvent(event)
if err != nil {
println("error from processWatcherEvent. Calling cancel()")
println("Calling kill")
return r.binary.Kill()
}
case <-quitChannel:
break exit
}
}
}
func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, debugBinaryProcess *process.Process, loglevel string) (*process.Process, error) {
func (r *Reloader) processWatcherEvent(event fsnotify.Event) error {
appBinary, err := buildApp(logger, outputType, ldflags, compilerCommand)
println()
if err != nil {
LogRed("Build error - continuing to run current version")
LogDarkYellow(err.Error())
return nil, nil
// Check for new directories
if event.Op&fsnotify.Create == fsnotify.Create {
// If this is a folder, add it to our watch list
if fs.DirExists(event.Name) {
if !strings.Contains(event.Name, "node_modules") {
err := r.watcher.Add(event.Name)
if err != nil {
return err
}
r.logger.Println("Watching directory: %s", event.Name)
}
}
return nil
}
// Kill existing binary if need be
if debugBinaryProcess != nil {
killError := debugBinaryProcess.Kill()
// Check for file writes
if event.Op&fsnotify.Write == fsnotify.Write {
if killError != nil {
logger.Fatal("Unable to kill debug binary (PID: %d)!", debugBinaryProcess.PID())
var rebuild bool
// Iterate all file patterns
for _, pattern := range r.extensionsThatTriggerARebuild {
if strings.HasSuffix(event.Name, pattern) {
rebuild = true
}
}
debugBinaryProcess = nil
if !rebuild {
return nil
}
r.logger.Println("\n%s[Build triggered] %s %s", ctc.ForegroundGreen|ctc.ForegroundBright, event.Name, ctc.Reset)
return r.rebuildBinary()
}
return nil
}
func (r *Reloader) rebuildBinary() error {
// rebuild binary
binary, err := r.buildApp()
if err != nil {
return err
}
// TODO: Generate `backend.js`
// Kill current binary if running
if r.binary != nil {
err = r.binary.Kill()
if err != nil {
return err
}
}
// Start up new binary
newProcess := process.NewProcess(logger, appBinary, "-loglevel", loglevel)
newProcess := process.NewProcess(r.ctx, r.logger, binary)
err = newProcess.Start()
if err != nil {
// Remove binary
deleteError := fs.DeleteFile(appBinary)
if deleteError != nil {
logger.Fatal("Unable to delete app binary: " + appBinary)
}
logger.Fatal("Unable to start application: %s", err.Error())
return err
}
return newProcess, nil
// Ensure process runs correctly
return nil
}
func buildApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string) (string, error) {
//func restartApp(logger *clilogger.CLILogger, outputType string, ldflags string, compilerCommand string, debugBinaryProcess *process.Process) (*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:")
// }
// logger.Println("Build new binary: %s", appBinary)
//
// // Kill existing binary if need be
// if debugBinaryProcess != nil {
// killError := debugBinaryProcess.Kill()
//
// if killError != nil {
// logger.Fatal("Unable to kill debug binary (PID: %d)!", debugBinaryProcess.PID())
// }
//
// debugBinaryProcess = nil
// }
//
// // TODO: Generate `backend.js`
//
// // Start up new binary
// newProcess := process.NewProcess(logger, appBinary)
// err = newProcess.Start()
// if err != nil {
// // Remove binary
// deleteError := fs.DeleteFile(appBinary)
// if deleteError != nil {
// logger.Fatal("Unable to delete app binary: " + appBinary)
// }
// logger.Fatal("Unable to start application: %s", err.Error())
// }
//
// // Check if port is open
// timeout := time.Second
// conn, err := net.DialTimeout("tcp", net.JoinHostPort("host", port), timeout)
// if err != nil {
// return
// }
// newProcess.Running
// return newProcess, nil
//}
// buildapp attempts to compile the application
// It returns the path to the new binary or an error
func (r *Reloader) buildApp() (string, error) {
// Create random output file
outputFile := fmt.Sprintf("dev-%d", time.Now().Unix())
outputFile := fmt.Sprintf("debug-%d", time.Now().Unix())
// Create BuildOptions
buildOptions := &build.Options{
Logger: logger,
OutputType: outputType,
Logger: r.logger,
OutputType: "dev",
Mode: build.Debug,
Pack: false,
Platform: runtime.GOOS,
LDFlags: ldflags,
Compiler: compilerCommand,
LDFlags: r.ldflags,
Compiler: r.compiler,
OutputFile: outputFile,
IgnoreFrontend: true,
}

View File

@@ -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 the Wails CLI.`)
command.LongDescription(`This command allows you to update your version of Wails.`)
// Setup flags
var prereleaseRequired bool
command.BoolFlag("pre", "Update CLI to latest Prerelease", &prereleaseRequired)
command.BoolFlag("pre", "Update to latest Prerelease", &prereleaseRequired)
var specificVersion string
command.StringFlag("version", "Install a specific version (Overrides other flags) of the CLI", &specificVersion)
command.StringFlag("version", "Install a specific version (Overrides other flags)", &specificVersion)
command.Action(func() error {
@@ -143,7 +143,7 @@ func updateToVersion(logger *clilogger.CLILogger, targetVersion *github.Semantic
}
fmt.Println()
logger.Print("Installing Wails CLI " + desiredVersion + "...")
logger.Print("Installing Wails " + 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 CLI updated to " + desiredVersion)
logger.Println("Wails updated to " + desiredVersion)
return nil
}

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"os"
"github.com/wailsapp/wails/v2/internal/colour"
"github.com/wzshiming/ctc"
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/update"
@@ -22,8 +22,20 @@ 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 col(colour ctc.Color, text string) string {
return fmt.Sprintf("%s%s%s", colour, text, ctc.Reset)
}
func Yellow(str string) string {
return col(ctc.ForegroundBrightYellow, str)
}
func Red(str string) string {
return col(ctc.ForegroundBrightRed, str)
}
func banner(cli *clir.Cli) string {
return fmt.Sprintf("%s %s - Go/HTML Application Framework", Yellow("Wails"), Red(version))
}
func main() {

View File

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

View File

@@ -20,9 +20,10 @@ 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/wzshiming/ctc v1.2.3 // indirect
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

View File

@@ -96,6 +96,8 @@ 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=

View File

@@ -20,10 +20,10 @@ func (a *App) Init() error {
}
// Set log levels
loglevel := flag.String("loglevel", "debug", "Loglevel to use - Trace, Debug, Info, Warning, Error")
greeting := flag.String("loglevel", "debug", "Loglevel to use - Trace, Debug, Info, Warning, Error")
flag.Parse()
if len(*loglevel) > 0 {
switch strings.ToLower(*loglevel) {
if len(*greeting) > 0 {
switch strings.ToLower(*greeting) {
case "trace":
a.logger.SetLogLevel(logger.TRACE)
case "info":

View File

@@ -117,6 +117,14 @@ 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()
@@ -124,7 +132,7 @@ func (a *App) Run() error {
return err
}
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback)
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
if err != nil {
return err
}
@@ -199,14 +207,6 @@ 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,10 +231,5 @@ func (a *App) Run() error {
return err
}
// Shutdown callback
if a.shutdownCallback != nil {
a.shutdownCallback()
}
return nil
}

View File

@@ -66,6 +66,7 @@ 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()
@@ -118,6 +119,14 @@ 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()
@@ -125,7 +134,7 @@ func (a *App) Run() error {
return err
}
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback)
runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
if err != nil {
return err
}
@@ -203,14 +212,6 @@ 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 {
@@ -235,10 +236,6 @@ func (a *App) Run() error {
return err
}
// Shutdown callback
if a.shutdownCallback != nil {
a.shutdownCallback()
}
return nil
}

View File

@@ -58,7 +58,7 @@ const backend = {`)
sortedPackageNames.Add(packageName)
}
sortedPackageNames.Sort()
sortedPackageNames.Each(func(packageName string) {
for _, packageName := range sortedPackageNames.AsSlice() {
packages := store[packageName]
output.WriteString(fmt.Sprintf(" \"%s\": {", packageName))
output.WriteString("\n")
@@ -67,8 +67,7 @@ const backend = {`)
sortedStructNames.Add(structName)
}
sortedStructNames.Sort()
sortedStructNames.Each(func(structName string) {
for _, structName := range sortedStructNames.AsSlice() {
structs := packages[structName]
output.WriteString(fmt.Sprintf(" \"%s\": {", structName))
output.WriteString("\n")
@@ -79,7 +78,7 @@ const backend = {`)
}
sortedMethodNames.Sort()
sortedMethodNames.Each(func(methodName string) {
for _, methodName := range sortedMethodNames.AsSlice() {
methodDetails := structs[methodName]
output.WriteString(" /**\n")
output.WriteString(" * " + methodName + "\n")
@@ -110,16 +109,13 @@ 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;`)

View File

@@ -1,89 +0,0 @@
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()
}

View File

@@ -498,6 +498,31 @@ 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
@@ -1812,15 +1837,6 @@ void Run(struct Application *app, int argc, char **argv) {
MEMFREE(internalCode);
}
// 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) {

View File

@@ -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);
}
@@ -509,19 +509,19 @@ unsigned long parseModifiers(const char **modifiers) {
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, "Control") ) {
result |= NSEventModifierFlagControl;
}
count++;

View File

@@ -1,6 +1,7 @@
package process
import (
"context"
"os"
"os/exec"
@@ -9,18 +10,16 @@ import (
// Process defines a process that can be executed
type Process struct {
logger *clilogger.CLILogger
cmd *exec.Cmd
exitChannel chan bool
Running bool
logger *clilogger.CLILogger
cmd *exec.Cmd
running bool
}
// NewProcess creates a new process struct
func NewProcess(logger *clilogger.CLILogger, cmd string, args ...string) *Process {
func NewProcess(ctx context.Context, logger *clilogger.CLILogger, cmd string, args ...string) *Process {
result := &Process{
logger: logger,
cmd: exec.Command(cmd, args...),
exitChannel: make(chan bool, 1),
logger: logger,
cmd: exec.CommandContext(ctx, cmd, args...),
}
result.cmd.Stdout = os.Stdout
result.cmd.Stderr = os.Stderr
@@ -35,35 +34,30 @@ func (p *Process) Start() error {
return err
}
p.Running = true
p.running = true
go func(cmd *exec.Cmd, running *bool, logger *clilogger.CLILogger, exitChannel chan bool) {
logger.Println("Starting process (PID: %d)", cmd.Process.Pid)
err := cmd.Wait()
go func(p *Process) {
p.logger.Println("Starting process (PID: %d)", p.cmd.Process.Pid)
err := p.cmd.Wait()
if err != nil {
if err.Error() != "signal: killed" {
logger.Fatal("Fatal error from app: " + err.Error())
}
p.running = false
p.logger.Println("Process failed to run: %s", err.Error())
return
}
logger.Println("Exiting process (PID: %d)", cmd.Process.Pid)
*running = false
exitChannel <- true
}(p.cmd, &p.Running, p.logger, p.exitChannel)
p.logger.Println("Exiting process (PID: %d)", p.cmd.Process.Pid)
}(p)
return nil
}
// Kill the process
func (p *Process) Kill() error {
if !p.Running {
return nil
if p.running {
println("Calling kill")
p.running = false
return p.cmd.Process.Kill()
}
err := p.cmd.Process.Kill()
// Wait for command to exit properly
<-p.exitChannel
return err
return nil
}
// PID returns the process PID

View File

@@ -779,18 +779,18 @@
function add_css$1() {
var style = element("style");
style.id = "svelte-1oysp7o-style";
style.textContent = ".menu.svelte-1oysp7o.svelte-1oysp7o{padding:3px;background-color:#0008;color:#EEF;border-radius:5px;margin-top:5px;position:absolute;backdrop-filter:blur(3px) saturate(160%) contrast(45%) brightness(140%);border:1px solid rgb(88,88,88);box-shadow:0 0 1px rgb(146,146,148) inset}.menuitem.svelte-1oysp7o.svelte-1oysp7o{display:flex;align-items:center;padding:1px 5px}.menuitem.svelte-1oysp7o.svelte-1oysp7o:hover{display:flex;align-items:center;background-color:rgb(57,131,223);padding:1px 5px;border-radius:5px}.menuitem.svelte-1oysp7o img.svelte-1oysp7o{padding-right:5px}";
style.id = "svelte-1ucacnf-style";
style.textContent = ".menu.svelte-1ucacnf.svelte-1ucacnf{padding:5px;background-color:#0008;color:#EEF;border-radius:5px;margin-top:5px;position:absolute;backdrop-filter:blur(3px) saturate(160%) contrast(45%) brightness(140%);border:1px solid rgb(88,88,88);box-shadow:0 0 1px rgb(146,146,148) inset}.menuitem.svelte-1ucacnf.svelte-1ucacnf{display:flex;align-items:center;padding:1px 5px}.menuitem.svelte-1ucacnf.svelte-1ucacnf:hover{display:flex;align-items:center;background-color:rgb(57,131,223);padding:1px 5px;border-radius:5px}.menuitem.svelte-1ucacnf img.svelte-1ucacnf{padding-right:5px}.separator.svelte-1ucacnf.svelte-1ucacnf{padding-top:5px;width:100%;padding-bottom:5px}.separator.svelte-1ucacnf.svelte-1ucacnf:hover{background-color:#0000}";
append(document.head, style);
}
function get_each_context(ctx, list, i) {
const child_ctx = ctx.slice();
child_ctx[2] = list[i];
child_ctx[3] = list[i];
return child_ctx;
}
// (8:0) {#if !hidden}
// (14:0) {#if visible}
function create_if_block$1(ctx) {
let div;
let if_block = /*menu*/ ctx[0].Menu && create_if_block_1(ctx);
@@ -799,7 +799,7 @@
c() {
div = element("div");
if (if_block) if_block.c();
attr(div, "class", "menu svelte-1oysp7o");
attr(div, "class", "menu svelte-1ucacnf");
},
m(target, anchor) {
insert(target, div, anchor);
@@ -826,7 +826,7 @@
};
}
// (10:4) {#if menu.Menu }
// (16:4) {#if menu.Menu }
function create_if_block_1(ctx) {
let each_1_anchor;
let each_value = /*menu*/ ctx[0].Menu.Items;
@@ -852,7 +852,7 @@
insert(target, each_1_anchor, anchor);
},
p(ctx, dirty) {
if (dirty & /*menu*/ 1) {
if (dirty & /*click, menu*/ 1) {
each_value = /*menu*/ ctx[0].Menu.Items;
let i;
@@ -882,44 +882,41 @@
};
}
// (13:12) {#if menuItem.Image }
function create_if_block_2(ctx) {
// (25:52)
function create_if_block_4(ctx) {
let div;
let img;
let img_src_value;
return {
c() {
div = element("div");
img = element("img");
attr(img, "alt", "");
if (img.src !== (img_src_value = "data:image/png;base64," + /*menuItem*/ ctx[2].Image)) attr(img, "src", img_src_value);
attr(img, "class", "svelte-1oysp7o");
div.innerHTML = `<hr/>`;
attr(div, "class", "separator svelte-1ucacnf");
},
m(target, anchor) {
insert(target, div, anchor);
append(div, img);
},
p(ctx, dirty) {
if (dirty & /*menu*/ 1 && img.src !== (img_src_value = "data:image/png;base64," + /*menuItem*/ ctx[2].Image)) {
attr(img, "src", img_src_value);
}
},
p: noop,
d(detaching) {
if (detaching) detach(div);
}
};
}
// (11:8) {#each menu.Menu.Items as menuItem}
function create_each_block(ctx) {
// (18:12) {#if menuItem.Type === "Text" }
function create_if_block_2(ctx) {
let div1;
let t0;
let div0;
let t1_value = /*menuItem*/ ctx[2].Label + "";
let t1_value = /*menuItem*/ ctx[3].Label + "";
let t1;
let t2;
let if_block = /*menuItem*/ ctx[2].Image && create_if_block_2(ctx);
let mounted;
let dispose;
let if_block = /*menuItem*/ ctx[3].Image && create_if_block_3(ctx);
function click_handler() {
return /*click_handler*/ ctx[2](/*menuItem*/ ctx[3]);
}
return {
c() {
@@ -930,7 +927,7 @@
t1 = text(t1_value);
t2 = space();
attr(div0, "class", "menulabel");
attr(div1, "class", "menuitem svelte-1oysp7o");
attr(div1, "class", "menuitem svelte-1ucacnf");
},
m(target, anchor) {
insert(target, div1, anchor);
@@ -939,13 +936,20 @@
append(div1, div0);
append(div0, t1);
append(div1, t2);
if (!mounted) {
dispose = listen(div1, "click", click_handler);
mounted = true;
}
},
p(ctx, dirty) {
if (/*menuItem*/ ctx[2].Image) {
p(new_ctx, dirty) {
ctx = new_ctx;
if (/*menuItem*/ ctx[3].Image) {
if (if_block) {
if_block.p(ctx, dirty);
} else {
if_block = create_if_block_2(ctx);
if_block = create_if_block_3(ctx);
if_block.c();
if_block.m(div1, t0);
}
@@ -954,18 +958,93 @@
if_block = null;
}
if (dirty & /*menu*/ 1 && t1_value !== (t1_value = /*menuItem*/ ctx[2].Label + "")) set_data(t1, t1_value);
if (dirty & /*menu*/ 1 && t1_value !== (t1_value = /*menuItem*/ ctx[3].Label + "")) set_data(t1, t1_value);
},
d(detaching) {
if (detaching) detach(div1);
if (if_block) if_block.d();
mounted = false;
dispose();
}
};
}
// (20:12) {#if menuItem.Image }
function create_if_block_3(ctx) {
let div;
let img;
let img_src_value;
return {
c() {
div = element("div");
img = element("img");
attr(img, "alt", "");
if (img.src !== (img_src_value = "data:image/png;base64," + /*menuItem*/ ctx[3].Image)) attr(img, "src", img_src_value);
attr(img, "class", "svelte-1ucacnf");
},
m(target, anchor) {
insert(target, div, anchor);
append(div, img);
},
p(ctx, dirty) {
if (dirty & /*menu*/ 1 && img.src !== (img_src_value = "data:image/png;base64," + /*menuItem*/ ctx[3].Image)) {
attr(img, "src", img_src_value);
}
},
d(detaching) {
if (detaching) detach(div);
}
};
}
// (17:8) {#each menu.Menu.Items as menuItem}
function create_each_block(ctx) {
let if_block_anchor;
function select_block_type(ctx, dirty) {
if (/*menuItem*/ ctx[3].Type === "Text") return create_if_block_2;
if (/*menuItem*/ ctx[3].Type === "Separator") return create_if_block_4;
}
let current_block_type = select_block_type(ctx);
let if_block = current_block_type && current_block_type(ctx);
return {
c() {
if (if_block) if_block.c();
if_block_anchor = empty();
},
m(target, anchor) {
if (if_block) if_block.m(target, anchor);
insert(target, if_block_anchor, anchor);
},
p(ctx, dirty) {
if (current_block_type === (current_block_type = select_block_type(ctx)) && if_block) {
if_block.p(ctx, dirty);
} else {
if (if_block) if_block.d(1);
if_block = current_block_type && current_block_type(ctx);
if (if_block) {
if_block.c();
if_block.m(if_block_anchor.parentNode, if_block_anchor);
}
}
},
d(detaching) {
if (if_block) {
if_block.d(detaching);
}
if (detaching) detach(if_block_anchor);
}
};
}
function create_fragment$1(ctx) {
let if_block_anchor;
let if_block = !/*hidden*/ ctx[1] && create_if_block$1(ctx);
let if_block = /*visible*/ ctx[1] && create_if_block$1(ctx);
return {
c() {
@@ -977,7 +1056,7 @@
insert(target, if_block_anchor, anchor);
},
p(ctx, [dirty]) {
if (!/*hidden*/ ctx[1]) {
if (/*visible*/ ctx[1]) {
if (if_block) {
if_block.p(ctx, dirty);
} else {
@@ -999,23 +1078,29 @@
};
}
function click(id) {
console.log("MenuItem", id, "pressed");
}
function instance$1($$self, $$props, $$invalidate) {
let { menu } = $$props;
let { hidden = true } = $$props;
console.log({ menu });
let { visible = false } = $$props;
const click_handler = menuItem => click(menuItem.ID);
$$self.$$set = $$props => {
if ("menu" in $$props) $$invalidate(0, menu = $$props.menu);
if ("hidden" in $$props) $$invalidate(1, hidden = $$props.hidden);
if ("visible" in $$props) $$invalidate(1, visible = $$props.visible);
};
return [menu, hidden];
return [menu, visible, click_handler];
}
class Menu extends SvelteComponent {
constructor(options) {
super();
if (!document.getElementById("svelte-1oysp7o-style")) add_css$1();
init(this, options, instance$1, create_fragment$1, safe_not_equal, { menu: 0, hidden: 1 });
if (!document.getElementById("svelte-1ucacnf-style")) add_css$1();
init(this, options, instance$1, create_fragment$1, safe_not_equal, { menu: 0, visible: 1 });
}
}
@@ -1030,7 +1115,7 @@
append(document_1.head, style);
}
// (47:4) {#if tray.ProcessedMenu }
// (48:4) {#if tray.ProcessedMenu }
function create_if_block$2(ctx) {
let menu;
let current;
@@ -1038,7 +1123,7 @@
menu = new Menu({
props: {
menu: /*tray*/ ctx[0].ProcessedMenu,
hidden: /*hidden*/ ctx[1]
visible: /*visible*/ ctx[1]
}
});
@@ -1053,7 +1138,7 @@
p(ctx, dirty) {
const menu_changes = {};
if (dirty & /*tray*/ 1) menu_changes.menu = /*tray*/ ctx[0].ProcessedMenu;
if (dirty & /*hidden*/ 2) menu_changes.hidden = /*hidden*/ ctx[1];
if (dirty & /*visible*/ 2) menu_changes.visible = /*visible*/ ctx[1];
menu.$set(menu_changes);
},
i(local) {
@@ -1157,6 +1242,7 @@
function clickOutside(node) {
const handleClick = event => {
if (node && !node.contains(event.target) && !event.defaultPrevented) {
console.log("click outside of node");
node.dispatchEvent(new CustomEvent("click_outside", node));
}
};
@@ -1171,7 +1257,7 @@
}
function instance$2($$self, $$props, $$invalidate) {
let hidden;
let visible;
let $selectedMenu;
component_subscribe($$self, selectedMenu, $$value => $$invalidate(4, $selectedMenu = $$value));
let { tray = null } = $$props;
@@ -1194,11 +1280,11 @@
$$self.$$.update = () => {
if ($$self.$$.dirty & /*$selectedMenu, tray*/ 17) {
$$invalidate(1, hidden = $selectedMenu !== tray);
$$invalidate(1, visible = $selectedMenu === tray);
}
};
return [tray, hidden, closeMenu, trayClicked, $selectedMenu];
return [tray, visible, closeMenu, trayClicked, $selectedMenu];
}
class TrayMenu extends SvelteComponent {

View File

@@ -2,19 +2,29 @@
export let menu;
export let hidden = true;
console.log({menu})
export let visible = false;
function click(id) {
console.log("MenuItem", id, "pressed")
}
</script>
{#if !hidden}
{#if visible}
<div class="menu">
{#if menu.Menu }
{#each menu.Menu.Items as menuItem}
<div class="menuitem">
{#if menuItem.Type === "Text" }
<div class="menuitem" on:click={() => click(menuItem.ID)}>
{#if menuItem.Image }
<div><img alt="" src="data:image/png;base64,{menuItem.Image}"/></div>
{/if}
<div class="menulabel">{menuItem.Label}</div>
<div class="menulabel">{menuItem.Label}</div>
</div>
{:else if menuItem.Type === "Separator"}
<div class="separator"><hr/></div>
{/if}
{/each}
{/if}
</div>
@@ -23,7 +33,7 @@
<style>
.menu {
padding: 3px;
padding: 5px;
background-color: #0008;
color: #EEF;
border-radius: 5px;
@@ -32,7 +42,6 @@
backdrop-filter: blur(3px) saturate(160%) contrast(45%) brightness(140%);
border: 1px solid rgb(88,88,88);
box-shadow: 0 0 1px rgb(146,146,148) inset;
}
.menuitem {
@@ -53,4 +62,13 @@
padding-right: 5px;
}
.separator {
padding-top: 5px;
width: 100%;
padding-bottom: 5px;
}
.separator:hover {
background-color: #0000;
}
</style>

View File

@@ -4,7 +4,7 @@
export let tray = null;
$: hidden = $selectedMenu !== tray;
$: visible = $selectedMenu === tray;
function closeMenu() {
selectedMenu.set(null);
@@ -23,6 +23,7 @@
const handleClick = event => {
if (node && !node.contains(event.target) && !event.defaultPrevented) {
console.log("click outside of node")
node.dispatchEvent(
new CustomEvent('click_outside', node)
)
@@ -45,7 +46,7 @@
<!--{/if}-->
<span class="label" on:click={trayClicked}>{tray.Label}</span>
{#if tray.ProcessedMenu }
<Menu menu="{tray.ProcessedMenu}" {hidden}/>
<Menu menu="{tray.ProcessedMenu}" visible="{visible}"/>
{/if}
</span>

View File

@@ -6,19 +6,20 @@ 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
Browser Browser
Events Events
Window Window
Dialog Dialog
System System
Menu Menu
Store *StoreProvider
Log Log
bus *servicebus.ServiceBus
shutdownCallback func()
}
// New creates a new runtime
func New(serviceBus *servicebus.ServiceBus) *Runtime {
func New(serviceBus *servicebus.ServiceBus, shutdownCallback func()) *Runtime {
result := &Runtime{
Browser: newBrowser(),
Events: newEvents(serviceBus),
@@ -35,6 +36,11 @@ func New(serviceBus *servicebus.ServiceBus) *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()")
}

View File

@@ -26,20 +26,25 @@ 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) (*Manager, error) {
func NewManager(ctx context.Context, cancel context.CancelFunc, bus *servicebus.ServiceBus, logger *logger.Logger, shutdownCallback func()) (*Manager, error) {
result := &Manager{
bus: bus,
logger: logger.CustomLogger("Event Manager"),
signalchannel: make(chan os.Signal, 2),
ctx: ctx,
cancel: cancel,
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
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),
}
return result, nil
@@ -62,6 +67,11 @@ 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()
@@ -70,4 +80,5 @@ func (m *Manager) Start() {
m.logger.Trace("Shutdown")
m.wg.Done()
}()
}

View File

@@ -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)) (*Runtime, error) {
func NewRuntime(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger, startupCallback func(*runtime.Runtime), shutdownCallback func()) (*Runtime, error) {
// Subscribe to log messages
runtimeChannel, err := bus.Subscribe("runtime:")
@@ -52,12 +52,13 @@ 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),
startupCallback: startupCallback,
ctx: ctx,
runtimeChannel: runtimeChannel,
hooksChannel: hooksChannel,
logger: logger.CustomLogger("Runtime Subsystem"),
runtime: runtime.New(bus, shutdownCallback),
startupCallback: startupCallback,
shutdownCallback: shutdownCallback,
ctx: ctx,
}
return result, nil

View File

@@ -4,8 +4,6 @@ import (
"fmt"
"io"
"os"
"github.com/wailsapp/wails/v2/internal/colour"
)
// CLILogger is used by the cli
@@ -53,9 +51,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, colour.Red("FATAL: "+temp))
_, err := fmt.Fprintln(c.Writer, "FATAL: "+temp)
if err != nil {
println(colour.Red("FATAL: " + err.Error()))
println("FATAL: ", err)
}
os.Exit(1)
}

View File

@@ -1,10 +1,5 @@
package keys
import (
"fmt"
"strings"
)
// Modifier is actually a string
type Modifier string
@@ -21,23 +16,6 @@ const (
ControlKey Modifier = "Control"
)
var modifierMap = map[string]Modifier{
"cmdorctrl": CmdOrCtrlKey,
"optionoralt": OptionOrAltKey,
"shift": ShiftKey,
"super": SuperKey,
"ctrl": ControlKey,
}
func parseModifier(text string) (*Modifier, error) {
result, valid := modifierMap[text]
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
@@ -47,14 +25,14 @@ type Accelerator struct {
// Key creates a standard key Accelerator
func Key(key string) *Accelerator {
return &Accelerator{
Key: strings.ToLower(key),
Key: key,
}
}
// CmdOrCtrl creates a 'CmdOrCtrl' Accelerator
func CmdOrCtrl(key string) *Accelerator {
return &Accelerator{
Key: strings.ToLower(key),
Key: key,
Modifiers: []Modifier{CmdOrCtrlKey},
}
}
@@ -62,7 +40,7 @@ func CmdOrCtrl(key string) *Accelerator {
// OptionOrAlt creates a 'OptionOrAlt' Accelerator
func OptionOrAlt(key string) *Accelerator {
return &Accelerator{
Key: strings.ToLower(key),
Key: key,
Modifiers: []Modifier{OptionOrAltKey},
}
}
@@ -70,7 +48,7 @@ func OptionOrAlt(key string) *Accelerator {
// Shift creates a 'Shift' Accelerator
func Shift(key string) *Accelerator {
return &Accelerator{
Key: strings.ToLower(key),
Key: key,
Modifiers: []Modifier{ShiftKey},
}
}
@@ -78,7 +56,7 @@ func Shift(key string) *Accelerator {
// Control creates a 'Control' Accelerator
func Control(key string) *Accelerator {
return &Accelerator{
Key: strings.ToLower(key),
Key: key,
Modifiers: []Modifier{ControlKey},
}
}
@@ -86,7 +64,7 @@ func Control(key string) *Accelerator {
// Super creates a 'Super' Accelerator
func Super(key string) *Accelerator {
return &Accelerator{
Key: strings.ToLower(key),
Key: key,
Modifiers: []Modifier{SuperKey},
}
}

View File

@@ -1,90 +0,0 @@
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
}

View File

@@ -1,38 +0,0 @@
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, "")
}
}