diff --git a/v2/internal/frontend/desktop/linux/frontend.go b/v2/internal/frontend/desktop/linux/frontend.go index 2942a112e..d3c886018 100644 --- a/v2/internal/frontend/desktop/linux/frontend.go +++ b/v2/internal/frontend/desktop/linux/frontend.go @@ -501,6 +501,8 @@ func (f *Frontend) processMessage(message string) { } go func() { + defer handlePanic(f.frontendOptions.PanicHandler, f.logger) + result, err := f.dispatcher.ProcessMessage(message, f) if err != nil { f.logger.Error(err.Error()) diff --git a/v2/internal/frontend/desktop/linux/panic_handler.go b/v2/internal/frontend/desktop/linux/panic_handler.go new file mode 100644 index 000000000..a4c301268 --- /dev/null +++ b/v2/internal/frontend/desktop/linux/panic_handler.go @@ -0,0 +1,100 @@ +//go:build linux +// +build linux + +package linux + +import ( + "fmt" + "runtime" + "runtime/debug" + "strings" + "time" + + "github.com/wailsapp/wails/v2/pkg/options" +) + +func getStackTrace(skipStart int, skipEnd int) string { + // Get all program counters first + pc := make([]uintptr, 32) + n := runtime.Callers(skipStart+1, pc) + if n == 0 { + return "" + } + + pc = pc[:n] + frames := runtime.CallersFrames(pc) + + // Collect all frames first + var allFrames []runtime.Frame + for { + frame, more := frames.Next() + allFrames = append(allFrames, frame) + if !more { + break + } + } + + // Remove frames from the end + if len(allFrames) > skipEnd { + allFrames = allFrames[:len(allFrames)-skipEnd] + } + + // Build the output string + var builder strings.Builder + for _, frame := range allFrames { + fmt.Fprintf(&builder, "%s\n\tat %s:%d\n", + frame.Function, frame.File, frame.Line) + } + return builder.String() +} + +type handlePanicOptions struct { + skipEnd int +} + +func newPanicDetails(err error, trace string) *options.PanicDetails { + return &options.PanicDetails{ + Error: err, + Time: time.Now(), + StackTrace: trace, + FullStackTrace: string(debug.Stack()), + } +} + +// handlePanic recovers from panics and processes them through the configured handler. +// Returns true if a panic was recovered. +func handlePanic(handler options.PanicHandler, logger interface{ Error(string, ...interface{}) }, opts ...handlePanicOptions) bool { + // Try to recover + e := recover() + if e == nil { + return false + } + + // Get the error + err, ok := e.(error) + if !ok { + err = fmt.Errorf("%v", e) + } + + // Get the stack trace + var stackTrace string + skipEnd := 0 + if len(opts) > 0 { + skipEnd = opts[0].skipEnd + } + stackTrace = getStackTrace(3, skipEnd) + + panicDetails := newPanicDetails(err, stackTrace) + + // Use custom handler if provided + if handler != nil { + handler(panicDetails) + return true + } + + // Default behavior: log the panic + if logger != nil { + logger.Error("panic error: %v\n%s", panicDetails.Error, panicDetails.StackTrace) + } + return true +} diff --git a/v2/pkg/options/options.go b/v2/pkg/options/options.go index 0f62d5e4b..6e6f3e8c4 100644 --- a/v2/pkg/options/options.go +++ b/v2/pkg/options/options.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "runtime" + "time" "github.com/wailsapp/wails/v2/pkg/options/assetserver" "github.com/wailsapp/wails/v2/pkg/options/linux" @@ -69,6 +70,12 @@ type App struct { // ErrorFormatter overrides the formatting of errors returned by backend methods ErrorFormatter ErrorFormatter + // PanicHandler is called when a panic occurs in a bound method. + // If not set, the panic will be logged and the application will continue. + // This is particularly useful on Linux where panics in cgo callbacks + // can cause signal handler issues. + PanicHandler PanicHandler + // CSS property to test for draggable elements. Default "--wails-draggable" CSSDragProperty string @@ -108,6 +115,18 @@ type App struct { type ErrorFormatter func(error) any +// PanicDetails contains information about a panic that occurred in a bound method +type PanicDetails struct { + StackTrace string + Error error + Time time.Time + FullStackTrace string +} + +// PanicHandler is a function that handles panics in bound methods. +// If not set, panics will be logged to the application logger. +type PanicHandler func(*PanicDetails) + type RGBA struct { R uint8 `json:"r"` G uint8 `json:"g"`