diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 712c4de7f..4ad726d72 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -17,12 +17,18 @@ After processing, the content will be moved to the main changelog and this file ## Added +- Add NVIDIA driver version info to `wails3 doctor` output on Linux by @leaanthony ## Changed +- **BREAKING**: Production builds are now the default. To create dev builds, set `DEV=true` in your Taskfiles. Generate a new project for examples by @leaanthony ## Fixed - Fix default `config.yml` would run `wails3 dev` with a production build by @mbaklor +- Fix iOS service stubs causing build failures due to non-existent package import by @leaanthony +- Fix structured logging in debug/info methods causing "no formatting directives" errors by @leaanthony +- Remove temporary debug print statements accidentally included from mobile platform merge by @leaanthony +- Fix WebKitGTK crash on Wayland with NVIDIA GPUs (Error 71 Protocol error) by auto-disabling DMA-BUF renderer by @leaanthony ## Deprecated diff --git a/v3/internal/assetserver/assetserver_webview.go b/v3/internal/assetserver/assetserver_webview.go index 656020042..21c01ede2 100644 --- a/v3/internal/assetserver/assetserver_webview.go +++ b/v3/internal/assetserver/assetserver_webview.go @@ -133,11 +133,6 @@ func (a *AssetServer) processWebViewRequestInternal(r webview.Request) { req.Header = header - // Debug iOS headers - if strings.Contains(uri, "wails://") { - fmt.Printf("🔍 iOS Request: URI=%s Headers=%v\n", uri, header) - } - if req.RemoteAddr == "" { // 192.0.2.0/24 is "TEST-NET" in RFC 5737 req.RemoteAddr = "192.0.2.1:1234" @@ -160,7 +155,6 @@ func (a *AssetServer) processWebViewRequestInternal(r webview.Request) { // iOS uses "localhost" while other platforms might use different hosts // Skip host check for iOS requests from wails:// scheme if expectedHost := a.ExpectedWebViewHost; expectedHost != "" && expectedHost != req.Host && !strings.HasPrefix(uri, "wails://") { - fmt.Printf("🔴 HOST MISMATCH: Expected='%s' Got='%s' URI=%s\n", expectedHost, req.Host, uri) a.webviewRequestErrorHandler(uri, rw, fmt.Errorf("expected host '%s' in request, but was '%s'", expectedHost, req.Host)) return } diff --git a/v3/internal/commands/build_assets/ios/main.m b/v3/internal/commands/build_assets/ios/main.m index 0f2e7e8e1..366767a62 100644 --- a/v3/internal/commands/build_assets/ios/main.m +++ b/v3/internal/commands/build_assets/ios/main.m @@ -1,3 +1,4 @@ +//go:build ios // Minimal bootstrap: delegate comes from Go archive (WailsAppDelegate) #import #include diff --git a/v3/internal/doctor/doctor_linux.go b/v3/internal/doctor/doctor_linux.go index a3b0e4ac5..e72528c4b 100644 --- a/v3/internal/doctor/doctor_linux.go +++ b/v3/internal/doctor/doctor_linux.go @@ -3,15 +3,38 @@ package doctor import ( + "os" + "strings" + "github.com/wailsapp/wails/v3/internal/doctor/packagemanager" "github.com/wailsapp/wails/v3/internal/operatingsystem" ) func getInfo() (map[string]string, bool) { result := make(map[string]string) + + // Check for NVIDIA driver + result["NVIDIA Driver"] = getNvidiaDriverInfo() + return result, true } +func getNvidiaDriverInfo() string { + version, err := os.ReadFile("/sys/module/nvidia/version") + if err != nil { + return "N/A" + } + + versionStr := strings.TrimSpace(string(version)) + + srcVersion, err := os.ReadFile("/sys/module/nvidia/srcversion") + if err != nil { + return versionStr + } + + return versionStr + " (" + strings.TrimSpace(string(srcVersion)) + ")" +} + func checkPlatformDependencies(result map[string]string, ok *bool) { info, _ := operatingsystem.Info() diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go index 1f1d2b4f3..ec1320b18 100644 --- a/v3/pkg/application/application.go +++ b/v3/pkg/application/application.go @@ -472,7 +472,6 @@ func (a *App) handleFatalError(err error) { } func (a *App) init() { - fmt.Println("🟠 [application.go] START App.init()") a.ctx, a.cancel = context.WithCancel(context.Background()) a.applicationEventHooks = make(map[uint][]*eventHook) a.applicationEventListeners = make(map[uint][]*EventListener) @@ -485,7 +484,6 @@ func (a *App) init() { a.wailsEventListeners = make([]WailsEventListener, 0) // Initialize managers - fmt.Println("🟠 [application.go] Initializing managers...") a.Window = newWindowManager(a) a.ContextMenu = newContextMenuManager(a) a.KeyBinding = newKeyBindingManager(a) @@ -497,7 +495,6 @@ func (a *App) init() { a.Screen = newScreenManager(a) a.Clipboard = newClipboardManager(a) a.SystemTray = newSystemTrayManager(a) - fmt.Println("🟠 [application.go] END App.init()") } func (a *App) Capabilities() capabilities.Capabilities { @@ -519,31 +516,21 @@ func (a *App) GetPID() int { } func (a *App) info(message string, args ...any) { - if a.Logger != nil { - go func() { - defer handlePanic() - // Avoid slog BADKEY by formatting printf-style messages ourselves - if len(args) > 0 { - a.Logger.Info(fmt.Sprintf(message, args...)) - } else { - a.Logger.Info(message) - } - }() - } + if a.Logger != nil { + go func() { + defer handlePanic() + a.Logger.Info(message, args...) + }() + } } func (a *App) debug(message string, args ...any) { - if a.Logger != nil { - go func() { - defer handlePanic() - // Avoid slog BADKEY by formatting printf-style messages ourselves - if len(args) > 0 { - a.Logger.Debug(fmt.Sprintf(message, args...)) - } else { - a.Logger.Debug(message) - } - }() - } + if a.Logger != nil { + go func() { + defer handlePanic() + a.Logger.Debug(message, args...) + }() + } } func (a *App) fatal(message string, args ...any) { @@ -560,12 +547,10 @@ func (a *App) error(message string, args ...any) { } func (a *App) Run() error { - fmt.Println("🟠 [application.go] START App.Run()") a.runLock.Lock() // Prevent double invocations. if a.starting || a.running { a.runLock.Unlock() - fmt.Println("🟠 [application.go] App already starting/running") return errors.New("application is running or a previous run has failed") } // Block further service registrations. @@ -576,17 +561,12 @@ func (a *App) Run() error { defer a.cancel() // Call post-create hooks - fmt.Println("🟠 [application.go] About to call preRun()") err := a.preRun() if err != nil { - fmt.Printf("🟠 [application.go] preRun() failed: %v\n", err) return err } - fmt.Println("🟠 [application.go] preRun() completed") - fmt.Println("🟠 [application.go] About to call newPlatformApp()") a.impl = newPlatformApp(a) - fmt.Println("🟠 [application.go] newPlatformApp() completed") // Ensure services are shut down in case of failures. defer a.shutdownServices() @@ -770,7 +750,7 @@ func (a *App) handleWindowMessage(event *windowMessage) { } a.windowsLock.RUnlock() - a.info("handleWindowMessage: Looking for window ID %d, available IDs: %v", event.windowId, ids) + a.info("handleWindowMessage: Looking for window", "windowId", event.windowId, "availableIDs", ids) if !ok { a.warning("WebviewWindow #%d not found", event.windowId) @@ -778,7 +758,7 @@ func (a *App) handleWindowMessage(event *windowMessage) { } // Check if the message starts with "wails:" if strings.HasPrefix(event.message, "wails:") { - a.info("handleWindowMessage: Processing wails message: %s", event.message) + a.info("handleWindowMessage: Processing wails message", "message", event.message) window.HandleMessage(event.message) } else { if a.options.RawMessageHandler != nil { @@ -791,12 +771,10 @@ func (a *App) handleWebViewRequest(request *webViewAssetRequest) { defer handlePanic() // Log that we're processing the request url, _ := request.Request.URL() - a.info(" handleWebViewRequest: Processing request for URL: %s", url) - fmt.Printf(" handleWebViewRequest: About to call ServeWebViewRequest for: %s\n", url) + a.info("handleWebViewRequest: Processing request", "url", url) // IMPORTANT: pass the wrapper request so our injected headers (x-wails-window-id/name) are used a.assets.ServeWebViewRequest(request) - a.info(" handleWebViewRequest: Request processing complete for: %s", url) - fmt.Printf(" handleWebViewRequest: Request complete for: %s\n", url) + a.info("handleWebViewRequest: Request processing complete", "url", url) } func (a *App) handleWindowEvent(event *windowEvent) { @@ -910,10 +888,8 @@ func SaveFileDialog() *SaveFileDialogStruct { } func (a *App) dispatchOnMainThread(fn func()) { - fmt.Println("🟠 [application.go] dispatchOnMainThread() called") // If we are on the main thread, just call the function if a.impl.isOnMainThread() { - fmt.Println("🟠 [application.go] Already on main thread, executing directly") fn() return } @@ -922,7 +898,6 @@ func (a *App) dispatchOnMainThread(fn func()) { id := generateFunctionStoreID() mainThreadFunctionStore[id] = fn mainThreadFunctionStoreLock.Unlock() - fmt.Printf("🟠 [application.go] Dispatching function with ID %d to main thread\n", id) // Call platform specific dispatch function a.impl.dispatchOnMainThread(id) } diff --git a/v3/pkg/application/application_darwin.go b/v3/pkg/application/application_darwin.go index b4c3955f8..b70f98870 100644 --- a/v3/pkg/application/application_darwin.go +++ b/v3/pkg/application/application_darwin.go @@ -363,7 +363,7 @@ func processMessage(windowID C.uint, message *C.char, origin *C.char, isMainFram func processURLRequest(windowID C.uint, wkUrlSchemeTask unsafe.Pointer) { window, ok := globalApplication.Window.GetByID(uint(windowID)) if !ok || window == nil { - globalApplication.debug("could not find window with id: %d", windowID) + globalApplication.debug("could not find window with id", "windowID", windowID) return } diff --git a/v3/pkg/application/application_debug.go b/v3/pkg/application/application_debug.go index 28657aedf..bc850c3e8 100644 --- a/v3/pkg/application/application_debug.go +++ b/v3/pkg/application/application_debug.go @@ -3,7 +3,6 @@ package application import ( - "fmt" "github.com/go-git/go-git/v5" "github.com/samber/lo" "github.com/wailsapp/wails/v3/internal/version" @@ -30,14 +29,11 @@ func init() { // We use this to patch the application to production mode. func newApplication(options Options) *App { - fmt.Println("🟣 [application_debug.go] START newApplication()") result := &App{ isDebugMode: true, options: options, } - fmt.Println("🟣 [application_debug.go] About to call result.init()") result.init() - fmt.Println("🟣 [application_debug.go] END newApplication() - App created") return result } diff --git a/v3/pkg/application/application_ios.go b/v3/pkg/application/application_ios.go index b683c3d43..8965f2afb 100644 --- a/v3/pkg/application/application_ios.go +++ b/v3/pkg/application/application_ios.go @@ -98,7 +98,7 @@ func LogInfo(source *C.char, message *C.char) { iosConsoleLogf("info", "[iOS-%s] %s", goSource, goMessage) if globalApplication != nil && globalApplication.Logger != nil { - globalApplication.info("[iOS-%s] %s", goSource, goMessage) + globalApplication.info("iOS log", "source", goSource, "message", goMessage) } } @@ -375,7 +375,7 @@ func HandleJSMessage(windowID C.uint, message *C.char) { } if globalApplication != nil { - globalApplication.info("[HandleJSMessage] Received '%s' from client", name) + globalApplication.info("HandleJSMessage received from client", "name", name) } windowMessageBuffer <- &windowMessage{ windowId: uint(windowID), @@ -386,7 +386,7 @@ func HandleJSMessage(windowID C.uint, message *C.char) { // Fallback for structured payloads without a "name" field if name, ok := msgData["message"].(string); ok && name != "" { if globalApplication != nil { - globalApplication.info("[HandleJSMessage] Received raw message field '%s' from client", name) + globalApplication.info("HandleJSMessage received raw message field from client", "name", name) } windowMessageBuffer <- &windowMessage{ windowId: uint(windowID), @@ -404,7 +404,7 @@ func HandleJSMessage(windowID C.uint, message *C.char) { // If not JSON or JSON without name/message, treat the entire payload as a string event if msg != "" { if globalApplication != nil { - globalApplication.info("[HandleJSMessage] Received raw '%s' from client", msg) + globalApplication.info("HandleJSMessage received raw message from client", "message", msg) } windowMessageBuffer <- &windowMessage{ windowId: uint(windowID), diff --git a/v3/pkg/application/application_linux.go b/v3/pkg/application/application_linux.go index 0d204f184..d0a32d1c5 100644 --- a/v3/pkg/application/application_linux.go +++ b/v3/pkg/application/application_linux.go @@ -34,6 +34,24 @@ func init() { (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 { diff --git a/v3/pkg/application/init_android.go b/v3/pkg/application/init_android.go index ac8b336bd..6c50e82c6 100644 --- a/v3/pkg/application/init_android.go +++ b/v3/pkg/application/init_android.go @@ -2,12 +2,8 @@ package application -import "fmt" - func init() { - fmt.Println("🤖 [init_android.go] START init()") // On Android, we don't call runtime.LockOSThread() // The Android runtime handles thread management via JNI // and calling LockOSThread can interfere with the JNI environment - fmt.Println("🤖 [init_android.go] END init() - no LockOSThread on Android") } diff --git a/v3/pkg/application/init_ios.go b/v3/pkg/application/init_ios.go index 30a9b3108..63c054294 100644 --- a/v3/pkg/application/init_ios.go +++ b/v3/pkg/application/init_ios.go @@ -2,12 +2,8 @@ package application -import "fmt" - func init() { - fmt.Println("🔵 [init_ios.go] START init()") // On iOS, we don't call runtime.LockOSThread() // The iOS runtime handles thread management differently // and calling LockOSThread can interfere with signal handling - fmt.Println("🔵 [init_ios.go] END init() - no LockOSThread on iOS") -} \ No newline at end of file +} diff --git a/v3/pkg/application/mainthread_android.go b/v3/pkg/application/mainthread_android.go index c3f69a9ad..396bffba7 100644 --- a/v3/pkg/application/mainthread_android.go +++ b/v3/pkg/application/mainthread_android.go @@ -2,8 +2,6 @@ package application -import "fmt" - // isOnMainThread returns whether the current goroutine is on the main thread func (a *androidApp) isOnMainThread() bool { // On Android, Go runs in its own thread separate from the UI thread @@ -13,14 +11,12 @@ func (a *androidApp) isOnMainThread() bool { // dispatchOnMainThread executes a function on the Android main/UI thread func (a *androidApp) dispatchOnMainThread(id uint) { - fmt.Printf("🤖 [mainthread_android.go] dispatchOnMainThread(id=%d)\n", id) // TODO: Implement via JNI callback to Activity.runOnUiThread() // For now, execute the callback directly mainThreadFunctionStoreLock.RLock() fn := mainThreadFunctionStore[id] if fn == nil { mainThreadFunctionStoreLock.RUnlock() - fmt.Printf("🤖 [mainthread_android.go] ERROR: dispatchOnMainThread called with invalid id: %d\n", id) return } delete(mainThreadFunctionStore, id) diff --git a/v3/pkg/application/mainthread_ios.go b/v3/pkg/application/mainthread_ios.go index f4e2031c8..dd13808de 100644 --- a/v3/pkg/application/mainthread_ios.go +++ b/v3/pkg/application/mainthread_ios.go @@ -21,20 +21,17 @@ static bool onMainThread() { } */ import "C" -import "fmt" func (a *iosApp) isOnMainThread() bool { return bool(C.onMainThread()) } func (a *iosApp) dispatchOnMainThread(id uint) { - fmt.Printf("🔵 [mainthread_ios.go] dispatchOnMainThread(id=%d)\n", id) C.dispatchOnMainThread(C.uint(id)) } //export dispatchOnMainThreadCallback func dispatchOnMainThreadCallback(callbackID C.uint) { - fmt.Printf("🔵 [mainthread_ios.go] dispatchOnMainThreadCallback(id=%d)\n", callbackID) mainThreadFunctionStoreLock.RLock() id := uint(callbackID) fn := mainThreadFunctionStore[id] diff --git a/v3/pkg/application/systemtray_windows.go b/v3/pkg/application/systemtray_windows.go index fd9ced4d1..55bea46e8 100644 --- a/v3/pkg/application/systemtray_windows.go +++ b/v3/pkg/application/systemtray_windows.go @@ -570,7 +570,7 @@ func (s *windowsSystemTray) Show() { nid.DwStateMask = w32.NIS_HIDDEN nid.DwState = 0 if !w32.ShellNotifyIcon(w32.NIM_MODIFY, &nid) { - globalApplication.debug("ShellNotifyIcon NIM_MODIFY show failed: %v", syscall.GetLastError()) + globalApplication.debug("ShellNotifyIcon NIM_MODIFY show failed", "error", syscall.GetLastError()) } } @@ -584,7 +584,7 @@ func (s *windowsSystemTray) Hide() { nid.DwStateMask = w32.NIS_HIDDEN nid.DwState = w32.NIS_HIDDEN if !w32.ShellNotifyIcon(w32.NIM_MODIFY, &nid) { - globalApplication.debug("ShellNotifyIcon NIM_MODIFY hide failed: %v", syscall.GetLastError()) + globalApplication.debug("ShellNotifyIcon NIM_MODIFY hide failed", "error", syscall.GetLastError()) } } diff --git a/v3/pkg/application/webview_window.go b/v3/pkg/application/webview_window.go index 87f90781f..2193a236b 100644 --- a/v3/pkg/application/webview_window.go +++ b/v3/pkg/application/webview_window.go @@ -570,16 +570,13 @@ func (w *WebviewWindow) SetMaxSize(maxWidth, maxHeight int) Window { // ExecJS executes the given javascript in the context of the window. func (w *WebviewWindow) ExecJS(js string) { if w.impl == nil || w.isDestroyed() { - fmt.Println("🔴 [ExecJS] Window impl is nil or destroyed") return } if w.runtimeLoaded { - fmt.Println("🟢 [ExecJS] Runtime loaded, executing JS immediately") InvokeSync(func() { w.impl.execJS(js) }) } else { - fmt.Printf("🟠 [ExecJS] Runtime NOT loaded yet, queuing JS (queue size: %d)\n", len(w.pendingJS)+1) w.pendingJS = append(w.pendingJS, js) } } @@ -726,12 +723,10 @@ func (w *WebviewWindow) HandleMessage(message string) { } } case message == "wails:runtime:ready": - fmt.Printf("🟢 [WebviewWindow] Runtime ready! Pending JS count: %d\n", len(w.pendingJS)) w.emit(events.Common.WindowRuntimeReady) w.runtimeLoaded = true w.SetResizable(!w.options.DisableResize) - for i, js := range w.pendingJS { - fmt.Printf("🟢 [WebviewWindow] Executing pending JS %d/%d\n", i+1, len(w.pendingJS)) + for _, js := range w.pendingJS { w.ExecJS(js) } w.pendingJS = nil @@ -1182,9 +1177,7 @@ func (w *WebviewWindow) SetFrameless(frameless bool) Window { } func (w *WebviewWindow) DispatchWailsEvent(event *CustomEvent) { - fmt.Printf("🔵 [WebviewWindow.DispatchWailsEvent] Dispatching to frontend: %s\n", event.Name) msg := fmt.Sprintf("_wails.dispatchWailsEvent(%s);", event.ToJSON()) - fmt.Printf("🔵 [WebviewWindow.DispatchWailsEvent] JS command: %s\n", msg) w.ExecJS(msg) } @@ -1242,7 +1235,6 @@ func (w *WebviewWindow) HandleDragAndDropMessage(filenames []string, dropZone *D ) for _, listener := range listeners { if listener == nil { - fmt.Println("[DragDropDebug] HandleDragAndDropMessage: Skipping nil listener") continue } listener.callback(thisEvent) diff --git a/v3/pkg/application/webview_window_ios.go b/v3/pkg/application/webview_window_ios.go index acea1bb21..bc3087f2c 100644 --- a/v3/pkg/application/webview_window_ios.go +++ b/v3/pkg/application/webview_window_ios.go @@ -16,7 +16,6 @@ void ios_window_set_background_color(void* viewController, unsigned char r, unsi */ import "C" import ( - "fmt" "unsafe" ) @@ -203,29 +202,22 @@ func (w *iosWebviewWindow) setParent(_ *WebviewWindow) error { } func (w *iosWebviewWindow) run() { - fmt.Printf("🔥 iosWebviewWindow.run() called! nativeHandle: %v\n", w.nativeHandle) // Create the native WebView when the window runs if w.nativeHandle == nil { // Get the Wails window ID from the parent wailsID := w.parent.ID() - fmt.Printf("🔥 Creating native WebView with Wails ID: %d\n", wailsID) // Create the native WebView with the Wails window ID w.nativeHandle = C.ios_create_webview_with_id(C.uint(wailsID)) if w.nativeHandle != nil { // Store the window ID (should match what we passed in) w.windowID = uint32(wailsID) - fmt.Printf("🔥 Native WebView created successfully! Handle: %v\n", w.nativeHandle) // Apply initial background colour if set (default white otherwise) rgba := w.parent.options.BackgroundColour C.ios_window_set_background_color( w.nativeHandle, C.uchar(rgba.Red), C.uchar(rgba.Green), C.uchar(rgba.Blue), C.uchar(rgba.Alpha), ) - } else { - fmt.Printf("🔴 FAILED to create native WebView!\n") } - } else { - fmt.Printf("🔥 Native WebView already exists!\n") } } diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index b15fce95e..8de7dc2a2 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -1478,7 +1478,7 @@ func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintp } } case w32.WM_SYSKEYDOWN: - globalApplication.info("w32.WM_SYSKEYDOWN: %v", uint(wparam)) + globalApplication.info("w32.WM_SYSKEYDOWN", "wparam", uint(wparam)) w.parent.emit(events.Windows.WindowKeyDown) if w.processKeyBinding(uint(wparam)) { return 0 diff --git a/wails-mimetype-migration b/wails-mimetype-migration deleted file mode 160000 index b7f2d4a43..000000000 --- a/wails-mimetype-migration +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b7f2d4a43bc526b12fd3af162e572294f62db8e1