Skip to content

fix(linux): split dialog dispatch between GTK3 and GTK4#5340

Merged
leaanthony merged 2 commits into
masterfrom
fix/split-gtk3-gtk4-dialogs
May 6, 2026
Merged

fix(linux): split dialog dispatch between GTK3 and GTK4#5340
leaanthony merged 2 commits into
masterfrom
fix/split-gtk3-gtk4-dialogs

Conversation

@leaanthony

@leaanthony leaanthony commented May 5, 2026

Copy link
Copy Markdown
Member

Summary

  • dialogs_linux.go gains a !gtk4 build constraint — GTK3 (and purego) continue to use InvokeAsync to marshal dialog calls onto the GTK main thread, which is required because GTK3 is not thread-safe.
  • New dialogs_linux_gtk4.go (build tag gtk4) replaces InvokeAsync with a plain goroutine for both showAboutDialog and linuxDialog.show().

Why

GTK4's runQuestionDialog already schedules the dialog display via its own InvokeAsync and then blocks on a Go channel waiting for the user's button callback. If called from within an outer InvokeAsync (i.e., from the main GTK thread), the channel block stalls the main loop, preventing the dialog callback from ever firing → deadlock.

GTK4 is thread-safe, so the goroutine approach is correct: runQuestionDialog can be called from any thread, its internal InvokeAsync queues the dialog display on the main thread without blocking it, and the goroutine waits for the result channel while the main loop stays free.

Test plan

  • GTK3 build (go build ./... on Linux without -tags gtk4): dialogs_linux.go compiles, showAboutDialog and message dialogs still work.
  • GTK4 build (go build -tags gtk4 ./...): dialogs_linux_gtk4.go compiles, showAboutDialog and message dialogs no longer deadlock.
  • Verify file-open/save dialogs still work on both GTK3 and GTK4 (the linuxOpenFileDialog/linuxSaveFileDialog impls are identical; they delegate to runChooserDialog which already handles its own threading).

Fixes #5338

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Optimized internal UI dispatching mechanism on Linux by transitioning from InvokeAsync to a new GTK-specific dispatch handler. Changes improve the threading model for dialogs, menu interactions, and window initialization across standard GTK and GTK4 environments.

Copilot AI review requested due to automatic review settings May 5, 2026 20:24
@coderabbitai

coderabbitai Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e3bfa43b-5048-43ad-aedc-4c22e93f0d9d

📥 Commits

Reviewing files that changed from the base of the PR and between 26a5231 and 57b1f69.

📒 Files selected for processing (5)
  • v3/pkg/application/dialogs_linux.go
  • v3/pkg/application/gtkdispatch_linux.go
  • v3/pkg/application/gtkdispatch_linux_gtk4.go
  • v3/pkg/application/systemtray_linux.go
  • v3/pkg/application/webview_window_linux.go

Walkthrough

Introduces a gtkDispatch abstraction for Linux that routes GTK work through platform-specific dispatch paths: GTK3 delegates to InvokeAsync, while GTK4 uses goroutines with panic handling. Replaces direct InvokeAsync calls in dialogs, systray, and webview window with this new abstraction.

Changes

GTK Dispatch Abstraction & Migration

Layer / File(s) Summary
Dispatch Abstraction
v3/pkg/application/gtkdispatch_linux.go, v3/pkg/application/gtkdispatch_linux_gtk4.go
New gtkDispatch(fn func()) implementations: GTK3/non-gtk4 path delegates to InvokeAsync; GTK4 path spawns goroutine with handlePanic deferred.
Core Callsites Migration
v3/pkg/application/dialogs_linux.go, v3/pkg/application/systemtray_linux.go, v3/pkg/application/webview_window_linux.go
All InvokeAsync calls in dialog presentation, systray click handlers, and webview window initialization replaced with gtkDispatch for consistent platform-aware dispatch.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • wailsapp/wails#4926: Prior GTK thread dispatch fix using InvokeAsync for dialog work; this PR centralizes that pattern into the new gtkDispatch abstraction.
  • wailsapp/wails#3907: Modifies Linux systray click handling in the same file; this PR updates the dispatch mechanism for those same click handlers.
  • wailsapp/wails#5339: Addresses GTK/GUI dispatch logic in dialogs_linux.go; directly related to the same control flow modifications.

Suggested labels

Linux, v3-alpha, lgtm

Suggested reviewers

  • atterpac

Poem

