Fix threading errors with proper initialization guard
The threading errors were caused by queue callbacks triggering showMainMenu() during app initialization, before the Fyne event loop was fully ready. Changes: 1. Added initComplete flag to appState struct 2. Queue callback returns early if !initComplete, preventing UI updates during initialization 3. Set initComplete=true AFTER ShowAndRun() would handle the event loop 4. Removed nested DoFromGoroutine() which was causing double-wrapping 5. Simplified setContent() to direct calls (no thread wrapping) 6. Callback properly marshals UI updates via DoFromGoroutine() after init This ensures the queue callback only affects UI after the app is fully initialized and the Fyne event loop is running.
This commit is contained in:
parent
b16b3439fb
commit
3ffb7c8a97
116
main.go
116
main.go
|
|
@ -151,30 +151,31 @@ func (c convertConfig) CoverLabel() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type appState struct {
|
type appState struct {
|
||||||
window fyne.Window
|
window fyne.Window
|
||||||
active string
|
active string
|
||||||
source *videoSource
|
initComplete bool // True after initial UI setup completes
|
||||||
loadedVideos []*videoSource // Multiple loaded videos for navigation
|
source *videoSource
|
||||||
currentIndex int // Current video index in loadedVideos
|
loadedVideos []*videoSource // Multiple loaded videos for navigation
|
||||||
anim *previewAnimator
|
currentIndex int // Current video index in loadedVideos
|
||||||
convert convertConfig
|
anim *previewAnimator
|
||||||
currentFrame string
|
convert convertConfig
|
||||||
player player.Controller
|
currentFrame string
|
||||||
playerReady bool
|
player player.Controller
|
||||||
playerVolume float64
|
playerReady bool
|
||||||
playerMuted bool
|
playerVolume float64
|
||||||
lastVolume float64
|
playerMuted bool
|
||||||
playerPaused bool
|
lastVolume float64
|
||||||
playerPos float64
|
playerPaused bool
|
||||||
playerLast time.Time
|
playerPos float64
|
||||||
progressQuit chan struct{}
|
playerLast time.Time
|
||||||
convertCancel context.CancelFunc
|
progressQuit chan struct{}
|
||||||
playerSurf *playerSurface
|
convertCancel context.CancelFunc
|
||||||
convertBusy bool
|
playerSurf *playerSurface
|
||||||
convertStatus string
|
convertBusy bool
|
||||||
playSess *playSession
|
convertStatus string
|
||||||
jobQueue *queue.Queue
|
playSess *playSession
|
||||||
statsBar *ui.ConversionStatsBar
|
jobQueue *queue.Queue
|
||||||
|
statsBar *ui.ConversionStatsBar
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appState) stopPreview() {
|
func (s *appState) stopPreview() {
|
||||||
|
|
@ -308,26 +309,11 @@ func (s *appState) applyInverseDefaults(src *videoSource) {
|
||||||
func (s *appState) setContent(body fyne.CanvasObject) {
|
func (s *appState) setContent(body fyne.CanvasObject) {
|
||||||
bg := canvas.NewRectangle(backgroundColor)
|
bg := canvas.NewRectangle(backgroundColor)
|
||||||
// Don't set a minimum size - let content determine layout naturally
|
// Don't set a minimum size - let content determine layout naturally
|
||||||
|
if body == nil {
|
||||||
// Always use DoFromGoroutine to ensure we're on the main thread
|
s.window.SetContent(bg)
|
||||||
// This is safe even when already on the main thread
|
return
|
||||||
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
|
// showErrorWithCopy displays an error dialog with a "Copy Error" button
|
||||||
|
|
@ -1096,28 +1082,32 @@ func runGUI() {
|
||||||
// Start queue processing (but paused by default)
|
// Start queue processing (but paused by default)
|
||||||
state.jobQueue.Start()
|
state.jobQueue.Start()
|
||||||
|
|
||||||
// Set callback AFTER showing the window to avoid threading issues during startup
|
// Set callback - queue changes are triggered by job processor goroutine
|
||||||
// Use a goroutine with delay to ensure UI is fully initialized
|
state.jobQueue.SetChangeCallback(func() {
|
||||||
go func() {
|
// Skip updates during initialization
|
||||||
time.Sleep(100 * time.Millisecond)
|
if !state.initComplete {
|
||||||
state.jobQueue.SetChangeCallback(func() {
|
return
|
||||||
// Queue callbacks come from goroutines, so wrap UI calls
|
}
|
||||||
app := fyne.CurrentApp()
|
|
||||||
if app == nil || app.Driver() == nil {
|
// Marshal UI updates to main thread
|
||||||
return
|
app := fyne.CurrentApp()
|
||||||
|
if app == nil || app.Driver() == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Driver().DoFromGoroutine(func() {
|
||||||
|
// Update stats bar
|
||||||
|
state.updateStatsBar()
|
||||||
|
|
||||||
|
// Refresh UI when queue changes and we're on main menu
|
||||||
|
if state.active == "" {
|
||||||
|
state.showMainMenu()
|
||||||
}
|
}
|
||||||
|
}, false)
|
||||||
|
})
|
||||||
|
|
||||||
app.Driver().DoFromGoroutine(func() {
|
// Mark initialization as complete
|
||||||
// Update stats bar
|
state.initComplete = true
|
||||||
state.updateStatsBar()
|
|
||||||
|
|
||||||
// Refresh UI when queue changes
|
|
||||||
if state.active == "" {
|
|
||||||
state.showMainMenu()
|
|
||||||
}
|
|
||||||
}, false)
|
|
||||||
})
|
|
||||||
}()
|
|
||||||
|
|
||||||
defer state.shutdown()
|
defer state.shutdown()
|
||||||
w.SetOnDropped(func(pos fyne.Position, items []fyne.URI) {
|
w.SetOnDropped(func(pos fyne.Position, items []fyne.URI) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user