mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 22:55:48 +01:00
* fix(v3): overhaul drag-and-drop for Linux reliability and simplify Windows This commit fixes drag-and-drop reliability on Linux and simplifies the Windows implementation. ## Linux - Rewrite GTK drag handlers to properly intercept external file drops - Fix HTML5 internal drag-and-drop being broken when file drop enabled - Add hover effects during file drag operations - Fix multiple app instances interfering with each other ## Windows - Remove native IDropTarget in favor of JavaScript approach (matches v2) - File drops now handled via chrome.webview.postMessageWithAdditionalObjects ## All Platforms - Rename EnableDragAndDrop to EnableFileDrop - Rename data-wails-drop-target to data-file-drop-target - Rename wails-drop-target-active to file-drop-target-active - Add comprehensive drag-and-drop documentation ## Breaking Changes - EnableDragAndDrop -> EnableFileDrop - data-wails-dropzone -> data-file-drop-target - wails-dropzone-hover -> file-drop-target-active - DropZoneDetails -> DropTargetDetails - Remove WindowDropZoneFilesDropped event (use WindowFilesDropped) * feat(macos): optimize drag event performance with debouncing and caching - Add 50ms debouncing to limit drag events to 20/sec (was 120/sec) - Implement window implementation caching to avoid repeated lookups - Maintain existing 5-pixel threshold for immediate response - Keep zero-allocation path with pre-allocated buffers - Rename linuxDragActive to nativeDragActive for clarity - Update IMPLEMENTATION.md with optimization details and Windows guidance Performance improvements: - 83% reduction in event frequency - ~6x reduction in CPU/memory usage during drag operations - Maintains smooth visual feedback with InvokeSync for timer callbacks * fix(windows): implement proper file drop support for Windows - Remove incorrect AllowExternalDrag(false) call that was blocking file drops - Fix message prefix from 'FilesDropped' to 'file:drop:' to match JS runtime - Fix coordinate parsing for 'file:drop:x:y' format (indices 2,3 not 1,2) - Add enableFileDrop flag injection to JS runtime during navigation - Update JS runtime to check enableFileDrop flag before processing drops - Always call preventDefault() to stop browser navigation on file drags - Show 'no drop' cursor when file drops are disabled - Update example to filter file drags from HTML drop zone handlers - Add documentation for combining file drop with HTML drag-and-drop * fix(v3): block file drops on Linux when EnableFileDrop is false - Add disableDND() to intercept and reject external file drags at GTK level - Show 'no drop' cursor when files are dragged over window - Allow internal HTML5 drag-and-drop to work normally - Initialize _wails.flags object in runtime core to prevent undefined errors - Inject enableFileDrop flag on Linux and macOS (matching Windows) - Fix bare _wails reference to use window._wails - Update docs with info about blocked drops and combining with HTML DnD * fix(darwin): add missing fmt import in webview_window_darwin.go * fix(macOS): implement hover effects for file drag-and-drop with optimizations - Added draggingUpdated: handler to track mouse movement during drag operations - Implemented macosOnDragEnter/Exit/Over export functions for real-time hover state - Fixed JS function call from '_wails.handlePlatformFileDrop' to correct 'wails.Window.HandlePlatformFileDrop' - Added EnableFileDrop flag checks to prevent hover effects when file drops are disabled - Renamed linuxDragActive to nativeDragActive for cross-platform consistency Performance optimizations: - Added 50ms debounce to reduce event frequency from ~120/sec to ~20/sec - Implemented 5-pixel movement threshold for immediate response - Added window caching with sync.Map to avoid repeated lookups - Zero-allocation JavaScript calls with pre-allocated 128-byte buffer - Reduced memory usage to ~18 bytes per event (6x reduction) Build improvements: - Updated runtime Taskfile to include documentation generation - Added docs:build task to runtime build process - Fixed build order: events → docs → runtime Documentation: - Added IMPLEMENTATION.md with optimization details - Included guidance for Windows implementation * chore(v3/examples): remove html-dnd-api example The drag-n-drop example now demonstrates both external file drops and internal HTML5 drag-and-drop, making this separate example redundant. * docs(v3): move drag-and-drop implementation details to runtime-internals - Add drag-and-drop section to contributing/runtime-internals.mdx - Remove IMPLEMENTATION.md from example (content now in proper docs) - Covers platform differences, debugging tips, and key files * fix(v3): remove html-dnd-api from example build list * fix(v3): remove duplicate json import in application_darwin.go * fix(v3): address CodeRabbit review feedback - Fix docs to use app.Window.NewWithOptions() instead of deprecated API - Add mutex protection to dragOverJSBuffer to prevent race conditions - Add mutex protection to dragThrottleState fields for thread safety * docs: add coderabbit pre-push requirement to AGENTS.md * fix(v3/test): use correct CSS class name file-drop-target-active * chore(v3/test): remove dnd-test directory This was a development test file that shouldn't be in the PR. The drag-n-drop example serves as the proper test case. * docs(v3): update Windows file drop comment to reflect implemented fix Remove stale TODO - enableFileDrop flag is now injected in navigationCompleted * refactor(v3): make handleDragAndDropMessage unexported Internal method only called by application event loop, not part of public API.
249 lines
9.1 KiB
Text
249 lines
9.1 KiB
Text
---
|
||
title: Runtime Internals
|
||
description: Deep-dive into how Wails v3 boots, runs, and talks to the OS
|
||
sidebar:
|
||
order: 3
|
||
---
|
||
|
||
The **runtime** is the layer that transforms ordinary Go functions into a
|
||
cross-platform desktop application.
|
||
This document explains the moving parts you will meet when tracing through the
|
||
source code.
|
||
|
||
---
|
||
|
||
## 1. Application Lifecycle
|
||
|
||
| Phase | Code Path | What Happens |
|
||
|-------|-----------|--------------|
|
||
| **Bootstrap** | `pkg/application/application.go:init()` | Registers build-time data, creates a global `application` singleton. |
|
||
| **New()** | `application.New(...)` | Validates `Options`, spins up the **AssetServer**, initialises logging. |
|
||
| **Run()** | `application.(*App).Run()` | 1. Calls platform `mainthread.X()` to enter the OS UI thread.<br />2. Boots the **runtime** (`internal/runtime`).<br />3. Blocks until the last window closes or `Quit()` is called. |
|
||
| **Shutdown** | `application.(*App).Quit()` | Broadcasts `application:shutdown` event, flushes log, tears down windows & services. |
|
||
|
||
The lifecycle is strictly **single-entry**: you may create many windows, but the
|
||
application object itself is initialised once.
|
||
|
||
---
|
||
|
||
## 2. Window Management
|
||
|
||
### Public API
|
||
|
||
```go
|
||
win := app.Window.New(&application.WebviewWindowOptions{
|
||
Title: "Dashboard",
|
||
Width: 1280,
|
||
Height: 720,
|
||
})
|
||
win.Show()
|
||
```
|
||
|
||
`NewWebviewWindow` delegates to `internal/runtime/webview_window_*.go` where
|
||
platform-specific constructors live:
|
||
|
||
```
|
||
internal/runtime/
|
||
├── webview_window_darwin.go
|
||
├── webview_window_linux.go
|
||
└── webview_window_windows.go
|
||
```
|
||
|
||
Each file:
|
||
|
||
1. Creates a native webview (WKWebView, WebKitGTK, WebView2).
|
||
2. Registers a **Message Processor** callback.
|
||
3. Maps Wails events (`WindowResized`, `Focus`, `DropFile`, …) to runtime
|
||
event IDs.
|
||
|
||
Windows are tracked by `screenmanager.go` which offers **query APIs** (all
|
||
displays, DPI, active window) and centralises resize / move bookkeeping.
|
||
|
||
---
|
||
|
||
## 3. Message Processing Pipeline
|
||
|
||
The bridge between JavaScript and Go is implemented by the **Message
|
||
Processor** family in `pkg/application/messageprocessor_*.go`.
|
||
|
||
Flow:
|
||
|
||
1. **JavaScript** calls `window.runtime.Invoke("Greet", "Alice")`.
|
||
2. The runtime serialises the request:
|
||
`{"t":"c","id":"123","m":"Greet","p":["Alice"]}`
|
||
(`t` = type, `c` = call).
|
||
3. **Go** receives this JSON via the webview callback.
|
||
4. `messageprocessor_call.go` looks up the bound method in the
|
||
generated table (`application.bindings.go`) and executes it.
|
||
5. The result or error is marshalled back to JS where a `Promise` resolves.
|
||
|
||
Specialised processors:
|
||
|
||
| File | Purpose |
|
||
|------|---------|
|
||
| `messageprocessor_window.go` | Window actions (hide, maximize, …) |
|
||
| `messageprocessor_dialog.go` | Native dialogs (`OpenFile`, `MessageBox`, …) |
|
||
| `messageprocessor_clipboard.go` | Clipboard read/write |
|
||
| `messageprocessor_events.go` | Event subscribe / emit |
|
||
| `messageprocessor_browser.go` | Browser navigation, devtools |
|
||
|
||
Processors are **stateless** – they pull everything they need from the
|
||
`ApplicationContext` passed with each message.
|
||
|
||
---
|
||
|
||
## 4. Events System
|
||
|
||
Events are namespaced strings dispatched across three layers:
|
||
|
||
1. **Application events**: global lifecycle (`application:ready`,
|
||
`application:shutdown`).
|
||
2. **Window events**: per-window (`window:focus`, `window:resize`).
|
||
3. **Custom events**: user-defined (`chat:new-message`).
|
||
|
||
Implementation details:
|
||
|
||
* Constants are generated from `internal/events/defaults.go`.
|
||
* Go side:
|
||
`app.On("window:focus", func(e application.WindowEvent) {...})`
|
||
* JS side:
|
||
`window.runtime.EventsOn("chat:new-message", cb)`
|
||
|
||
Under the hood both map to the same **EventBus** in
|
||
`pkg/application/events.go`.
|
||
Subscribers are reference-counted; when a window closes, its callbacks are
|
||
auto-unregistered to avoid leaks.
|
||
|
||
---
|
||
|
||
## 5. Platform-Specific Implementations
|
||
|
||
Conditional compilation keeps the public API identical while hiding OS wrinkles.
|
||
|
||
| Concern | Darwin | Linux | Windows |
|
||
|---------|--------|-------|---------|
|
||
| Main Thread | `mainthread_darwin.go` (Cgo to Foundation) | `mainthread_linux.go` (GTK) | `mainthread_windows.go` (Win32 `AttachThreadInput`) |
|
||
| Dialogs | `dialogs_darwin.*` (NSAlert) | `dialogs_linux.go` (GtkFileChooser) | `dialogs_windows.go` (IFileOpenDialog) |
|
||
| Clipboard | `clipboard_darwin.go` | `clipboard_linux.go` | `clipboard_windows.go` |
|
||
| Tray Icons | `systemtray_darwin.*` | `systemtray_linux.go` (DBus) | `systemtray_windows.go` (Shell_NotifyIcon) |
|
||
|
||
Key principles:
|
||
|
||
* **No Cgo on Windows/Linux** unless unavoidable (performance, portability).
|
||
* Use **build tags** (`//go:build darwin && !production`) to keep files readable.
|
||
* Expose **capabilities** via `internal/capabilities` so higher layers can
|
||
degrade gracefully.
|
||
|
||
---
|
||
|
||
## 6. File Guide
|
||
|
||
| File | Why You’d Touch It |
|
||
|------|--------------------|
|
||
| `internal/runtime/runtime_*.go` | Change global startup logic, add debug hooks. |
|
||
| `internal/runtime/webview_window_*.go` | Implement a new window hint or behaviour. |
|
||
| `pkg/application/messageprocessor_*.go` | Add a new bridge command callable from JS. |
|
||
| `pkg/application/events_*.go` | Extend built-in event definitions. |
|
||
| `internal/assetserver/*` | Tweak dev/production asset handling. |
|
||
|
||
---
|
||
|
||
## 7. Debugging Tips
|
||
|
||
* Launch with `WAILS_LOG_LEVEL=debug` to print every message crossing the bridge.
|
||
* Use `wails3 dev -verbose` to see live reload & asset requests.
|
||
* On macOS run under `lldb --` to catch Objective-C exceptions early.
|
||
* For Windows Chromium issues, enable WebView2 debug logs:
|
||
`set WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=9222`
|
||
|
||
---
|
||
|
||
## 8. Extending the Runtime
|
||
|
||
1. Define a **capability flag** in `internal/capabilities`.
|
||
2. Implement the feature in each platform file using build tags.
|
||
3. Add public API in `pkg/application`.
|
||
4. Register a new message type or event if JS needs to call it.
|
||
5. Update at least one example in `v3/examples/` exercising the feature.
|
||
|
||
Follow this checklist and you'll keep the cross-platform contract intact.
|
||
|
||
---
|
||
|
||
## 9. Drag-and-Drop
|
||
|
||
File drag-and-drop uses a **JavaScript-first approach** on all platforms. The native layer intercepts OS drag events, but the actual drop handling and DOM interaction happens in JavaScript.
|
||
|
||
### Flow
|
||
|
||
1. User drags files from OS over the Wails window
|
||
2. Native layer detects the drag and notifies JavaScript for hover effects
|
||
3. User drops files
|
||
4. Native layer sends file paths + coordinates to JavaScript
|
||
5. JavaScript finds the drop target element (`data-file-drop-target`)
|
||
6. JavaScript sends file paths + element details to Go backend
|
||
7. Go emits `WindowFilesDropped` event with full context
|
||
|
||
### Platform Implementations
|
||
|
||
| Platform | Native Layer | Key Challenge |
|
||
|----------|--------------|---------------|
|
||
| **Windows** | WebView2's built-in drag support | Coordinates in CSS pixels, no conversion needed |
|
||
| **macOS** | NSWindow drag delegates | Convert window-relative to webview-relative coords |
|
||
| **Linux** | GTK3 drag signals | Must distinguish file drags from internal HTML5 drags |
|
||
|
||
### Linux: Distinguishing Drag Types
|
||
|
||
GTK and WebKit both want to handle drag events. The key is checking the drag target type:
|
||
|
||
```c
|
||
static gboolean is_file_drag(GdkDragContext *context) {
|
||
GList *targets = gdk_drag_context_list_targets(context);
|
||
for (GList *l = targets; l != NULL; l = l->next) {
|
||
GdkAtom atom = GDK_POINTER_TO_ATOM(l->data);
|
||
gchar *name = gdk_atom_name(atom);
|
||
if (name && g_strcmp0(name, "text/uri-list") == 0) {
|
||
g_free(name);
|
||
return TRUE; // External file drag
|
||
}
|
||
g_free(name);
|
||
}
|
||
return FALSE; // Internal HTML5 drag
|
||
}
|
||
```
|
||
|
||
Signal handlers return `FALSE` for internal drags (letting WebKit handle them) and `TRUE` for file drags (handling them ourselves).
|
||
|
||
### Blocking File Drops
|
||
|
||
When `EnableFileDrop` is `false`, we still need to prevent the browser from navigating to dropped files. Each platform handles this differently:
|
||
|
||
- **Windows**: JavaScript calls `preventDefault()` on drag events
|
||
- **macOS**: JavaScript calls `preventDefault()` on drag events
|
||
- **Linux**: GTK signal handlers intercept and reject file drags at the native level
|
||
|
||
### Key Files
|
||
|
||
| File | Purpose |
|
||
|------|---------|
|
||
| `pkg/application/linux_cgo.go` | GTK drag signal handlers (C code in cgo preamble) |
|
||
| `pkg/application/webview_window_darwin.go` | macOS drag delegates |
|
||
| `pkg/application/webview_window_windows.go` | WebView2 message handling |
|
||
| `internal/runtime/desktop/@wailsio/runtime/src/window.ts` | JavaScript drop handling |
|
||
|
||
### Debugging
|
||
|
||
- **Linux**: Add `printf` in C code (remember `fflush(stdout)`)
|
||
- **Windows**: Use `globalApplication.debug()`
|
||
- **JavaScript**: Check browser console, enable debug mode
|
||
|
||
Common issues:
|
||
1. **Internal HTML5 drag not working**: Native handler intercepting it (return `FALSE` for non-file drags)
|
||
2. **Hover effects not showing**: JavaScript handlers not being called
|
||
3. **Wrong coordinates**: Check coordinate space conversions
|
||
|
||
---
|
||
|
||
You now have a guided tour of the runtime internals. Combine this knowledge with
|
||
the **Codebase Layout** map and the **Asset Server** docs to navigate confidently
|
||
and make impactful contributions. Happy coding!
|