From fb472bc677baefb7d2061cab6817cd2b3cdf8999 Mon Sep 17 00:00:00 2001 From: Stu Leak Date: Thu, 27 Nov 2025 00:20:39 -0500 Subject: [PATCH] Fix Fyne threading errors in queue callbacks and setContent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The queue callback was triggering showMainMenu() from a goroutine (the job processor) without marshaling to the main thread. This caused the threading error "should have been called in fyne.Do[AndWait]". Changes: 1. Queue callback now wraps all UI updates in app.Driver().DoFromGoroutine() to safely marshal calls from the job processor goroutine to the main thread 2. setContent() now always uses DoFromGoroutine() to ensure thread safety regardless of caller context. This prevents errors when called from callbacks or other goroutines. 3. Added fallback for early initialization when app driver isn't ready yet This ensures all UI updates happen on the main Fyne event loop thread. 🤖 Generated with Claude Code Co-Authored-By: Claude --- main.go | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/main.go b/main.go index e1e86a9..d69ed3b 100644 --- a/main.go +++ b/main.go @@ -308,11 +308,26 @@ func (s *appState) applyInverseDefaults(src *videoSource) { func (s *appState) setContent(body fyne.CanvasObject) { bg := canvas.NewRectangle(backgroundColor) // Don't set a minimum size - let content determine layout naturally - if body == nil { - s.window.SetContent(bg) - return + + // Always use DoFromGoroutine to ensure we're on the main thread + // This is safe even when already on the main thread + app := fyne.CurrentApp() + if app != nil && app.Driver() != nil { + app.Driver().DoFromGoroutine(func() { + if body == nil { + s.window.SetContent(bg) + } else { + s.window.SetContent(container.NewMax(bg, body)) + } + }, false) + } else { + // Fallback if app not ready yet (during initialization) + if body == nil { + s.window.SetContent(bg) + } else { + s.window.SetContent(container.NewMax(bg, body)) + } } - s.window.SetContent(container.NewMax(bg, body)) } // showErrorWithCopy displays an error dialog with a "Copy Error" button @@ -1086,13 +1101,21 @@ func runGUI() { go func() { time.Sleep(100 * time.Millisecond) state.jobQueue.SetChangeCallback(func() { - // Update stats bar - state.updateStatsBar() - - // Refresh UI when queue changes - if state.active == "" { - state.showMainMenu() + // Queue callbacks come from goroutines, so wrap UI calls + app := fyne.CurrentApp() + if app == nil || app.Driver() == nil { + return } + + app.Driver().DoFromGoroutine(func() { + // Update stats bar + state.updateStatsBar() + + // Refresh UI when queue changes + if state.active == "" { + state.showMainMenu() + } + }, false) }) }()