Fix hacked GTK3/GLFW crash by vendoring a patched sqweek/dialog

The built hacked binary segfaults on Linux the moment a native GTK3
dialog (file/folder browse) opens while a GLFW/OpenGL context is live:
sqweek/dialog's Linux backend never calls XInitThreads() before
gtk_init(), and GLFW's own X11 backend installs a process-wide Xlib
error handler that collides with GTK's.

Vendor a pinned copy of github.com/sqweek/dialog (New BSD) into
modding/sqweek-dialog/ so it can be patched like every other vendored
source here: sqweek-dialog-patches/01-xinitthreads.patch adds the
missing XInitThreads() call, and hacked-patches/01-use-patched-dialog.patch
adds a go.mod replace directive pointing hacked's own sqweek/dialog
dependency at that local copy. build-hacked.sh now stages both hacked/
and sqweek-dialog/ into a scratch modding/build/ directory and applies
their patch series before compiling, rather than building hacked/ in
place; .gitignore picks up that new scratch directory.

Also expand modding/README.md: number its existing sections and add a
worked "Example: Editing a message" walkthrough covering Static World
Data, the Messages window's clipboard-only text editing, saving the
Mod Location, and the absolute-path requirement for run.sh's mod-folder
argument.
This commit is contained in:
ml
2026-09-26 10:37:01 +02:00
parent c91080aba7
commit e410d82680
20 changed files with 994 additions and 5 deletions
@@ -0,0 +1,20 @@
package main
import (
"fmt"
"github.com/sqweek/dialog"
)
func main() {
/* Note that spawning a dialog from a non-graphical app like this doesn't
** quite work properly in OSX. The dialog appears fine, and mouse
** interaction works but keypresses go straight through the dialog.
** I'm guessing it has something to do with not having a main loop? */
dialog.Message("%s", "Please select a file").Title("Hello world!").Info()
file, err := dialog.File().Title("Save As").Filter("All Files", "*").Save()
fmt.Println(file)
fmt.Println("Error:", err)
dialog.Message("You chose file: %s", file).Title("Goodbye world!").Error()
dialog.Directory().Title("Now find a dir").Browse()
}
+73
View File
@@ -0,0 +1,73 @@
package main
import (
"fmt"
"image"
"image/color"
"image/draw"
"github.com/skelterjohn/go.wde" // nolint: goimports
_ "github.com/skelterjohn/go.wde/init"
"github.com/sqweek/dialog"
)
var loadR, saveR image.Rectangle
func events(events <-chan interface{}) {
for ei := range events {
switch e := ei.(type) {
case wde.MouseUpEvent:
switch e.Which { // nolint: gocritic
case wde.LeftButton:
var f string
var err error
/* launching dialogs within the event loop like this has
** a serious problem in practice. On Linux/Windows the
** dialog is not modal, which means events on the main
** window still get queued up.
**
** But the dialog functions are synchronous, so we don't
** process any of these events until the dialog closes. End
** result is if the user clicks multiple times they end up
** facing multiple dialogs, one after the other.
**
** For this reason, it is recommended to launch dialogs
** in a separate goroutine, and arrange modality via some
** other mechanism (if desired). */
if e.Where.In(loadR) {
f, err = dialog.File().Title("LOL").Filter("Image", "png").Filter("Audio", "mp3").Filter("All files", "*").Load()
} else {
f, err = dialog.File().Title("Hilarious").Save()
}
fmt.Println(f)
fmt.Println("Error:", err)
}
case wde.KeyTypedEvent:
switch {
case e.Glyph == "a":
fmt.Println(dialog.Message("Is this sentence false?").YesNo())
case e.Glyph == "b":
fmt.Println(dialog.Message("R U OK?").Title("Just checking").YesNo())
case e.Glyph == "c":
dialog.Message("Operation failed").Error()
}
case wde.CloseEvent:
wde.Stop()
return
}
}
}
func main() {
go func() {
w, _ := wde.NewWindow(300, 300)
loadR = image.Rect(0, 0, 300, 150)
saveR = image.Rect(0, 150, 300, 300)
w.Show()
draw.Draw(w.Screen(), loadR, &image.Uniform{color.RGBA{0, 0xff, 0, 0xff}}, image.ZP, draw.Src)
draw.Draw(w.Screen(), saveR, &image.Uniform{color.RGBA{0xff, 0, 0, 0xff}}, image.ZP, draw.Src)
w.FlushImage()
go events(w.EventChan())
}()
wde.Run()
}