From 1f453594852f9098e319c5a8e3985bf184ed4ec9 Mon Sep 17 00:00:00 2001 From: ddmoney420 <130018552+ddmoney420@users.noreply.github.com> Date: Mon, 2 Feb 2026 12:09:40 -0700 Subject: [PATCH] fix(v3/linux): fix OpenFileDialog crash from GTK thread violation (#4926) * fix(v3/linux): fix OpenFileDialog crash from GTK thread violation The runChooserDialog function had a race condition that caused GTK assertion failures and crashes on Linux: 1. InvokeAsync scheduled gtk_dialog_run on the GTK thread 2. After dialog closed, a goroutine was spawned OFF the GTK thread 3. gtk_widget_destroy was called immediately (before goroutine ran) 4. Goroutine tried to call gtk_file_chooser_get_filenames on destroyed widget Fix: - Extract filenames on GTK thread BEFORE destroying widget - Call gtk_widget_destroy on GTK thread AFTER extraction - Goroutine only handles sending Go strings (no GTK calls) Fixes #3683 Co-Authored-By: Claude Opus 4.5 * docs: add changelog entry for OpenFileDialog crash fix Co-Authored-By: Claude Opus 4.5 * fix: properly free all filenames to prevent memory leak When more than 1024 files are selected, the previous code leaked memory because g_slist_free() only frees list nodes, not the data pointers. Now we iterate through ALL entries, freeing each filename string. Co-Authored-By: Claude Opus 4.5 * refactor: remove arbitrary 1024 file selection limit GTK's gtk_file_chooser_get_filenames() has no documented maximum. The limit was arbitrary and unnecessary. Co-Authored-By: Claude Opus 4.5 * docs: add comment about no file selection limit Consistent with Windows/macOS behavior. Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: ddmoney420 Co-authored-by: Claude Opus 4.5 Co-authored-by: Lea Anthony --- v3/UNRELEASED_CHANGELOG.md | 1 + v3/pkg/application/linux_cgo.go | 28 +++++++++++++++------------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 719b9cfe7..c9e35a240 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -25,6 +25,7 @@ After processing, the content will be moved to the main changelog and this file ## Fixed +- 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 }