🐰 Dispatches dance on Linux ground,
GTK3 and Four both homeward bound—
Where threads once tangled, segfaults did ring,
Now gtkDispatch makes harmony sing! 🎵

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'fix(linux): split dialog dispatch between GTK3 and GTK4' accurately describes the main change: introducing separate dispatch mechanisms for GTK3 and GTK4 dialog handling on Linux.
Description check ✅ Passed The PR description covers the summary of changes, rationale for the split, and includes a test plan. However, it lacks completion of the issue template checklist items (type of change, test configuration, and general checklist).
Linked Issues check ✅ Passed The PR directly addresses issue #5338 by implementing separate dialog dispatch paths for GTK3 (InvokeAsync) and GTK4 (goroutine) to fix segfaults and deadlocks respectively. All coding requirements from the issue are met.
Out of Scope Changes check ✅ Passed All changes are within scope: new platform-specific files (dialogs_linux_gtk4.go, gtkdispatch_linux.go, gtkdispatch_linux_gtk4.go) and modifications to existing Linux files replace InvokeAsync with gtkDispatch consistently, all related to the dialog dispatch fix.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/split-gtk3-gtk4-dialogs

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.1)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes Linux dialog dispatching by separating GTK3 and GTK4 implementations to avoid GTK3 thread-safety crashes while also preventing GTK4 deadlocks caused by nesting InvokeAsync around a GTK4 runQuestionDialog that already schedules onto the main loop and then blocks for a response.

Changes:

  • Add a !gtk4 build constraint to the existing Linux dialogs implementation so GTK3 (and purego) keep using InvokeAsync for main-thread marshaling.
  • Introduce a GTK4-specific Linux dialogs implementation that dispatches message/about dialogs via a goroutine instead of InvokeAsync, avoiding main-loop deadlock with GTK4’s channel-waiting dialog flow.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
v3/pkg/application/dialogs_linux.go Adds !gtk4 build constraint to ensure GTK3 continues using main-thread dispatch via InvokeAsync.
v3/pkg/application/dialogs_linux_gtk4.go New GTK4-only dialogs implementation using goroutines for message/about dialog dispatch to avoid deadlocks.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread v3/pkg/application/dialogs_linux_gtk4.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
v3/pkg/application/dialogs_linux.go (1)

61-87: ⚡ Quick win

Extract identical file-dialog implementations to a shared file.

linuxOpenFileDialog, linuxSaveFileDialog, their constructors, and show() methods are byte-for-byte identical in both dialogs_linux.go and dialogs_linux_gtk4.go. They contain no GTK-version-specific logic (both delegate to runOpenFileDialog/runSaveFileDialog). A new dialogs_linux_filedialogs.go guarded by //go:build linux && !android && !server would be the single source of truth and eliminate the risk of future drift.

♻️ Proposed extraction — `dialogs_linux_filedialogs.go`
// new file: v3/pkg/application/dialogs_linux_filedialogs.go
+//go:build linux && !android && !server
+
+package application
+
+type linuxOpenFileDialog struct {
+	dialog *OpenFileDialogStruct
+}
+
+func newOpenFileDialogImpl(d *OpenFileDialogStruct) *linuxOpenFileDialog {
+	return &linuxOpenFileDialog{dialog: d}
+}
+
+func (m *linuxOpenFileDialog) show() (chan string, error) {
+	return runOpenFileDialog(m.dialog)
+}
+
+type linuxSaveFileDialog struct {
+	dialog *SaveFileDialogStruct
+}
+
+func newSaveFileDialogImpl(d *SaveFileDialogStruct) *linuxSaveFileDialog {
+	return &linuxSaveFileDialog{dialog: d}
+}
+
+func (m *linuxSaveFileDialog) show() (chan string, error) {
+	return runSaveFileDialog(m.dialog)
+}

Then remove Lines 61–87 from dialogs_linux.go and the equivalent block from dialogs_linux_gtk4.go.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/pkg/application/dialogs_linux.go` around lines 61 - 87, Create a new file
guarded with the build tag "//go:build linux && !android && !server" (suggested
name dialogs_linux_filedialogs.go) and move the identical types and methods
there: linuxOpenFileDialog, linuxSaveFileDialog, newOpenFileDialogImpl,
newSaveFileDialogImpl and their show() methods; keep the show() implementations
delegating to runOpenFileDialog and runSaveFileDialog, respectively, then remove
the duplicate blocks from dialogs_linux.go and dialogs_linux_gtk4.go so the new
file is the single source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@v3/pkg/application/dialogs_linux.go`:
- Around line 61-87: Create a new file guarded with the build tag "//go:build
linux && !android && !server" (suggested name dialogs_linux_filedialogs.go) and
move the identical types and methods there: linuxOpenFileDialog,
linuxSaveFileDialog, newOpenFileDialogImpl, newSaveFileDialogImpl and their
show() methods; keep the show() implementations delegating to runOpenFileDialog
and runSaveFileDialog, respectively, then remove the duplicate blocks from
dialogs_linux.go and dialogs_linux_gtk4.go so the new file is the single source
of truth.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: af69b210-fad4-4cca-9be0-5e6f54e88cea

