diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go index c8a62502f..9cc7bae08 100644 --- a/v3/pkg/application/application.go +++ b/v3/pkg/application/application.go @@ -1,6 +1,7 @@ package application import ( + "github.com/samber/lo" "log" "net/http" "os" @@ -23,6 +24,10 @@ func init() { runtime.LockOSThread() } +type EventListener struct { + callback func() +} + func New(appOptions Options) *App { if globalApplication != nil { return globalApplication @@ -32,7 +37,7 @@ func New(appOptions Options) *App { result := &App{ options: appOptions, - applicationEventListeners: make(map[uint][]func()), + applicationEventListeners: make(map[uint][]*EventListener), systemTrays: make(map[uint]*SystemTray), log: logger.New(appOptions.Logger.CustomLoggers...), contextMenus: make(map[string]*Menu), @@ -155,7 +160,7 @@ var webviewRequests = make(chan *webViewAssetRequest) type App struct { options Options - applicationEventListeners map[uint][]func() + applicationEventListeners map[uint][]*EventListener applicationEventListenersLock sync.RWMutex // Windows @@ -216,14 +221,25 @@ func (a *App) deleteWindowByID(id uint) { delete(a.windows, id) } -func (a *App) On(eventType events.ApplicationEventType, callback func()) { +func (a *App) On(eventType events.ApplicationEventType, callback func()) func() { eventID := uint(eventType) a.applicationEventListenersLock.Lock() defer a.applicationEventListenersLock.Unlock() - a.applicationEventListeners[eventID] = append(a.applicationEventListeners[eventID], callback) + listener := &EventListener{ + callback: callback, + } + a.applicationEventListeners[eventID] = append(a.applicationEventListeners[eventID], listener) if a.impl != nil { go a.impl.on(eventID) } + + return func() { + // lock the map + a.applicationEventListenersLock.Lock() + defer a.applicationEventListenersLock.Unlock() + // Remove listener + a.applicationEventListeners[eventID] = lo.Without(a.applicationEventListeners[eventID], listener) + } } func (a *App) NewWebviewWindow() *WebviewWindow { return a.NewWebviewWindowWithOptions(&WebviewWindowOptions{}) @@ -383,7 +399,7 @@ func (a *App) handleApplicationEvent(event uint) { return } for _, listener := range listeners { - go listener() + go listener.callback() } } diff --git a/v3/pkg/application/webview_window.go b/v3/pkg/application/webview_window.go index 67cfc3c2c..0dc2b1355 100644 --- a/v3/pkg/application/webview_window.go +++ b/v3/pkg/application/webview_window.go @@ -2,6 +2,7 @@ package application import ( "fmt" + "github.com/samber/lo" "sync" "time" @@ -61,17 +62,25 @@ type ( } ) +type WindowEventListener struct { + callback func(ctx *WindowEventContext) +} + type WebviewWindow struct { options *WebviewWindowOptions impl webviewWindowImpl implLock sync.RWMutex id uint - eventListeners map[uint][]func(ctx *WindowEventContext) + eventListeners map[uint][]*WindowEventListener eventListenersLock sync.RWMutex contextMenus map[string]*Menu contextMenusLock sync.RWMutex + + // A map of listener cancellation functions + cancellersLock sync.RWMutex + cancellers []func() } var windowID uint @@ -84,6 +93,13 @@ func getWindowID() uint { return windowID } +// Use onApplicationEvent to register a callback for an application event from a window. +// This will handle tidying up the callback when the window is destroyed +func (w *WebviewWindow) onApplicationEvent(eventType events.ApplicationEventType, callback func()) { + cancelFn := globalApplication.On(eventType, callback) + w.addCancellationFunction(cancelFn) +} + func NewWindow(options *WebviewWindowOptions) *WebviewWindow { if options.Width == 0 { options.Width = 800 @@ -98,13 +114,19 @@ func NewWindow(options *WebviewWindowOptions) *WebviewWindow { result := &WebviewWindow{ id: getWindowID(), options: options, - eventListeners: make(map[uint][]func(ctx *WindowEventContext)), + eventListeners: make(map[uint][]*WindowEventListener), contextMenus: make(map[string]*Menu), } return result } +func (w *WebviewWindow) addCancellationFunction(canceller func()) { + w.cancellersLock.Lock() + defer w.cancellersLock.Unlock() + w.cancellers = append(w.cancellers, canceller) +} + func (w *WebviewWindow) SetTitle(title string) *WebviewWindow { w.implLock.RLock() defer w.implLock.RUnlock() @@ -371,20 +393,30 @@ func (w *WebviewWindow) Center() { w.impl.center() } -func (w *WebviewWindow) On(eventType events.WindowEventType, callback func(ctx *WindowEventContext)) { +func (w *WebviewWindow) On(eventType events.WindowEventType, callback func(ctx *WindowEventContext)) func() { eventID := uint(eventType) w.eventListenersLock.Lock() defer w.eventListenersLock.Unlock() - w.eventListeners[eventID] = append(w.eventListeners[eventID], callback) + windowEventListener := &WindowEventListener{ + callback: callback, + } + w.eventListeners[eventID] = append(w.eventListeners[eventID], windowEventListener) if w.impl != nil { w.impl.on(eventID) } + + return func() { + w.eventListenersLock.Lock() + defer w.eventListenersLock.Unlock() + w.eventListeners[eventID] = lo.Without(w.eventListeners[eventID], windowEventListener) + } + } func (w *WebviewWindow) handleWindowEvent(id uint) { w.eventListenersLock.RLock() - for _, callback := range w.eventListeners[id] { - go callback(blankWindowEventContext) + for _, listener := range w.eventListeners[id] { + go listener.callback(blankWindowEventContext) } w.eventListenersLock.RUnlock() } @@ -416,6 +448,10 @@ func (w *WebviewWindow) Destroy() { if w.impl == nil { return } + // Cancel the callbacks + for _, cancelFunc := range w.cancellers { + cancelFunc() + } w.impl.destroy() } @@ -631,7 +667,7 @@ func (w *WebviewWindow) handleDragAndDropMessage(event *dragAndDropMessage) { ctx := newWindowEventContext() ctx.setDroppedFiles(event.filenames) for _, listener := range w.eventListeners[uint(events.FilesDropped)] { - listener(ctx) + listener.callback(ctx) } } diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index 409964dd0..b47b3738e 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -140,7 +140,8 @@ func (w *windowsWebviewWindow) _run() { switch options.Windows.Theme { case SystemDefault: w.updateTheme(w32.IsCurrentlyDarkMode()) - globalApplication.On(events.Windows.SystemThemeChanged, func() { + // Setup a listener to respond to theme changes + w.parent.onApplicationEvent(events.Windows.SystemThemeChanged, func() { w.updateTheme(w32.IsCurrentlyDarkMode()) }) case Light: