Skip to content

feat(linux): CGO-free Linux backend via purego (GTK4 + WebKitGTK 6.0)#5715

Draft
taliesin-ai wants to merge 2 commits into
masterfrom
worktree-linux-purego
Draft

feat(linux): CGO-free Linux backend via purego (GTK4 + WebKitGTK 6.0)#5715
taliesin-ai wants to merge 2 commits into
masterfrom
worktree-linux-purego

Conversation

@taliesin-ai

Copy link
Copy Markdown
Collaborator

What

Revives CGO-free Linux support (removed as unmaintained in #5661), rebuilt from scratch against the current GTK4 + WebKitGTK 6.0 cgo backend. Build with:

CGO_ENABLED=0 go build -tags purego ./...

The purego tag selects the new backend; the default cgo backend (and the gtk3 variant) are unchanged and mutually exclusive via !purego tags. Cross-compiling Linux GUI apps from any host now works, since no C toolchain is involved.

Design

  • linux_purego_lib.go — dlopen layer with per-distro soname fallbacks and one aggregate, actionable error when libraries/symbols are missing (package names per distro instead of a linker one-liner). Full typed function registry for GLib/GObject/Gio/GTK4/WebKitGTK-6.0/JSC/libsoup.
  • Runtime capability guards replace compile-time gates: version-dependent symbols resolve optionally and are nil-checked — e.g. gdk_monitor_get_scale (GTK 4.14+) falls back to gdk_monitor_get_scale_factor on older GTK4, where the cgo build won't even start; GDK X11 symbols are optional so Wayland-only GTK builds work.
  • linux_purego_callbacks.go — pure-Go port of linux_cgo.c: a fixed set of purego.NewCallback trampolines (never per-window/item), GAction menus, GTK4 dialogs, DND, clipboard, X11 helpers via dlsym(RTLD_DEFAULT), and the [v3] Freezes with open inspector on linux. #5527 SA_ONSTACK signal fix (incl. the SIGUSR1/JSC exemption) via libc sigaction.
  • internal/assetserver/webview/*_linux_purego.go — preserves the Linux: repeated SIGABRT/SIGSEGV in WebKitGTK/JSC main loop on v3.0.0-alpha2.103 #5631/fix(v3/linux): confine remaining WebKit request ops to the GTK main thread (#5631) #5668 main-thread confinement design exactly (atomic enabled-check + schedule, inline-after-shutdown).
  • The dbus theme monitor, portal global shortcuts, permissions helpers and the system tray were already pure Go and are now shared via (cgo || purego) tags. X11 global shortcuts (XGrabKey) ported against dlopen'd libX11.
  • Not bug-for-bug: defects found in the cgo backend during translation are fixed in the port and catalogued in v3/pkg/application/BUGS_FOUND.md (invalid free of a GLib-owned string, NULL GList deref, clipboard reentrancy, unsynchronised menu maps, >100-file dialog deadlock, message-dialog zombie/UAF, vestigial import "C" that made the tray cgo-only). Design notes: v3/pkg/application/PUREGO_LINUX.md.

Verified

  • GOOS=linux GOARCH=amd64|arm64 CGO_ENABLED=0 go build -tags purego ./... (also purego,production / purego,devtools), vet clean apart from the standard purego unsafeptr idiom notices.
  • On Ubuntu 26.04 (GTK 4.22.2, WebKitGTK 2.52.3): examples/window built CGO-free runs on a real X server — native window + GTK4 menu bar render, popover menus with registered accelerators work, a menu action creates a second window, the wails:// scheme serves assets, and the JS→Go bridge round-trips (clicking the page's setPos moved the native window via XMoveWindow, verified with xdotool). Main-window close exits 0.
  • Regression checks on the same box: default cgo build + tests pass, gtk3 cgo variant builds, purego go test ./pkg/application passes.
  • Known environmental issue documented in BUGS_FOUND.md: closing a child window on DRI-less Xvfb crashes with an X BadDrawable — reproduced byte-identically with the cgo binary, so it's upstream WebKitGTK software-rendering teardown, not a port regression.

🤖 Generated with Claude Code

Revives Linux purego support (removed in #5661) rebuilt from scratch against
the current GTK4/webkitgtk-6.0 cgo backend, selected with -tags purego and
CGO_ENABLED=0. Libraries are dlopen'ed at runtime with per-distro soname
fallbacks and actionable install-hint errors; version-dependent symbols are
resolved optionally and guarded at runtime (e.g. gdk_monitor_get_scale with
scale-factor fallback on GTK < 4.14).

- linux_purego_lib.go: dlopen layer + full C function registry
- linux_purego_callbacks.go: pure-Go port of linux_cgo.c (signal
  trampolines, GAction menus, GTK4 dialogs, DND, clipboard, X11 helpers,
  SA_ONSTACK signal fix incl. the #5527 SIGUSR1 exemption)
- linux_purego.go: port of the linux_cgo.go shim surface
- application_linux_purego.go: GApplication lifecycle twin
- global_shortcut_linux_x11_purego.go: XGrabKey shortcuts via dlopen'd libX11
- internal/assetserver/webview/*_linux_purego.go: URI-scheme request
  plumbing preserving the #5631/#5668 main-thread confinement
- internal/operatingsystem/webkit_linux_purego.go: doctor version info
- dbus theme monitor, portal shortcuts, permissions and the (previously
  accidentally cgo-only) system tray are now shared via (cgo || purego) tags

Known cgo bugs were fixed rather than ported and are catalogued in
pkg/application/BUGS_FOUND.md; design notes in pkg/application/PUREGO_LINUX.md.
…l); document runtime validation

g_strstrip/g_strsplit/g_strfreev were registered but unused (pattern
splitting is done in Go) and g_strstrip is a macro over g_strchug/g_strchomp,
so resolving it failed on every system. Caught by the loader's own
missing-symbol report on first run — the aggregate error path worked as
designed.

Also records the runtime validation results (Ubuntu 26.04, GTK 4.22.2,
WebKitGTK 2.52.3, Xvfb): window + menus + accelerators render, JS→Go bridge
round-trips (setPos moved the window via XMoveWindow), child windows create
via GMenu actions, main-window close exits cleanly; and documents the
child-window-close BadDrawable crash on DRI-less Xvfb as present identically
in the cgo backend (upstream WebKitGTK, not a port regression).
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c5910ebc-f08f-40d9-9059-5b9f5473fe1f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-linux-purego

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.

@taliesin-ai

Copy link
Copy Markdown
Collaborator Author

Follow-up: the 8 cgo-backend defects catalogued in BUGS_FOUND.md are now fixed in the cgo backend itself in #5718.

leaanthony added a commit that referenced this pull request Jul 3, 2026
…rego port (#5718)

The purego Linux port (#5715) translated linux_cgo.go/linux_cgo.c line by
line and surfaced the following defects, catalogued in that PR's
BUGS_FOUND.md. This applies the same fixes to the cgo backend:

1. appName() freed the GLib-owned string returned by
   g_get_application_name() (undefined behaviour; GLib returns the same
   pointer on the next call) and could pass NULL to GoString. Copy, never
   free, nil-check.
2. getWindows() dereferenced the GList head before the nil check, crashing
   on App.Hide()/Show()/isVisible() when no windows exist. Standard
   nil-checked loop.
3. clipboard_get_text_sync() parked its result in static globals while
   spinning a nested g_main_context_iteration loop; a second read
   dispatched by that loop clobbered the first (wrong text or infinite
   spin). Per-call stack-allocated state.
4. gtkSignalToMenuItem was written from menu-building goroutines and read
   on the GTK main thread with no synchronisation (fatal concurrent map
   access). RWMutex.
5. menuItemActions/menuItemActionCounter likewise unguarded between
   generateActionName (menu construction) and
   setChecked/setDisabled/accelerator reads from API goroutines. RWMutex +
   lookupActionName helper.
6. fileDialogCallback ran on the GTK main thread and sent each selected
   path into a channel with a fixed buffer of 100; selecting >100 files
   blocked the main loop permanently. Copy paths, deliver from a
   goroutine.
7. systemtray_linux.go carried a vestigial `import "C"` with zero C
   usages, silently making the pure-Go dbus tray cgo-only.
8. on_message_dialog_close returned TRUE from close-request after freeing
   MessageDialogData: GTK4 treats TRUE as "don't close", leaving a zombie
   dialog whose buttons pointed at freed memory. Return FALSE so the
   default handler destroys the window.

Verified on Ubuntu 26.04 (GTK 4.22.2 / WebKitGTK 2.52.3): go build ./...,
go test ./pkg/application, go vet, gtk3-tagged build, and a headless run
of examples/window all pass.

Co-authored-by: taliesin-ai <lea.anthony@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants