wails/v3/pkg/application/application_linux.go
Lea Anthony c4b614cb10
fix: use structured logging for debug/info methods (#4767)
* fix: use structured logging for debug/info methods

The debug() and info() methods were using fmt.Sprintf() which expects
printf-style format directives, but callers were using slog-style
key-value pairs. Changed to pass args directly to Logger.Debug/Info
which properly handles structured logging.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs: add changelog entries for build fixes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: remove temporary debug print statements from mobile merge

Remove emoji debug logs (🔴, 🟢, 🟠, 🔵, 🔥) that were accidentally left in
from the iOS/Android mobile platform support merge. These were development
debugging statements that should not have been included in the final code.

Files cleaned:
- application.go
- application_debug.go
- init_android.go
- init_ios.go
- mainthread_android.go
- mainthread_ios.go
- webview_window.go
- webview_window_ios.go

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs: add changelog entries for debug cleanup and breaking change

- Add breaking change note: production builds are now default
- Add entry for debug print statement removal

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: convert remaining printf-style debug calls to slog-style

Convert three debug() calls that were still using printf-style format
strings to slog-style structured logging (key-value pairs):

- systemtray_windows.go: ShellNotifyIcon show/hide failures
- application_darwin.go: window lookup failure

This addresses CodeRabbit review feedback and ensures consistency
with the refactored debug() method.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: convert all printf-style info() calls to slog-style

Convert remaining info() calls that were using printf-style format
strings to slog-style structured logging (key-value pairs):

- application_ios.go: iOS log messages and HandleJSMessage calls
- webview_window_windows.go: WM_SYSKEYDOWN logging
- application.go: handleWindowMessage and handleWebViewRequest logging

Also removed debug fmt.Printf statements from handleWebViewRequest.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: add build tag to main.m to prevent Go from compiling it on non-iOS platforms

Go's toolchain tries to process .m (Objective-C) files when they're in a
directory with Go files. Adding a //go:build ios tag tells Go to only
process this file when building for iOS, matching how darwin .m files
handle this (e.g., //go:build darwin && !ios).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: remove orphaned wails-mimetype-migration submodule reference

The iOS merge added a submodule reference without a corresponding
.gitmodules file, causing Cloudflare and other CI systems to fail
with "No url found for submodule path" errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: auto-disable DMA-BUF renderer on Wayland with NVIDIA to prevent crashes

WebKitGTK has a known issue with the DMA-BUF renderer on NVIDIA proprietary
drivers running Wayland, causing "Error 71 (Protocol error)" crashes.

This fix automatically detects NVIDIA GPUs (via /sys/module/nvidia) and sets
WEBKIT_DISABLE_DMABUF_RENDERER=1 when running on Wayland.

Also removes leftover debug print statements from mobile platform merge.

See: https://bugs.webkit.org/show_bug.cgi?id=262607

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: add NVIDIA driver info to wails3 doctor on Linux

Shows NVIDIA driver version and srcversion in doctor output to help
diagnose Wayland/NVIDIA compatibility issues.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-12 09:03:11 +11:00

327 lines
8.4 KiB
Go

//go:build linux && !android
package application
/*
#include "gtk/gtk.h"
#include "webkit2/webkit2.h"
static guint get_compiled_gtk_major_version() { return GTK_MAJOR_VERSION; }
static guint get_compiled_gtk_minor_version() { return GTK_MINOR_VERSION; }
static guint get_compiled_gtk_micro_version() { return GTK_MICRO_VERSION; }
static guint get_compiled_webkit_major_version() { return WEBKIT_MAJOR_VERSION; }
static guint get_compiled_webkit_minor_version() { return WEBKIT_MINOR_VERSION; }
static guint get_compiled_webkit_micro_version() { return WEBKIT_MICRO_VERSION; }
*/
import "C"
import (
"fmt"
"os"
"slices"
"strings"
"sync"
"path/filepath"
"github.com/godbus/dbus/v5"
"github.com/wailsapp/wails/v3/internal/operatingsystem"
"github.com/wailsapp/wails/v3/pkg/events"
)
func init() {
// FIXME: This should be handled appropriately in the individual files most likely.
// Set GDK_BACKEND=x11 if currently unset and XDG_SESSION_TYPE is unset, unspecified or x11 to prevent warnings
if os.Getenv("GDK_BACKEND") == "" &&
(os.Getenv("XDG_SESSION_TYPE") == "" || os.Getenv("XDG_SESSION_TYPE") == "unspecified" || os.Getenv("XDG_SESSION_TYPE") == "x11") {
_ = os.Setenv("GDK_BACKEND", "x11")
}
// Disable DMA-BUF renderer on Wayland with NVIDIA to prevent "Error 71 (Protocol error)" crashes.
// This is a known WebKitGTK issue with NVIDIA proprietary drivers on Wayland.
// See: https://bugs.webkit.org/show_bug.cgi?id=262607
if os.Getenv("WEBKIT_DISABLE_DMABUF_RENDERER") == "" &&
os.Getenv("XDG_SESSION_TYPE") == "wayland" &&
isNVIDIAGPU() {
_ = os.Setenv("WEBKIT_DISABLE_DMABUF_RENDERER", "1")
}
}
// isNVIDIAGPU checks if an NVIDIA GPU is present by looking for the nvidia kernel module.
func isNVIDIAGPU() bool {
// Check if nvidia module is loaded (most reliable for proprietary driver)
if _, err := os.Stat("/sys/module/nvidia"); err == nil {
return true
}
return false
}
type linuxApp struct {
application pointer
parent *App
startupActions []func()
// Native -> uint
windowMap map[windowPointer]uint
windowMapLock sync.Mutex
theme string
icon pointer
}
func (a *linuxApp) GetFlags(options Options) map[string]any {
if options.Flags == nil {
options.Flags = make(map[string]any)
}
return options.Flags
}
func getNativeApplication() *linuxApp {
return globalApplication.impl.(*linuxApp)
}
func (a *linuxApp) hide() {
a.hideAllWindows()
}
func (a *linuxApp) show() {
a.showAllWindows()
}
func (a *linuxApp) on(eventID uint) {
// TODO: Test register/unregister events
//C.registerApplicationEvent(l.application, C.uint(eventID))
}
func (a *linuxApp) name() string {
return appName()
}
type rnr struct {
f func()
}
func (r rnr) run() {
r.f()
}
func (a *linuxApp) setApplicationMenu(menu *Menu) {
// FIXME: How do we avoid putting a menu?
if menu == nil {
// Create a default menu
menu = DefaultApplicationMenu()
globalApplication.applicationMenu = menu
}
}
func (a *linuxApp) run() error {
if len(os.Args) == 2 { // Case: program + 1 argument
arg1 := os.Args[1]
// Check if the argument is likely a URL from a custom protocol invocation
if strings.Contains(arg1, "://") {
a.parent.info("Application launched with argument, potentially a URL from custom protocol", "url", arg1)
eventContext := newApplicationEventContext()
eventContext.setURL(arg1)
applicationEvents <- &ApplicationEvent{
Id: uint(events.Common.ApplicationLaunchedWithUrl),
ctx: eventContext,
}
} else {
// Check if the argument matches any file associations
if a.parent.options.FileAssociations != nil {
ext := filepath.Ext(arg1)
if slices.Contains(a.parent.options.FileAssociations, ext) {
a.parent.info("File opened via file association", "file", arg1, "extension", ext)
eventContext := newApplicationEventContext()
eventContext.setOpenedWithFile(arg1)
applicationEvents <- &ApplicationEvent{
Id: uint(events.Common.ApplicationOpenedWithFile),
ctx: eventContext,
}
return nil
}
}
a.parent.info("Application launched with single argument (not a URL), potential file open?", "arg", arg1)
}
} else if len(os.Args) > 2 {
// Log if multiple arguments are passed
a.parent.info("Application launched with multiple arguments", "args", os.Args[1:])
}
a.parent.Event.OnApplicationEvent(events.Linux.ApplicationStartup, func(evt *ApplicationEvent) {
// TODO: What should happen here?
})
a.setupCommonEvents()
a.monitorThemeChanges()
return appRun(a.application)
}
func (a *linuxApp) unregisterWindow(w windowPointer) {
a.windowMapLock.Lock()
delete(a.windowMap, w)
a.windowMapLock.Unlock()
// If this was the last window...
if len(a.windowMap) == 0 && !a.parent.options.Linux.DisableQuitOnLastWindowClosed {
a.destroy()
}
}
func (a *linuxApp) destroy() {
if !globalApplication.shouldQuit() {
return
}
globalApplication.cleanup()
appDestroy(a.application)
}
func (a *linuxApp) isOnMainThread() bool {
return isOnMainThread()
}
// register our window to our parent mapping
func (a *linuxApp) registerWindow(window pointer, id uint) {
a.windowMapLock.Lock()
a.windowMap[windowPointer(window)] = id
a.windowMapLock.Unlock()
}
func (a *linuxApp) isDarkMode() bool {
return strings.Contains(a.theme, "dark")
}
func (a *linuxApp) getAccentColor() string {
// Linux doesn't have a unified system accent color API
// Return a default blue color
return "rgb(0,122,255)"
}
func (a *linuxApp) monitorThemeChanges() {
go func() {
defer handlePanic()
conn, err := dbus.ConnectSessionBus()
if err != nil {
a.parent.info(
"[WARNING] Failed to connect to session bus; monitoring for theme changes will not function:",
err,
)
return
}
defer conn.Close()
if err = conn.AddMatchSignal(
dbus.WithMatchObjectPath("/org/freedesktop/portal/desktop"),
); err != nil {
panic(err)
}
c := make(chan *dbus.Signal, 10)
conn.Signal(c)
getTheme := func(body []interface{}) (string, bool) {
if len(body) < 2 {
return "", false
}
if entry, ok := body[0].(string); !ok || entry != "org.gnome.desktop.interface" {
return "", false
}
if entry, ok := body[1].(string); ok && entry == "color-scheme" {
return body[2].(dbus.Variant).Value().(string), true
}
return "", false
}
for v := range c {
theme, ok := getTheme(v.Body)
if !ok {
continue
}
if theme != a.theme {
a.theme = theme
event := newApplicationEvent(events.Linux.SystemThemeChanged)
event.Context().setIsDarkMode(a.isDarkMode())
applicationEvents <- event
}
}
}()
}
func newPlatformApp(parent *App) *linuxApp {
name := strings.ToLower(strings.Replace(parent.options.Name, " ", "", -1))
if name == "" {
name = "undefined"
}
app := &linuxApp{
parent: parent,
application: appNew(name),
windowMap: map[windowPointer]uint{},
}
if parent.options.Linux.ProgramName != "" {
setProgramName(parent.options.Linux.ProgramName)
}
return app
}
// logPlatformInfo logs the platform information to the console
func (a *App) logPlatformInfo() {
info, err := operatingsystem.Info()
if err != nil {
a.error("error getting OS info: %w", err)
return
}
wkVersion := operatingsystem.GetWebkitVersion()
platformInfo := info.AsLogSlice()
platformInfo = append(platformInfo, "Webkit2Gtk", wkVersion)
a.info("Platform Info:", platformInfo...)
}
//export processWindowEvent
func processWindowEvent(windowID C.uint, eventID C.uint) {
windowEvents <- &windowEvent{
WindowID: uint(windowID),
EventID: uint(eventID),
}
}
func buildVersionString(major, minor, micro C.uint) string {
return fmt.Sprintf("%d.%d.%d", uint(major), uint(minor), uint(micro))
}
func (a *App) platformEnvironment() map[string]any {
result := map[string]any{}
result["gtk3-compiled"] = buildVersionString(
C.get_compiled_gtk_major_version(),
C.get_compiled_gtk_minor_version(),
C.get_compiled_gtk_micro_version(),
)
result["gtk3-runtime"] = buildVersionString(
C.gtk_get_major_version(),
C.gtk_get_minor_version(),
C.gtk_get_micro_version(),
)
result["webkit2gtk-compiled"] = buildVersionString(
C.get_compiled_webkit_major_version(),
C.get_compiled_webkit_minor_version(),
C.get_compiled_webkit_micro_version(),
)
result["webkit2gtk-runtime"] = buildVersionString(
C.webkit_get_major_version(),
C.webkit_get_minor_version(),
C.webkit_get_micro_version(),
)
return result
}
func fatalHandler(errFunc func(error)) {
// Stub for windows function
return
}