Graceful shutdown

This commit is contained in:
Lea Anthony
2021-02-03 07:14:44 +11:00
parent 5cd08e45f0
commit e1b7332c47
15 changed files with 414 additions and 253 deletions

View File

@@ -3,6 +3,9 @@
package app package app
import ( import (
"context"
"sync"
"github.com/wailsapp/wails/v2/internal/binding" "github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/ffenestri" "github.com/wailsapp/wails/v2/internal/ffenestri"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
@@ -26,10 +29,10 @@ type App struct {
options *options.App options *options.App
// Subsystems // Subsystems
log *subsystem.Log log *subsystem.Log
runtime *subsystem.Runtime runtime *subsystem.Runtime
event *subsystem.Event event *subsystem.Event
binding *subsystem.Binding //binding *subsystem.Binding
call *subsystem.Call call *subsystem.Call
menu *subsystem.Menu menu *subsystem.Menu
dispatcher *messagedispatcher.Dispatcher dispatcher *messagedispatcher.Dispatcher
@@ -109,8 +112,13 @@ func (a *App) Run() error {
var err error var err error
// Setup a context
var subsystemWaitGroup sync.WaitGroup
parentContext := context.WithValue(context.Background(), "waitgroup", &subsystemWaitGroup)
ctx, cancel := context.WithCancel(parentContext)
// Setup signal handler // Setup signal handler
signalsubsystem, err := signal.NewManager(a.servicebus, a.logger) signalsubsystem, err := signal.NewManager(ctx, cancel, a.servicebus, a.logger, a.shutdownCallback)
if err != nil { if err != nil {
return err return err
} }
@@ -124,7 +132,7 @@ func (a *App) Run() error {
return err return err
} }
runtimesubsystem, err := subsystem.NewRuntime(a.servicebus, a.logger, a.startupCallback, a.shutdownCallback) runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback, a.shutdownCallback)
if err != nil { if err != nil {
return err return err
} }
@@ -138,17 +146,6 @@ func (a *App) Run() error {
a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel) a.loglevelStore = a.runtime.GoRuntime().Store.New("wails:loglevel", a.options.LogLevel)
a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options) a.appconfigStore = a.runtime.GoRuntime().Store.New("wails:appconfig", a.options)
// Start the binding subsystem
bindingsubsystem, err := subsystem.NewBinding(a.servicebus, a.logger, a.bindings, a.runtime.GoRuntime())
if err != nil {
return err
}
a.binding = bindingsubsystem
err = a.binding.Start()
if err != nil {
return err
}
// Start the logging subsystem // Start the logging subsystem
log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore) log, err := subsystem.NewLog(a.servicebus, a.logger, a.loglevelStore)
if err != nil { if err != nil {
@@ -172,18 +169,18 @@ func (a *App) Run() error {
} }
// Start the eventing subsystem // Start the eventing subsystem
event, err := subsystem.NewEvent(a.servicebus, a.logger) eventsubsystem, err := subsystem.NewEvent(ctx, a.servicebus, a.logger)
if err != nil { if err != nil {
return err return err
} }
a.event = event a.event = eventsubsystem
err = a.event.Start() err = a.event.Start()
if err != nil { if err != nil {
return err return err
} }
// Start the menu subsystem // Start the menu subsystem
menusubsystem, err := subsystem.NewMenu(a.servicebus, a.logger, a.menuManager) menusubsystem, err := subsystem.NewMenu(ctx, a.servicebus, a.logger, a.menuManager)
if err != nil { if err != nil {
return err return err
} }
@@ -194,11 +191,11 @@ func (a *App) Run() error {
} }
// Start the call subsystem // Start the call subsystem
call, err := subsystem.NewCall(a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime()) callSubsystem, err := subsystem.NewCall(ctx, a.servicebus, a.logger, a.bindings.DB(), a.runtime.GoRuntime())
if err != nil { if err != nil {
return err return err
} }
a.call = call a.call = callSubsystem
err = a.call.Start() err = a.call.Start()
if err != nil { if err != nil {
return err return err
@@ -210,12 +207,31 @@ func (a *App) Run() error {
return err return err
} }
result := a.window.Run(dispatcher, bindingDump, a.debug) err = a.window.Run(dispatcher, bindingDump, a.debug)
a.logger.Trace("Ffenestri.Run() exited") a.logger.Trace("Ffenestri.Run() exited")
if err != nil {
return err
}
// Close down all the subsystems
a.logger.Trace("Cancelling subsystems")
cancel()
subsystemWaitGroup.Wait()
a.logger.Trace("Cancelling dispatcher")
dispatcher.Close()
// Close log
a.logger.Trace("Stopping log")
log.Close()
a.logger.Trace("Stopping Service bus")
err = a.servicebus.Stop() err = a.servicebus.Stop()
if err != nil { if err != nil {
return err return err
} }
return result println("Desktop.Run() finished")
return nil
} }

View File