📥 Commits

Reviewing files that changed from the base of the PR and between c451385 and 5eb303e.

📒 Files selected for processing (2)
  • v3/pkg/application/dialogs_linux.go
  • v3/pkg/application/dialogs_linux_gtk4.go

GTK3 requires all GTK calls on the main thread; GTK4 is thread-safe.

Add two tiny build-tag-gated files:
  - gtkdispatch_linux.go  (!gtk4): gtkDispatch = InvokeAsync
  - gtkdispatch_linux_gtk4.go (gtk4): gtkDispatch = goroutine

Replace all shared-file InvokeAsync dispatch calls with gtkDispatch
so a single implementation lives in each shared file:
  - dialogs_linux.go (showAboutDialog, linuxDialog.show)
  - webview_window_linux.go (JS/CSS injection on load)
  - systemtray_linux.go (tray menu item click dispatch)

InvokeAsync calls inside gtk3/gtk4-specific functions (linux_cgo.go,
linux_cgo_gtk4.go) are internal to those functions and unchanged.

The GTK4 goroutine approach avoids deadlock: runQuestionDialog already
uses InvokeAsync internally and blocks on a result channel, so calling
it from an outer InvokeAsync (main thread) would stall the GTK loop.

Fixes #5338

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
@leaanthony leaanthony force-pushed the fix/split-gtk3-gtk4-dialogs branch from 26a5231 to 57b1f69 Compare May 5, 2026 20:38
@leaanthony leaanthony added Bug Something isn't working go Pull requests that update Go code Linux v3 labels May 6, 2026
@leaanthony

Copy link
Copy Markdown
Member Author

Investigating build and static verification on Linux (Ubuntu 24.04, GTK 3.24.52 / GTK 4.22.2 / WebKit 2.52.0).

Build results:

  • GTK3 (go build ./pkg/application/...): ✅ PASS
  • GTK4 (go build -tags gtk4 ./pkg/application/...): ✅ PASS (pre-existing X11/GDK deprecation warnings in C code unrelated to this PR)
  • Dialogs example GTK3 (go build ./examples/dialogs/...): ✅ PASS
  • Dialogs example GTK4 (go build -tags gtk4 ./examples/dialogs/...): ✅ PASS

Unit tests: go test ./pkg/application/... — all pass

Static checks:

  • gtkdispatch_linux.go and gtkdispatch_linux_gtk4.go present with correct mutually-exclusive build constraints (!gtk4 / gtk4)
  • dialogs_linux.go contains zero direct InvokeAsync calls — uses gtkDispatch throughout ✅
  • File-open/save dialogs unchanged (they delegate to runOpenFileDialog/runSaveFileDialog which manage their own threading) ✅
  • handlePanic() correctly deferred in the GTK4 goroutine ✅

Pre-existing issues (not introduced by this PR):

  • go vet exits 1 due to unsafe.Pointer warnings in dialogs_linux.go and linux_cgo.go — these originate from the pre-existing pointer(parent) call and are not related to the dispatch change
  • C deprecation warnings for gdk_x11_display_get_xdisplay / gdk_x11_surface_get_xid in linux_cgo_gtk4.c — pre-existing, unrelated to this PR

Not yet verified: Interactive GTK4 dialog runtime test (deadlock absence). Awaiting a live GTK4 session for confirmation.

@leaanthony leaanthony merged commit 165da4c into master May 6, 2026
66 of 78 checks passed
@leaanthony leaanthony deleted the fix/split-gtk3-gtk4-dialogs branch May 6, 2026 12:28
pull Bot pushed a commit to WangNingkai/wails that referenced this pull request May 6, 2026
…plit dialog dispatch between GTK3 and GTK4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Something isn't working go Pull requests that update Go code Linux v3

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[v3] Dialogs are broken on Linux/GTK3

2 participants