diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index a56a12da7..d82198c9c 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -26,6 +26,7 @@ After processing, the content will be moved to the main changelog and this file ## Fixed - Fix JS/CSS options in WebviewWindowOptions not being executed when using URL navigation (not just HTML) +- Fix OpenFileDialog crash on Linux due to GTK thread safety violation (#3683) by @ddmoney420 - Fix SIGSEGV crash when calling `Focus()` on a hidden or destroyed window (#4890) by @ddmoney420 - Fix potential panic when setting empty icon or bitmap on Linux (#4923) by @ddmoney420 - Fix ErrorDialog crash when called from service binding on macOS (#3631) by @leaanthony diff --git a/v3/pkg/application/linux_cgo.go b/v3/pkg/application/linux_cgo.go index ba34fa31a..ee5df0af3 100644 --- a/v3/pkg/application/linux_cgo.go +++ b/v3/pkg/application/linux_cgo.go @@ -2117,25 +2117,27 @@ func runChooserDialog(window pointer, allowMultiple, createFolders, showHidden b // run this on the gtk thread InvokeAsync(func() { response := C.gtk_dialog_run((*C.GtkDialog)(fc)) + // Extract results on GTK thread BEFORE destroying widget + var results []string + if response == C.GTK_RESPONSE_ACCEPT { + // No artificial limit - consistent with Windows/macOS behavior + filenames := C.gtk_file_chooser_get_filenames((*C.GtkFileChooser)(fc)) + for iter := filenames; iter != nil; iter = iter.next { + results = append(results, buildStringAndFree(C.gpointer(iter.data))) + } + C.g_slist_free(filenames) + } + // Destroy widget after extracting results (on GTK thread) + C.gtk_widget_destroy((*C.GtkWidget)(unsafe.Pointer(fc))) + // Send results from goroutine (safe - no GTK calls) go func() { defer handlePanic() - if response == C.GTK_RESPONSE_ACCEPT { - filenames := C.gtk_file_chooser_get_filenames((*C.GtkFileChooser)(fc)) - iter := filenames - count := 0 - for { - selections <- buildStringAndFree(C.gpointer(iter.data)) - iter = iter.next - if iter == nil || count == 1024 { - break - } - count++ - } + for _, result := range results { + selections <- result } close(selections) }() }) - C.gtk_widget_destroy((*C.GtkWidget)(unsafe.Pointer(fc))) return selections, nil }