@@ -1,11 +1,12 @@
package ffenestri package ffenestri
import ( import (
"github.com/wailsapp/wails/v2/internal/menumanager"
"runtime" "runtime"
"strings" "strings"
"unsafe" "unsafe"
"github.com/wailsapp/wails/v2/internal/menumanager"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/messagedispatcher" "github.com/wailsapp/wails/v2/internal/messagedispatcher"
"github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options"
@@ -118,7 +119,8 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug
fullscreen := a.bool2Cint(a.config.Fullscreen) fullscreen := a.bool2Cint(a.config.Fullscreen)
startHidden := a.bool2Cint(a.config.StartHidden) startHidden := a.bool2Cint(a.config.StartHidden)
logLevel := C.int(a.config.LogLevel) logLevel := C.int(a.config.LogLevel)
app := C.NewApplication(title, width, height, resizable, devtools, fullscreen, startHidden, logLevel) hideWindowOnClose := a.bool2Cint(a.config.HideWindowOnClose)
app := C.NewApplication(title, width, height, resizable, devtools, fullscreen, startHidden, logLevel, hideWindowOnClose)
// Save app reference // Save app reference
a.app = (*C.struct_Application)(app) a.app = (*C.struct_Application)(app)
@@ -167,6 +169,7 @@ func (a *Application) Run(incomingDispatcher Dispatcher, bindings string, debug
// Yes - Save memory reference and run app, cleaning up afterwards // Yes - Save memory reference and run app, cleaning up afterwards
a.saveMemoryReference(unsafe.Pointer(app)) a.saveMemoryReference(unsafe.Pointer(app))
C.Run(app, 0, nil) C.Run(app, 0, nil)
println("Back in ffenestri.go")
} else { } else {
// Oh no! We couldn't initialise the application // Oh no! We couldn't initialise the application
a.logger.Fatal("Cannot initialise Application.") a.logger.Fatal("Cannot initialise Application.")

View File

@@ -4,7 +4,7 @@
#include <stdio.h> #include <stdio.h>
struct Application; struct Application;
extern struct Application *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel); extern struct Application *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose);
extern void SetMinWindowSize(struct Application*, int minWidth, int minHeight); extern void SetMinWindowSize(struct Application*, int minWidth, int minHeight);
extern void SetMaxWindowSize(struct Application*, int maxWidth, int maxHeight); extern void SetMaxWindowSize(struct Application*, int maxWidth, int maxHeight);
extern void Run(struct Application*, int argc, char **argv); extern void Run(struct Application*, int argc, char **argv);

View File

@@ -34,6 +34,12 @@ BOOL yes(id self, SEL cmd)
return YES; return YES;
} }
// no command simply returns NO!
BOOL no(id self, SEL cmd)
{
return NO;
}
// Prints a hashmap entry // Prints a hashmap entry
int hashmap_log(void *const context, struct hashmap_element_s *const e) { int hashmap_log(void *const context, struct hashmap_element_s *const e) {
printf("%s: %p ", (char*)e->key, e->data); printf("%s: %p ", (char*)e->key, e->data);
@@ -65,6 +71,7 @@ struct Application {
// Cocoa data // Cocoa data
id application; id application;
id delegate; id delegate;
id windowDelegate;
id mainWindow; id mainWindow;
id wkwebview; id wkwebview;
id manager; id manager;
@@ -92,6 +99,7 @@ struct Application {
const char *appearance; const char *appearance;
int decorations; int decorations;
int logLevel; int logLevel;
int hideWindowOnClose;
// Features // Features
int frame; int frame;
@@ -120,6 +128,9 @@ struct Application {
// Bindings // Bindings
const char *bindings; const char *bindings;
// shutting down flag
bool shuttingDown;
}; };
// Debug works like sprintf but mutes if the global debug flag is true // Debug works like sprintf but mutes if the global debug flag is true
@@ -222,6 +233,9 @@ void applyWindowColour(struct Application *app) {
} }
void SetColour(struct Application *app, int red, int green, int blue, int alpha) { void SetColour(struct Application *app, int red, int green, int blue, int alpha) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
app->red = red; app->red = red;
app->green = green; app->green = green;
app->blue = blue; app->blue = blue;
@@ -235,12 +249,18 @@ void FullSizeContent(struct Application *app) {
} }
void Hide(struct Application *app) { void Hide(struct Application *app) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
msg(app->application, s("hide:")) msg(app->application, s("hide:"))
); );
} }
void Show(struct Application *app) { void Show(struct Application *app) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
msg(app->mainWindow, s("makeKeyAndOrderFront:"), NULL); msg(app->mainWindow, s("makeKeyAndOrderFront:"), NULL);
msg(app->application, s("activateIgnoringOtherApps:"), YES); msg(app->application, s("activateIgnoringOtherApps:"), YES);
@@ -422,6 +442,7 @@ void freeDialogIconCache(struct Application *app) {
} }
void DestroyApplication(struct Application *app) { void DestroyApplication(struct Application *app) {
app->shuttingDown = true;
Debug(app, "Destroying Application"); Debug(app, "Destroying Application");
// Free the bindings // Free the bindings
@@ -466,10 +487,16 @@ void DestroyApplication(struct Application *app) {
msg(app->manager, s("removeScriptMessageHandlerForName:"), str("error")); msg(app->manager, s("removeScriptMessageHandlerForName:"), str("error"));
// Close main window // Close main window
msg(app->mainWindow, s("close")); if( app->windowDelegate != NULL ) {
printf("\n\n\n\nReleasing window delegate\n\n\n\n\n");
msg(app->windowDelegate, s("release"));
printf("\n\n\n\n\nRemoving window delegate\n\n\n\n\n\n\n");
msg(app->mainWindow, s("setDelegate:"), NULL);
}
// msg(app->mainWindow, s("close"));
// Terminate app
msg(c("NSApp"), s("terminate:"), NULL);
Debug(app, "Finished Destroying Application"); Debug(app, "Finished Destroying Application");
} }
@@ -477,11 +504,32 @@ void DestroyApplication(struct Application *app) {
// used by the application // used by the application
void Quit(struct Application *app) { void Quit(struct Application *app) {
Debug(app, "Quit Called"); Debug(app, "Quit Called");
DestroyApplication(app); 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 // SetTitle sets the main window title to the given string
void SetTitle(struct Application *app, const char *title) { void SetTitle(struct Application *app, const char *title) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
Debug(app, "SetTitle Called"); Debug(app, "SetTitle Called");
ON_MAIN_THREAD( ON_MAIN_THREAD(
msg(app->mainWindow, s("setTitle:"), str(title)); msg(app->mainWindow, s("setTitle:"), str(title));
@@ -503,6 +551,9 @@ bool isFullScreen(struct Application *app) {
// Fullscreen sets the main window to be fullscreen // Fullscreen sets the main window to be fullscreen
void Fullscreen(struct Application *app) { void Fullscreen(struct Application *app) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
Debug(app, "Fullscreen Called"); Debug(app, "Fullscreen Called");
if( ! isFullScreen(app) ) { if( ! isFullScreen(app) ) {
ToggleFullscreen(app); ToggleFullscreen(app);
@@ -511,6 +562,9 @@ void Fullscreen(struct Application *app) {
// UnFullscreen resets the main window after a fullscreen // UnFullscreen resets the main window after a fullscreen
void UnFullscreen(struct Application *app) { void UnFullscreen(struct Application *app) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
Debug(app, "UnFullscreen Called"); Debug(app, "UnFullscreen Called");
if( isFullScreen(app) ) { if( isFullScreen(app) ) {
ToggleFullscreen(app); ToggleFullscreen(app);
@@ -518,6 +572,9 @@ void UnFullscreen(struct Application *app) {
} }
void Center(struct Application *app) { void Center(struct Application *app) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
Debug(app, "Center Called"); Debug(app, "Center Called");
ON_MAIN_THREAD( ON_MAIN_THREAD(
MAIN_WINDOW_CALL("center"); MAIN_WINDOW_CALL("center");
@@ -532,23 +589,35 @@ void ToggleMaximise(struct Application *app) {
} }
void Maximise(struct Application *app) { void Maximise(struct Application *app) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
if( app->maximised == 0) { if( app->maximised == 0) {
ToggleMaximise(app); ToggleMaximise(app);
} }
} }
void Unmaximise(struct Application *app) { void Unmaximise(struct Application *app) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
if( app->maximised == 1) { if( app->maximised == 1) {
ToggleMaximise(app); ToggleMaximise(app);
} }
} }
void Minimise(struct Application *app) { void Minimise(struct Application *app) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
MAIN_WINDOW_CALL("miniaturize:"); MAIN_WINDOW_CALL("miniaturize:");
); );
} }
void Unminimise(struct Application *app) { void Unminimise(struct Application *app) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
MAIN_WINDOW_CALL("deminiaturize:"); MAIN_WINDOW_CALL("deminiaturize:");
); );
@@ -572,6 +641,9 @@ void dumpFrame(struct Application *app, const char *message, CGRect frame) {
} }
void SetSize(struct Application *app, int width, int height) { void SetSize(struct Application *app, int width, int height) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
id screen = getCurrentScreen(app); id screen = getCurrentScreen(app);
@@ -588,6 +660,9 @@ void SetSize(struct Application *app, int width, int height) {
} }
void SetPosition(struct Application *app, int x, int y) { void SetPosition(struct Application *app, int x, int y) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
id screen = getCurrentScreen(app); id screen = getCurrentScreen(app);
CGRect screenFrame = GET_FRAME(screen); CGRect screenFrame = GET_FRAME(screen);
@@ -613,6 +688,9 @@ void processDialogButton(id alert, char *buttonTitle, char *cancelButton, char *
} }
extern void MessageDialog(struct Application *app, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton) { extern void MessageDialog(struct Application *app, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
id alert = ALLOC_INIT("NSAlert"); id alert = ALLOC_INIT("NSAlert");
char *dialogType = type; char *dialogType = type;
@@ -726,6 +804,9 @@ extern void MessageDialog(struct Application *app, char *callbackID, char *type,
// OpenDialog opens a dialog to select files/directories // OpenDialog opens a dialog to select files/directories
void OpenDialog(struct Application *app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) { void OpenDialog(struct Application *app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
Debug(app, "OpenDialog Called with callback id: %s", callbackID); Debug(app, "OpenDialog Called with callback id: %s", callbackID);
// Create an open panel // Create an open panel
@@ -814,6 +895,9 @@ void OpenDialog(struct Application *app, char *callbackID, char *title, char *fi
// SaveDialog opens a dialog to select files/directories // SaveDialog opens a dialog to select files/directories
void SaveDialog(struct Application *app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) { void SaveDialog(struct Application *app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
Debug(app, "SaveDialog Called with callback id: %s", callbackID); Debug(app, "SaveDialog Called with callback id: %s", callbackID);
// Create an open panel // Create an open panel
@@ -891,6 +975,9 @@ void DisableFrame(struct Application *app)
void setMinMaxSize(struct Application *app) void setMinMaxSize(struct Application *app)
{ {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
if (app->maxHeight > 0 && app->maxWidth > 0) if (app->maxHeight > 0 && app->maxWidth > 0)
{ {
msg(app->mainWindow, s("setMaxSize:"), CGSizeMake(app->maxWidth, app->maxHeight)); msg(app->mainWindow, s("setMaxSize:"), CGSizeMake(app->maxWidth, app->maxHeight));
@@ -917,6 +1004,9 @@ void setMinMaxSize(struct Application *app)
void SetMinWindowSize(struct Application *app, int minWidth, int minHeight) void SetMinWindowSize(struct Application *app, int minWidth, int minHeight)
{ {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
app->minWidth = minWidth; app->minWidth = minWidth;
app->minHeight = minHeight; app->minHeight = minHeight;
@@ -930,6 +1020,9 @@ void SetMinWindowSize(struct Application *app, int minWidth, int minHeight)
void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight) void SetMaxWindowSize(struct Application *app, int maxWidth, int maxHeight)
{ {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
app->maxWidth = maxWidth; app->maxWidth = maxWidth;
app->maxHeight = maxHeight; app->maxHeight = maxHeight;
@@ -950,24 +1043,40 @@ void SetDebug(void *applicationPointer, int flag) {
// AddContextMenu sets the context menu map for this application // AddContextMenu sets the context menu map for this application
void AddContextMenu(struct Application *app, const char *contextMenuJSON) { void AddContextMenu(struct Application *app, const char *contextMenuJSON) {
AddContextMenuToStore(app->contextMenuStore, contextMenuJSON); // Guard against calling during shutdown
if( app->shuttingDown ) return;
AddContextMenuToStore(app->contextMenuStore, contextMenuJSON);
} }
void UpdateContextMenu(struct Application *app, const char* contextMenuJSON) { void UpdateContextMenu(struct Application *app, const char* contextMenuJSON) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
UpdateContextMenuInStore(app->contextMenuStore, contextMenuJSON); UpdateContextMenuInStore(app->contextMenuStore, contextMenuJSON);
} }
void AddTrayMenu(struct Application *app, const char *trayMenuJSON) { void AddTrayMenu(struct Application *app, const char *trayMenuJSON) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
AddTrayMenuToStore(app->trayMenuStore, trayMenuJSON); AddTrayMenuToStore(app->trayMenuStore, trayMenuJSON);
} }
void SetTrayMenu(struct Application *app, const char* trayMenuJSON) { void SetTrayMenu(struct Application *app, const char* trayMenuJSON) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
UpdateTrayMenuInStore(app->trayMenuStore, trayMenuJSON); UpdateTrayMenuInStore(app->trayMenuStore, trayMenuJSON);
); );
} }
void UpdateTrayMenuLabel(struct Application* app, const char* JSON) { void UpdateTrayMenuLabel(struct Application* app, const char* JSON) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
UpdateTrayMenuLabelInStore(app->trayMenuStore, JSON); UpdateTrayMenuLabelInStore(app->trayMenuStore, JSON);
); );
@@ -1034,6 +1143,9 @@ void createApplication(struct Application *app) {
} }
void DarkModeEnabled(struct Application *app, const char *callbackID) { void DarkModeEnabled(struct Application *app, const char *callbackID) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
ON_MAIN_THREAD( ON_MAIN_THREAD(
const char *result = isDarkMode(app) ? "T" : "F"; const char *result = isDarkMode(app) ? "T" : "F";
@@ -1054,9 +1166,9 @@ void DarkModeEnabled(struct Application *app, const char *callbackID) {
void createDelegate(struct Application *app) { void createDelegate(struct Application *app) {
// Define delegate // Define delegate
Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "AppDelegate", 0); Class delegateClass = objc_allocateClassPair((Class) c("NSObject"), "AppDelegate", 0);
bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSApplicationDelegate")); bool resultAddProtoc = class_addProtocol(delegateClass, objc_getProtocol("NSApplicationDelegate"));
class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) yes, "c@:@"); class_addMethod(delegateClass, s("applicationShouldTerminateAfterLastWindowClosed:"), (IMP) no, "c@:@");
class_addMethod(delegateClass, s("applicationWillTerminate:"), (IMP) closeWindow, "v@:@"); // class_addMethod(delegateClass, s("applicationWillTerminate:"), (IMP) closeWindow, "v@:@");
class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@"); class_addMethod(delegateClass, s("applicationWillFinishLaunching:"), (IMP) willFinishLaunching, "v@:@");
// All Menu Items use a common callback // All Menu Items use a common callback
@@ -1082,6 +1194,11 @@ void createDelegate(struct Application *app) {
msg(app->application, s("setDelegate:"), delegate); msg(app->application, s("setDelegate:"), delegate);
} }
bool windowShouldClose(id self, SEL cmd, id sender) {
msg(sender, s("orderBack:"));
return false;
}
void createMainWindow(struct Application *app) { void createMainWindow(struct Application *app) {
// Create main window // Create main window
id mainWindow = ALLOC("NSWindow"); id mainWindow = ALLOC("NSWindow");
@@ -1100,6 +1217,15 @@ void createMainWindow(struct Application *app) {
msg(mainWindow, s("setTitlebarAppearsTransparent:"), app->titlebarAppearsTransparent ? YES : NO); msg(mainWindow, s("setTitlebarAppearsTransparent:"), app->titlebarAppearsTransparent ? YES : NO);
msg(mainWindow, s("setTitleVisibility:"), app->hideTitle); 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"));
class_replaceMethod(delegateClass, s("windowShouldClose:"), (IMP) windowShouldClose, "v@:@");
app->windowDelegate = msg((id)delegateClass, s("new"));
msg(mainWindow, s("setDelegate:"), app->windowDelegate);
}
app->mainWindow = mainWindow; app->mainWindow = mainWindow;
} }
@@ -1186,7 +1312,7 @@ void parseMenuRole(struct Application *app, id parentMenu, JsonNode *item) {
return; return;
} }
if ( STREQ(roleName, "quit")) { if ( STREQ(roleName, "quit")) {
addMenuItem(parentMenu, "Quit (More work TBD)", "terminate:", "q", FALSE); addMenuItem(parentMenu, "Quit", "terminate:", "q", FALSE);
return; return;
} }
if ( STREQ(roleName, "togglefullscreen")) { if ( STREQ(roleName, "togglefullscreen")) {
@@ -1464,6 +1590,9 @@ void updateMenu(struct Application *app, const char *menuAsJSON) {
// SetApplicationMenu sets the initial menu for the application // SetApplicationMenu sets the initial menu for the application
void SetApplicationMenu(struct Application *app, const char *menuAsJSON) { void SetApplicationMenu(struct Application *app, const char *menuAsJSON) {
// Guard against calling during shutdown
if( app->shuttingDown ) return;
if ( app->applicationMenu == NULL ) { if ( app->applicationMenu == NULL ) {
app->applicationMenu = NewApplicationMenu(menuAsJSON); app->applicationMenu = NewApplicationMenu(menuAsJSON);
return; return;
@@ -1705,11 +1834,13 @@ void Run(struct Application *app, int argc, char **argv) {
Debug(app, "Run called"); Debug(app, "Run called");
msg(app->application, s("run")); msg(app->application, s("run"));
DestroyApplication(app);
MEMFREE(internalCode); MEMFREE(internalCode);
} }
void* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel) { void* NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose) {
// Load the tray icons // Load the tray icons
LoadTrayIcons(); LoadTrayIcons();
@@ -1730,6 +1861,7 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
result->startHidden = startHidden; result->startHidden = startHidden;
result->decorations = 0; result->decorations = 0;
result->logLevel = logLevel; result->logLevel = logLevel;
result->hideWindowOnClose = hideWindowOnClose;
result->mainWindow = NULL; result->mainWindow = NULL;
result->mouseEvent = NULL; result->mouseEvent = NULL;
@@ -1758,12 +1890,17 @@ void* NewApplication(const char *title, int width, int height, int resizable, in
// Context Menus // Context Menus
result->contextMenuStore = NewContextMenuStore(); result->contextMenuStore = NewContextMenuStore();
// Window delegate
result->windowDelegate = NULL;
// Window Appearance // Window Appearance
result->titlebarAppearsTransparent = 0; result->titlebarAppearsTransparent = 0;
result->webviewIsTranparent = 0; result->webviewIsTranparent = 0;
result->sendMessageToBackend = (ffenestriCallback) messageFromWindowCallback; result->sendMessageToBackend = (ffenestriCallback) messageFromWindowCallback;
result->shuttingDown = false;
return (void*) result; return (void*) result;
} }

View File

@@ -1,6 +1,7 @@
package messagedispatcher package messagedispatcher
import ( import (
"context"
"encoding/json" "encoding/json"
"strconv" "strconv"
"strings" "strings"
@@ -24,7 +25,6 @@ type Dispatcher struct {
dialogChannel <-chan *servicebus.Message dialogChannel <-chan *servicebus.Message
systemChannel <-chan *servicebus.Message systemChannel <-chan *servicebus.Message
menuChannel <-chan *servicebus.Message menuChannel <-chan *servicebus.Message
running bool
servicebus *servicebus.ServiceBus servicebus *servicebus.ServiceBus
logger logger.CustomLogger logger logger.CustomLogger
@@ -32,6 +32,13 @@ type Dispatcher struct {
// Clients // Clients
clients map[string]*DispatchClient clients map[string]*DispatchClient
lock sync.RWMutex lock sync.RWMutex
// Context for cancellation
ctx context.Context
cancel context.CancelFunc
// internal wait group
wg sync.WaitGroup
} }
// New dispatcher. Needs a service bus to send to. // New dispatcher. Needs a service bus to send to.
@@ -76,6 +83,9 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher,
return nil, err return nil, err
} }
// Create context
ctx, cancel := context.WithCancel(context.Background())
result := &Dispatcher{ result := &Dispatcher{
servicebus: servicebus, servicebus: servicebus,
eventChannel: eventChannel, eventChannel: eventChannel,
@@ -87,6 +97,8 @@ func New(servicebus *servicebus.ServiceBus, logger *logger.Logger) (*Dispatcher,
dialogChannel: dialogChannel, dialogChannel: dialogChannel,
systemChannel: systemChannel, systemChannel: systemChannel,
menuChannel: menuChannel, menuChannel: menuChannel,
ctx: ctx,
cancel: cancel,
} }
return result, nil return result, nil
@@ -97,15 +109,18 @@ func (d *Dispatcher) Start() error {
d.logger.Trace("Starting") d.logger.Trace("Starting")
d.running = true d.wg.Add(1)
// Spin off a go routine // Spin off a go routine
go func() { go func() {
for d.running { defer d.logger.Trace("Shutdown")
for {
select { select {
case <-d.ctx.Done():
d.wg.Done()
return
case <-d.quitChannel: case <-d.quitChannel:
d.processQuit() d.processQuit()
d.running = false
case resultMessage := <-d.resultChannel: case resultMessage := <-d.resultChannel:
d.processCallResult(resultMessage) d.processCallResult(resultMessage)
case eventMessage := <-d.eventChannel: case eventMessage := <-d.eventChannel:
@@ -120,9 +135,6 @@ func (d *Dispatcher) Start() error {
d.processMenuMessage(menuMessage) d.processMenuMessage(menuMessage)
} }
} }
// Call shutdown
d.shutdown()
}() }()
return nil return nil
@@ -136,10 +148,6 @@ func (d *Dispatcher) processQuit() {
} }
} }
func (d *Dispatcher) shutdown() {
d.logger.Trace("Shutdown")
}
// RegisterClient will register the given callback with the dispatcher // RegisterClient will register the given callback with the dispatcher
// and return a DispatchClient that the caller can use to send messages // and return a DispatchClient that the caller can use to send messages
func (d *Dispatcher) RegisterClient(client Client) *DispatchClient { func (d *Dispatcher) RegisterClient(client Client) *DispatchClient {
@@ -524,3 +532,8 @@ func (d *Dispatcher) processMenuMessage(result *servicebus.Message) {
d.logger.Error("Unknown menufrontend command: %s", command) d.logger.Error("Unknown menufrontend command: %s", command)
} }
} }
func (d *Dispatcher) Close() {
d.cancel()
d.wg.Wait()
}

View File

@@ -6,19 +6,20 @@ import (
// Runtime is a means for the user to interact with the application at runtime // Runtime is a means for the user to interact with the application at runtime
type Runtime struct { type Runtime struct {
Browser Browser Browser Browser
Events Events Events Events
Window Window Window Window
Dialog Dialog Dialog Dialog
System System System System
Menu Menu Menu Menu
Store *StoreProvider Store *StoreProvider
Log Log Log Log
bus *servicebus.ServiceBus bus *servicebus.ServiceBus
shutdownCallback func()
} }
// New creates a new runtime // New creates a new runtime
func New(serviceBus *servicebus.ServiceBus) *Runtime { func New(serviceBus *servicebus.ServiceBus, shutdownCallback func()) *Runtime {
result := &Runtime{ result := &Runtime{
Browser: newBrowser(), Browser: newBrowser(),
Events: newEvents(serviceBus), Events: newEvents(serviceBus),
@@ -35,5 +36,11 @@ func New(serviceBus *servicebus.ServiceBus) *Runtime {
// Quit the application // Quit the application
func (r *Runtime) Quit() { func (r *Runtime) Quit() {
// Call back to user's shutdown method if defined
if r.shutdownCallback != nil {
r.shutdownCallback()
}
// Start shutdown of Wails
r.bus.Publish("quit", "runtime.Quit()") r.bus.Publish("quit", "runtime.Quit()")
} }

View File

@@ -1,6 +1,7 @@
package servicebus package servicebus
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
"sync" "sync"
@@ -12,23 +13,26 @@ import (
type ServiceBus struct { type ServiceBus struct {
listeners map[string][]chan *Message listeners map[string][]chan *Message
messageQueue chan *Message messageQueue chan *Message
quitChannel chan struct{}
wg sync.WaitGroup
lock sync.RWMutex lock sync.RWMutex
closed bool closed bool
debug bool debug bool
logger logger.CustomLogger logger logger.CustomLogger
ctx context.Context
cancel context.CancelFunc
} }
// New creates a new ServiceBus // New creates a new ServiceBus
// The internal message queue is set to 100 messages // The internal message queue is set to 100 messages
// Listener queues are set to 10 // Listener queues are set to 10
func New(logger *logger.Logger) *ServiceBus { func New(logger *logger.Logger) *ServiceBus {
ctx, cancel := context.WithCancel(context.Background())
return &ServiceBus{ return &ServiceBus{
listeners: make(map[string][]chan *Message), listeners: make(map[string][]chan *Message),
messageQueue: make(chan *Message, 100), messageQueue: make(chan *Message, 100),
quitChannel: make(chan struct{}, 1),
logger: logger.CustomLogger("Service Bus"), logger: logger.CustomLogger("Service Bus"),
ctx: ctx,
cancel: cancel,
} }
} }
@@ -63,24 +67,22 @@ func (s *ServiceBus) Debug() {
// Start the service bus // Start the service bus
func (s *ServiceBus) Start() error { func (s *ServiceBus) Start() error {
s.logger.Trace("Starting")
// Prevent starting when closed // Prevent starting when closed
if s.closed { if s.closed {
return fmt.Errorf("cannot call start on closed servicebus") return fmt.Errorf("cannot call start on closed servicebus")
} }
// We run in a different thread s.logger.Trace("Starting")
s.wg.Add(1)
go func() { go func() {
defer s.logger.Trace("Stopped")
quit := false
// Loop until we get a quit message // Loop until we get a quit message
for !quit { for {
select { select {
case <-s.ctx.Done():
return
// Listen for messages // Listen for messages
case message := <-s.messageQueue: case message := <-s.messageQueue:
@@ -91,16 +93,9 @@ func (s *ServiceBus) Start() error {
} }
// Dispatch message // Dispatch message
s.dispatchMessage(message) s.dispatchMessage(message)
// Listen for quit messages
case <-s.quitChannel:
quit = true
} }
} }
// Indicate we have shut down
s.wg.Done()
}() }()
return nil return nil
@@ -117,10 +112,7 @@ func (s *ServiceBus) Stop() error {
s.closed = true s.closed = true
// Send quit message // Send quit message
s.quitChannel <- struct{}{} s.cancel()
// Wait for dispatcher to stop
s.wg.Wait()
// Close down subscriber channels // Close down subscriber channels
s.lock.Lock() s.lock.Lock()
@@ -135,7 +127,6 @@ func (s *ServiceBus) Stop() error {
// Close message queue // Close message queue
close(s.messageQueue) close(s.messageQueue)
s.logger.Trace("Stopped")
return nil return nil
} }
@@ -172,7 +163,6 @@ func (s *ServiceBus) Subscribe(topic string) (<-chan *Message, error) {
func (s *ServiceBus) Publish(topic string, data interface{}) { func (s *ServiceBus) Publish(topic string, data interface{}) {
// Prevent publish when closed // Prevent publish when closed
if s.closed { if s.closed {
s.logger.Fatal("cannot call publish on closed servicebus")
return return
} }
@@ -184,7 +174,6 @@ func (s *ServiceBus) Publish(topic string, data interface{}) {
func (s *ServiceBus) PublishForTarget(topic string, data interface{}, target string) { func (s *ServiceBus) PublishForTarget(topic string, data interface{}, target string) {
// Prevent publish when closed // Prevent publish when closed
if s.closed { if s.closed {
s.logger.Fatal("cannot call publish on closed servicebus")
return return
} }
message := NewMessageForTarget(topic, data, target) message := NewMessageForTarget(topic, data, target)

View File

@@ -1,8 +1,10 @@
package signal package signal
import ( import (
"context"
"os" "os"
gosignal "os/signal" gosignal "os/signal"
"sync"
"syscall" "syscall"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
@@ -20,24 +22,29 @@ type Manager struct {
// signalChannel // signalChannel
signalchannel chan os.Signal signalchannel chan os.Signal
// Quit channel // ctx
quitChannel <-chan *servicebus.Message 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 // NewManager creates a new signal manager
func NewManager(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) {
// Register quit channel
quitChannel, err := bus.Subscribe("quit")
if err != nil {
return nil, err
}
result := &Manager{ result := &Manager{
bus: bus, bus: bus,
logger: logger.CustomLogger("Event Manager"), logger: logger.CustomLogger("Event Manager"),
signalchannel: make(chan os.Signal, 2), signalchannel: make(chan os.Signal, 2),
quitChannel: quitChannel, ctx: ctx,
cancel: cancel,
shutdownCallback: shutdownCallback,
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
} }
return result, nil return result, nil
@@ -49,20 +56,28 @@ func (m *Manager) Start() {
// Hook into interrupts // Hook into interrupts
gosignal.Notify(m.signalchannel, os.Interrupt, syscall.SIGTERM) gosignal.Notify(m.signalchannel, os.Interrupt, syscall.SIGTERM)
// Spin off signal listener m.wg.Add(1)
// Spin off signal listener and wait for either a cancellation
// or signal
go func() { go func() {
running := true select {
for running { case <-m.signalchannel:
select { println()
case <-m.signalchannel: m.logger.Trace("Ctrl+C detected. Shutting down...")
println() m.bus.Publish("quit", "ctrl-c pressed")
m.logger.Trace("Ctrl+C detected. Shutting down...")
m.bus.Publish("quit", "ctrl-c pressed") // Shutdown app first
case <-m.quitChannel: if m.shutdownCallback != nil {
running = false m.shutdownCallback()
break
} }
// Start shutdown of Wails
m.cancel()
case <-m.ctx.Done():
} }
m.logger.Trace("Shutdown") m.logger.Trace("Shutdown")
m.wg.Done()
}() }()
} }

View File

@@ -10,9 +10,9 @@ import (
// Binding is the Binding subsystem. It manages all service bus messages // Binding is the Binding subsystem. It manages all service bus messages
// starting with "binding". // starting with "binding".
type Binding struct { type Binding struct {
quitChannel <-chan *servicebus.Message
bindingChannel <-chan *servicebus.Message bindingChannel <-chan *servicebus.Message
running bool
running bool
// Binding db // Binding db
bindings *binding.Bindings bindings *binding.Bindings
@@ -27,12 +27,6 @@ type Binding struct {
// NewBinding creates a new binding subsystem. Uses the given bindings db for reference. // NewBinding creates a new binding subsystem. Uses the given bindings db for reference.
func NewBinding(bus *servicebus.ServiceBus, logger *logger.Logger, bindings *binding.Bindings, runtime *runtime.Runtime) (*Binding, error) { func NewBinding(bus *servicebus.ServiceBus, logger *logger.Logger, bindings *binding.Bindings, runtime *runtime.Runtime) (*Binding, error) {
// Register quit channel
quitChannel, err := bus.Subscribe("quit")
if err != nil {
return nil, err
}
// Subscribe to event messages // Subscribe to event messages
bindingChannel, err := bus.Subscribe("binding") bindingChannel, err := bus.Subscribe("binding")
if err != nil { if err != nil {
@@ -40,7 +34,6 @@ func NewBinding(bus *servicebus.ServiceBus, logger *logger.Logger, bindings *bin
} }
result := &Binding{ result := &Binding{
quitChannel: quitChannel,
bindingChannel: bindingChannel, bindingChannel: bindingChannel,
logger: logger.CustomLogger("Binding Subsystem"), logger: logger.CustomLogger("Binding Subsystem"),
bindings: bindings, bindings: bindings,
@@ -61,20 +54,16 @@ func (b *Binding) Start() error {
go func() { go func() {
for b.running { for b.running {
select { select {
case <-b.quitChannel:
b.running = false
case bindingMessage := <-b.bindingChannel: case bindingMessage := <-b.bindingChannel:
b.logger.Trace("Got binding message: %+v", bindingMessage) b.logger.Trace("Got binding message: %+v", bindingMessage)
} }
} }
b.logger.Trace("Shutdown")
// Call shutdown
b.shutdown()
}() }()
return nil return nil
} }
func (b *Binding) shutdown() { func (b *Binding) Close() {
b.logger.Trace("Shutdown") b.running = false
} }

View File

@@ -1,10 +1,13 @@
package subsystem package subsystem
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"strings" "strings"
"sync"
"github.com/wailsapp/wails/v2/pkg/options/dialog"
"github.com/wailsapp/wails/v2/internal/binding" "github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
@@ -16,9 +19,10 @@ import (
// Call is the Call subsystem. It manages all service bus messages // Call is the Call subsystem. It manages all service bus messages
// starting with "call". // starting with "call".
type Call struct { type Call struct {
quitChannel <-chan *servicebus.Message
callChannel <-chan *servicebus.Message callChannel <-chan *servicebus.Message
running bool
// quit flag
shouldQuit bool
// bindings DB // bindings DB
DB *binding.DB DB *binding.DB
@@ -31,16 +35,16 @@ type Call struct {
// runtime // runtime
runtime *runtime.Runtime runtime *runtime.Runtime
// context
ctx context.Context
// parent waitgroup
wg *sync.WaitGroup
} }
// NewCall creates a new call subsystem // NewCall creates a new call subsystem
func NewCall(bus *servicebus.ServiceBus, logger *logger.Logger, DB *binding.DB, runtime *runtime.Runtime) (*Call, error) { func NewCall(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger, DB *binding.DB, runtime *runtime.Runtime) (*Call, error) {
// Register quit channel
quitChannel, err := bus.Subscribe("quit")
if err != nil {
return nil, err
}
// Subscribe to event messages // Subscribe to event messages
callChannel, err := bus.Subscribe("call:invoke") callChannel, err := bus.Subscribe("call:invoke")
@@ -49,12 +53,13 @@ func NewCall(bus *servicebus.ServiceBus, logger *logger.Logger, DB *binding.DB,
} }
result := &Call{ result := &Call{
quitChannel: quitChannel,
callChannel: callChannel, callChannel: callChannel,
logger: logger.CustomLogger("Call Subsystem"), logger: logger.CustomLogger("Call Subsystem"),
DB: DB, DB: DB,
bus: bus, bus: bus,
runtime: runtime, runtime: runtime,
ctx: ctx,
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
} }
return result, nil return result, nil
@@ -63,22 +68,21 @@ func NewCall(bus *servicebus.ServiceBus, logger *logger.Logger, DB *binding.DB,
// Start the subsystem // Start the subsystem
func (c *Call) Start() error { func (c *Call) Start() error {
c.running = true c.wg.Add(1)
// Spin off a go routine // Spin off a go routine
go func() { go func() {
for c.running { defer c.logger.Trace("Shutdown")
for {
select { select {
case <-c.quitChannel: case <-c.ctx.Done():
c.running = false c.wg.Done()
return
case callMessage := <-c.callChannel: case callMessage := <-c.callChannel:
// TODO: Check if this works ok in a goroutine
c.processCall(callMessage) c.processCall(callMessage)
} }
} }
// Call shutdown
c.shutdown()
}() }()
return nil return nil
@@ -190,10 +194,6 @@ func (c *Call) sendError(err error, payload *message.CallMessage, clientID strin
c.bus.PublishForTarget("call:result", string(messageData), clientID) c.bus.PublishForTarget("call:result", string(messageData), clientID)
} }
func (c *Call) shutdown() {
c.logger.Trace("Shutdown")
}
// CallbackMessage defines a message that contains the result of a call // CallbackMessage defines a message that contains the result of a call
type CallbackMessage struct { type CallbackMessage struct {
Result interface{} `json:"result"` Result interface{} `json:"result"`

View File

@@ -1,6 +1,7 @@
package subsystem package subsystem
import ( import (
"context"
"strings" "strings"
"sync" "sync"
@@ -22,9 +23,7 @@ type eventListener struct {
// Event is the Eventing subsystem. It manages all service bus messages // Event is the Eventing subsystem. It manages all service bus messages
// starting with "event". // starting with "event".
type Event struct { type Event struct {
quitChannel <-chan *servicebus.Message
eventChannel <-chan *servicebus.Message eventChannel <-chan *servicebus.Message
running bool
// Event listeners // Event listeners
listeners map[string][]*eventListener listeners map[string][]*eventListener
@@ -32,16 +31,16 @@ type Event struct {
// logger // logger
logger logger.CustomLogger logger logger.CustomLogger
// ctx
ctx context.Context
// parent waitgroup
wg *sync.WaitGroup
} }
// NewEvent creates a new log subsystem // NewEvent creates a new log subsystem
func NewEvent(bus *servicebus.ServiceBus, logger *logger.Logger) (*Event, error) { func NewEvent(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger) (*Event, error) {
// Register quit channel
quitChannel, err := bus.Subscribe("quit")
if err != nil {
return nil, err
}
// Subscribe to event messages // Subscribe to event messages
eventChannel, err := bus.Subscribe("event") eventChannel, err := bus.Subscribe("event")
@@ -50,10 +49,11 @@ func NewEvent(bus *servicebus.ServiceBus, logger *logger.Logger) (*Event, error)
} }
result := &Event{ result := &Event{
quitChannel: quitChannel,
eventChannel: eventChannel, eventChannel: eventChannel,
logger: logger.CustomLogger("Event Subsystem"), logger: logger.CustomLogger("Event Subsystem"),
listeners: make(map[string][]*eventListener), listeners: make(map[string][]*eventListener),
ctx: ctx,
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
} }
return result, nil return result, nil
@@ -80,15 +80,16 @@ func (e *Event) Start() error {
e.logger.Trace("Starting") e.logger.Trace("Starting")
e.running = true e.wg.Add(1)
// Spin off a go routine // Spin off a go routine
go func() { go func() {
for e.running { defer e.logger.Trace("Shutdown")
for {
select { select {
case <-e.quitChannel: case <-e.ctx.Done():
e.running = false e.wg.Done()
break return
case eventMessage := <-e.eventChannel: case eventMessage := <-e.eventChannel:
splitTopic := strings.Split(eventMessage.Topic(), ":") splitTopic := strings.Split(eventMessage.Topic(), ":")
eventType := splitTopic[1] eventType := splitTopic[1]
@@ -128,8 +129,6 @@ func (e *Event) Start() error {
} }
} }
// Call shutdown
e.shutdown()
}() }()
return nil return nil
@@ -190,7 +189,3 @@ func (e *Event) notifyListeners(eventName string, message *message.EventMessage)
// Unlock // Unlock
e.notifyLock.Unlock() e.notifyLock.Unlock()
} }
func (e *Event) shutdown() {
e.logger.Trace("Shutdown")
}

View File

@@ -1,8 +1,10 @@
package subsystem package subsystem
import ( import (
"context"
"strconv" "strconv"
"strings" "strings"
"sync"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/runtime" "github.com/wailsapp/wails/v2/internal/runtime"
@@ -12,15 +14,23 @@ import (
// Log is the Logging subsystem. It handles messages with topics starting // Log is the Logging subsystem. It handles messages with topics starting
// with "log:" // with "log:"
type Log struct { type Log struct {
logChannel <-chan *servicebus.Message logChannel <-chan *servicebus.Message
quitChannel <-chan *servicebus.Message
running bool // quit flag
shouldQuit bool
// Logger! // Logger!
logger *logger.Logger logger *logger.Logger
// Loglevel store // Loglevel store
logLevelStore *runtime.Store logLevelStore *runtime.Store
// Context for shutdown
ctx context.Context
cancel context.CancelFunc
// internal waitgroup
wg sync.WaitGroup
} }
// NewLog creates a new log subsystem // NewLog creates a new log subsystem
@@ -32,17 +42,14 @@ func NewLog(bus *servicebus.ServiceBus, logger *logger.Logger, logLevelStore *ru
return nil, err return nil, err
} }
// Subscribe to quit messages ctx, cancel := context.WithCancel(context.Background())
quitChannel, err := bus.Subscribe("quit")
if err != nil {
return nil, err
}
result := &Log{ result := &Log{
logChannel: logChannel, logChannel: logChannel,
quitChannel: quitChannel,
logger: logger, logger: logger,
logLevelStore: logLevelStore, logLevelStore: logLevelStore,
ctx: ctx,
cancel: cancel,
} }
return result, nil return result, nil
@@ -51,15 +58,17 @@ func NewLog(bus *servicebus.ServiceBus, logger *logger.Logger, logLevelStore *ru
// Start the subsystem // Start the subsystem
func (l *Log) Start() error { func (l *Log) Start() error {
l.running = true l.wg.Add(1)
// Spin off a go routine // Spin off a go routine
go func() { go func() {
for l.running { defer l.logger.Trace("Logger Shutdown")
for l.shouldQuit == false {
select { select {
case <-l.quitChannel: case <-l.ctx.Done():
l.running = false l.wg.Done()
break return
case logMessage := <-l.logChannel: case logMessage := <-l.logChannel:
logType := strings.TrimPrefix(logMessage.Topic(), "log:") logType := strings.TrimPrefix(logMessage.Topic(), "log:")
switch logType { switch logType {
@@ -98,8 +107,12 @@ func (l *Log) Start() error {
} }
} }
} }
l.logger.Trace("Logger Shutdown")
}() }()
return nil return nil
} }
func (l *Log) Close() {
l.cancel()
l.wg.Wait()
}

View File

@@ -1,9 +1,12 @@
package subsystem package subsystem
import ( import (
"context"
"encoding/json" "encoding/json"
"github.com/wailsapp/wails/v2/pkg/menu"
"strings" "strings"
"sync"
"github.com/wailsapp/wails/v2/pkg/menu"
"github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/menumanager" "github.com/wailsapp/wails/v2/internal/menumanager"
@@ -13,9 +16,10 @@ import (
// Menu is the subsystem that handles the operation of menus. It manages all service bus messages // Menu is the subsystem that handles the operation of menus. It manages all service bus messages
// starting with "menu". // starting with "menu".
type Menu struct { type Menu struct {
quitChannel <-chan *servicebus.Message
menuChannel <-chan *servicebus.Message menuChannel <-chan *servicebus.Message
running bool
// shutdown flag
shouldQuit bool
// logger // logger
logger logger.CustomLogger logger logger.CustomLogger
@@ -25,16 +29,16 @@ type Menu struct {
// Menu Manager // Menu Manager
menuManager *menumanager.Manager menuManager *menumanager.Manager
// ctx
ctx context.Context
// parent waitgroup
wg *sync.WaitGroup
} }
// NewMenu creates a new menu subsystem // NewMenu creates a new menu subsystem
func NewMenu(bus *servicebus.ServiceBus, logger *logger.Logger, menuManager *menumanager.Manager) (*Menu, error) { func NewMenu(ctx context.Context, bus *servicebus.ServiceBus, logger *logger.Logger, menuManager *menumanager.Manager) (*Menu, error) {
// Register quit channel
quitChannel, err := bus.Subscribe("quit")
if err != nil {
return nil, err
}
// Subscribe to menu messages // Subscribe to menu messages
menuChannel, err := bus.Subscribe("menu:") menuChannel, err := bus.Subscribe("menu:")
@@ -43,11 +47,12 @@ func NewMenu(bus *servicebus.ServiceBus, logger *logger.Logger, menuManager *men
} }
result := &Menu{ result := &Menu{
quitChannel: quitChannel,
menuChannel: menuChannel, menuChannel: menuChannel,
logger: logger.CustomLogger("Menu Subsystem"), logger: logger.CustomLogger("Menu Subsystem"),
bus: bus, bus: bus,
menuManager: menuManager, menuManager: menuManager,
ctx: ctx,
wg: ctx.Value("waitgroup").(*sync.WaitGroup),
} }
return result, nil return result, nil
@@ -58,15 +63,16 @@ func (m *Menu) Start() error {
m.logger.Trace("Starting") m.logger.Trace("Starting")
m.running = true m.wg.Add(1)
// Spin off a go routine // Spin off a go routine
go func() { go func() {
for m.running { defer m.logger.Trace("Shutdown")
for {
select { select {
case <-m.quitChannel: case <-m.ctx.Done():
m.running = false m.wg.Done()
break return
case menuMessage := <-m.menuChannel: case menuMessage := <-m.menuChannel:
splitTopic := strings.Split(menuMessage.Topic(), ":") splitTopic := strings.Split(menuMessage.Topic(), ":")
menuMessageType := splitTopic[1] menuMessageType := splitTopic[1]
@@ -147,14 +153,7 @@ func (m *Menu) Start() error {
} }
} }
} }
// Call shutdown
m.shutdown()
}() }()
return nil return nil
} }
func (m *Menu) shutdown() {
m.logger.Trace("Shutdown")
}

View File

@@ -1,6 +1,7 @@
package subsystem package subsystem
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
@@ -12,7 +13,6 @@ import (
// Runtime is the Runtime subsystem. It handles messages with topics starting // Runtime is the Runtime subsystem. It handles messages with topics starting
// with "runtime:" // with "runtime:"
type Runtime struct { type Runtime struct {
quitChannel <-chan *servicebus.Message
runtimeChannel <-chan *servicebus.Message runtimeChannel <-chan *servicebus.Message
// The hooks channel allows us to hook into frontend startup // The hooks channel allows us to hook into frontend startup
@@ -20,22 +20,20 @@ type Runtime struct {
startupCallback func(*runtime.Runtime) startupCallback func(*runtime.Runtime)
shutdownCallback func() shutdownCallback func()
running bool // quit flag
shouldQuit bool
logger logger.CustomLogger logger logger.CustomLogger
// Runtime library // Runtime library
runtime *runtime.Runtime runtime *runtime.Runtime
//ctx
ctx context.Context
} }
// NewRuntime creates a new runtime subsystem // NewRuntime creates a new runtime subsystem
func NewRuntime(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), shutdownCallback func()) (*Runtime, error) {
// Register quit channel
quitChannel, err := bus.Subscribe("quit")
if err != nil {
return nil, err
}
// Subscribe to log messages // Subscribe to log messages
runtimeChannel, err := bus.Subscribe("runtime:") runtimeChannel, err := bus.Subscribe("runtime:")
@@ -50,13 +48,13 @@ func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, startupCallba
} }
result := &Runtime{ result := &Runtime{
quitChannel: quitChannel,
runtimeChannel: runtimeChannel, runtimeChannel: runtimeChannel,
hooksChannel: hooksChannel, hooksChannel: hooksChannel,
logger: logger.CustomLogger("Runtime Subsystem"), logger: logger.CustomLogger("Runtime Subsystem"),
runtime: runtime.New(bus), runtime: runtime.New(bus, shutdownCallback),
startupCallback: startupCallback, startupCallback: startupCallback,
shutdownCallback: shutdownCallback, shutdownCallback: shutdownCallback,
ctx: ctx,
} }
return result, nil return result, nil
@@ -65,15 +63,11 @@ func NewRuntime(bus *servicebus.ServiceBus, logger *logger.Logger, startupCallba
// Start the subsystem // Start the subsystem
func (r *Runtime) Start() error { func (r *Runtime) Start() error {
r.running = true
// Spin off a go routine // Spin off a go routine
go func() { go func() {
for r.running { defer r.logger.Trace("Shutdown")
for {
select { select {
case <-r.quitChannel:
r.running = false
break
case hooksMessage := <-r.hooksChannel: case hooksMessage := <-r.hooksChannel:
r.logger.Trace(fmt.Sprintf("Received hooksmessage: %+v", hooksMessage)) r.logger.Trace(fmt.Sprintf("Received hooksmessage: %+v", hooksMessage))
messageSlice := strings.Split(hooksMessage.Topic(), ":") messageSlice := strings.Split(hooksMessage.Topic(), ":")
@@ -113,11 +107,10 @@ func (r *Runtime) Start() error {
if err != nil { if err != nil {
r.logger.Error(err.Error()) r.logger.Error(err.Error())
} }
case <-r.ctx.Done():
return
} }
} }
// Call shutdown
r.shutdown()
}() }()
return nil return nil
@@ -128,15 +121,6 @@ func (r *Runtime) GoRuntime() *runtime.Runtime {
return r.runtime return r.runtime
} }
func (r *Runtime) shutdown() {
if r.shutdownCallback != nil {
go r.shutdownCallback()
} else {
r.logger.Warning("no shutdown callback registered!")
}
r.logger.Trace("Shutdown")
}
func (r *Runtime) processBrowserMessage(method string, data interface{}) error { func (r *Runtime) processBrowserMessage(method string, data interface{}) error {
switch method { switch method {
case "open": case "open":

View File

@@ -14,27 +14,28 @@ import (
// App contains options for creating the App // App contains options for creating the App
type App struct { type App struct {
Title string Title string
Width int Width int
Height int Height int
DisableResize bool DisableResize bool
Fullscreen bool Fullscreen bool
MinWidth int MinWidth int
MinHeight int MinHeight int
MaxWidth int MaxWidth int
MaxHeight int MaxHeight int
StartHidden bool StartHidden bool
DevTools bool HideWindowOnClose bool
RGBA int DevTools bool
ContextMenus []*menu.ContextMenu RGBA int
TrayMenus []*menu.TrayMenu ContextMenus []*menu.ContextMenu
Menu *menu.Menu TrayMenus []*menu.TrayMenu
Mac *mac.Options Menu *menu.Menu
Logger logger.Logger `json:"-"` Mac *mac.Options
LogLevel logger.LogLevel Logger logger.Logger `json:"-"`
Startup func(*wailsruntime.Runtime) `json:"-"` LogLevel logger.LogLevel
Shutdown func() `json:"-"` Startup func(*wailsruntime.Runtime) `json:"-"`
Bind []interface{} Shutdown func() `json:"-"`
Bind []interface{}
} }
// MergeDefaults will set the minimum default values for an application // MergeDefaults will set the minimum default values for an application