mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 22:55:48 +01:00
docs: fix hallucinated API patterns across v3 documentation
Audit all v3 documentation pages against actual source code on v3-alpha
branch. Fix incorrect API patterns that were hallucinated during docs
generation.
Key fixes across 38 files:
- app.NewWebviewWindow() → app.Window.New()
- app.NewWebviewWindowWithOptions() → app.Window.NewWithOptions()
- app.NewSystemTray() → app.SystemTray.New()
- app.EmitEvent() → app.Event.Emit()
- app.OnEvent() → app.Event.On()
- app.CurrentWindow() → app.Window.Current()
- app.GetWindowByName() → app.Window.GetByName()
- app.OnWindowCreation() → app.Window.OnCreate()
- app.NewMenu() → application.NewMenu()
- *application.Application → *application.App
- *application.WailsEvent → *application.CustomEvent
- window.SetFocus() → window.Focus()
- window.SetFullscreen() → window.ToggleFullscreen()
- SetWindowOffset() → WindowOffset()
- SetWindowDebounce() → WindowDebounce()
- KeyBinding callback: func(*WebviewWindow) → func(Window)
- SetBackgroundColour(r,g,b,a) → SetBackgroundColour(RGBA{})
- app.Events.On() → app.Event.On()/app.Event.OnApplicationEvent()
Sections audited: reference/, features/, concepts/, guides/,
tutorials/, migration/, contributing/, getting-started/, quick-start/,
faq, whats-new, DEVELOPER_GUIDE
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
718fd92f85
commit
182eeee8b9
140 changed files with 42075 additions and 0 deletions
542
docs/src/content/docs/DEVELOPER_GUIDE.md
Normal file
542
docs/src/content/docs/DEVELOPER_GUIDE.md
Normal file
|
|
@ -0,0 +1,542 @@
|
|||
---
|
||||
title: Wails v3 Developer Guide
|
||||
description: Comprehensive onboarding for engineering and debugging the Wails v3 codebase
|
||||
slug: developer-guide
|
||||
sidebar:
|
||||
label: Developer Guide
|
||||
order: 50
|
||||
---
|
||||
|
||||
## How to Use This Guide
|
||||
- **Scope**: Everything documented here applies to the `v3/` branch of Wails, tracking the current workspace state (commit you have checked out) and intentionally ignores the legacy `v2/` runtime.
|
||||
- **Audience**: Senior Go + desktop developers who need to become productive at fixing bugs or extending the v3 runtime, CLI, or build tooling.
|
||||
- **Format**: Top-down description → component drill-down → pseudocode for critical flows → teardown guidance → reference tables.
|
||||
- **Navigation**: Skim the diagrams first, then expand sections relevant to the subsystem you are editing.
|
||||
|
||||
```d2
|
||||
direction: down
|
||||
Developer: "Developer"
|
||||
CLI: "CLI Layer\n(cmd/wails3)"
|
||||
Commands: "Command Handlers\n(internal/commands)"
|
||||
TaskRunner: "Task Runner\n(internal/commands/task.go)"
|
||||
Taskfile: "Taskfile\n(v3/Taskfile.yaml)"
|
||||
BuildPipeline: "Build/Packaging\n(internal/*)"
|
||||
Templates: "Templates\n(internal/templates)"
|
||||
RuntimeGen: "Runtime Assets\n(internal/runtime)"
|
||||
Application: "Application Core\n(pkg/application)"
|
||||
AssetServer: "Asset Server\n(internal/assetserver)"
|
||||
MessageBridge: "Message Processor\n(pkg/application/messageprocessor*.go)"
|
||||
Webview: "WebView Implementations\n(pkg/application/webview_window_*.go)"
|
||||
Services: "Services & Bindings\n(pkg/services/*)"
|
||||
Platform: "Platform Layers\n(pkg/mac, pkg/w32, pkg/events, pkg/ui)"
|
||||
|
||||
Developer -> CLI: "runs wails3"
|
||||
CLI -> Commands: "registers subcommands"
|
||||
Commands -> TaskRunner: "wraps build/package/dev"
|
||||
TaskRunner -> Taskfile: "executes tasks"
|
||||
Commands -> BuildPipeline: "invokes packagers & generators"
|
||||
BuildPipeline -> Templates: "renders scaffolds"
|
||||
Commands -> RuntimeGen: "builds runtime JS"
|
||||
RuntimeGen -> Application: "embedded assets"
|
||||
Application -> AssetServer: "serves HTTP"
|
||||
Application -> MessageBridge: "routes runtime calls"
|
||||
MessageBridge -> Webview: "bridge over WebView"
|
||||
Application -> Services: "binds Go services"
|
||||
Application -> Platform: "dispatches to OS"
|
||||
AssetServer -> Webview: "feeds frontend"
|
||||
Services -> MessageBridge: "expose methods"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Repository Layout (v3 only)
|
||||
| Path | Purpose | Highlights |
|
||||
| --- | --- | --- |
|
||||
| `v3/cmd/wails3` | CLI entrypoint | Command registration, build info capture (`main.go`). |
|
||||
| `v3/internal/commands` | Implementation for each CLI subcommand | Includes build/package/dev tooling, template generators, diagnostics. |
|
||||
| `v3/internal/flags` | Typed flag definitions for CLI commands | Shared flag structs (e.g. `Build`, `Package`, `ServiceInit`). |
|
||||
| `v3/internal/assetserver` | Embedded HTTP server powering the runtime | Request routing, middleware, webview handoff. |
|
||||
| `v3/internal/runtime` | Generates runtime JS payload | Platform-specific flag injection, dev/prod toggles. |
|
||||
| `v3/internal/templates` | Project/frontend scaffolding | Multiple frontend stacks with shared `_common` assets. |
|
||||
| `v3/internal/service` | Service template generator | Creates Go service skeletons. |
|
||||
| `v3/internal/packager` | Wrapper around nfpm for Linux packaging | Generates `.deb`, `.rpm`, `.apk`, etc. |
|
||||
| `v3/internal/term`, `term2` | Console styling helpers | Shared across commands. |
|
||||
| `v3/pkg/application` | Core runtime used by user apps | Windowing, events, bindings, asset hosting, shutdown. |
|
||||
| `v3/pkg/events` | Enumerates runtime event IDs | Shared constants used on Go and JS sides. |
|
||||
| `v3/pkg/services` | Built-in services (badge, notifications, sqlite, etc.) | Reference implementations for custom services. |
|
||||
| `v3/pkg/icons`, `v3/pkg/ui` | Icon tooling & UI examples | Support packages used by CLI utilities. |
|
||||
| `v3/pkg/mac`, `v3/pkg/w32`, `v3/pkg/mcp` | Platform bindings | cgo / pure Go glue for OS integrations. |
|
||||
| `v3/scripts`, `v3/tasks`, `v3/test`, `v3/tests` | Automation helpers | Task runners, Docker harnesses, regression suites. |
|
||||
| `v3/Taskfile.yaml` | Root Taskfile consumed by CLI wrappers | Defines build/test workflows referenced by commands. |
|
||||
|
||||
---
|
||||
|
||||
## CLI and Tooling Layer
|
||||
### Entry Point: `v3/cmd/wails3/main.go`
|
||||
The CLI uses `github.com/leaanthony/clir` to declaratively register subcommands. Build metadata is captured in `init()` by reading `debug.ReadBuildInfo` and storing it into `internal/commands.BuildSettings` for later introspection (`wails3 tool buildinfo`).
|
||||
|
||||
```pseudo
|
||||
func main():
|
||||
app := clir.NewCli("wails", "The Wails3 CLI", "v3")
|
||||
register simple actions (docs, sponsor) -> open browser
|
||||
register functional subcommands that delegate to internal/commands
|
||||
init -> commands.Init
|
||||
build -> commands.Build (wraps task)
|
||||
dev -> commands.Dev (starts watcher)
|
||||
package -> commands.Package
|
||||
doctor -> commands.Doctor
|
||||
releasenotes -> commands.ReleaseNotes
|
||||
task -> commands.RunTask (Taskfile wrapper)
|
||||
generate subtree -> {build-assets, icons, syso, runtime, template, ...}
|
||||
update subtree -> {build-assets, cli}
|
||||
service -> {init}
|
||||
tool subtree -> {checkport, watcher, cp, buildinfo, package, version}
|
||||
version -> commands.Version
|
||||
defer printFooter() (prints docs/sponsor hints unless disabled)
|
||||
on error -> log with pterm and exit 1
|
||||
```
|
||||
Key takeaways:
|
||||
- All heavy lifting happens inside `internal/commands`. The CLI just constructs flag structs and hands them off.
|
||||
- Build/package commands are aliases for `wails3 task <name>`; real work is defined in `v3/Taskfile.yaml`.
|
||||
- `commands.DisableFooter` prevents duplicate footer output when commands open browsers or produce their own footers.
|
||||
|
||||
### Flag Definitions: `v3/internal/flags`
|
||||
- Each subcommand gets a struct tagged with descriptions/defaults used by `clir`.
|
||||
- Example: `flags.Build` holds options for `wails3 build`, while `flags.GenerateBindingsOptions` describes binding generator inputs.
|
||||
- Flag structs double as configuration objects passed into command implementations, so the same struct layout must be respected by tests.
|
||||
|
||||
### Command Implementations: Highlights
|
||||
| File | Responsibility |
|
||||
| --- | --- |
|
||||
| `internal/commands/init.go` | Scaffolds new projects from templates, resolves template metadata, writes config (`wails.json`). |
|
||||
| `internal/commands/dev.go` | Sets up the dev server port, populates `FRONTEND_DEVSERVER_URL`, and forwards to the file watcher. |
|
||||
| `internal/commands/watcher.go` | Loads `taskfile`-compatible YAML (`dev_mode`), instantiates `refresh/engine`, registers signal handlers, and keeps the engine alive until interrupted. |
|
||||
| `internal/commands/build-assets.go` | Materializes packaging assets from embedded templates (`gosod` extractor). Handles defaults, path normalization, and YAML config ingestion. |
|
||||
| `internal/commands/generate_template.go` | Creates template stubs by reading `internal/templates`. |
|
||||
| `internal/commands/tool_*` | Misc utilities: port checks, file copy, semantic version bump (`tool_version.go`), etc. |
|
||||
| `internal/commands/task_wrapper.go` | Implements `wails3 build/package` alias behavior by re-invoking the `task` subcommand with rewired `os.Args`. |
|
||||
|
||||
```pseudo
|
||||
func wrapTask(command string, otherArgs []string):
|
||||
warn("alias for task")
|
||||
newArgs := ["wails3", "task", command] + otherArgs
|
||||
os.Args = newArgs
|
||||
return RunTask(&RunTaskOptions{Name: command}, otherArgs)
|
||||
```
|
||||
|
||||
### Task Runner Integration: `internal/commands/task.go`
|
||||
- Wraps `github.com/wailsapp/task/v3` for cross-platform task execution.
|
||||
- Accepts both positional task names (`wails3 task dev -- <extra>`) and `--name` flags.
|
||||
- Validates mutually exclusive options (e.g., `--dir` vs `--taskfile`).
|
||||
- Supports list/status/JSON output inherited from upstream Task library.
|
||||
- Uses `BuildSettings` to print the bundled Task version when `--version` is passed.
|
||||
|
||||
### Dev Mode Flow (`wails3 dev`)
|
||||
1. Resolve Vite port preference (`--port`, `WAILS_VITE_PORT`, default `9245`).
|
||||
2. Check that the port is free by attempting to `net.Listen` and immediately closing.
|
||||
3. Export `WAILS_VITE_PORT` and set `FRONTEND_DEVSERVER_URL` (`http` vs `https` based on `--secure`).
|
||||
4. Invoke `Watcher` with the configured Taskfile path (defaults to `./build/config.yml`).
|
||||
|
||||
### Packaging & Distribution Commands
|
||||
- `GenerateSyso` (Windows resource) writes `.syso` files for icon/resource embedding.
|
||||
- `GenerateIcons` converts SVG/PNGs into platform icon bundles.
|
||||
- `ToolPackage` uses `internal/packager` to drive `nfpm` for Linux packages.
|
||||
- `GenerateAppImage`, `generate_webview2`, and `package/msix.go` provide OS-specific installers.
|
||||
|
||||
---
|
||||
|
||||
## Template & Asset Generation
|
||||
### Project Templates: `v3/internal/templates`
|
||||
- Each frontend stack lives under its own directory (React, Vue, Svelte, Solid, Qwik, Vanilla, Lit, Preact) with `-ts` variants.
|
||||
- `_common` contains shared scaffolding (Go module layout, `wails.json`, default assets).
|
||||
- `generate template` extracts template files via `gosod` into the target project directory, honoring parameters collected during `wails3 init`.
|
||||
|
||||
### Build Assets: `internal/commands/build-assets.go`
|
||||
Embedded assets define packaging metadata such as installers, file associations, protocol handlers, and Windows installer scripts.
|
||||
|
||||
```pseudo
|
||||
func GenerateBuildAssets(opts):
|
||||
opts.Dir = abs(opts.Dir); mkdir if missing
|
||||
fill empty fields (ProductComments, Identifier, BinaryName, etc.) with sensible defaults
|
||||
load `build_assets` FS subtree and render via gosod using opts (includes file associations + protocols)
|
||||
render `updatable_build_assets` on top so user edits stay intact
|
||||
```
|
||||
|
||||
### Runtime Bundles: `internal/runtime`
|
||||
- `Core()` concatenates `runtimeInit + flags + invoke + environment` to produce the JS bootstrap injected into every window.
|
||||
- Platform-specific files (`runtime_windows.go`, `runtime_linux.go`, etc.) define how Go exposes `window._wails.invoke` and system flags (resize handle sizes on Windows via `pkg/w32`).
|
||||
- `GenerateRuntime` command serializes pre-built runtime assets for embedding.
|
||||
|
||||
---
|
||||
|
||||
## Runtime Boot Sequence (pkg/application)
|
||||
### Application Construction: `pkg/application/application.go`
|
||||
A singleton `App` is created via `application.New(options)` and stored in `globalApplication`.
|
||||
|
||||
```pseudo
|
||||
func New(options):
|
||||
if globalApplication exists -> return it (enforces singleton)
|
||||
mergeApplicationDefaults(options)
|
||||
app := newApplication(options) // debug vs production build tags
|
||||
globalApplication = app
|
||||
fatalHandler(app.handleFatalError)
|
||||
configure Logger (debug -> DefaultLogger, prod -> discard)
|
||||
install default signal handler unless disabled (ctrl+c, SIGTERM -> App.Quit)
|
||||
log startup + platform info
|
||||
customEventProcessor := NewWailsEventProcessor(app.Event.dispatch)
|
||||
messageProc := NewMessageProcessor(app.Logger)
|
||||
assetOpts := assetserver.Options {
|
||||
Handler: options.Assets.Handler (default BundledAssetFileServer)
|
||||
Middleware: Chain(user middleware, internal middleware for /wails endpoints)
|
||||
Logger: app.Logger (or discard when DisableLogging)
|
||||
}
|
||||
asset server intercepts:
|
||||
/wails/runtime.js -> bundled runtime asset
|
||||
/wails/runtime -> messageProc.ServeHTTP
|
||||
/wails/capabilities -> emits capabilities JSON
|
||||
/wails/flags -> marshals platform flags via impl.GetFlags
|
||||
app.assets = AssetServer(assetOpts)
|
||||
app.bindings = NewBindings(options.MarshalError, options.BindAliases)
|
||||
app.options.Services = clone(options.Services)
|
||||
process key bindings if provided
|
||||
register OnShutdown hook if provided
|
||||
if SingleInstance configured -> newSingleInstanceManager
|
||||
return app
|
||||
```
|
||||
|
||||
Key supporting structures:
|
||||
- `Options` (`application_options.go`) configures assets, services, platform-specific knobs, signal handling, keybindings, custom marshaling, and single-instance behavior.
|
||||
- `signal.NewSignalHandler` (`internal/signal`) watches OS signals, invoking `App.Quit` while printing customizable exit messages.
|
||||
|
||||
### Run Loop & Lifecycle
|
||||
`App.Run()` orchestrates startup, service lifecycle, channel fan-out, and the platform event loop.
|
||||
|
||||
```pseudo
|
||||
func (a *App) Run():
|
||||
lock runLock; guard against double runs
|
||||
defer cancel app.Context (ensures goroutines exit on failure)
|
||||
execute a.preRun() (noop in production, debug logging otherwise)
|
||||
a.impl = newPlatformApp(a) // windowsApp, darwinApp, linuxApp depending on GOOS
|
||||
defer a.shutdownServices()
|
||||
services := clone(options.Services); options.Services = nil
|
||||
for service in services:
|
||||
startupService(service) // binds methods, registers HTTP routes, calls ServiceStartup
|
||||
append to options.Services so shutdown order is reversed
|
||||
spawn goroutines reading buffered channels:
|
||||
applicationEvents -> EventManager.handleApplicationEvent
|
||||
windowEvents -> handleWindowEvent
|
||||
webviewRequests -> assets.ServeWebViewRequest
|
||||
windowMessageBuffer -> handleWindowMessage (custom vs wails: prefix)
|
||||
windowKeyEvents -> handleWindowKeyEvent
|
||||
windowDragAndDropBuffer -> handleDragAndDropMessage (debug logs)
|
||||
menuItemClicked -> Menu.handleMenuItemClicked
|
||||
mark running=true; flush pendingRun queue by invoking runnable.Run() asynchronously
|
||||
if GOOS == darwin -> set application menu immediately
|
||||
if Icon provided -> impl.setIcon
|
||||
return a.impl.run() (enters platform main loop)
|
||||
```
|
||||
|
||||
```d2
|
||||
direction: down
|
||||
root: "App.Run dispatch hub"
|
||||
root -> applicationEvents: "goroutine"
|
||||
applicationEvents -> EventManager: "Event.handleApplicationEvent"
|
||||
root -> windowEvents: "goroutine"
|
||||
windowEvents -> Windows: "handleWindowEvent"
|
||||
root -> webviewRequests
|
||||
webviewRequests -> AssetServer: "ServeWebViewRequest"
|
||||
root -> windowMessages
|
||||
windowMessages -> MessageHandlers: "HandleMessage / RawMessageHandler"
|
||||
root -> windowKeyEvents
|
||||
windowKeyEvents -> KeyBinding: "HandleKeyEvent"
|
||||
root -> dragDropBuffer
|
||||
dragDropBuffer -> Windows: "HandleDragAndDrop"
|
||||
root -> menuItemClicked
|
||||
menuItemClicked -> MenuManager
|
||||
```
|
||||
|
||||
### Shutdown Path
|
||||
- `shutdownServices()` iterates bound services in reverse start order, invoking `ServiceShutdown` when implemented and cancelling the app context.
|
||||
- `OnShutdown` hooks run synchronously on the main thread; `PostShutdown` runs last (useful on macOS where `Run` may block indefinitely).
|
||||
- `cleanup()` (triggered via `impl.destroy()` and `App.Quit`) sets `performingShutdown`, cancels the context, runs queued shutdown tasks, releases the single-instance manager, and closes windows/system trays.
|
||||
|
||||
---
|
||||
|
||||
## Message Bridge & Asset Server
|
||||
### Message Processor (`messageprocessor.go` et al.)
|
||||
- Handles `/wails/runtime` POST requests from the frontend.
|
||||
- Dispatch keyed by `object` query parameter:
|
||||
- `callRequest` (0) → service bindings.
|
||||
- `clipboardRequest`, `applicationRequest`, `eventsRequest`, `contextMenuRequest`, `dialogRequest`, `windowRequest`, `screensRequest`, `systemRequest`, `browserRequest`, `cancelCallRequest`.
|
||||
- Uses HTTP headers `x-wails-window-id` / `name` to resolve the target `Window` (`getTargetWindow`).
|
||||
- Maintains `runningCalls` map to support cancellation by call ID.
|
||||
|
||||
```pseudo
|
||||
func processCallMethod(method, rw, req, window, params):
|
||||
args := params.Args()
|
||||
callID := args.String("call-id")
|
||||
if method == CallBinding:
|
||||
options := params.ToStruct(CallOptions)
|
||||
ctx, cancel := context.WithCancel(request.Context)
|
||||
register cancel in runningCalls[callID]
|
||||
respond 200 OK immediately
|
||||
go func():
|
||||
boundMethod := lookup by name or ID (honour aliases)
|
||||
if not found -> CallError(kind=ReferenceError)
|
||||
if window != nil -> ctx = context.WithValue(ctx, WindowKey, window)
|
||||
result, err := boundMethod.Call(ctx, options.Args)
|
||||
on CallError -> window.CallError(callID, json, knownError=true)
|
||||
marshal result -> jsonResult
|
||||
window.CallResponse(callID, jsonResult)
|
||||
cleanup runningCalls entry and cancel context
|
||||
```
|
||||
```
|
||||
|
||||
- Errors bubble to the frontend via `CallError` JSON with `ReferenceError`, `TypeError`, or `RuntimeError` kinds.
|
||||
- Cancellation (`cancelCallRequest`) removes the call ID from `runningCalls` and cancels the context.
|
||||
|
||||
### Asset Server (`internal/assetserver`)
|
||||
- `AssetServer.ServeHTTP` wraps responses with MIME sniffing, logs duration, window metadata, and status codes.
|
||||
- `serveHTTP` intercepts root/index requests to optionally serve a localized index fallback (`defaultIndexHTML` uses `accept-language`).
|
||||
- `/wails/*` special routes and user-defined services share the same middleware chain, allowing injection of auth, logging, or routing.
|
||||
- Dev vs production logic uses environment variable `FRONTEND_DEVSERVER_URL`; when present, requests proxy to the external dev server instead of the embedded assets (`asset_fileserver.go`).
|
||||
- `AttachServiceHandler` mounts service-provided HTTP handlers under custom routes (`ServiceOptions.Route`).
|
||||
- WebView-specific request/response types are implemented in `internal/assetserver/webview`, providing platform-native bridges to feed asset bytes directly into the webview without round-tripping through TCP when possible.
|
||||
|
||||
### Runtime JS Exposure
|
||||
- `/wails/runtime.js` serves the concatenated runtime string produced by `internal/runtime`, ensuring the frontend has access to `window.wails` helpers.
|
||||
- `/wails/flags` serializes `Options.Flags` extended by the platform implementation (`windowsApp.GetFlags` injects resize handles, etc.), allowing frontend startup logic to adjust to platform constraints.
|
||||
- `/wails/capabilities` exposes a JSON describing features like native drag support (populated by `internal/capabilities`).
|
||||
|
||||
---
|
||||
|
||||
## Windowing & UI Layer (`pkg/application`)
|
||||
### Window Manager (`window_manager.go`)
|
||||
- Maintains the authoritative map of `Window` instances keyed by numeric ID.
|
||||
- Defers actual `Run` execution if the app has not started (`runOrDeferToAppRun`).
|
||||
- Provides lookup by name/ID, iteration (`GetAll`), and lifecycle hooks (`OnCreate`).
|
||||
- Works in tandem with `App.pendingRun` to ensure windows created before `App.Run` are executed after the platform loop is ready.
|
||||
|
||||
### Webview Windows (`webview_window.go` + `webview_window_<platform>.go`)
|
||||
- `WebviewWindow` wraps a platform-specific `webviewWindowImpl` with synchronized maps for event listeners, key bindings, menus, and asynchronous cancellers.
|
||||
- Supports full window control API: sizing, positioning, zoom, devtools, menu bar toggles, context menu injection, border size queries, drag/resize operations.
|
||||
- `HandleMessage`, `HandleKeyEvent`, and drag-and-drop handlers are invoked by the central channels in `App.Run`.
|
||||
- On runtime readiness, windows emit `events.Common.WindowRuntimeReady` to allow frontends to hydrate state once the JS bridge is loaded.
|
||||
|
||||
### Event System (`events.go`, `context_*`)
|
||||
- `ApplicationEvent` and `WindowEvent` objects carry strongly-typed contexts (`ApplicationEventContext`, `WindowEventContext`) to provide structured data (files dropped, URLs, screen info, etc.).
|
||||
- Channels `applicationEvents`, `windowEvents`, and `menuItemClicked` are buffered to avoid blocking the OS event loop.
|
||||
- `EventProcessor` manages custom user events, offering `On`, `Once`, `Emit`, and hook registration for pre-dispatch inspection.
|
||||
- Platform-specific files (`events_common_windows.go`, `events_common_darwin.go`, etc.) translate native callbacks into the common channel structure.
|
||||
|
||||
### Managers & Subsystems
|
||||
| Manager | File | Responsibility |
|
||||
| --- | --- | --- |
|
||||
| `ContextMenuManager` | `context_menu_manager.go` | Creates native context menus, wires handlers. |
|
||||
| `DialogManager` | `dialog_manager.go` + OS-specific files | Wraps native file/message dialogs. |
|
||||
| `ClipboardManager` | `clipboard_manager.go` | Provides cross-platform clipboard access, proxies to OS-specific implementations. |
|
||||
| `ScreenManager` | `screenmanager.go` | Exposes multi-monitor info, resolution, scaling. |
|
||||
| `SystemTrayManager` | `system_tray_manager.go` | Manages tray icons, menu interactions. |
|
||||
| `BrowserManager` | `browser_manager.go` | Handles window navigation and devtools. |
|
||||
| `KeyBindingManager` | `key_binding_manager.go` | Registers accelerators and callbacks. |
|
||||
| `EnvironmentManager` | `environment_manager.go` | Tracks env state (dark mode, accent color) exposed to the frontend. |
|
||||
|
||||
---
|
||||
|
||||
## Services & Binding Engine
|
||||
### Service Definition (`service.go`, `bindings.go`)
|
||||
- Services are user-provided structs implementing optional interfaces:
|
||||
- `ServiceStartup(ctx context.Context, opts ServiceOptions) error`
|
||||
- `ServiceShutdown() error`
|
||||
- `ServeHTTP` (when exposing HTTP routes)
|
||||
- Methods to be bound must be exported, live on pointer receivers of named types, and cannot be generic.
|
||||
|
||||
```pseudo
|
||||
func startupService(service):
|
||||
bindings.Add(service) // reflect, hash method signatures, register alias map
|
||||
if service.Route != "":
|
||||
if instance implements http.Handler -> attach to asset server
|
||||
else -> attach fallback handler returning 503
|
||||
if instance implements ServiceStartup -> call with app context
|
||||
```
|
||||
|
||||
- `Bindings.Add` enumerates exported methods via reflection, hashes fully qualified names using `internal/hash`, and stores them in `boundMethods` (by name) and `boundByID` (by numeric ID). Hash collisions are guarded with explicit error messages instructing developers to rename methods.
|
||||
- `CallOptions` submitted from the frontend include `MethodID`, `MethodName`, and JSON-encoded args; the binding engine supports both to allow smaller payloads in production (IDs) while keeping dev ergonomics (names).
|
||||
|
||||
### Error Marshalling & Aliases
|
||||
- Custom error marshaling can be provided per service (`ServiceOptions.MarshalError`) or globally (`Options.MarshalError`), allowing user-defined JSON payloads.
|
||||
- `BindAliases` maps alternative IDs to primary method IDs, helpful when generated bindings are versioned and need to stay stable across refactors.
|
||||
|
||||
### Context Propagation
|
||||
- During calls, the bridge injects `context.Context` with the window (`context.WithValue(ctx, WindowKey, window)`) so services can inspect which window invoked them (e.g., to push events back via `window.Emit`).
|
||||
- Cancellation is propagated when the frontend aborts a promise or the app shuts down (`App.cancel()`).
|
||||
|
||||
---
|
||||
|
||||
## Configuration Options & Their Effects
|
||||
### `application.Options` Highlights (`application_options.go`)
|
||||
| Field | Effect on Runtime |
|
||||
| --- | --- |
|
||||
| `Assets.Handler` | Base HTTP handler serving static assets. Overrides default embedded bundle, but `/wails/*` middleware still executes. |
|
||||
| `Assets.Middleware` | Injects custom middleware before internal routes; can short-circuit requests to implement routing or authentication. |
|
||||
| `Assets.DisableLogging` | Swaps the asset server logger to a discard handler to avoid noisy logs. |
|
||||
| `Flags` | Merged with platform flags and exposed to the frontend at `/wails/flags`. Changing affects frontend boot configuration. |
|
||||
| `Services` | List of services auto-registered during `Run`. Order matters for startup/shutdown. |
|
||||
| `BindAliases` | Remaps method IDs used by the runtime. Critical when regenerating bindings without breaking existing frontend code. |
|
||||
| `KeyBindings` | Global accelerator map executed per window. Processed during `New`, stored per window at runtime. |
|
||||
| `OnShutdown` / `PostShutdown` | Lifecycle hooks executed during teardown. `OnShutdown` runs before services shut down; `PostShutdown` runs after platform loop returns (if ever). |
|
||||
| `ShouldQuit` | Gatekeeper invoked when the user attempts to quit (e.g., Cmd+Q). Returning `false` keeps the app alive. |
|
||||
| `RawMessageHandler` | Receives messages that do not start with the `wails:` prefix, enabling custom bridge protocols aside from service calls. |
|
||||
| `WarningHandler` / `ErrorHandler` | Overrides default slog warnings/errors for system-level diagnostics. |
|
||||
| `FileAssociations` | Used during packaging and when launch arguments are parsed on Windows/macOS to emit `ApplicationOpenedWithFile`. |
|
||||
| `SingleInstance` | Triggers single-instance manager setup; options include encryption key, exit code, and `OnSecondInstanceLaunch` callback. |
|
||||
|
||||
### Single Instance Workflow (`single_instance.go`)
|
||||
```pseudo
|
||||
func newSingleInstanceManager(app, opts):
|
||||
if opts == nil -> return nil
|
||||
start goroutine that reads secondInstanceBuffer and dispatches OnSecondInstanceLaunch
|
||||
lock := newPlatformLock(opts.UniqueID) // OS-specific
|
||||
if lock.acquire fails -> already running
|
||||
return manager
|
||||
|
||||
func manager.notifyFirstInstance():
|
||||
data := {Args, WorkingDir, AdditionalData}
|
||||
payload := json.Marshal(data) or encrypt(AES-256-GCM)
|
||||
lock.notify(payload)
|
||||
```
|
||||
- On startup, if another instance is detected, the CLI prints a warning, invokes `notifyFirstInstance`, and exits with the configured code.
|
||||
- Platform locks live in `single_instance_<os>.go`, using named pipes, mutexes, or DBus depending on OS.
|
||||
|
||||
---
|
||||
|
||||
## Platform Implementations
|
||||
| Platform | Entry Files | Notes |
|
||||
| --- | --- | --- |
|
||||
| Windows | `application_windows.go`, `webview_window_windows*.go`, `pkg/w32` | Integrates with Win32 APIs, WebView2 via `go-webview2`. Handles taskbar recreation, dark mode, accent color, custom title bars, drag/resize, `WndProcInterceptor`. |
|
||||
| macOS | `application_darwin.go`, `webview_window_darwin*.go`, `pkg/mac` | Objective-C bridges (via cgo) for NSApplication, implements activation policy, app/URL events, and main thread run loops. |
|
||||
| Linux | `application_linux.go`, `webview_window_linux*.go`, `pkg/application/linux_*` | GTK/WebKit2 integration, theme detection, DBus single-instance hooks. Pure Go fallback provided (`linux_purego`). |
|
||||
| Runtime JS | `runtime_<os>.go` | Defines `window._wails.invoke` binding for each platform (e.g., `webkit.messageHandlers.external.postMessage` on Linux/macOS, `chrome.webview.postMessage` on Windows). |
|
||||
|
||||
Key responsibilities of each platform struct (e.g., `windowsApp`):
|
||||
- Manage native window/class registration, system tray IDs, focus management.
|
||||
- Emit platform-specific events into `applicationEvents` when OS notifications occur (power events, taskbar resets, theme changes).
|
||||
- Provide platform-specific implementations of `setApplicationMenu`, `dispatchOnMainThread`, `GetFlags`, etc.
|
||||
|
||||
---
|
||||
|
||||
## Packaging & Distribution Pipeline
|
||||
- **Linux NFPM**: `internal/packager/packager.go` wraps `nfpm` to parse YAML recipes and build `.deb`, `.rpm`, `.apk`, `.ipk`, or Arch packages. CLI `wails3 tool package` selects the format and output path.
|
||||
- **Windows**: `msix.go`, `generate_syso.go`, and build assets templates generate installers, embedding certificate details and executable paths from `BuildAssetsOptions`.
|
||||
- **macOS**: `dmg` command scaffolds DMG packaging scripts.
|
||||
- **AppImage**: `GenerateAppImage` compiles a portable AppImage by invoking the bundled `linuxdeploy` scripts (`appimage_testfiles` contains test fixtures).
|
||||
- **Build Info**: `tool_buildinfo.go` prints deduced module versions using the captured `BuildSettings` map.
|
||||
|
||||
During release automation (see `v3/tasks/release`), these commands are orchestrated by Taskfile recipes to build cross-platform artifacts.
|
||||
|
||||
---
|
||||
|
||||
## Diagnostics, Logging & Error Handling
|
||||
- Logging defaults differ: debug builds use structured logging (`DefaultLogger`), production builds discard framework logs unless a logger is provided.
|
||||
- `fatalHandler` sets a package-level panic handler that exits the process on unrecoverable errors (panic + formatted message).
|
||||
- `App.handleError`/`handleWarning` route errors to user-provided handlers or `Logger.Error/Warn`.
|
||||
- Signal handling (SIGINT/SIGTERM) ensures the watcher and the app cleanly shut down, releasing OS resources and closing windows.
|
||||
- `internal/term` and `term2` provide colored console output; used heavily in CLI warnings/errors to standardize UX (`term.Warningf`, `term.Hyperlink`).
|
||||
|
||||
---
|
||||
|
||||
## Developer Workflow Checklist
|
||||
1. **Familiarize with Taskfile**: Run `task -l` or `wails3 task --list` to see available workflows (`install`, `precommit`, example builds).
|
||||
2. **Local CLI Install**: `task v3:install` compiles the CLI (`go install` inside `cmd/wails3`).
|
||||
3. **Run Dev Mode**: `wails3 dev` (or `task dev`) spawns the watcher, sets `FRONTEND_DEVSERVER_URL`, and tails the Go backend.
|
||||
4. **Build Release**: `wails3 build` → alias for `task build` (see `Taskfile.yaml` for pipeline details, including `go build`, asset bundling, templating, packaging).
|
||||
5. **Packaging Tests**: Use `wails3 tool package --type deb --config ...` or `GenerateBuildAssets` to refresh installer scaffolding.
|
||||
6. **Pre-commit routine**: `task v3:precommit` runs `go test ./...` and repository formatters before opening PRs.
|
||||
|
||||
---
|
||||
|
||||
## Lifecycles at a Glance
|
||||
### Application Startup Timeline
|
||||
1. CLI entry (user application) calls `wails.Run()` (inside user code) → constructs `application.Options`.
|
||||
2. `application.New` merges defaults, sets up asset server, bindings, single-instance manager, event processor.
|
||||
3. User code configures windows/services (often via `app.Window.New()` or services via `application.NewService()`).
|
||||
4. `App.Run`:
|
||||
- Validates no concurrent runs.
|
||||
- Instantiates platform app (calls Objective-C/Win32/GTK setup).
|
||||
- Starts services, mounts HTTP routes.
|
||||
- Spins up channel goroutines and flushes deferred runnables.
|
||||
- Enters platform main loop (`impl.run()`), bridging native events into Go.
|
||||
5. Frontend loads `runtime.js` → initializes `window.wails`, fetches `/wails/flags` & `/wails/capabilities`, and begins invoking bound methods via JSON payloads.
|
||||
|
||||
### Shutdown Timeline
|
||||
1. `App.Quit` (called from user code, signal handler, or platform event) sets `performingShutdown`.
|
||||
2. Cancels `context.Context` to abort long-running service calls.
|
||||
3. Executes `OnShutdown` tasks in registration order.
|
||||
4. Invokes `shutdownServices` in reverse startup order; each service can release resources.
|
||||
5. Releases single-instance lock, destroys windows/system trays, and calls platform `destroy()`.
|
||||
6. Executes `PostShutdown` hook (if provided) then exits the process.
|
||||
|
||||
---
|
||||
|
||||
## Key Channels & Buffers
|
||||
| Channel | Producer | Consumer | Purpose |
|
||||
| --- | --- | --- | --- |
|
||||
| `applicationEvents` (`events.go`) | Platform apps (`application_<os>.go`) and system signals | `App.Event.handleApplicationEvent` | Broadcast application-level events (startup, theme changes, power events). |
|
||||
| `windowEvents` | Platform window callbacks | `App.handleWindowEvent` | Window lifecycle events (move, resize, focus). |
|
||||
| `webviewRequests` | Asset server (`webview/request.go`) | `App.handleWebViewRequest` | Direct webview resource streaming. |
|
||||
| `windowMessageBuffer` | Platform message pump | `App.handleWindowMessage` | Messages from WebView (prefixed `wails:`) -> service invocations or raw handlers. |
|
||||
| `windowDragAndDropBuffer` | Drag/drop handlers | `App.handleDragAndDropMessage` | File drop notifications with DOM metadata. |
|
||||
| `windowKeyEvents` | Accelerator handlers | `App.handleWindowKeyEvent` | Dispatch registered key bindings to the appropriate window. |
|
||||
| `menuItemClicked` | Native menu callbacks | `Menu.handleMenuItemClicked` | Execute Go handlers for menu events. |
|
||||
|
||||
All buffers default to size 5 to absorb bursty events without blocking UI threads.
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables & Configuration Hooks
|
||||
| Variable | Used By | Effect |
|
||||
| --- | --- | --- |
|
||||
| `FRONTEND_DEVSERVER_URL` | Asset server (`GetDevServerURL`) | Redirects asset requests to external dev server instead of embedded bundle. |
|
||||
| `WAILS_VITE_PORT` | `commands.Dev` | Controls Vite dev server port advertised to frontend tasks. |
|
||||
| `NO_COLOR` | Task runner | Disables colored CLI output. |
|
||||
| `WAILSENV` | User applications (common pattern) | Choose between dev/prod logic when bootstrapping `Options`. |
|
||||
|
||||
---
|
||||
|
||||
## Debugging Tips
|
||||
- **Inspect Call Flow**: Enable verbose logging (`Options.LogLevel = slog.LevelDebug`) to trace binding calls and HTTP requests.
|
||||
- **Capture Runtime JS**: Hit `http://127.0.0.1:<port>/wails/runtime.js` while running in dev to verify injected flags and capabilities.
|
||||
- **Watch Service Registration**: Look for `Registering bound method` debug lines (`bindings.go`) to confirm service methods were detected.
|
||||
- **Single Instance Issues**: Check platform lock files (e.g., `%AppData%\Wails\Locks` on Windows, `$XDG_RUNTIME_DIR` on Linux) and ensure the encryption key is consistent.
|
||||
- **Asset Server Paths**: Use `assetserver.ServeFile` error output (logged via slog) to diagnose missing assets or MIME type issues.
|
||||
|
||||
---
|
||||
|
||||
## Extending the Codebase
|
||||
1. **Adding a CLI Command**: Implement function in `internal/commands`, define flags under `internal/flags`, register in `cmd/wails3/main.go`. Document the Taskfile hook if it wraps tasks.
|
||||
2. **New Service Template**: Update `internal/service/template`, ensure `gosod` placeholders map to new options, and adjust `flags.ServiceInit` accordingly.
|
||||
3. **Runtime Feature Flags**: Modify `internal/runtime/runtime_<os>.go` to expose additional data via `/wails/flags`; update frontend expectations.
|
||||
4. **Custom Middleware**: Provide `Options.Assets.Middleware` to inject auth/logging; remember middleware runs before internal routes, so call `next` for default behavior.
|
||||
5. **Platform-Specific Fixes**: Locate corresponding `application_<os>.go` and `webview_window_<os>*.go` files. Keep cross-platform interfaces (`platformApp`, `webviewWindowImpl`) stable.
|
||||
|
||||
---
|
||||
|
||||
## Reference: Critical Files by Responsibility
|
||||
| Responsibility | File(s) | Notes |
|
||||
| --- | --- | --- |
|
||||
| CLI bootstrap | `v3/cmd/wails3/main.go` | Command registration, browser helpers. |
|
||||
| Build info capture | `v3/cmd/wails3/main.go:init` | Populates `commands.BuildSettings`. |
|
||||
| Task aliasing | `v3/internal/commands/task_wrapper.go` | Rewrites `os.Args` and invokes `RunTask`. |
|
||||
| Asset server core | `v3/internal/assetserver/assetserver.go` | Middleware, logging, fallback handling. |
|
||||
| Application singleton | `v3/pkg/application/application.go:56` | Global `App` creation. |
|
||||
| Service binding | `v3/pkg/application/bindings.go` | Reflection, aliasing, error marshaling. |
|
||||
| Message bridge | `v3/pkg/application/messageprocessor*.go` | Runtime call routing. |
|
||||
| Event channels | `v3/pkg/application/events.go` | Buffered channels + processors. |
|
||||
| Window API | `v3/pkg/application/webview_window.go` | Platform interface and user-facing API. |
|
||||
| Single-instance control | `v3/pkg/application/single_instance*.go` | Locking and IPC. |
|
||||
| Platform adapters | `v3/pkg/application/application_<os>.go` | Native message loops, start/stop. |
|
||||
| Packaging | `v3/internal/packager/packager.go`, `internal/commands/tool_package.go` | Linux packaging wrappers. |
|
||||
|
||||
---
|
||||
|
||||
Armed with these maps, pseudocode, and lifecycle notes, you can confidently trace any bug from CLI invocation through runtime dispatch, into platform-specific glue, and back. Use the pseudocode as a mental model, verify concrete behavior by jumping into the referenced files, and lean on the diagrams to understand how data flows between layers.
|
||||
206
docs/src/content/docs/blog/2021-09-27-v2-beta1-release-notes.md
Normal file
206
docs/src/content/docs/blog/2021-09-27-v2-beta1-release-notes.md
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
---
|
||||
slug: blog/wails-v2-beta-for-windows
|
||||
title: Wails v2 Beta for Windows
|
||||
authors: [leaanthony]
|
||||
tags: [wails, v2]
|
||||
date: 2021-09-27
|
||||
---
|
||||
|
||||

|
||||
|
||||
When I first announced Wails on Reddit, just over 2 years ago from a train in
|
||||
Sydney, I did not expect it to get much attention. A few days later, a prolific
|
||||
tech vlogger released a tutorial video, gave it a positive review and from that
|
||||
point on, interest in the project has skyrocketed.
|
||||
|
||||
It was clear that people were excited about adding web frontends to their Go
|
||||
projects, and almost immediately pushed the project beyond the proof of concept
|
||||
that I had created. At the time, Wails used the
|
||||
[webview](https://github.com/webview/webview) project to handle the frontend,
|
||||
and the only option for Windows was the IE11 renderer. Many bug reports were
|
||||
rooted in this limitation: poor JavaScript/CSS support and no dev tools to debug
|
||||
it. This was a frustrating development experience but there wasn't much that
|
||||
could have been done to rectify it.
|
||||
|
||||
For a long time, I'd firmly believed that Microsoft would eventually have to
|
||||
sort out their browser situation. The world was moving on, frontend development
|
||||
was booming and IE wasn't cutting it. When Microsoft announced the move to using
|
||||
Chromium as the basis for their new browser direction, I knew it was only a
|
||||
matter of time until Wails could use it, and move the Windows developer
|
||||
experience to the next level.
|
||||
|
||||
Today, I am pleased to announce: **Wails v2 Beta for Windows**! There's a huge
|
||||
amount to unpack in this release, so grab a drink, take a seat and we'll
|
||||
begin...
|
||||
|
||||
### No CGO Dependency!
|
||||
|
||||
No, I'm not joking: _No_ _CGO_ _dependency_ 🤯! The thing about Windows is that,
|
||||
unlike MacOS and Linux, it doesn't come with a default compiler. In addition,
|
||||
CGO requires a mingw compiler and there's a ton of different installation
|
||||
options. Removing the CGO requirement has massively simplified setup, as well as
|
||||
making debugging an awful lot easier. Whilst I have put a fair bit of effort in
|
||||
getting this working, the majority of the credit should go to
|
||||
[John Chadwick](https://github.com/jchv) for not only starting a couple of
|
||||
projects to make this possible, but also being open to someone taking those
|
||||
projects and building on them. Credit also to
|
||||
[Tad Vizbaras](https://github.com/tadvi) whose
|
||||
[winc](https://github.com/tadvi/winc) project started me down this path.
|
||||
|
||||
### WebView2 Chromium Renderer
|
||||
|
||||

|
||||
|
||||
Finally, Windows developers get a first class rendering engine for their
|
||||
applications! Gone are the days of contorting your frontend code to work on
|
||||
Windows. On top of that, you get a first-class developer tools experience!
|
||||
|
||||
The WebView2 component does, however, have a requirement to have the
|
||||
`WebView2Loader.dll` sitting alongside the binary. This makes distribution just
|
||||
that little bit more painful than we gophers are used to. All solutions and
|
||||
libraries (that I know of) that use WebView2 have this dependency.
|
||||
|
||||
However, I'm really excited to announce that Wails applications _have no such
|
||||
requirement_! Thanks to the wizardry of
|
||||
[John Chadwick](https://github.com/jchv), we are able to bundle this dll inside
|
||||
the binary and get Windows to load it as if it were present on disk.
|
||||
|
||||
Gophers rejoice! The single binary dream lives on!
|
||||
|
||||
### New Features
|
||||
|
||||

|
||||
|
||||
There were a lot of requests for native menu support. Wails has finally got you
|
||||
covered. Application menus are now available and include support for most native
|
||||
menu features. This includes standard menu items, checkboxes, radio groups,
|
||||
submenus and separators.
|
||||
|
||||
There were a huge number of requests in v1 for the ability to have greater
|
||||
control of the window itself. I'm happy to announce that there's new runtime
|
||||
APIs specifically for this. It's feature-rich and supports multi-monitor
|
||||
configurations. There is also an improved dialogs API: Now, you can have modern,
|
||||
native dialogs with rich configuration to cater for all your dialog needs.
|
||||
|
||||
There is now the option to generate IDE configuration along with your project.
|
||||
This means that if you open your project in a supported IDE, it will already be
|
||||
configured for building and debugging your application. Currently VSCode is
|
||||
supported but we hope to support other IDEs such as Goland soon.
|
||||
|
||||

|
||||
|
||||
### No requirement to bundle assets
|
||||
|
||||
A huge pain-point of v1 was the need to condense your entire application down to
|
||||
single JS & CSS files. I'm happy to announce that for v2, there is no
|
||||
requirement to bundle assets, in any way, shape or form. Want to load a local
|
||||
image? Use an `<img>` tag with a local src path. Want to use a cool font? Copy
|
||||
it in and add the path to it in your CSS.
|
||||
|
||||
> Wow, that sounds like a webserver...
|
||||
|
||||
Yes, it works just like a webserver, except it isn't.
|
||||
|
||||
> So how do I include my assets?
|
||||
|
||||
You just pass a single `embed.FS` that contains all your assets into your
|
||||
application configuration. They don't even need to be in the top directory -
|
||||
Wails will just work it out for you.
|
||||
|
||||
### New Development Experience
|
||||
|
||||

|
||||
|
||||
Now that assets don't need to be bundled, it's enabled a whole new development
|
||||
experience. The new `wails dev` command will build and run your application, but
|
||||
instead of using the assets in the `embed.FS`, it loads them directly from disk.
|
||||
|
||||
It also provides the additional features:
|
||||
|
||||
- Hot reload - Any changes to frontend assets will trigger and auto reload of
|
||||
the application frontend
|
||||
- Auto rebuild - Any changes to your Go code will rebuild and relaunch your
|
||||
application
|
||||
|
||||
In addition to this, a webserver will start on port 34115. This will serve your
|
||||
application to any browser that connects to it. All connected web browsers will
|
||||
respond to system events like hot reload on asset change.
|
||||
|
||||
In Go, we are used to dealing with structs in our applications. It's often
|
||||
useful to send structs to our frontend and use them as state in our application.
|
||||
In v1, this was a very manual process and a bit of a burden on the developer.
|
||||
I'm happy to announce that in v2, any application run in dev mode will
|
||||
automatically generate TypeScript models for all structs that are input or
|
||||
output parameters to bound methods. This enables seamless interchange of data
|
||||
models between the two worlds.
|
||||
|
||||
In addition to this, another JS module is dynamically generated wrapping all
|
||||
your bound methods. This provides JSDoc for your methods, providing code
|
||||
completion and hinting in your IDE. It's really cool when you get data models
|
||||
auto-imported when hitting tab in an auto-generated module wrapping your Go
|
||||
code!
|
||||
|
||||
### Remote Templates
|
||||
|
||||

|
||||
|
||||
Getting an application up and running quickly was always a key goal for the
|
||||
Wails project. When we launched, we tried to cover a lot of the modern
|
||||
frameworks at the time: react, vue and angular. The world of frontend
|
||||
development is very opinionated, fast moving and hard to keep on top of! As a
|
||||
result, we found our base templates getting out of date pretty quickly and this
|
||||
caused a maintenance headache. It also meant that we didn't have cool modern
|
||||
templates for the latest and greatest tech stacks.
|
||||
|
||||
With v2, I wanted to empower the community by giving you the ability to create
|
||||
and host templates yourselves, rather than rely on the Wails project. So now you
|
||||
can create projects using community supported templates! I hope this will
|
||||
inspire developers to create a vibrant ecosystem of project templates. I'm
|
||||
really quite excited about what our developer community can create!
|
||||
|
||||
### In Conclusion
|
||||
|
||||
Wails v2 represents a new foundation for the project. The aim of this release is
|
||||
to get feedback on the new approach, and to iron out any bugs before a full
|
||||
release. Your input would be most welcome. Please direct any feedback to the
|
||||
[v2 Beta](https://github.com/wailsapp/wails/discussions/828) discussion board.
|
||||
|
||||
There were many twists and turns, pivots and u-turns to get to this point. This
|
||||
was due partly to early technical decisions that needed changing, and partly
|
||||
because some core problems we had spent time building workarounds for were fixed
|
||||
upstream: Go’s embed feature is a good example. Fortunately, everything came
|
||||
together at the right time, and today we have the very best solution that we can
|
||||
have. I believe the wait has been worth it - this would not have been possible
|
||||
even 2 months ago.
|
||||
|
||||
I also need to give a huge thank you :pray: to the following people because
|
||||
without them, this release just wouldn't exist:
|
||||
|
||||
- [Misite Bao](https://github.com/misitebao) - An absolute workhorse on the
|
||||
Chinese translations and an incredible bug finder.
|
||||
- [John Chadwick](https://github.com/jchv) - His amazing work on
|
||||
[go-webview2](https://github.com/jchv/go-webview2) and
|
||||
[go-winloader](https://github.com/jchv/go-winloader) have made the Windows
|
||||
version we have today possible.
|
||||
- [Tad Vizbaras](https://github.com/tadvi) - Experimenting with his
|
||||
[winc](https://github.com/tadvi/winc) project was the first step down the path
|
||||
to a pure Go Wails.
|
||||
- [Mat Ryer](https://github.com/matryer) - His support, encouragement and
|
||||
feedback has really helped drive the project forward.
|
||||
|
||||
And finally, I'd like to give a special thank you to all the
|
||||
[project sponsors](/credits#sponsors), including
|
||||
[JetBrains](https://www.jetbrains.com?from=Wails), whose support drives the
|
||||
project in many ways behind the scenes.
|
||||
|
||||
I look forward to seeing what people build with Wails in this next exciting
|
||||
phase of the project!
|
||||
|
||||
Lea.
|
||||
|
||||
PS: MacOS and Linux users need not feel left out - porting to this new
|
||||
foundation is actively under way and most of the hard work has already been
|
||||
done. Hang in there!
|
||||
|
||||
PPS: If you or your company find Wails useful, please consider
|
||||
[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks!
|
||||
166
docs/src/content/docs/blog/2021-11-08-v2-beta2-release-notes.md
Normal file
166
docs/src/content/docs/blog/2021-11-08-v2-beta2-release-notes.md
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
---
|
||||
slug: blog/wails-v2-beta-for-mac
|
||||
title: Wails v2 Beta for MacOS
|
||||
authors: [leaanthony]
|
||||
tags: [wails, v2]
|
||||
date: 2021-11-08
|
||||
---
|
||||
|
||||

|
||||
|
||||
Today marks the first beta release of Wails v2 for Mac! It's taken quite a while
|
||||
to get to this point and I'm hoping that today's release will give you something
|
||||
that's reasonably useful. There have been a number of twists and turns to get to
|
||||
this point and I'm hoping, with your help, to iron out the crinkles and get the
|
||||
Mac port polished for the final v2 release.
|
||||
|
||||
You mean this isn't ready for production? For your use case, it may well be
|
||||
ready, but there are still a number of known issues so keep your eye on
|
||||
[this project board](https://github.com/wailsapp/wails/projects/7) and if you
|
||||
would like to contribute, you'd be very welcome!
|
||||
|
||||
So what's new for Wails v2 for Mac vs v1? Hint: It's pretty similar to the
|
||||
Windows Beta :wink:
|
||||
|
||||
### New Features
|
||||
|
||||

|
||||
|
||||
There were a lot of requests for native menu support. Wails has finally got you
|
||||
covered. Application menus are now available and include support for most native
|
||||
menu features. This includes standard menu items, checkboxes, radio groups,
|
||||
submenus and separators.
|
||||
|
||||
There were a huge number of requests in v1 for the ability to have greater
|
||||
control of the window itself. I'm happy to announce that there's new runtime
|
||||
APIs specifically for this. It's feature-rich and supports multi-monitor
|
||||
configurations. There is also an improved dialogs API: Now, you can have modern,
|
||||
native dialogs with rich configuration to cater for all your dialog needs.
|
||||
|
||||
### Mac Specific Options
|
||||
|
||||
In addition to the normal application options, Wails v2 for Mac also brings some
|
||||
Mac extras:
|
||||
|
||||
- Make your window all funky and translucent, like all the pretty swift apps!
|
||||
- Highly customisable titlebar
|
||||
- We support the NSAppearance options for the application
|
||||
- Simple config to auto-create an "About" menu
|
||||
|
||||
### No requirement to bundle assets
|
||||
|
||||
A huge pain-point of v1 was the need to condense your entire application down to
|
||||
single JS & CSS files. I'm happy to announce that for v2, there is no
|
||||
requirement to bundle assets, in any way, shape or form. Want to load a local
|
||||
image? Use an `<img>` tag with a local src path. Want to use a cool font? Copy
|
||||
it in and add the path to it in your CSS.
|
||||
|
||||
> Wow, that sounds like a webserver...
|
||||
|
||||
Yes, it works just like a webserver, except it isn't.
|
||||
|
||||
> So how do I include my assets?
|
||||
|
||||
You just pass a single `embed.FS` that contains all your assets into your
|
||||
application configuration. They don't even need to be in the top directory -
|
||||
Wails will just work it out for you.
|
||||
|
||||
### New Development Experience
|
||||
|
||||
Now that assets don't need to be bundled, it's enabled a whole new development
|
||||
experience. The new `wails dev` command will build and run your application, but
|
||||
instead of using the assets in the `embed.FS`, it loads them directly from disk.
|
||||
|
||||
It also provides the additional features:
|
||||
|
||||
- Hot reload - Any changes to frontend assets will trigger and auto reload of
|
||||
the application frontend
|
||||
- Auto rebuild - Any changes to your Go code will rebuild and relaunch your
|
||||
application
|
||||
|
||||
In addition to this, a webserver will start on port 34115. This will serve your
|
||||
application to any browser that connects to it. All connected web browsers will
|
||||
respond to system events like hot reload on asset change.
|
||||
|
||||
In Go, we are used to dealing with structs in our applications. It's often
|
||||
useful to send structs to our frontend and use them as state in our application.
|
||||
In v1, this was a very manual process and a bit of a burden on the developer.
|
||||
I'm happy to announce that in v2, any application run in dev mode will
|
||||
automatically generate TypeScript models for all structs that are input or
|
||||
output parameters to bound methods. This enables seamless interchange of data
|
||||
models between the two worlds.
|
||||
|
||||
In addition to this, another JS module is dynamically generated wrapping all
|
||||
your bound methods. This provides JSDoc for your methods, providing code
|
||||
completion and hinting in your IDE. It's really cool when you get data models
|
||||
auto-imported when hitting tab in an auto-generated module wrapping your Go
|
||||
code!
|
||||
|
||||
### Remote Templates
|
||||
|
||||

|
||||
|
||||
Getting an application up and running quickly was always a key goal for the
|
||||
Wails project. When we launched, we tried to cover a lot of the modern
|
||||
frameworks at the time: react, vue and angular. The world of frontend
|
||||
development is very opinionated, fast moving and hard to keep on top of! As a
|
||||
result, we found our base templates getting out of date pretty quickly and this
|
||||
caused a maintenance headache. It also meant that we didn't have cool modern
|
||||
templates for the latest and greatest tech stacks.
|
||||
|
||||
With v2, I wanted to empower the community by giving you the ability to create
|
||||
and host templates yourselves, rather than rely on the Wails project. So now you
|
||||
can create projects using community supported templates! I hope this will
|
||||
inspire developers to create a vibrant ecosystem of project templates. I'm
|
||||
really quite excited about what our developer community can create!
|
||||
|
||||
### Native M1 Support
|
||||
|
||||
Thanks to the amazing support of [Mat Ryer](https://github.com/matryer/), the
|
||||
Wails project now supports M1 native builds:
|
||||
|
||||

|
||||
|
||||
You can also specify `darwin/amd64` as a target too:
|
||||
|
||||

|
||||
|
||||
Oh, I almost forgot.... you can also do `darwin/universal`.... :wink:
|
||||
|
||||

|
||||
|
||||
### Cross Compilation to Windows
|
||||
|
||||
Because Wails v2 for Windows is pure Go, you can target Windows builds without
|
||||
docker.
|
||||
|
||||

|
||||
bu
|
||||
|
||||
### WKWebView Renderer
|
||||
|
||||
V1 relied on a (now deprecated) WebView component. V2 uses the most recent
|
||||
WKWebKit component so expect the latest and greatest from Apple.
|
||||
|
||||
### In Conclusion
|
||||
|
||||
As I'd said in the Windows release notes, Wails v2 represents a new foundation
|
||||
for the project. The aim of this release is to get feedback on the new approach,
|
||||
and to iron out any bugs before a full release. Your input would be most
|
||||
welcome! Please direct any feedback to the
|
||||
[v2 Beta](https://github.com/wailsapp/wails/discussions/828) discussion board.
|
||||
|
||||
And finally, I'd like to give a special thank you to all the
|
||||
[project sponsors](/credits#sponsors), including
|
||||
[JetBrains](https://www.jetbrains.com?from=Wails), whose support drive the
|
||||
project in many ways behind the scenes.
|
||||
|
||||
I look forward to seeing what people build with Wails in this next exciting
|
||||
phase of the project!
|
||||
|
||||
Lea.
|
||||
|
||||
PS: Linux users, you're next!
|
||||
|
||||
PPS: If you or your company find Wails useful, please consider
|
||||
[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks!
|
||||
129
docs/src/content/docs/blog/2022-02-22-v2-beta3-release-notes.md
Normal file
129
docs/src/content/docs/blog/2022-02-22-v2-beta3-release-notes.md
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
---
|
||||
slug: blog/wails-v2-beta-for-linux
|
||||
title: Wails v2 Beta for Linux
|
||||
authors: [leaanthony]
|
||||
tags: [wails, v2]
|
||||
date: 2022-02-22
|
||||
---
|
||||
|
||||

|
||||
|
||||
I'm pleased to finally announce that Wails v2 is now in beta for Linux! It is
|
||||
somewhat ironic that the very first experiments with v2 was on Linux and yet it
|
||||
has ended up as the last release. That being said, the v2 we have today is very
|
||||
different from those first experiments. So without further ado, let's go over
|
||||
the new features:
|
||||
|
||||
### New Features
|
||||
|
||||

|
||||
|
||||
There were a lot of requests for native menu support. Wails has finally got you
|
||||
covered. Application menus are now available and include support for most native
|
||||
menu features. This includes standard menu items, checkboxes, radio groups,
|
||||
submenus and separators.
|
||||
|
||||
There were a huge number of requests in v1 for the ability to have greater
|
||||
control of the window itself. I'm happy to announce that there's new runtime
|
||||
APIs specifically for this. It's feature-rich and supports multi-monitor
|
||||
configurations. There is also an improved dialogs API: Now, you can have modern,
|
||||
native dialogs with rich configuration to cater for all your dialog needs.
|
||||
|
||||
### No requirement to bundle assets
|
||||
|
||||
A huge pain-point of v1 was the need to condense your entire application down to
|
||||
single JS & CSS files. I'm happy to announce that for v2, there is no
|
||||
requirement to bundle assets, in any way, shape or form. Want to load a local
|
||||
image? Use an `<../../../assets/blog-images>` tag with a local src path. Want to
|
||||
use a cool font? Copy it in and add the path to it in your CSS.
|
||||
|
||||
> Wow, that sounds like a webserver...
|
||||
|
||||
Yes, it works just like a webserver, except it isn't.
|
||||
|
||||
> So how do I include my assets?
|
||||
|
||||
You just pass a single `embed.FS` that contains all your assets into your
|
||||
application configuration. They don't even need to be in the top directory -
|
||||
Wails will just work it out for you.
|
||||
|
||||
### New Development Experience
|
||||
|
||||
Now that assets don't need to be bundled, it's enabled a whole new development
|
||||
experience. The new `wails dev` command will build and run your application, but
|
||||
instead of using the assets in the `embed.FS`, it loads them directly from disk.
|
||||
|
||||
It also provides the additional features:
|
||||
|
||||
- Hot reload - Any changes to frontend assets will trigger an auto reload of the
|
||||
application frontend
|
||||
- Auto rebuild - Any changes to your Go code will rebuild and relaunch your
|
||||
application
|
||||
|
||||
In addition to this, a webserver will start on port 34115. This will serve your
|
||||
application to any browser that connects to it. All connected web browsers will
|
||||
respond to system events like hot reload on asset change.
|
||||
|
||||
In Go, we are used to dealing with structs in our applications. It's often
|
||||
useful to send structs to our frontend and use them as state in our application.
|
||||
In v1, this was a very manual process and a bit of a burden on the developer.
|
||||
I'm happy to announce that in v2, any application run in dev mode will
|
||||
automatically generate TypeScript models for all structs that are input or
|
||||
output parameters to bound methods. This enables seamless interchange of data
|
||||
models between the two worlds.
|
||||
|
||||
In addition to this, another JS module is dynamically generated wrapping all
|
||||
your bound methods. This provides JSDoc for your methods, providing code
|
||||
completion and hinting in your IDE. It's really cool when you get data models
|
||||
auto-imported when hitting tab in an auto-generated module wrapping your Go
|
||||
code!
|
||||
|
||||
### Remote Templates
|
||||
|
||||

|
||||
|
||||
Getting an application up and running quickly was always a key goal for the
|
||||
Wails project. When we launched, we tried to cover a lot of the modern
|
||||
frameworks at the time: react, vue and angular. The world of frontend
|
||||
development is very opinionated, fast moving and hard to keep on top of! As a
|
||||
result, we found our base templates getting out of date pretty quickly and this
|
||||
caused a maintenance headache. It also meant that we didn't have cool modern
|
||||
templates for the latest and greatest tech stacks.
|
||||
|
||||
With v2, I wanted to empower the community by giving you the ability to create
|
||||
and host templates yourselves, rather than rely on the Wails project. So now you
|
||||
can create projects using community supported templates! I hope this will
|
||||
inspire developers to create a vibrant ecosystem of project templates. I'm
|
||||
really quite excited about what our developer community can create!
|
||||
|
||||
### Cross Compilation to Windows
|
||||
|
||||
Because Wails v2 for Windows is pure Go, you can target Windows builds without
|
||||
docker.
|
||||
|
||||

|
||||
|
||||
### In Conclusion
|
||||
|
||||
As I'd said in the Windows release notes, Wails v2 represents a new foundation
|
||||
for the project. The aim of this release is to get feedback on the new approach,
|
||||
and to iron out any bugs before a full release. Your input would be most
|
||||
welcome! Please direct any feedback to the
|
||||
[v2 Beta](https://github.com/wailsapp/wails/discussions/828) discussion board.
|
||||
|
||||
Linux is **hard** to support. We expect there to be a number of quirks with the
|
||||
beta. Please help us to help you by filing detailed bug reports!
|
||||
|
||||
Finally, I'd like to give a special thank you to all the
|
||||
[project sponsors](/credits#sponsors) whose support drives the project in many
|
||||
ways behind the scenes.
|
||||
|
||||
I look forward to seeing what people build with Wails in this next exciting
|
||||
phase of the project!
|
||||
|
||||
Lea.
|
||||
|
||||
PS: The v2 release isn't far off now!
|
||||
|
||||
PPS: If you or your company find Wails useful, please consider
|
||||
[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks!
|
||||
198
docs/src/content/docs/blog/2022-09-22-v2-release-notes.md
Normal file
198
docs/src/content/docs/blog/2022-09-22-v2-release-notes.md
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
---
|
||||
slug: blog/wails-v2-released
|
||||
title: Wails v2 Released
|
||||
authors: [leaanthony]
|
||||
tags: [wails, v2]
|
||||
date: 2022-09-22
|
||||
---
|
||||
|
||||

|
||||
|
||||
# It's here!
|
||||
|
||||
Today marks the release of [Wails](https://wails.io) v2. It's been about 18
|
||||
months since the first v2 alpha and about a year from the first beta release.
|
||||
I'm truly grateful to everyone involved in the evolution of the project.
|
||||
|
||||
Part of the reason it took that long was due to wanting to get to some
|
||||
definition of completeness before officially calling it v2. The truth is,
|
||||
there's never a perfect time to tag a release - there's always outstanding
|
||||
issues or "just one more" feature to squeeze in. What tagging an imperfect major
|
||||
release does do, however, is to provide a bit of stability for users of the
|
||||
project, as well as a bit of a reset for the developers.
|
||||
|
||||
This release is more than I'd ever expected it to be. I hope it gives you as
|
||||
much pleasure as it has given us to develop it.
|
||||
|
||||
# What _is_ Wails?
|
||||
|
||||
If you are unfamiliar with Wails, it is a project that enables Go programmers to
|
||||
provide rich frontends for their Go programs using familiar web technologies.
|
||||
It's a lightweight, Go alternative to Electron. Much more information can be
|
||||
found on the [official site](https://wails.io/docs/introduction).
|
||||
|
||||
# What's new?
|
||||
|
||||
The v2 release is a huge leap forward for the project, addressing many of the
|
||||
pain points of v1. If you have not read any of the blog posts on the Beta
|
||||
releases for [macOS](/blog/wails-v2-beta-for-mac),
|
||||
[Windows](/blog/wails-v2-beta-for-windows) or
|
||||
[Linux](/blog/wails-v2-beta-for-linux), then I encourage you to do so as it
|
||||
covers all the major changes in more detail. In summary:
|
||||
|
||||
- Webview2 component for Windows that supports modern web standards and
|
||||
debugging capabilities.
|
||||
- [Dark / Light theme](https://wails.io/docs/reference/options#theme) +
|
||||
[custom theming](https://wails.io/docs/reference/options#customtheme) on Windows.
|
||||
- Windows now has no CGO requirements.
|
||||
- Out-of-the-box support for Svelte, Vue, React, Preact, Lit & Vanilla project
|
||||
templates.
|
||||
- [Vite](https://vitejs.dev/) integration providing a hot-reload development
|
||||
environment for your application.
|
||||
- Native application
|
||||
[menus](https://wails.io/docs/guides/application-development#application-menu) and
|
||||
[dialogs](https://wails.io/docs/reference/runtime/dialog).
|
||||
- Native window translucency effects for
|
||||
[Windows](https://wails.io/docs/reference/options#windowistranslucent) and
|
||||
[macOS](https://wails.io/docs/reference/options#windowistranslucent-1). Support for Mica &
|
||||
Acrylic backdrops.
|
||||
- Easily generate an [NSIS installer](https://wails.io/docs/guides/windows-installer) for
|
||||
Windows deployments.
|
||||
- A rich [runtime library](https://wails.io/docs/reference/runtime/intro) providing utility
|
||||
methods for window manipulation, eventing, dialogs, menus and logging.
|
||||
- Support for [obfuscating](https://wails.io/docs/guides/obfuscated) your application using
|
||||
[garble](https://github.com/burrowers/garble).
|
||||
- Support for compressing your application using [UPX](https://upx.github.io/).
|
||||
- Automatic TypeScript generation of Go structs. More info
|
||||
[here](https://wails.io/docs/howdoesitwork#calling-bound-go-methods).
|
||||
- No extra libraries or DLLs are required to be shipped with your application.
|
||||
For any platform.
|
||||
- No requirement to bundle frontend assets. Just develop your application like
|
||||
any other web application.
|
||||
|
||||
# Credit & Thanks
|
||||
|
||||
Getting to v2 has been a huge effort. There have been ~2.2K commits by 89
|
||||
contributors between the initial alpha and the release today, and many, many
|
||||
more that have provided translations, testing, feedback and help on the
|
||||
discussion forums as well as the issue tracker. I'm so unbelievably grateful to
|
||||
each one of you. I'd also like to give an extra special thank you to all the
|
||||
project sponsors who have provided guidance, advice and feedback. Everything you
|
||||
do is hugely appreciated.
|
||||
|
||||
There are a few people I'd like to give special mention to:
|
||||
|
||||
Firstly, a **huge** thank you to [@stffabi](https://github.com/stffabi) who has
|
||||
provided so many contributions which we all benefit from, as well as providing a
|
||||
lot of support on many issues. He has provided some key features such as the
|
||||
external dev server support which transformed our dev mode offering by allowing
|
||||
us to hook into [Vite](https://vitejs.dev/)'s superpowers. It's fair to say that
|
||||
Wails v2 would be a far less exciting release without his
|
||||
[incredible contributions](https://github.com/wailsapp/wails/commits?author=stffabi&since=2020-01-04).
|
||||
Thank you so much @stffabi!
|
||||
|
||||
I'd also like to give a huge shout-out to
|
||||
[@misitebao](https://github.com/misitebao) who has tirelessly been maintaining
|
||||
the website, as well as providing Chinese translations, managing Crowdin and
|
||||
helping new translators get up to speed. This is a hugely important task, and
|
||||
I'm extremely grateful for all the time and effort put into this! You rock!
|
||||
|
||||
Last, but not least, a huge thank you to Mat Ryer who has provided advice and
|
||||
support during the development of v2. Writing xBar together using an early Alpha
|
||||
of v2 was helpful in shaping the direction of v2, as well as give me an
|
||||
understanding of some design flaws in the early releases. I'm happy to announce
|
||||
that as of today, we will start to port xBar to Wails v2, and it will become the
|
||||
flagship application for the project. Cheers Mat!
|
||||
|
||||
# Lessons Learnt
|
||||
|
||||
There are a number of lessons learnt in getting to v2 that will shape
|
||||
development moving forward.
|
||||
|
||||
## Smaller, Quicker, Focused Releases
|
||||
|
||||
In the course of developing v2, there were many features and bug fixes that were
|
||||
developed on an ad-hoc basis. This led to longer release cycles and were harder
|
||||
to debug. Moving forward, we are going to create releases more often that will
|
||||
include a reduced number of features. A release will involve updates to
|
||||
documentation as well as thorough testing. Hopefully, these smaller, quicker,
|
||||
focussed releases will lead to fewer regressions and better quality
|
||||
documentation.
|
||||
|
||||
## Encourage Engagement
|
||||
|
||||
When starting this project, I wanted to immediately help everyone who had a
|
||||
problem. Issues were "personal" and I wanted them resolved as quickly as
|
||||
possible. This is unsustainable and ultimately works against the longevity of
|
||||
the project. Moving forward, I will be giving more space for people to get
|
||||
involved in answering questions and triaging issues. It would be good to get
|
||||
some tooling to help with this so if you have any suggestions, please join in
|
||||
the discussion [here](https://github.com/wailsapp/wails/discussions/1855).
|
||||
|
||||
## Learning to say No
|
||||
|
||||
The more people that engage with an Open Source project, the more requests there
|
||||
will be for additional features that may or may not be useful to the majority of
|
||||
people. These features will take an initial amount of time to develop and debug,
|
||||
and incur an ongoing maintenance cost from that point on. I myself am the most
|
||||
guilty of this, often wanting to "boil the sea" rather than provide the minimum
|
||||
viable feature. Moving forward, we will need to say "No" a bit more to adding
|
||||
core features and focus our energies on a way to empower developers to provide
|
||||
that functionality themselves. We are looking seriously into plugins for this
|
||||
scenario. This will allow anyone to extend the project as they see fit, as well
|
||||
as providing an easy way to contribute towards the project.
|
||||
|
||||
# Looking to the Future
|
||||
|
||||
There are so many core features we are looking at to add to Wails in the next
|
||||
major development cycle already. The
|
||||
[roadmap](https://github.com/wailsapp/wails/discussions/1484) is full of
|
||||
interesting ideas, and I'm keen to start work on them. One of the big asks has
|
||||
been for multiple window support. It's a tricky one and to do it right, and we
|
||||
may need to look at providing an alternative API, as the current one was not
|
||||
designed with this in mind. Based on some preliminary ideas and feedback, I
|
||||
think you'll like where we're looking to go with it.
|
||||
|
||||
I'm personally very excited at the prospect of getting Wails apps running on
|
||||
mobile. We already have a demo project showing that it is possible to run a
|
||||
Wails app on Android, so I'm really keen to explore where we can go with this!
|
||||
|
||||
A final point I'd like to raise is that of feature parity. It has long been a
|
||||
core principle that we wouldn't add anything to the project without there being
|
||||
full cross-platform support for it. Whilst this has proven to be (mainly)
|
||||
achievable so far, it has really held the project back in releasing new
|
||||
features. Moving forward, we will be adopting a slightly different approach: any
|
||||
new feature that cannot be immediately released for all platforms will be
|
||||
released under an experimental configuration or API. This allows early adopters
|
||||
on certain platforms to try the feature and provide feedback that will feed into
|
||||
the final design of the feature. This, of course, means that there are no
|
||||
guarantees of API stability until it is fully supported by all the platforms it
|
||||
can be supported on, but at least it will unblock development.
|
||||
|
||||
# Final Words
|
||||
|
||||
I'm really proud of what we've been able to achieve with the V2 release. It's
|
||||
amazing to see what people have already been able to build using the beta
|
||||
releases so far. Quality applications like [Varly](https://varly.app/),
|
||||
[Surge](https://getsurge.io/) and [October](https://october.utf9k.net/). I
|
||||
encourage you to check them out.
|
||||
|
||||
This release was achieved through the hard work of many contributors. Whilst it
|
||||
is free to download and use, it has not come about through zero cost. Make no
|
||||
mistakes, this project has come at considerable cost. It has not only been my
|
||||
time and the time of each and every contributor, but also the cost of absence
|
||||
from friends and families of each of those people too. That's why I'm extremely
|
||||
grateful for every second that has been dedicated to making this project happen.
|
||||
The more contributors we have, the more this effort can be spread out and the
|
||||
more we can achieve together. I'd like to encourage you all to pick one thing
|
||||
that you can contribute, whether it is confirming someone's bug, suggesting a
|
||||
fix, making a documentation change or helping out someone who needs it. All of
|
||||
these small things have such a huge impact! It would be so awesome if you too
|
||||
were part of the story in getting to v3.
|
||||
|
||||
Enjoy!
|
||||
|
||||
‐ Lea
|
||||
|
||||
PS: If you or your company find Wails useful, please consider
|
||||
[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks!
|
||||
256
docs/src/content/docs/blog/2023-01-17-v3-roadmap.md
Normal file
256
docs/src/content/docs/blog/2023-01-17-v3-roadmap.md
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
---
|
||||
slug: blog/the-road-to-wails-v3
|
||||
title: The Road to Wails v3
|
||||
authors: [leaanthony]
|
||||
tags: [wails, v3]
|
||||
date: 2023-01-17
|
||||
---
|
||||
|
||||

|
||||
|
||||
# Introduction
|
||||
|
||||
Wails is a project that simplifies the ability to write cross-platform desktop
|
||||
applications using Go. It uses native webview components for the frontend (not
|
||||
embedded browsers), bringing the power of the world's most popular UI system to
|
||||
Go, whilst remaining lightweight.
|
||||
|
||||
Version 2 was released on the 22nd of September 2022 and brought with it a lot
|
||||
of enhancements including:
|
||||
|
||||
- Live development, leveraging the popular Vite project
|
||||
- Rich functionality for managing windows and creating menus
|
||||
- Microsoft's WebView2 component
|
||||
- Generation of Typescript models that mirror your Go structs
|
||||
- Creating of NSIS Installer
|
||||
- Obfuscated builds
|
||||
|
||||
Right now, Wails v2 provides powerful tooling for creating rich, cross-platform
|
||||
desktop applications.
|
||||
|
||||
This blog post aims to look at where the project is at right now and what we can
|
||||
improve on moving forward.
|
||||
|
||||
# Where are we now?
|
||||
|
||||
It's been incredible to see the popularity of Wails rising since the v2 release.
|
||||
I'm constantly amazed by the creativity of the community and the wonderful
|
||||
things that are being built with it. With more popularity, comes more eyes on
|
||||
the project. And with that, more feature requests and bug reports.
|
||||
|
||||
Over time, I've been able to identify some of the most pressing issues facing
|
||||
the project. I've also been able to identify some of the things that are holding
|
||||
the project back.
|
||||
|
||||
## Current issues
|
||||
|
||||
I've identified the following areas that I feel are holding the project back:
|
||||
|
||||
- The API
|
||||
- Bindings generation
|
||||
- The Build System
|
||||
|
||||
### The API
|
||||
|
||||
The API to build a Wails application currently consists of 2 parts:
|
||||
|
||||
- The Application API
|
||||
- The Runtime API
|
||||
|
||||
The Application API famously has only 1 function: `Run()` which takes a heap of
|
||||
options which govern how the application will work. Whilst this is very simple
|
||||
to use, it is also very limiting. It is a "declarative" approach which hides a
|
||||
lot of the underlying complexity. For instance, there is no handle to the main
|
||||
window, so you can't interact with it directly. For that, you need to use the
|
||||
Runtime API. This is a problem when you start to want to do more complex things
|
||||
like create multiple windows.
|
||||
|
||||
The Runtime API provides a lot of utility functions for the developer. This
|
||||
includes:
|
||||
|
||||
- Window management
|
||||
- Dialogs
|
||||
- Menus
|
||||
- Events
|
||||
- Logs
|
||||
|
||||
There are a number of things I am not happy with the Runtime API. The first is
|
||||
that it requires a "context" to be passed around. This is both frustrating and
|
||||
confusing for new developers who pass in a context and then get a runtime error.
|
||||
|
||||
The biggest issue with the Runtime API is that it was designed for applications
|
||||
that only use a single window. Over time, the demand for multiple windows has
|
||||
grown and the API is not well suited to this.
|
||||
|
||||
### Thoughts on the v3 API
|
||||
|
||||
Wouldn't it be great if we could do something like this?
|
||||
|
||||
```go
|
||||
func main() {
|
||||
app := wails.NewApplication(options.App{})
|
||||
myWindow := app.NewWindow(options.Window{})
|
||||
myWindow.SetTitle("My Window")
|
||||
myWindow.On(events.Window.Close, func() {
|
||||
app.Quit()
|
||||
})
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
This programmatic approach is far more intuitive and allows the developer to
|
||||
interact with the application elements directly. All current runtime methods for
|
||||
windows would simply be methods on the window object. For the other runtime
|
||||
methods, we could move them to the application object like so:
|
||||
|
||||
```go
|
||||
app := wails.NewApplication(options.App{})
|
||||
app.NewInfoDialog(options.InfoDialog{})
|
||||
app.Log.Info("Hello World")
|
||||
```
|
||||
|
||||
This is a much more powerful API which will allow for more complex applications
|
||||
to be built. It also allows for the creation of multiple windows,
|
||||
[the most up-voted feature on GitHub](https://github.com/wailsapp/wails/issues/1480):
|
||||
|
||||
```go
|
||||
func main() {
|
||||
app := wails.NewApplication(options.App{})
|
||||
myWindow := app.NewWindow(options.Window{})
|
||||
myWindow.SetTitle("My Window")
|
||||
myWindow.On(events.Window.Close, func() {
|
||||
app.Quit()
|
||||
})
|
||||
myWindow2 := app.NewWindow(options.Window{})
|
||||
myWindow2.SetTitle("My Window 2")
|
||||
myWindow2.On(events.Window.Close, func() {
|
||||
app.Quit()
|
||||
})
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
### Bindings generation
|
||||
|
||||
One of the key features of Wails is generating bindings for your Go methods so
|
||||
they may be called from Javascript. The current method for doing this is a bit
|
||||
of a hack. It involves building the application with a special flag and then
|
||||
running the resultant binary which uses reflection to determine what has been
|
||||
bound. This leads to a bit of a chicken and egg situation: You can't build the
|
||||
application without the bindings and you can't generate the bindings without
|
||||
building the application. There are many ways around this but the best one would
|
||||
be not to use this approach at all.
|
||||
|
||||
There were a number of attempts at writing a static analyser for Wails projects
|
||||
but they didn't get very far. In more recent times, it has become slightly
|
||||
easier to do this with more material available on the subject.
|
||||
|
||||
Compared to reflection, the AST approach is much faster however it is
|
||||
significantly more complicated. To start with, we may need to impose certain
|
||||
constraints on how to specify bindings in the code. The goal is to support the
|
||||
most common use cases and then expand it later on.
|
||||
|
||||
### The Build System
|
||||
|
||||
Like the declarative approach to the API, the build system was created to hide
|
||||
the complexities of building a desktop application. When you run `wails build`,
|
||||
it does a lot of things behind the scenes:
|
||||
|
||||
- Builds the backend binary for bindings and generates the bindings
|
||||
- Installs the frontend dependencies
|
||||
- Builds the frontend assets
|
||||
- Determines if the application icon is present and if so, embeds it
|
||||
- Builds the final binary
|
||||
- If the build is for `darwin/universal` it builds 2 binaries, one for
|
||||
`darwin/amd64` and one for `darwin/arm64` and then creates a fat binary using
|
||||
`lipo`
|
||||
- If compression is required, it compresses the binary with UPX
|
||||
- Determines if this binary is to be packaged and if so:
|
||||
- Ensures the icon and application manifest are compiled into the binary
|
||||
(Windows)
|
||||
- Builds out the application bundle, generates the icon bundle and copies it,
|
||||
the binary and Info.plist to the application bundle (Mac)
|
||||
- If an NSIS installer is required, it builds it
|
||||
|
||||
This entire process, whilst very powerful, is also very opaque. It is very
|
||||
difficult to customise it and it is very difficult to debug.
|
||||
|
||||
To address this in v3, I would like to move to a build system that exists
|
||||
outside of Wails. After using [Task](https://taskfile.dev/) for a while, I am a
|
||||
big fan of it. It is a great tool for configuring build systems and should be
|
||||
reasonably familiar to anyone who has used Makefiles.
|
||||
|
||||
The build system would be configured using a `Taskfile.yml` file which would be
|
||||
generated by default with any of the supported templates. This would have all of
|
||||
the steps required to do all the current tasks, such as building or packaging
|
||||
the application, allowing for easy customisation.
|
||||
|
||||
There will be no external requirement for this tooling as it would form part of
|
||||
the Wails CLI. This means that you can still use `wails build` and it will do
|
||||
all the things it does today. However, if you want to customise the build
|
||||
process, you can do so by editing the `Taskfile.yml` file. It also means you can
|
||||
easily understand the build steps and use your own build system if you wish.
|
||||
|
||||
The missing piece in the build puzzle is the atomic operations in the build
|
||||
process, such as icon generation, compression and packaging. To require a bunch
|
||||
of external tooling would not be a great experience for the developer. To
|
||||
address this, the Wails CLI will provide all these capabilities as part of the
|
||||
CLI. This means that the builds still work as expected, with no extra external
|
||||
tooling, however you can replace any step of the build with any tool you like.
|
||||
|
||||
This will be a much more transparent build system which will allow for easier
|
||||
customisation and address a lot of the issues that have been raised around it.
|
||||
|
||||
## The Payoff
|
||||
|
||||
These positive changes will be a huge benefit to the project:
|
||||
|
||||
- The new API will be much more intuitive and will allow for more complex
|
||||
applications to be built.
|
||||
- Using static analysis for bindings generation will be much faster and reduce a
|
||||
lot of the complexity around the current process.
|
||||
- Using an established, external build system will make the build process
|
||||
completely transparent, allowing for powerful customisation.
|
||||
|
||||
Benefits to the project maintainers are:
|
||||
|
||||
- The new API will be much easier to maintain and adapt to new features and
|
||||
platforms.
|
||||
- The new build system will be much easier to maintain and extend. I hope this
|
||||
will lead to a new ecosystem of community driven build pipelines.
|
||||
- Better separation of concerns within the project. This will make it easier to
|
||||
add new features and platforms.
|
||||
|
||||
## The Plan
|
||||
|
||||
A lot of the experimentation for this has already been done and it's looking
|
||||
good. There is no current timeline for this work but I'm hoping by the end of Q1
|
||||
2023, there will be an alpha release for Mac to allow the community to test,
|
||||
experiment with and provide feedback.
|
||||
|
||||
## Summary
|
||||
|
||||
- The v2 API is declarative, hides a lot from the developer and not suitable for
|
||||
features such as multiple windows. A new API will be created which will be
|
||||
simpler, intuitive and more powerful.
|
||||
- The build system is opaque and difficult to customise so we will move to an
|
||||
external build system which will open it all up.
|
||||
- The bindings generation is slow and complex so we will move to static analysis
|
||||
which will remove a lot of the complexity the current method has.
|
||||
|
||||
There has been a lot of work put into the guts of v2 and it's solid. It's now
|
||||
time to address the layer on top of it and make it a much better experience for
|
||||
the developer.
|
||||
|
||||
I hope you are as excited about this as I am. I'm looking forward to hearing
|
||||
your thoughts and feedback.
|
||||
|
||||
Regards,
|
||||
|
||||
‐ Lea
|
||||
|
||||
PS: If you or your company find Wails useful, please consider
|
||||
[sponsoring the project](https://github.com/sponsors/leaanthony). Thanks!
|
||||
|
||||
PPS: Yes, that's a genuine screenshot of a multi-window application built with
|
||||
Wails. It's not a mockup. It's real. It's awesome. It's coming soon.
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
title: Alpha 10 Released - A New Chapter for Wails
|
||||
description: Announcing Wails v3 Alpha 10 and our new daily release strategy
|
||||
date: 2024-12-03
|
||||
author: The Wails Team
|
||||
---
|
||||
|
||||
# Alpha 10 Released - A New Chapter for Wails
|
||||
|
||||
We're thrilled to announce the release of **Wails v3 Alpha 10** - and it's big! While our release cadence may have seemed slow recently, there's been an incredible amount of work happening behind the scenes. Today marks not just another release, but a shift in how we approach the development of Wails.
|
||||
|
||||
## Why the Wait?
|
||||
|
||||
Like many development teams, we fell into the trap of trying to achieve perfection before each release. We wanted to squash every bug, polish every feature, and ensure everything was just right. But here's the thing - software is never truly bug-free, and waiting for that mythical state only delays getting improvements into your hands.
|
||||
|
||||
## Our New Release Strategy
|
||||
|
||||
Starting today, we're adopting a **daily release strategy**. Any new changes merged during the day will be released at the end of that day. This approach will:
|
||||
|
||||
- Get bug fixes and features to you faster
|
||||
- Provide more frequent feedback loops
|
||||
- Speed up our journey to Beta
|
||||
- Make the development process more transparent
|
||||
|
||||
This means you might see more frequent, smaller releases rather than occasional large ones. We believe this will benefit everyone in the Wails community.
|
||||
|
||||
## How You Can Help
|
||||
|
||||
We've been overwhelmed by the number of people wanting to contribute to Wails! To make it easier for contributors to get involved, we're implementing a new system:
|
||||
|
||||
- Firstly, we are opening up bug reporting for alpha releases. The frequent releases allow us to do this.
|
||||
- Issues marked with **"Ready for Work"** are open for community contributions
|
||||
- These issues have clear requirements and are ready to be tackled
|
||||
- Most importantly, **we need people testing PRs** - this is one of the most valuable contributions you can make
|
||||
|
||||
Testing doesn't require deep knowledge of the codebase, but it provides immense value by ensuring changes work across different environments and use cases.
|
||||
|
||||
## What's Next?
|
||||
|
||||
With our new daily release strategy, expect to see rapid progress toward Beta. We're committed to maintaining momentum and getting Wails v3 to a stable release as quickly as possible without sacrificing quality.
|
||||
|
||||
And who knows? There might be a few surprises along the way... 😉
|
||||
|
||||
## Thank You
|
||||
|
||||
To everyone who has contributed code, tested releases, reported bugs, or simply used Wails - thank you. Your support and feedback drive this project forward.
|
||||
|
||||
Here's to more frequent releases, faster iteration, and an exciting journey ahead!
|
||||
|
||||
---
|
||||
|
||||
*Want to get involved? Check out our [GitHub repository](https://github.com/wailsapp/wails) for issues marked "ready for work", or join our community to help test the latest changes.*
|
||||
802
docs/src/content/docs/changelog.mdx
Normal file
802
docs/src/content/docs/changelog.mdx
Normal file
|
|
@ -0,0 +1,802 @@
|
|||
---
|
||||
title: Changelog
|
||||
---
|
||||
|
||||
|
||||
Legend:
|
||||
- - macOS
|
||||
- ⊞ - Windows
|
||||
- 🐧 - Linux
|
||||
|
||||
/*--
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
- `Added` for new features.
|
||||
- `Changed` for changes in existing functionality.
|
||||
- `Deprecated` for soon-to-be removed features.
|
||||
- `Removed` for now removed features.
|
||||
- `Fixed` for any bug fixes.
|
||||
- `Security` in case of vulnerabilities.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
** PLEASE DO NOT UPDATE THIS FILE **
|
||||
Updates should be added to `v3/UNRELEASED_CHANGELOG.md`
|
||||
Thank you!
|
||||
*/
|
||||
## [Unreleased]
|
||||
|
||||
## v3.0.0-alpha.31 - 2025-09-27
|
||||
|
||||
## Fixed
|
||||
- Windows: Flicker of window at start and hidden windows being shown incorrectly in [PR](https://github.com/wailsapp/wails/pull/4600) by @leaanthony.
|
||||
- Fixed Wayland window size maximising issues (https://github.com/wailsapp/wails/issues/4429) by [@samstanier](https://github.com/samstanier)
|
||||
|
||||
## v3.0.0-alpha.30 - 2025-09-26
|
||||
|
||||
## Fixed
|
||||
- Fixed Wayland window size maximising issues (https://github.com/wailsapp/wails/issues/4429) by [@samstanier](https://github.com/samstanier)
|
||||
|
||||
## v3.0.0-alpha.29 - 2025-09-25
|
||||
|
||||
## Added
|
||||
- macOS: Shows native window controls in the menu bar in [#4588](https://github.com/wailsapp/wails/pull/4588) by @nidib
|
||||
- Add macOS Dock service to hide/show app icon in the dock @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4451)
|
||||
|
||||
## Changed
|
||||
- macOS: Use `visibleFrame` instead of `frame` for window centering to exclude menu bar and dock areas
|
||||
|
||||
## Fixed
|
||||
- Fixed redefinition error for liquid glass demo in [#4542](https://github.com/wailsapp/wails/pull/4542) by @Etesam913
|
||||
- Fixed issue where AssetServer can crash on MacOS in [#4576](https://github.com/wailsapp/wails/pull/4576) by @jghiloni
|
||||
- Fixed compilation issue when building with NextJs. Fixed in [#4585](https://github.com/wailsapp/wails/pull/4585) by @rev42
|
||||
- Fixed pipelines for nightly release in [#4597](https://github.com/wailsapp/wails/pull/4597) by @riadafridishibly
|
||||
|
||||
## v3.0.0-alpha.29 - 2025-09-25
|
||||
|
||||
## Added
|
||||
- Add macOS Dock service to hide/show app icon in the dock @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4451)
|
||||
|
||||
## Changed
|
||||
- macOS: Use `visibleFrame` instead of `frame` for window centering to exclude menu bar and dock areas
|
||||
|
||||
## Fixed
|
||||
- Fixed redefinition error for liquid glass demo in [#4542](https://github.com/wailsapp/wails/pull/4542) by @Etesam913
|
||||
- Fixed issue where AssetServer can crash on MacOS in [#4576](https://github.com/wailsapp/wails/pull/4576) by @jghiloni
|
||||
- Fixed compilation issue when building with NextJs. Fixed in [#4585](https://github.com/wailsapp/wails/pull/4585) by @rev42
|
||||
- Fixed pipelines for nightly release in [#4597](https://github.com/wailsapp/wails/pull/4597) by @riadafridishibly
|
||||
|
||||
## v3.0.0-alpha.27 - 2025-09-07
|
||||
|
||||
## Fixed
|
||||
- Fixed redefinition error for liquid glass demo in [#4542](https://github.com/wailsapp/wails/pull/4542) by @Etesam913
|
||||
|
||||
## v3.0.0-alpha.26 - 2025-08-24
|
||||
|
||||
## Added
|
||||
- Add native Liquid Glass effect support for macOS with NSGlassEffectView (macOS 15.0+) and NSVisualEffectView fallback, including comprehensive material customization options by @leaanthony in [#4534](https://github.com/wailsapp/wails/pull/4534)
|
||||
|
||||
## v3.0.0-alpha.25 - 2025-08-16
|
||||
|
||||
## Changed
|
||||
- When running `wails3 update build-assets` with the `-config` parameter, values set via the `-product*` parameters are
|
||||
no longer ignored, and override the config value.
|
||||
|
||||
## v3.0.0-alpha.24 - 2025-08-13
|
||||
|
||||
## Added
|
||||
- Browser URL Sanitisation by @leaanthony in [#4500](https://github.dev/wailsapp/wails/pull/4500). Based on [#4484](https://github.com/wailsapp/wails/pull/4484) by @APShenkin.
|
||||
|
||||
## v3.0.0-alpha.23 - 2025-08-11
|
||||
|
||||
## Fixed
|
||||
- Fix SetBackgroundColour on Windows by @PPTGamer in [PR](https://github.com/wailsapp/wails/pull/4492)
|
||||
|
||||
## v3.0.0-alpha.22 - 2025-08-10
|
||||
|
||||
## Added
|
||||
- Add Content Protection on Windows/Mac by [@leaanthony](https://github.com/leaanthony) based on the original work of [@Taiterbase](https://github.com/Taiterbase) in this [PR](https://github.com/wailsapp/wails/pull/4241)
|
||||
- Add support for passing CLI variables to Task commands through `wails3 build` and `wails3 package` aliases (#4422) by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/4488)
|
||||
|
||||
## Changed
|
||||
- `window.NativeWindowHandle()` -> `window.NativeWindow()` by @leaanthony in [#4471](https://github.com/wailsapp/wails/pull/4471)
|
||||
- Refactor internal window handling by @leaanthony in [#4471](https://github.com/wailsapp/wails/pull/4471)
|
||||
+ Fix extra-broad Linux package dependencies, fix outdated RPM dependencies.
|
||||
|
||||
## v3.0.0-alpha.21 - 2025-08-07
|
||||
|
||||
## Fixed
|
||||
- Update docs to reflect changes from Manager API Refactoring by @yulesxoxo in [PR #4476](https://github.com/wailsapp/wails/pull/4476)
|
||||
- Fix Linux .desktop file appicon variable in Linux taskfile [PR #4477](https://github.com/wailsapp/wails/pull/4477)
|
||||
|
||||
## v3.0.0-alpha.20 - 2025-08-06
|
||||
|
||||
## Fixed
|
||||
- Update docs to reflect changes from Manager API Refactoring by @yulesxoxo in [PR #4476](https://github.com/wailsapp/wails/pull/4476)
|
||||
|
||||
## v3.0.0-alpha.19 - 2025-08-05
|
||||
|
||||
## Added
|
||||
- Support for dropzones with event sourcing dropped element data [@atterpac](https://github.com/atterpac) in [#4318](https://github.com/wailsapp/wails/pull/4318)
|
||||
- Added `AdditionalLaunchArgs` to `WindowsWindow` options to allow for additional command line arguments to be passed to the WebView2 browser. in [PR](https://github.com/wailsapp/wails/pull/4467)
|
||||
- Added Run go mod tidy automatically after wails init [@triadmoko](https://github.com/triadmoko) in [PR](https://github.com/wailsapp/wails/pull/4286)
|
||||
- Windows Snapassist feature by @leaanthony in [PR](https://github.dev/wailsapp/wails/pull/4463)
|
||||
|
||||
## Fixed
|
||||
- Fix Windows nil pointer dereference bug reported in [#4456](https://github.com/wailsapp/wails/issues/4456) by @leaanthony in [#4460](https://github.com/wailsapp/wails/pull/4460)
|
||||
- Add support for `allowsBackForwardNavigationGestures` in macOS WKWebView to enable two-finger swipe navigation gestures (#1857)
|
||||
- Fixes issue where onClick didn't work for menu items initially set as disabled by @leaanthony in [PR #4469](https://github.com/wailsapp/wails/pull/4469). Thanks to @IanVS for the initial investigation.
|
||||
- Fix Vite server not being cleaned up when build fails (#4403)
|
||||
- Fixed panic when closing or cancelling a `SaveFileDialog` on windows. Fixed in [PR](https://github.com/wailsapp/wails/pull/4284) by @hkhere
|
||||
- Fixed HTML level drag and drop on Windows by [@mbaklor](https://github.com/mbaklor) in [#4259](https://github.com/wailsapp/wails/pull/4259)
|
||||
|
||||
## v3.0.0-alpha.18 - 2025-08-03
|
||||
|
||||
## Added
|
||||
- Added `AdditionalLaunchArgs` to `WindowsWindow` options to allow for additional command line arguments to be passed to the WebView2 browser. in [PR](https://github.com/wailsapp/wails/pull/4467)
|
||||
- Added Run go mod tidy automatically after wails init [@triadmoko](https://github.com/triadmoko) in [PR](https://github.com/wailsapp/wails/pull/4286)
|
||||
- Windows Snapassist feature by @leaanthony in [PR](https://github.dev/wailsapp/wails/pull/4463)
|
||||
|
||||
## Fixed
|
||||
- Add support for `allowsBackForwardNavigationGestures` in macOS WKWebView to enable two-finger swipe navigation gestures (#1857)
|
||||
- Fixes issue where onClick didn't work for menu items initially set as disabled by @leaanthony in [PR #4469](https://github.com/wailsapp/wails/pull/4469). Thanks to @IanVS for the initial investigation.
|
||||
- Fix Vite server not being cleaned up when build fails (#4403)
|
||||
|
||||
## v3.0.0-alpha.17 - 2025-07-31
|
||||
|
||||
## Fixed
|
||||
- Fixed notification parsing on Windows @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4450)
|
||||
|
||||
## v3.0.0-alpha.16 - 2025-07-25
|
||||
|
||||
## Added
|
||||
- Add Windows `getAccentColor` implementation by [@almas-x](https://github.com/almas-x) in [PR](https://github.com/wailsapp/wails/pull/4427)
|
||||
|
||||
## v3.0.0-alpha.15 - 2025-07-25
|
||||
|
||||
## Added
|
||||
- Add Windows `getAccentColor` implementation by [@almas-x](https://github.com/almas-x) in [PR](https://github.com/wailsapp/wails/pull/4427)
|
||||
|
||||
## v3.0.0-alpha.14 - 2025-07-25
|
||||
|
||||
## Added
|
||||
- Windows dark theme menus + menubar. By @leaanthony in [a29b4f0861b1d0a700e9eb213c6f1076ec40efd5](https://github.com/wailsapp/wails/commit/a29b4f0861b1d0a700e9eb213c6f1076ec40efd5)
|
||||
- Rename built-in services for clearer JS/TS bindings by @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4405)
|
||||
|
||||
## v3.0.0-alpha.12 - 2025-07-15
|
||||
|
||||
### Added
|
||||
- `app.Env.GetAccentColor` to get the accent color of a user's system. Works on MacOS. by [@etesam913](https://github.com/etesam913)
|
||||
- Add `window.ToggleFrameless()` api by [@atterpac](https://github.com/atterpac) in [#4137](https://github.com/wailsapp/wails/pull/4137)
|
||||
|
||||
### Fixed
|
||||
- Fixed doctor command to check for Windows SDK dependencies by [@kodumulo](https://github.com/kodumulo) in [#4390](https://github.com/wailsapp/wails/issues/4390)
|
||||
|
||||
|
||||
## v3.0.0-alpha.11 - 2025-07-12
|
||||
|
||||
## Added
|
||||
- Add distribution-specific build dependencies for Linux by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/4345)
|
||||
- Added bindings guide by @atterpac in [PR](https://github.com/wailsapp/wails/pull/4404)
|
||||
|
||||
## v3.0.0-alpha.10 - 2025-07-06
|
||||
|
||||
### Breaking Changes
|
||||
- **Manager API Refactoring**: Reorganized application API from flat structure to organized managers for better code organization and discoverability by [@leaanthony](https://github.com/leaanthony) in [#4359](https://github.com/wailsapp/wails/pull/4359)
|
||||
- `app.NewWebviewWindow()` → `app.Window.New()`
|
||||
- `app.CurrentWindow()` → `app.Window.Current()`
|
||||
- `app.GetAllWindows()` → `app.Window.GetAll()`
|
||||
- `app.WindowByName()` → `app.Window.GetByName()`
|
||||
- `app.EmitEvent()` → `app.Event.Emit()`
|
||||
- `app.OnApplicationEvent()` → `app.Event.OnApplicationEvent()`
|
||||
- `app.OnWindowEvent()` → `app.Event.OnWindowEvent()`
|
||||
- `app.SetApplicationMenu()` → `app.Menu.SetApplicationMenu()`
|
||||
- `app.OpenFileDialog()` → `app.Dialog.OpenFile()`
|
||||
- `app.SaveFileDialog()` → `app.Dialog.SaveFile()`
|
||||
- `app.MessageDialog()` → `app.Dialog.Message()`
|
||||
- `app.InfoDialog()` → `app.Dialog.Info()`
|
||||
- `app.WarningDialog()` → `app.Dialog.Warning()`
|
||||
- `app.ErrorDialog()` → `app.Dialog.Error()`
|
||||
- `app.QuestionDialog()` → `app.Dialog.Question()`
|
||||
- `app.NewSystemTray()` → `app.SystemTray.New()`
|
||||
- `app.GetSystemTray()` → `app.SystemTray.Get()`
|
||||
- `app.ShowContextMenu()` → `app.ContextMenu.Show()`
|
||||
- `app.RegisterKeybinding()` → `app.KeyBinding.Register()`
|
||||
- `app.UnregisterKeybinding()` → `app.KeyBinding.Unregister()`
|
||||
- `app.GetPrimaryScreen()` → `app.Screen.GetPrimary()`
|
||||
- `app.GetAllScreens()` → `app.Screen.GetAll()`
|
||||
- `app.BrowserOpenURL()` → `app.Browser.OpenURL()`
|
||||
- `app.Environment()` → `app.Env.GetAll()`
|
||||
- `app.ClipboardGetText()` → `app.Clipboard.Text()`
|
||||
- `app.ClipboardSetText()` → `app.Clipboard.SetText()`
|
||||
- Renamed Service methods: `Name` -> `ServiceName`, `OnStartup` -> `ServiceStartup`, `OnShutdown` -> `ServiceShutdown` by [@leaanthony](https://github.com/leaanthony)
|
||||
- Moved `Path` and `Paths` methods to `application` package by [@leaanthony](https://github.com/leaanthony)
|
||||
- The application menu is now macOS only by [@leaanthony](https://github.com/leaanthony)
|
||||
|
||||
### Added
|
||||
|
||||
- **Organized Testing Infrastructure**: Moved Docker test files to dedicated `test/docker/` directory with optimized images and enhanced build reliability by [@leaanthony](https://github.com/leaanthony) in [#4359](https://github.com/wailsapp/wails/pull/4359)
|
||||
- **Improved Resource Management Patterns**: Added proper event handler cleanup and context-aware goroutine management in examples by [@leaanthony](https://github.com/leaanthony) in [#4359](https://github.com/wailsapp/wails/pull/4359)
|
||||
- Support aarch64 AppImage builds by [@AkshayKalose](https://github.com/AkshayKalose) in [#3981](https://github.com/wailsapp/wails/pull/3981)
|
||||
- Add diagnostics section to `wails doctor` by [@leaanthony](https://github.com/leaanthony)
|
||||
- Add window to context when calling a service method by [@leaanthony](https://github.com/leaanthony)
|
||||
- Add `window-call` example to demonstrate how to know which window is calling a service by [@leaanthony](https://github.com/leaanthony)
|
||||
- New Menu guide by [@leaanthony](https://github.com/leaanthony)
|
||||
- Better panic handling by [@leaanthony](https://github.com/leaanthony)
|
||||
- New Menu guide by [@leaanthony](https://github.com/leaanthony)
|
||||
- Add doc comments for Service API by [@fbbdev](https://github.com/fbbdev) in [#4024](https://github.com/wailsapp/wails/pull/4024)
|
||||
- Add function `application.NewServiceWithOptions` to initialise services with additional configuration by [@leaanthony](https://github.com/leaanthony) in [#4024](https://github.com/wailsapp/wails/pull/4024)
|
||||
- Improved menu control by [@FalcoG](https://github.com/FalcoG) and [@leaanthony](https://github.com/leaanthony) in [#4031](https://github.com/wailsapp/wails/pull/4031)
|
||||
- More documentation by [@leaanthony](https://github.com/leaanthony)
|
||||
- Support cancellation of events in standard event listeners by [@leaanthony](https://github.com/leaanthony)
|
||||
- Systray `Hide`, `Show` and `Destroy` support by [@leaanthony](https://github.com/leaanthony)
|
||||
- Systray `SetTooltip` support by [@leaanthony](https://github.com/leaanthony). Original idea by [@lujihong](https://github.com/wailsapp/wails/issues/3487#issuecomment-2633242304)
|
||||
- Report package path in binding generator warnings about unsupported types by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Add binding generator support for generic aliases by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Add binding generator support for `omitzero` JSON flag by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Add `//wails:ignore` directive to prevent binding generation for chosen service methods by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Add `//wails:internal` directive on services and models to allow for types that are exported in Go but not in JS/TS by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Add binding generator support for constants of alias type to allow for weakly typed enums by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Add binding generator tests for Go 1.24 features by [@fbbdev](https://github.com/fbbdev) in [#4068](https://github.com/wailsapp/wails/pull/4068)
|
||||
- Add support for macOS 15 "Sequoia" to `OSInfo.Branding` for improved OS version detection in [#4065](https://github.com/wailsapp/wails/pull/4065)
|
||||
- Add `PostShutdown` hook for running custom code after the shutdown process completes by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Add `FatalError` struct to support detection of fatal errors in custom error handlers by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Standardise and document service startup and shutdown order by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Add test harness for application startup/shutdown sequence and service startup/shutdown tests by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Add `RegisterService` method for registering services after the application has been created by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Add `MarshalError` field in application and service options for custom error handling in binding calls by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Add cancellable promise wrapper that propagates cancellation requests through promise chains by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
|
||||
- Add the ability to tie binding call cancellation to an `AbortSignal` by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
|
||||
- Support `data-wml-*` attributes for WML alongside the usual `wml-*` attributes by [@leaanthony](https://github.com/leaanthony)
|
||||
- Add `Configure` method on all services for late configuration/dynamic reconfiguration by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- `fileserver` service sends a 503 Service Unavailable response when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- `kvstore` service provides an in-memory key-value store by default when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Add `Load` method on `kvstore` service to reload data from file after config changes by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Add `Clear` method on `kvstore` service to delete all keys by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Add type `Level` in `log` service to provide JS-side log-level constants by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Add `Log` method on `log` service to specify log-level dynamically by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- `sqlite` service provides an in-memory DB by default when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Add method `Close` on `sqlite` service to close the DB manually by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Add cancellation support for query methods on `sqlite` service by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Add prepared statement support to `sqlite` service with JS bindings by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Gin support by [Lea Anthony](https://github.com/leaanthony) in [PR](https://github.com/wailsapp/wails/pull/3537) based on the original work of [@AnalogJ](https://github.com/AnalogJ) in this [PR](https://github.com/wailsapp/wails/pull/3537)
|
||||
- Fix auto save and password auto save always enabled by [@oSethoum](https://github.com/osethoum) in [#4134](https://github.com/wailsapp/wails/pull/4134)
|
||||
- Add `SetMenu()` on window to allow for setting a menu on a window by [@leaanthony](https://github.com/leaanthony)
|
||||
- Add Notification support by [@popaprozac](https://github.com/popaprozac) in [#4098](https://github.com/wailsapp/wails/pull/4098)
|
||||
- Add File Association support for mac by [@wimaha](https://github.com/wimaha) in [#4177](https://github.com/wailsapp/wails/pull/4177)
|
||||
- Add `wails3 tool version` for semantic version bumping by [@leaanthony](https://github.com/leaanthony)
|
||||
- Add badging support for macOS and Windows by [@popaprozac](https://github.com/popaprozac) in [#](https://github.com/wailsapp/wails/pull/4234)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed nil pointer dereference in processURLRequest for Mac by [@etesam913](https://github.com/etesam913) in [#4366](https://github.com/wailsapp/wails/pull/4366)
|
||||
- Fixed a linux bug preventing filtered dialogs by [@bh90210](https://github.com/bh90210) in [#4287](https://github.com/wailsapp/wails/pull/4287)
|
||||
- Fixed Windows+Linux Edit Menu issues by [@leaanthony](https://github.com/leaanthony) in [#3f78a3a](https://github.com/wailsapp/wails/commit/3f78a3a8ce7837e8b32242c8edbbed431c68c062)
|
||||
- Updated the minimum system version in macOS .plist files from 10.13.0 to 10.15.0 by [@AkshayKalose](https://github.com/AkshayKalose) in [#3981](https://github.com/wailsapp/wails/pull/3981)
|
||||
- Window ID skip issue by [@leaanthony](https://github.com/leaanthony)
|
||||
- Fix nil menu issue when calling RegisterContextMenu by [@leaanthony](https://github.com/leaanthony)
|
||||
- Fixed dependency cycles in binding generator output by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001)
|
||||
- Fixed use-before-define errors in binding generator output by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001)
|
||||
- Pass build flags to binding generator by [@fbbdev](https://github.com/fbbdev) in [#4023](https://github.com/wailsapp/wails/pull/4023)
|
||||
- Change paths in windows Taskfile to forward slashes to ensure it works on non-Windows platforms by [@leaanthony](https://github.com/leaanthony)
|
||||
- Mac + Mac JS events now fixed by [@leaanthony](https://github.com/leaanthony)
|
||||
- Fixed event deadlock for macOS by [@leaanthony](https://github.com/leaanthony)
|
||||
- Fixed a `Parameter incorrect` error in Window initialisation on Windows when HTML provided but no JS by [@leaanthony](https://github.com/leaanthony)
|
||||
- Fixed size of response prefix used for content type sniffing in asset server by [@fbbdev](https://github.com/fbbdev) in [#4049](https://github.com/wailsapp/wails/pull/4049)
|
||||
- Fixed handling of non-404 responses on root index path in asset server by [@fbbdev](https://github.com/fbbdev) in [#4049](https://github.com/wailsapp/wails/pull/4049)
|
||||
- Fixed undefined behaviour in binding generator when testing properties of generic types by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Fixed binding generator output for models when underlying type has not the same properties as named wrapper by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Fixed binding generator output for map key types and preprocessing by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Fixed binding generator output for structs that implement marshaler interfaces by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Fixed detection of type cycles involving generic types in binding generator by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Fixed invalid references to unexported models in binding generator output by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Moved injected code to the end of service files by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Fixed handling of errors from file close operations in binding generator by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Suppressed warnings for services that define lifecycle or http methods but no other bound methods by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Fixed non-React templates failing to display Hello World footer when using light system colour scheme by [@marcus-crane](https://github.com/marcus-crane) in [#4056](https://github.com/wailsapp/wails/pull/4056)
|
||||
- Fixed hidden menu items on macOS by [@leaanthony](https://github.com/leaanthony)
|
||||
- Fixed handling and formatting of errors in message processors by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Fixed skipped service shutdown when quitting application by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Ensure menu updates occur on the main thread by [@leaanthony](https://github.com/leaanthony)
|
||||
- The dragging and resizing mechanism is now more robust and matches expected platform behaviour more closely by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
|
||||
- Fixed [#4097](https://github.com/wailsapp/wails/issues/4097) Webpack/angular discards runtime init code by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
|
||||
- Fixed initially-hidden menu items by [@IanVS](https://github.com/IanVS) in [#4116](https://github.com/wailsapp/wails/pull/4116)
|
||||
- Fixed assetFileServer not serving `.html` files when non-extension request when `[request]` doesn't exist but `[request].html` does
|
||||
- Fixed icon generation paths by [@robin-samuel](https://github.com/robin-samuel) in [#4125](https://github.com/wailsapp/wails/pull/4125)
|
||||
- Fixed `fullscreen`, `unfullscreen`, `unminimise` and `unmaximise` events not being emitted by [@oSethoum](https://github.com/osethoum) in [#4130](https://github.com/wailsapp/wails/pull/4130)
|
||||
- Fixed NSIS Error because of incorrect prefix on default version in config by [@robin-samuel](https://github.com/robin-samuel) in [#4126](https://github.com/wailsapp/wails/pull/4126)
|
||||
- Fixed Dialogs runtime function returning escaped paths on Windows by [TheGB0077](https://github.com/TheGB0077) in [#4188](https://github.com/wailsapp/wails/pull/4188)
|
||||
- Fixed Webview2 detection path in HKCU by [@leaanthony](https://github.com/leaanthony).
|
||||
- Fixed input issue with macOS by [@leaanthony](https://github.com/leaanthony).
|
||||
- Fixed Windows icon generation task file name by [@yulesxoxo](https://github.com/yulesxoxo) in [#4219](https://github.com/wailsapp/wails/pull/4219).
|
||||
- Fixed transparency issue for frameless windows by [@leaanthony](https://github.com/leaanthony) based on work by @kron.
|
||||
- Fixed focus calls when window is disabled or minimised by [@leaanthony](https://github.com/leaanthony) based on work by @kron.
|
||||
- Fixed system trays not showing after taskbar restarts by [@leaanthony](https://github.com/leaanthony) based on work by @kron.
|
||||
- Fixed fallbackResponseWriter not implementing Flush() in [#4245](https://github.com/wailsapp/wails/pull/4245)
|
||||
- Fixed fallbackResponseWriter not implementing Flush() by [@superDingda] in [#4236](https://github.com/wailsapp/wails/issues/4236)
|
||||
- Fixed macOS window close with pending async Go-bound function call crashes by [@joshhardy](https://github.com/joshhardy) in [#4354](https://github.com/wailsapp/wails/pull/4354)
|
||||
- Fixed Windows Efficiency mode startup race condition by [@leaanthony](https://github.com/leaanthony)
|
||||
- Fixed Windows icon handle cleanup by [@leaanthony](https://github.com/leaanthony).
|
||||
- Fixed `OpenFileManager` on Windows by [@PPTGamer](https://github.com/PPTGamer) in [#4375](https://github.com/wailsapp/wails/pull/4375).
|
||||
|
||||
### Changed
|
||||
|
||||
- Removed `application.WindowIDKey` and `application.WindowNameKey` (replaced by `application.WindowKey`) by [@leaanthony](https://github.com/leaanthony)
|
||||
- ContextMenuData now returns a string instead of any by [@leaanthony](https://github.com/leaanthony)
|
||||
- In JS/TS bindings, class fields of fixed-length array types are now initialized with their expected length instead of being empty by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001)
|
||||
- ContextMenuData now returns a string instead of any by [@leaanthony](https://github.com/leaanthony)
|
||||
- `application.NewService` does not accept options as an optional parameter anymore (use `application.NewServiceWithOptions` instead) by [@leaanthony](https://github.com/leaanthony) in [#4024](https://github.com/wailsapp/wails/pull/4024)
|
||||
- Removed `nanoid` dependency by [@leaanthony](https://github.com/leaanthony)
|
||||
- Updated Window example for mica/acrylic/tabbed window styles by [@leaanthony](https://github.com/leaanthony)
|
||||
- In JS/TS bindings, `internal.js/ts` model files have been removed; all models can now be found in `models.js/ts` by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- In JS/TS bindings, named types are never rendered as aliases for other named types; the old behaviour is now restricted to aliases by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- In JS/TS bindings, in class mode, struct fields whose type is a type parameter are marked optional and never initialised automatically by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
|
||||
- Remove ESLint from templates by by [@IanVS](https://github.com/IanVS) in [#4059](https://github.com/wailsapp/wails/pull/4059)
|
||||
- Update copyright date to 2025 by [@IanVS](https://github.com/IanVS) in [#4037](https://github.com/wailsapp/wails/pull/4037)
|
||||
- Add docs for event.Sender by [@IanVS](https://github.com/IanVS) in [#4075](https://github.com/wailsapp/wails/pull/4075)
|
||||
- Go 1.24 support by [@leaanthony](https://github.com/leaanthony)
|
||||
- `ServiceStartup` hooks are now invoked when `App.Run` is called, not in `application.New` by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- `ServiceStartup` errors are now returned from `App.Run` instead of terminating the process by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Binding and dialog calls from JS now reject with error objects instead of strings by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
|
||||
- Improved systray menu positioning on Windows by [@leaanthony](https://github.com/leaanthony)
|
||||
- The JS runtime has been ported to TypeScript by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
|
||||
- The runtime initialises as soon as it is imported, no need to wait for the window to load by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
|
||||
- The runtime does not export an init method anymore. A side effects import can be used to initialise it by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
|
||||
- Bound methods now return a `CancellablePromise` that rejects with a `CancelError` if cancelled. The actual result of the call is discarded by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
|
||||
- Built-in service types are now consistently called `Service` by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Built-in service creation functions with options are now consistently called `NewWithConfig` by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- `Select` method on `sqlite` service is now named `Query` for consistency with Go APIs by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
|
||||
- Templates: moved runtime to "dependencies", organized package.json files by [@IanVS](https://github.com/IanVS) in [#4133](https://github.com/wailsapp/wails/pull/4133)
|
||||
- Creates and ad-hoc signs app bundles in dev to enable certain macOS APIs by [@popaprozac](https://github.com/popaprozac) in [#4171](https://github.com/wailsapp/wails/pull/4171)
|
||||
|
||||
## v3.0.0-alpha.9 - 2025-01-13
|
||||
|
||||
### Added
|
||||
|
||||
- `app.OpenFileManager(path string, selectFile bool)` to open the system file manager to the path `path` with optional highlighting via `selectFile` by [@Krzysztofz01](https://github.com/Krzysztofz01) [@rcalixte](https://github.com/rcalixte)
|
||||
- New `-git` flag for `wails3 init` command by [@leaanthony](https://github.com/leaanthony)
|
||||
- New `wails3 generate webview2bootstrapper` command by [@leaanthony](https://github.com/leaanthony)
|
||||
- Added `init()` method in runtime to allow manual initialisation of the runtime by [@leaanthony](https://github.com/leaanthony)
|
||||
- Added `WindowDidMoveDebounceMS` option to Window's WindowOptions by [@leaanthony](https://github.com/leaanthony)
|
||||
- Added Single Instance feature by [@leaanthony](https://github.com/leaanthony). Based on the [v2 PR](https://github.com/wailsapp/wails/pull/2951) by @APshenkin.
|
||||
- `wails3 generate template` command by [@leaanthony](https://github.com/leaanthony)
|
||||
- `wails3 releasenotes` command by [@leaanthony](https://github.com/leaanthony)
|
||||
- `wails3 update cli` command by [@leaanthony](https://github.com/leaanthony)
|
||||
- `-clean` option for `wails3 generate bindings` command by [@leaanthony](https://github.com/leaanthony)
|
||||
- Allow for aarch64 (arm64) AppImage Linux builds by [@AkshayKalose](https://github.com/AkshayKalose) in [#3981](https://github.com/wailsapp/wails/pull/3981)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed min/max width options for linux by @atterpac in [#3979](https://github.com/wailsapp/wails/pull/3979)
|
||||
- Typescript templates types definitions via npm version bump by @atterpac in [#3966](https://github.com/wailsapp/wails/pull/3966)
|
||||
- Fix Sveltekit template CSS referance by @atterpac in [#3945](https://github.com/wailsapp/wails/pull/3945)
|
||||
- Ensure key callbacks in window run() are called on the main thread by [@leaanthony](https://github.com/leaanthony)
|
||||
- Fix dialog directory chooser examples by [@leaanthony](https://github.com/leaanthony)
|
||||
- Created new Chinese error page when index.html is missing by [@leaanthony](https://github.com/leaanthony)
|
||||
- Ensure `windowDidBecomeKey` callback is running on main thread by [@leaanthony](https://github.com/leaanthony)
|
||||
- Support fullscreen for frameless windows by [@leaanthony](https://github.com/leaanthony)
|
||||
- Improved window destroying logic by [@leaanthony](https://github.com/leaanthony)
|
||||
- Fix window position logic when attached to system trays by [@leaanthony](https://github.com/leaanthony)
|
||||
- Support fullscreen for frameless windows by [@leaanthony](https://github.com/leaanthony)
|
||||
- Fix event handling by [@leaanthony](https://github.com/leaanthony)
|
||||
- Fixed window shutdown logic by [@leaanthony](https://github.com/leaanthony)
|
||||
- Common taskfile now defaults to generating Typescript bindings for Typescript templates by [@leaanthony](https://github.com/leaanthony)
|
||||
- Fix Close application on WM_CLOSE message when no windows are open/systray only by [@mmalcek](https://github.com/mmalcek) in [#3990](https://github.com/wailsapp/wails/pull/3990)
|
||||
- Fixed garble build by @5aaee9 in [#3192](https://github.com/wailsapp/wails/pull/3192)
|
||||
- Fixed windows nsis builds by [@leaanthony](https://github.com/leaanthony)
|
||||
|
||||
### Changed
|
||||
|
||||
- Moved build assets to platform specific directories by [@leaanthony](https://github.com/leaanthony)
|
||||
- Moved and renamed Taskfiles to platform specific directories by [@leaanthony](https://github.com/leaanthony)
|
||||
- Created a much better experience when `index.html` is missing by [@leaanthony](https://github.com/leaanthony)
|
||||
- [Windows] Improved performance of minimise and restore by [@leaanthony](https://github.com/leaanthony). Based on original [PR](https://github.com/wailsapp/wails/pull/3955) by [562589540](https://github.com/562589540)
|
||||
- Removed `ShouldClose` option (Register a hook for events.Common.WindowClosing instead) by [@leaanthony](https://github.com/leaanthony)
|
||||
- [Windows] Reduced flicker when opening a window by [@leaanthony](https://github.com/leaanthony)
|
||||
- Removed `Window.Destroy` as this was intended to be an internal function by [@leaanthony](https://github.com/leaanthony)
|
||||
- Renamed `WindowClose` events to `WindowClosing` by [@leaanthony](https://github.com/leaanthony)
|
||||
- Frontend builds now use vite environment "development" or "production" depending on build type by [@leaanthony](https://github.com/leaanthony)
|
||||
- Update to go-webview2 v1.19 by [@leaanthony](https://github.com/leaanthony)
|
||||
|
||||
## v3.0.0-alpha.8.3 - 2024-12-07
|
||||
|
||||
### Changed
|
||||
|
||||
- Ensure fork of taskfile is used by @leaanthony
|
||||
|
||||
## v3.0.0-alpha.8.2 - 2024-12-07
|
||||
|
||||
### Changed
|
||||
|
||||
- Update fork of Taskfile to fix version issues when installing using
|
||||
`go install` by @leaanthony
|
||||
|
||||
## v3.0.0-alpha.8.1 - 2024-12-07
|
||||
|
||||
### Changed
|
||||
|
||||
- Using fork of Taskfile to fix version issues when installing using
|
||||
`go install` by @leaanthony
|
||||
|
||||
## v3.0.0-alpha.8 - 2024-12-06
|
||||
|
||||
### Added
|
||||
|
||||
- Added hyperlink for sponsor by @ansxuman in [#3958](https://github.com/wailsapp/wails/pull/3958)
|
||||
- Support of linux packaging of deb,rpm, and arch linux packager builds by
|
||||
@atterpac in [#3909](https://github.com/wailsapp/wails/3909)
|
||||
- Added Support for darwin universal builds and packages by
|
||||
[ansxuman](https://github.com/ansxuman) in
|
||||
[#3902](https://github.com/wailsapp/wails/pull/3902)
|
||||
- Events documentation to the website by
|
||||
[atterpac](https://github.com/atterpac) in
|
||||
[#3867](https://github.com/wailsapp/wails/pull/3867)
|
||||
- Templates for sveltekit and sveltekit-ts that are set for non-SSR development
|
||||
by [atterpac](https://github.com/atterpac) in
|
||||
[#3829](https://github.com/wailsapp/wails/pull/3829)
|
||||
- Update build assets using new `wails3 update build-assets` command by
|
||||
[leaanthony](https://github.com/leaanthony)
|
||||
- Example to test the HTML Drag and Drop API by
|
||||
[FerroO2000](https://github.com/FerroO2000) in
|
||||
[#3856](https://github.com/wailsapp/wails/pull/3856)
|
||||
- File Association support by [leaanthony](https://github.com/leaanthony) in
|
||||
[#3873](https://github.com/wailsapp/wails/pull/3873)
|
||||
- New `wails3 generate runtime` command by
|
||||
[leaanthony](https://github.com/leaanthony)
|
||||
- New `InitialPosition` option to specify if the window should be centered or
|
||||
positioned at the given X/Y location by
|
||||
[leaanthony](https://github.com/leaanthony) in
|
||||
[#3885](https://github.com/wailsapp/wails/pull/3885)
|
||||
- Add `Path` & `Paths` methods to `application` package by
|
||||
[ansxuman](https://github.com/ansxuman) and
|
||||
[leaanthony](https://github.com/leaanthony) in
|
||||
[#3823](https://github.com/wailsapp/wails/pull/3823)
|
||||
- Added `GeneralAutofillEnabled` and `PasswordAutosaveEnabled` Windows options
|
||||
by [leaanthony](https://github.com/leaanthony) in
|
||||
[#3766](https://github.com/wailsapp/wails/pull/3766)
|
||||
- Added the ability to retrieve the window calling a service method by
|
||||
[leaanthony](https://github.com/leaanthony) in
|
||||
[#3888](https://github.com/wailsapp/wails/pull/3888)
|
||||
- Added `EnabledFeatures` and `DisabledFeatures` options for Webview2 by
|
||||
[leaanthony](https://github.com/leaanthony).
|
||||
-
|
||||
|
||||
### Changed
|
||||
|
||||
- `service.OnStartup` now shutdowns the application on error and runs
|
||||
`service.OnShutdown`for any prior services that started by @atterpac in
|
||||
[#3920](https://github.com/wailsapp/wails/pull/3920)
|
||||
- Refactored systray click messaging to better align with user interactions by
|
||||
@atterpac in [#3907](https://github.com/wailsapp/wails/pull/3907)
|
||||
- Asset embed to include `all:frontend/dist` to support frameworks that generate
|
||||
subfolders by @atterpac in
|
||||
[#3887](https://github.com/wailsapp/wails/pull/3887)
|
||||
- Taskfile refactor by [leaanthony](https://github.com/leaanthony) in
|
||||
[#3748](https://github.com/wailsapp/wails/pull/3748)
|
||||
- Upgrade to `go-webview2` v1.0.16 by
|
||||
[leaanthony](https://github.com/leaanthony)
|
||||
- Fixed `Screen` type to include `ID` not `Id` by
|
||||
[etesam913](https://github.com/etesam913) in
|
||||
[#3778](https://github.com/wailsapp/wails/pull/3778)
|
||||
- Update `go.mod.tmpl` wails version to support `application.ServiceOptions` by
|
||||
[northes](https://github.com/northes) in
|
||||
[#3836](https://github.com/wailsapp/wails/pull/3836)
|
||||
- Fixed service name determination by [windom](https://github.com/windom/) in
|
||||
[#3827](https://github.com/wailsapp/wails/pull/3827)
|
||||
- mkdocs serve now uses docker by [leaanthony](https://github.com/leaanthony)
|
||||
- Consolidated dev config into `config.yml` by
|
||||
[leaanthony](https://github.com/leaanthony)
|
||||
- Systray dialog now defaults to the application icon if available (Windows) by
|
||||
[@leaanthony](https://github.com/leaanthony)
|
||||
- Better reporting of GPU + Memory for macOS by
|
||||
[@leaanthony](https://github.com/leaanthony)
|
||||
- Removed `WebviewGpuIsDisabled` and `EnableFraudulentWebsiteWarnings`
|
||||
(superseded by `EnabledFeatures` and `DisabledFeatures` options) by
|
||||
[leaanthony](https://github.com/leaanthony)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed deadlock in Linux dialog for multiple selections caused by unclosed
|
||||
channel variable by @michael-freling in
|
||||
[#3925](https://github.com/wailsapp/wails/pull/3925)
|
||||
- Fixed cross-platform cleanup for .syso files during Windows build by
|
||||
[ansxuman](https://github.com/ansxuman) in
|
||||
[#3924](https://github.com/wailsapp/wails/pull/3924)
|
||||
- Fixed amd64 appimage compile by @atterpac in
|
||||
[#3898](https://github.com/wailsapp/wails/pull/3898)
|
||||
- Fixed build assets update by @ansxuman in
|
||||
[#3901](https://github.com/wailsapp/wails/pull/3901)
|
||||
- Fixed Linux systray `OnClick` and `OnRightClick` implementation by @atterpac
|
||||
in [#3886](https://github.com/wailsapp/wails/pull/3886)
|
||||
- Fixed `AlwaysOnTop` not working on Mac by
|
||||
[leaanthony](https://github.com/leaanthony) in
|
||||
[#3841](https://github.com/wailsapp/wails/pull/3841)
|
||||
- Fixed `application.NewEditMenu` including a duplicate
|
||||
`PasteAndMatchStyle` role in the edit menu on Darwin by
|
||||
[johnmccabe](https://github.com/johnmccabe) in
|
||||
[#3839](https://github.com/wailsapp/wails/pull/3839)
|
||||
- 🐧 Fixed aarch64 compilation
|
||||
[#3840](https://github.com/wailsapp/wails/issues/3840) in
|
||||
[#3854](https://github.com/wailsapp/wails/pull/3854) by
|
||||
[kodflow](https://github.com/kodflow)
|
||||
- ⊞ Fixed radio group menu items by
|
||||
[@leaanthony](https://github.com/leaanthony)
|
||||
- Fix error on building runnable .app on MacOS when 'name' and 'outputfilename'
|
||||
are different. by @nickisworking in
|
||||
[#3789](https://github.com/wailsapp/wails/pull/3789)
|
||||
|
||||
## v3.0.0-alpha.7 - 2024-09-18
|
||||
|
||||
### Added
|
||||
|
||||
- ⊞ New DIP system for Enhanced High DPI Monitor Support by
|
||||
[mmghv](https://github.com/mmghv) in
|
||||
[#3665](https://github.com/wailsapp/wails/pull/3665)
|
||||
- ⊞ Window class name option by [windom](https://github.com/windom/) in
|
||||
[#3682](https://github.com/wailsapp/wails/pull/3682)
|
||||
- Services have been expanded to provide plugin functionality. By
|
||||
[atterpac](https://github.com/atterpac) and
|
||||
[leaanthony](https://github.com/leaanthony) in
|
||||
[#3570](https://github.com/wailsapp/wails/pull/3570)
|
||||
|
||||
### Changed
|
||||
|
||||
- Events API change: `On`/`Emit` -> user events, `OnApplicationEvent` ->
|
||||
Application Events `OnWindowEvent` -> Window Events, by
|
||||
[leaanthony](https://github.com/leaanthony)
|
||||
- Fix for Events API on Linux by [TheGB0077](https://github.com/TheGB0077) in
|
||||
[#3734](https://github.com/wailsapp/wails/pull/3734)
|
||||
- [CI] improvements to actions & enable to run actions also in forks and
|
||||
branches prefixed with `v3/` or `v3-` by
|
||||
[stendler](https://github.com/stendler) in
|
||||
[#3747](https://github.com/wailsapp/wails/pull/3747)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed bug with usage of customEventProcessor in drag-n-drop example by
|
||||
[etesam913](https://github.com/etesam913) in
|
||||
[#3742](https://github.com/wailsapp/wails/pull/3742)
|
||||
- 🐧 Fixed linux compile error introduced by IgnoreMouseEvents addition by
|
||||
[atterpac](https://github.com/atterpac) in
|
||||
[#3721](https://github.com/wailsapp/wails/pull/3721)
|
||||
- ⊞ Fixed syso icon file generation bug by
|
||||
[atterpac](https://github.com/atterpac) in
|
||||
[#3675](https://github.com/wailsapp/wails/pull/3675)
|
||||
- 🐧 Fix to run natively in wayland incorporated from
|
||||
[#1811](https://github.com/wailsapp/wails/pull/1811) in
|
||||
[#3614](https://github.com/wailsapp/wails/pull/3614) by
|
||||
[@stendler](https://github.com/stendler)
|
||||
- Do not bind internal service methods in
|
||||
[#3720](https://github.com/wailsapp/wails/pull/3720) by
|
||||
[leaanthony](https://github.com/leaanthony)
|
||||
- ⊞ Fixed system tray startup panic in
|
||||
[#3693](https://github.com/wailsapp/wails/issues/3693) by
|
||||
[@DeltaLaboratory](https://github.com/DeltaLaboratory)
|
||||
- Do not bind internal service methods in
|
||||
[#3720](https://github.com/wailsapp/wails/pull/3720) by
|
||||
[leaanthony](https://github.com/leaanthony)
|
||||
- ⊞ Fixed system tray startup panic in
|
||||
[#3693](https://github.com/wailsapp/wails/issues/3693) by
|
||||
[@DeltaLaboratory](https://github.com/DeltaLaboratory)
|
||||
- Major menu item refactor and event handling. Mainly improves macOS for now. By
|
||||
[leaanthony](https://github.com/leaanthony)
|
||||
- Fix tests after plugins and event refactor in
|
||||
[#3746](https://github.com/wailsapp/wails/pull/3746) by
|
||||
[@stendler](https://github.com/stendler)
|
||||
- ⊞ Fixed `Failed to unregister class Chrome_WidgetWin_0` warning. By
|
||||
[leaanthony](https://github.com/leaanthony)
|
||||
|
||||
## v3.0.0-alpha.6 - 2024-07-30
|
||||
|
||||
### Fixed
|
||||
|
||||
- Module issues
|
||||
|
||||
## v3.0.0-alpha.5 - 2024-07-30
|
||||
|
||||
### Added
|
||||
|
||||
- 🐧 WindowDidMove / WindowDidResize events in
|
||||
[#3580](https://github.com/wailsapp/wails/pull/3580)
|
||||
- ⊞ WindowDidResize event in
|
||||
[#3580](https://github.com/wailsapp/wails/pull/3580)
|
||||
- add Event ApplicationShouldHandleReopen to be able to handle dock
|
||||
icon click by @5aaee9 in [#2991](https://github.com/wailsapp/wails/pull/2991)
|
||||
- add getPrimaryScreen/getScreens to impl by @tmclane in
|
||||
[#2618](https://github.com/wailsapp/wails/pull/2618)
|
||||
- add option for showing the toolbar in fullscreen mode on macOS by
|
||||
[@fbbdev](https://github.com/fbbdev) in
|
||||
[#3282](https://github.com/wailsapp/wails/pull/3282)
|
||||
- 🐧 add onKeyPress logic to convert linux keypress into an accelerator
|
||||
@[Atterpac](https://github.com/Atterpac)
|
||||
in[#3022](https://github.com/wailsapp/wails/pull/3022])
|
||||
- 🐧 add task `run:linux` by
|
||||
[@marcus-crane](https://github.com/marcus-crane) in
|
||||
[#3146](https://github.com/wailsapp/wails/pull/3146)
|
||||
- Export `SetIcon` method by [@almas-x](https://github.com/almas-x) in
|
||||
[PR](https://github.com/wailsapp/wails/pull/3147)
|
||||
- Improve `OnShutdown` by [@almas-x](https://github.com/almas-x) in
|
||||
[PR](https://github.com/wailsapp/wails/pull/3189)
|
||||
- Restore `ToggleMaximise` method in `Window` interface by
|
||||
[@fbbdev](https://github.com/fbbdev) in
|
||||
[#3281](https://github.com/wailsapp/wails/pull/3281)
|
||||
- Added more information to `Environment()`. By @leaanthony in
|
||||
[aba82cc](https://github.com/wailsapp/wails/commit/aba82cc52787c97fb99afa58b8b63a0004b7ff6c)
|
||||
based on [PR](https://github.com/wailsapp/wails/pull/2044) by @Mai-Lapyst
|
||||
- Expose the `WebviewWindow.IsFocused` method on the `Window` interface by
|
||||
[@fbbdev](https://github.com/fbbdev) in
|
||||
[#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- Support multiple space-separated trigger events in the WML system by
|
||||
[@fbbdev](https://github.com/fbbdev) in
|
||||
[#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- Add ESM exports from the bundled JS runtime script by
|
||||
[@fbbdev](https://github.com/fbbdev) in
|
||||
[#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- Add binding generator flag for using the bundled JS runtime script instead of
|
||||
the npm package by [@fbbdev](https://github.com/fbbdev) in
|
||||
[#3334](https://github.com/wailsapp/wails/pull/3334)
|
||||
- Implement `setIcon` on linux by [@abichinger](https://github.com/abichinger)
|
||||
in [#3354](https://github.com/wailsapp/wails/pull/3354)
|
||||
- Add flag `-port` to dev command and support environment variable
|
||||
`WAILS_VITE_PORT` by [@abichinger](https://github.com/abichinger) in
|
||||
[#3429](https://github.com/wailsapp/wails/pull/3429)
|
||||
- Add tests for bound method calls by
|
||||
[@abichinger](https://github.com/abichinger) in
|
||||
[#3431](https://github.com/wailsapp/wails/pull/3431)
|
||||
- ⊞ add `SetIgnoreMouseEvents` for already created window by
|
||||
[@bruxaodev](https://github.com/bruxaodev) in
|
||||
[#3667](https://github.com/wailsapp/wails/pull/3667)
|
||||
- Add ability to set a window's stacking level (order) by
|
||||
[@OlegGulevskyy](https://github.com/OlegGulevskyy) in
|
||||
[#3674](https://github.com/wailsapp/wails/pull/3674)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed resize event messaging by [atterpac](https://github.com/atterpac) in
|
||||
[#3606](https://github.com/wailsapp/wails/pull/3606)
|
||||
- 🐧Fixed theme handling error on NixOS by
|
||||
[tmclane](https://github.com/tmclane) in
|
||||
[#3515](https://github.com/wailsapp/wails/pull/3515)
|
||||
- Fixed cross volume project install for windows by
|
||||
[atterpac](https://github.com/atterac) in
|
||||
[#3512](https://github.com/wailsapp/wails/pull/3512)
|
||||
- Fixed react template css to show footer by
|
||||
[atterpac](https://github.com/atterpac) in
|
||||
[#3477](https://github.com/wailsapp/wails/pull/3477)
|
||||
- Fixed zombie processes when working in devmode by updating to latest refresh
|
||||
by [Atterpac](https://github.com/atterpac) in
|
||||
[#3320](https://github.com/wailsapp/wails/pull/3320).
|
||||
- Fixed appimage webkit file sourcing by [Atterpac](https://github.com/atterpac)
|
||||
in [#3306](https://github.com/wailsapp/wails/pull/3306).
|
||||
- Fixed Doctor apt package verify by [Atterpac](https://github.com/Atterpac) in
|
||||
[#2972](https://github.com/wailsapp/wails/pull/2972).
|
||||
- Fixed application frozen when quit (Darwin) by @5aaee9 in
|
||||
[#2982](https://github.com/wailsapp/wails/pull/2982)
|
||||
- Fixed background colours of examples on Windows by
|
||||
[mmghv](https://github.com/mmghv) in
|
||||
[#2750](https://github.com/wailsapp/wails/pull/2750).
|
||||
- Fixed default context menus by [mmghv](https://github.com/mmghv) in
|
||||
[#2753](https://github.com/wailsapp/wails/pull/2753).
|
||||
- Fixed hex values for arrow keys on Darwin by
|
||||
[jaybeecave](https://github.com/jaybeecave) in
|
||||
[#3052](https://github.com/wailsapp/wails/pull/3052).
|
||||
- Set drag-n-drop for windows to working. Added by
|
||||
[@pylotlight](https://github.com/pylotlight) in
|
||||
[PR](https://github.com/wailsapp/wails/pull/3039)
|
||||
- Fixed bug for linux in doctor in the event user doesn't have proper drivers
|
||||
installed. Added by [@pylotlight](https://github.com/pylotlight) in
|
||||
[PR](https://github.com/wailsapp/wails/pull/3032)
|
||||
- Fix dpi scaling on start up (windows). Changed by [@almas-x](https://github.com/almas-x) in
|
||||
[PR](https://github.com/wailsapp/wails/pull/3145)
|
||||
- Fix replace line in `go.mod` to use relative paths. Fixes Windows paths with
|
||||
spaces - @leaanthony.
|
||||
- Fix MacOS systray click handling when no attached window by
|
||||
[thomas-senechal](https://github.com/thomas-senechal) in PR
|
||||
[#3207](https://github.com/wailsapp/wails/pull/3207)
|
||||
- Fix failing Windows build due to unknown option by
|
||||
[thomas-senechal](https://github.com/thomas-senechal) in PR
|
||||
[#3208](https://github.com/wailsapp/wails/pull/3208)
|
||||
- Fix crash on windows left clicking the systray icon when not having an
|
||||
attached window [tw1nk](https://github.com/tw1nk) in PR
|
||||
[#3271](https://github.com/wailsapp/wails/pull/3271)
|
||||
- Fix wrong baseURL when open window twice by @5aaee9 in PR
|
||||
[#3273](https://github.com/wailsapp/wails/pull/3273)
|
||||
- Fix ordering of if branches in `WebviewWindow.Restore` method by
|
||||
[@fbbdev](https://github.com/fbbdev) in
|
||||
[#3279](https://github.com/wailsapp/wails/pull/3279)
|
||||
- Correctly compute `startURL` across multiple `GetStartURL` invocations when
|
||||
`FRONTEND_DEVSERVER_URL` is present.
|
||||
[#3299](https://github.com/wailsapp/wails/pull/3299)
|
||||
- Fix the JS type of the `Screen` struct to match its Go counterpart by
|
||||
[@fbbdev](https://github.com/fbbdev) in
|
||||
[#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- Fix the `WML.Reload` method to ensure proper cleanup of registered event
|
||||
listeners by [@fbbdev](https://github.com/fbbdev) in
|
||||
[#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- Fix custom context menu closing immediately on linux by
|
||||
[@abichinger](https://github.com/abichinger) in
|
||||
[#3330](https://github.com/wailsapp/wails/pull/3330)
|
||||
- Fix the output path and extension of model files produced by the binding
|
||||
generator by [@fbbdev](https://github.com/fbbdev) in
|
||||
[#3334](https://github.com/wailsapp/wails/pull/3334)
|
||||
- Fix the import paths of model files in JS code produced by the binding
|
||||
generator by [@fbbdev](https://github.com/fbbdev) in
|
||||
[#3334](https://github.com/wailsapp/wails/pull/3334)
|
||||
- Fix drag-n-drop on some linux distros by
|
||||
[@abichinger](https://github.com/abichinger) in
|
||||
[#3346](https://github.com/wailsapp/wails/pull/3346)
|
||||
- Fix missing task for macOS when using `wails3 task dev` by
|
||||
[@hfoxy](https://github.com/hfoxy) in
|
||||
[#3417](https://github.com/wailsapp/wails/pull/3417)
|
||||
- Fix registering events causing a nil map assignment by
|
||||
[@hfoxy](https://github.com/hfoxy) in
|
||||
[#3426](https://github.com/wailsapp/wails/pull/3426)
|
||||
- Fix unmarshaling of bound method parameters by
|
||||
[@fbbdev](https://github.com/fbbdev) in
|
||||
[#3431](https://github.com/wailsapp/wails/pull/3431)
|
||||
- Fix handling of multiple return values from bound methods by
|
||||
[@fbbdev](https://github.com/fbbdev) in
|
||||
[#3431](https://github.com/wailsapp/wails/pull/3431)
|
||||
- Fix doctor detection of npm that is not installed with system package manager
|
||||
by [@pekim](https://github.com/pekim) in
|
||||
[#3458](https://github.com/wailsapp/wails/pull/3458)
|
||||
- Fix missing MicrosoftEdgeWebview2Setup.exe. Thanks to
|
||||
[@robin-samuel](https://github.com/robin-samuel).
|
||||
- Fix random crash on linux due to window ID handling by @leaanthony. Based on
|
||||
PR [#3466](https://github.com/wailsapp/wails/pull/3622) by
|
||||
[@5aaee9](https://github.com/5aaee9).
|
||||
- Fix systemTray.setIcon crashing on Linux by
|
||||
[@windom](https://github.com/windom/) in
|
||||
[#3636](https://github.com/wailsapp/wails/pull/3636).
|
||||
- Fix Ensure Window Frame is Applied on First Call in `setFrameless` Function on
|
||||
Windows by [@bruxaodev](https://github.com/bruxaodev/) in
|
||||
[#3691](https://github.com/wailsapp/wails/pull/3691).
|
||||
|
||||
### Changed
|
||||
|
||||
- Renamed `AbsolutePosition()` to `Position()` by
|
||||
[mmghv](https://github.com/mmghv) in
|
||||
[#3611](https://github.com/wailsapp/wails/pull/3611)
|
||||
- Update linux webkit dependency to webkit2gtk-4.1 over webkitgtk2-4.0 to
|
||||
support Ubuntu 24.04 LTS by [atterpac](https://github.com/atterpac) in
|
||||
[#3461](https://github.com/wailsapp/wails/pull/3461)
|
||||
- The bundled JS runtime script is now an ESM module: script tags importing it
|
||||
must have the `type="module"` attribute. By
|
||||
[@fbbdev](https://github.com/fbbdev) in
|
||||
[#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- The `@wailsio/runtime` package does not publish its API on the `window.wails`
|
||||
object, and does not start the WML system. This has been done to improve
|
||||
encapsulation. The WML system can be started manually if desired by calling
|
||||
the new `WML.Enable` method. The bundled JS runtime script still performs both
|
||||
operations automatically. By [@fbbdev](https://github.com/fbbdev) in
|
||||
[#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- The Window API module `@wailsio/runtime/src/window` now exposes the containing
|
||||
window object as a default export. It is not possible anymore to import
|
||||
individual methods through ESM named or namespace import syntax.
|
||||
- The JS window API has been updated to match the current Go `WebviewWindow`
|
||||
API. Some methods have changed name or prototype, specifically: `Screen`
|
||||
becomes `GetScreen`; `GetZoomLevel`/`SetZoomLevel` become `GetZoom`/`SetZoom`;
|
||||
`GetZoom`, `Width` and `Height` now return values directly instead of wrapping
|
||||
them within objects. By [@fbbdev](https://github.com/fbbdev) in
|
||||
[#3295](https://github.com/wailsapp/wails/pull/3295)
|
||||
- The binding generator now uses calls by ID by default. The `-id` CLI option
|
||||
has been removed. Use the `-names` CLI option to switch back to calls by name.
|
||||
By [@fbbdev](https://github.com/fbbdev) in
|
||||
[#3468](https://github.com/wailsapp/wails/pull/3468)
|
||||
- New binding code layout: output files were previously organised in folders
|
||||
named after their containing package; now full Go import paths are used,
|
||||
including the module path. By [@fbbdev](https://github.com/fbbdev) in
|
||||
[#3468](https://github.com/wailsapp/wails/pull/3468)
|
||||
- The struct field `application.Options.Bind` has been renamed to
|
||||
`application.Options.Services`. By [@fbbdev](https://github.com/fbbdev) in
|
||||
[#3468](https://github.com/wailsapp/wails/pull/3468)
|
||||
- New syntax for binding services: service instances must now be wrapped in a
|
||||
call to `application.NewService`. By [@fbbdev](https://github.com/fbbdev) in
|
||||
[#3468](https://github.com/wailsapp/wails/pull/3468)
|
||||
- Disable spinner on Non-Terminal or CI Environment by
|
||||
[@DeltaLaboratory](https://github.com/DeltaLaboratory) in
|
||||
[#3574](https://github.com/wailsapp/wails/pull/3574)
|
||||
27
docs/src/content/docs/community/links.md
Normal file
27
docs/src/content/docs/community/links.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
title: Links
|
||||
---
|
||||
|
||||
This page serves as a list for community related links.
|
||||
|
||||
:::tip[How to Submit a Link]
|
||||
|
||||
You can click the `Edit page` at the bottom of this page to submit a PR.
|
||||
|
||||
:::
|
||||
|
||||
## Awesome Wails
|
||||
|
||||
The [definitive list](https://github.com/wailsapp/awesome-wails) of links
|
||||
related to Wails.
|
||||
|
||||
## Support Channels
|
||||
|
||||
- [Wails Discord Server](https://discord.gg/bdj28QNHmT)
|
||||
- [Github Issues](https://github.com/wailsapp/wails/issues)
|
||||
|
||||
## Social Media
|
||||
|
||||
- [Twitter](https://x.com/wailsapp)
|
||||
- [Wails Chinese Community QQ Group](https://qm.qq.com/cgi-bin/qm/qr?k=PmIURne5hFGNd7QWzW5qd6FV-INEjNJv&jump_from=webapi) -
|
||||
Group number: 1067173054
|
||||
25
docs/src/content/docs/community/showcase/_template.md
Normal file
25
docs/src/content/docs/community/showcase/_template.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
title: My Project
|
||||
draft: true
|
||||
---
|
||||
|
||||
<!--
|
||||
TEMPLATE FOR SHOWCASE ENTRIES
|
||||
|
||||
1. Replace "My Project" with your project name
|
||||
2. Add your screenshot to: src/assets/showcase-images/your-project.webp
|
||||
3. Update the image path below
|
||||
4. Add a description of your project
|
||||
5. Add a link to your project website/repo
|
||||
6. Remove the "draft: true" line when ready to publish
|
||||
-->
|
||||
|
||||

|
||||
|
||||
<!-- Add a description of your project here -->
|
||||
|
||||
Your project description goes here. Explain what it does, what makes it special, and why you built it with Wails.
|
||||
|
||||
<!-- Add a link to your project -->
|
||||
|
||||
[Visit Project Website](https://your-project.com) | [View on GitHub](https://github.com/yourusername/your-project)
|
||||
16
docs/src/content/docs/community/showcase/bulletinboard.md
Normal file
16
docs/src/content/docs/community/showcase/bulletinboard.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: BulletinBoard
|
||||
---
|
||||
|
||||

|
||||
|
||||
The [BulletinBoard](https://github.com/raguay/BulletinBoard) application is a
|
||||
versital message board for static messages or dialogs to get information from
|
||||
the user for a script. It has a TUI for creating new dialogs that can latter be
|
||||
used to get information from the user. It's design is to stay running on your
|
||||
system and show the information as needed and then hide away. I have a process
|
||||
for watching a file on my system and sending the contents to BulletinBoard when
|
||||
changed. It works great with my workflows. T here is also an
|
||||
[Alfred workflow](https://github.com/raguay/MyAlfred/blob/master/Alfred%205/EmailIt.alfredworkflow)
|
||||
for sending information to the program. The workflow is also for working with
|
||||
[EmailIt](https://github.com/raguay/EmailIt).
|
||||
36
docs/src/content/docs/community/showcase/cfntracker.md
Normal file
36
docs/src/content/docs/community/showcase/cfntracker.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
title: CFN Tracker
|
||||
---
|
||||
|
||||

|
||||
|
||||
[CFN Tracker](https://github.com/williamsjokvist/cfn-tracker) - Track any Street
|
||||
Fighter 6 or V CFN profile's live matches. Check
|
||||
[the website](https://cfn.williamsjokvist.se/) to get started.
|
||||
|
||||
## Features
|
||||
|
||||
- Real-time match tracking
|
||||
- Storing match logs and statistics
|
||||
- Support for displaying live stats to OBS via Browser Source
|
||||
- Support for both SF6 and SFV
|
||||
- Ability for users to create their own OBS Browser themes with CSS
|
||||
|
||||
### Major tech used alongside Wails
|
||||
|
||||
- [Task](https://github.com/go-task/task) - wrapping the Wails CLI to make
|
||||
common commands easy to use
|
||||
- [React](https://github.com/facebook/react) - chosen for its rich ecosystem
|
||||
(radix, framer-motion)
|
||||
- [Bun](https://github.com/oven-sh/bun) - used for its fast dependency
|
||||
resolution and build-time
|
||||
- [Rod](https://github.com/go-rod/rod) - headless browser automation for
|
||||
authentication and polling changes
|
||||
- [SQLite](https://github.com/mattn/go-sqlite3) - used for storing matches,
|
||||
sessions and profiles
|
||||
- [Server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) -
|
||||
a http stream to send tracking updates to OBS browser sources
|
||||
- [i18next](https://github.com/i18next/) - with backend connector to serve
|
||||
localization objects from the Go layer
|
||||
- [xstate](https://github.com/statelyai/xstate) - state machines for auth
|
||||
process and tracking
|
||||
18
docs/src/content/docs/community/showcase/clave.md
Normal file
18
docs/src/content/docs/community/showcase/clave.md
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
title: Clave
|
||||
---
|
||||
|
||||

|
||||
|
||||
Key Features
|
||||
|
||||
- 🎨 Simple, intuitive design for hassle-free experience
|
||||
- ✅ Add accounts easily via manual entry or QR code image import.
|
||||
- 🔒 End-to-end encryption with PIN or Touch ID(macOS only).
|
||||
- 💻 Available for macOS, Windows, and Linux
|
||||
- ⚡ Quick access from system tray for convenience
|
||||
- 📂 Easily backup and restore your profiles
|
||||
|
||||
Try Clave Today!
|
||||
|
||||
💻 [https://clave.rocks](https://clave.rocks)
|
||||
14
docs/src/content/docs/community/showcase/emailit.md
Normal file
14
docs/src/content/docs/community/showcase/emailit.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
title: EmailIt
|
||||
---
|
||||
|
||||

|
||||
|
||||
[EmailIt](https://github.com/raguay/EmailIt/) is a Wails 2 program that is a
|
||||
markdown based email sender only with nine notepads, scripts to manipulate the
|
||||
text, and templates. It also has a scripts terminal to run scripts in EmailIt on
|
||||
files in your system. The scripts and templates can be used from the commandline
|
||||
itself or with the Alfred, Keyboard Maestro, Dropzone, or PopClip extensions. It
|
||||
also supports scripts and themes downloaded form GitHub. Documentation is not
|
||||
complete, but the programs works. It’s built using Wails2 and Svelte, and the
|
||||
download is a universal macOS application.
|
||||
16
docs/src/content/docs/community/showcase/encrypteasy.md
Normal file
16
docs/src/content/docs/community/showcase/encrypteasy.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: EncryptEasy
|
||||
---
|
||||
|
||||

|
||||
|
||||
**[EncryptEasy](https://www.encrypteasy.app) is a simple and easy to use PGP
|
||||
encryption tool, managing all your and your contacts keys. Encryption should be
|
||||
simple. Developed with Wails.**
|
||||
|
||||
Encrypting messages using PGP is the industry standard. Everyone has a private
|
||||
and a public key. Your private key, well, needs to be kept private so only you
|
||||
can read messages. Your public key is distributed to anyone who wants to send
|
||||
you secret, encrypted messages. Managing keys, encrypting messages and
|
||||
decrypting messages should be a smooth experience. EncryptEasy is all about
|
||||
making it easy.
|
||||
9
docs/src/content/docs/community/showcase/espstudio.md
Normal file
9
docs/src/content/docs/community/showcase/espstudio.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
title: ESP Studio
|
||||
---
|
||||
|
||||

|
||||
|
||||
[ESP Studio](https://github.com/torabian/esp-studio) - Cross platform, Desktop,
|
||||
Cloud, and Embedded software for controlling ESP/Arduino devices, and building
|
||||
complex IOT workflows and control systems
|
||||
29
docs/src/content/docs/community/showcase/filehound.md
Normal file
29
docs/src/content/docs/community/showcase/filehound.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
title: FileHound Export Utility
|
||||
---
|
||||
|
||||

|
||||
|
||||
[FileHound Export Utility](https://www.filehound.co.uk/) FileHound is a cloud
|
||||
document management platform made for secure file retention, business process
|
||||
automation and SmartCapture capabilities.
|
||||
|
||||
The FileHound Export Utility allows FileHound Administrators the ability to run
|
||||
a secure document and data extraction tasks for alternative back-up and recovery
|
||||
purposes. This application will download all documents and/or meta data saved in
|
||||
FileHound based on the filters you choose. The metadata will be exported in both
|
||||
JSON and XML formats.
|
||||
|
||||
Backend built with:
|
||||
|
||||
- Go 1.15
|
||||
- Wails 1.11.0
|
||||
- go-sqlite3 1.14.6
|
||||
- go-linq 3.2
|
||||
|
||||
Frontend with:
|
||||
|
||||
- Vue 2.6.11
|
||||
- Vuex 3.4.0
|
||||
- TypeScript
|
||||
- Tailwind 1.9.6
|
||||
8
docs/src/content/docs/community/showcase/hiposter.md
Normal file
8
docs/src/content/docs/community/showcase/hiposter.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
title: hiposter
|
||||
---
|
||||
|
||||

|
||||
|
||||
[hiposter](https://github.com/obity/hiposter) is a simple and efficient http API
|
||||
testing client tool. Based on Wails, Go and sveltejs.
|
||||
189
docs/src/content/docs/community/showcase/index.mdx
Normal file
189
docs/src/content/docs/community/showcase/index.mdx
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
---
|
||||
title: Overview
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { ShowcaseImage } from "starlight-showcases";
|
||||
import { Steps } from "@astrojs/starlight/components";
|
||||
|
||||
:::tip[See how to add your project]
|
||||
|
||||
Check out the
|
||||
[How to add my project in showcase](#how-to-add-my-project-in-showcase) section.
|
||||
|
||||
:::
|
||||
|
||||
<ShowcaseImage
|
||||
entries={[
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/bboard.webp"),
|
||||
href: "/community/showcase/bulletinboard",
|
||||
title: "BulletinBoard",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/cfntracker.webp"),
|
||||
href: "/community/showcase/cfntracker",
|
||||
title: "CFN Tracker",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/clave.png"),
|
||||
href: "/community/showcase/clave",
|
||||
title: "Clave",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/emailit.webp"),
|
||||
href: "/community/showcase/emailit",
|
||||
title: "EmailIt",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/encrypteasy.webp"),
|
||||
href: "/community/showcase/encrypteasy",
|
||||
title: "EncryptEasy",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/esp-studio.png"),
|
||||
href: "/community/showcase/espstudio",
|
||||
title: "ESP Studio",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/filehound.webp"),
|
||||
href: "/community/showcase/filehound",
|
||||
title: "FileHound Export Utility",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/hiposter.webp"),
|
||||
href: "/community/showcase/hiposter",
|
||||
title: "hiposter",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/mchat.png"),
|
||||
href: "/community/showcase/mchat",
|
||||
title: "Mchat",
|
||||
},
|
||||
{
|
||||
thumbnail: import(
|
||||
"../../../../assets/showcase-images/minecraft-mod-updater.webp"
|
||||
),
|
||||
href: "/community/showcase/minecraftupdater",
|
||||
title: "Minecraft Updater",
|
||||
},
|
||||
{
|
||||
thumbnail: import(
|
||||
"../../../../assets/showcase-images/minesweeper-xp.webp"
|
||||
),
|
||||
href: "/community/showcase/minesweeper-xp",
|
||||
title: "Minesweeper XP",
|
||||
},
|
||||
{
|
||||
thumbnail: import(
|
||||
"../../../../assets/showcase-images/modalfilemanager.webp"
|
||||
),
|
||||
href: "/community/showcase/modalfilemanager",
|
||||
title: "Modal File Manager",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/mollywallet.webp"),
|
||||
href: "/community/showcase/mollywallet",
|
||||
title: "Molly Wallet",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/october.webp"),
|
||||
href: "/community/showcase/october",
|
||||
title: "October",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/optimus.webp"),
|
||||
href: "/community/showcase/optimus",
|
||||
title: "Optimus",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/portfall.webp"),
|
||||
href: "/community/showcase/portfall",
|
||||
title: "Portfall",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/resizem.webp"),
|
||||
href: "/community/showcase/resizem",
|
||||
title: "Resizem",
|
||||
},
|
||||
{
|
||||
thumbnail: import(
|
||||
"../../../../assets/showcase-images/restic-browser-2.png"
|
||||
),
|
||||
href: "/community/showcase/restic-browser",
|
||||
title: "Restic Browser",
|
||||
},
|
||||
{
|
||||
thumbnail: import(
|
||||
"../../../../assets/showcase-images/riftshare-main.webp"
|
||||
),
|
||||
href: "/community/showcase/riftshare",
|
||||
title: "RiftShare",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/scriptbar.webp"),
|
||||
href: "/community/showcase/scriptbar",
|
||||
title: "ScriptBar",
|
||||
},
|
||||
{
|
||||
thumbnail: import(
|
||||
"../../../../assets/showcase-images/snippetexpandergui-search-and-paste.png"
|
||||
),
|
||||
href: "/community/showcase/snippetexpander",
|
||||
title: "Snippet Expander",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/surge.png"),
|
||||
href: "/community/showcase/surge",
|
||||
title: "Surge",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/tiny-rdm1.webp"),
|
||||
href: "/community/showcase/tinyrdm",
|
||||
title: "Tiny RDM",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/wailsterm.webp"),
|
||||
href: "/community/showcase/wailsterm",
|
||||
title: "WailsTerm",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/wally.webp"),
|
||||
href: "/community/showcase/wally",
|
||||
title: "Wally",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/warmine1.png"),
|
||||
href: "/community/showcase/warmine",
|
||||
title: "WarMine",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/wombat.webp"),
|
||||
href: "/community/showcase/wombat",
|
||||
title: "Wombat",
|
||||
},
|
||||
{
|
||||
thumbnail: import("../../../../assets/showcase-images/ytd.webp"),
|
||||
href: "/community/showcase/ytd",
|
||||
title: "Ytd",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
## How to add my project in showcase
|
||||
|
||||
<Steps>
|
||||
|
||||
1. Make a fork of the repository.
|
||||
2. Add the image(s) under `docs/src/assets/showcase-images` folder.
|
||||
3. Make a copy of the `_template.md` file under
|
||||
`docs/src/content/docs/community/showcase` folder.
|
||||
4. Rename the copied file to the name of your project. (Name should not start
|
||||
with `_`))
|
||||
5. Update the title, image, link and content of the file.
|
||||
6. Add it on the above list in
|
||||
`docs/src/content/docs/community/showcase/index.mdx`.
|
||||
7. Submit a PR.
|
||||
|
||||
</Steps>
|
||||
8
docs/src/content/docs/community/showcase/mchat.md
Normal file
8
docs/src/content/docs/community/showcase/mchat.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
title: Mchat
|
||||
---
|
||||
|
||||

|
||||
|
||||
[Official page](https://marcio199226.github.io/mchat-site/public/) Fully
|
||||
anonymous end2end encrypted chat.
|
||||
10
docs/src/content/docs/community/showcase/minecraftupdater.md
Normal file
10
docs/src/content/docs/community/showcase/minecraftupdater.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: Minecraft Updater
|
||||
---
|
||||
|
||||

|
||||
|
||||
[Minecraft Updater](https://github.com/Gurkengewuerz/MinecraftModUpdater) is a
|
||||
utility tool to update and synchronize Minecraft mods for your userbase. It’s
|
||||
built using Wails2 and React with [antd](https://ant.design/) as frontend
|
||||
framework.
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
title: Minesweeper XP
|
||||
---
|
||||
|
||||

|
||||
|
||||
[Minesweeper-XP](https://git.new/Minesweeper-XP) allows you to experience the
|
||||
classic Minesweeper XP (+ 98 and 3.1) on macOS, Windows, and Linux!
|
||||
21
docs/src/content/docs/community/showcase/modalfilemanager.md
Normal file
21
docs/src/content/docs/community/showcase/modalfilemanager.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
title: Modal File Manager
|
||||
---
|
||||
|
||||

|
||||
|
||||
[Modal File Manager](https://github.com/raguay/ModalFileManager) is a dual pane
|
||||
file manager using web technologies. My original design was based on NW.js and
|
||||
can be found [here](https://github.com/raguay/ModalFileManager-NWjs). This
|
||||
version uses the same Svelte based frontend code (but it has be greatly modified
|
||||
since the departure from NW.js), but the backend is a
|
||||
[Wails 2](https://wails.io/) implementation. By using this implementation, I no
|
||||
longer use command line `rm`, `cp`, etc. commands, but a git install has to be
|
||||
on the system to download themes and extensions. It is fully coded using Go and
|
||||
runs much faster than the previous versions.
|
||||
|
||||
This file manager is designed around the same principle as Vim: a state
|
||||
controlled keyboard actions. The number of states isn't fixed, but very
|
||||
programmable. Therefore, an infinite number of keyboard configurations can be
|
||||
created and used. This is the main difference from other file managers. There
|
||||
are themes and extensions available to download from GitHub.
|
||||
9
docs/src/content/docs/community/showcase/mollywallet.md
Normal file
9
docs/src/content/docs/community/showcase/mollywallet.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
title: Molley Wallet
|
||||
---
|
||||
|
||||

|
||||
|
||||
[Molly Wallet](https://github.com/grvlle/constellation_wallet/) the official
|
||||
$DAG wallet of the Constellation Network. It'll let users interact with the
|
||||
Hypergraph Network in various ways, not limited to producing $DAG transactions.
|
||||
16
docs/src/content/docs/community/showcase/october.md
Normal file
16
docs/src/content/docs/community/showcase/october.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: October
|
||||
---
|
||||
|
||||

|
||||
|
||||
[October](https://october.utf9k.net) is a small Wails application that makes it
|
||||
really easy to extract highlights from
|
||||
[Kobo eReaders](https://en.wikipedia.org/wiki/Kobo_eReader) and then forward
|
||||
them to [Readwise](https://readwise.io).
|
||||
|
||||
It has a relatively small scope with all platform versions weighing in under
|
||||
10MB, and that's without enabling [UPX compression](https://upx.github.io/)!
|
||||
|
||||
In contrast, the author's previous attempts with Electron quickly bloated to
|
||||
several hundred megabytes.
|
||||
9
docs/src/content/docs/community/showcase/optimus.md
Normal file
9
docs/src/content/docs/community/showcase/optimus.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
title: Optimus
|
||||
---
|
||||
|
||||

|
||||
|
||||
[Optimus](https://github.com/splode/optimus) is a desktop image optimization
|
||||
application. It supports conversion and compression between WebP, JPEG, and PNG
|
||||
image formats.
|
||||
8
docs/src/content/docs/community/showcase/portfall.md
Normal file
8
docs/src/content/docs/community/showcase/portfall.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
title: Portfall
|
||||
---
|
||||
|
||||

|
||||
|
||||
[Portfall](https://github.com/rekon-oss/portfall) - A desktop k8s
|
||||
port-forwarding portal for easy access to all your cluster UIs
|
||||
9
docs/src/content/docs/community/showcase/resizem.md
Normal file
9
docs/src/content/docs/community/showcase/resizem.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
title: Resizem
|
||||
---
|
||||
|
||||

|
||||
|
||||
[Resizem](https://github.com/barats/resizem) - is an app designed for bulk image
|
||||
process. It is particularly useful for users who need to resize, convert, and
|
||||
manage large numbers of image files at once.
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
title: Restic Browser
|
||||
---
|
||||
|
||||

|
||||
|
||||
[Restic-Browser](https://github.com/emuell/restic-browser) - A simple,
|
||||
cross-platform [restic](https://github.com/restic/restic) backup GUI for
|
||||
browsing and restoring restic repositories.
|
||||
23
docs/src/content/docs/community/showcase/riftshare.md
Normal file
23
docs/src/content/docs/community/showcase/riftshare.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
title: RiftShare
|
||||
---
|
||||
|
||||

|
||||
|
||||
Easy, Secure, and Free file sharing for everyone. Learn more at
|
||||
[Riftshare.app](https://riftshare.app)
|
||||
|
||||
## Features
|
||||
|
||||
- Easy secure file sharing between computers both in the local network and
|
||||
through the internet
|
||||
- Supports sending files or directories securely through the
|
||||
[magic wormhole protocol](https://magic-wormhole.readthedocs.io/en/latest/)
|
||||
- Compatible with all other apps using magic wormhole (magic-wormhole or
|
||||
wormhole-william CLI, wormhole-gui, etc.)
|
||||
- Automatic zipping of multiple selected files to send at once
|
||||
- Full animations, progress bar, and cancellation support for sending and
|
||||
receiving
|
||||
- Native OS File Selection
|
||||
- Open files in one click once received
|
||||
- Auto Update - don't worry about having the latest release!
|
||||
14
docs/src/content/docs/community/showcase/scriptbar.md
Normal file
14
docs/src/content/docs/community/showcase/scriptbar.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
title: ScriptBar
|
||||
---
|
||||
|
||||

|
||||
|
||||
[ScriptBar](https://GitHub.com/raguay/ScriptBarApp) is a program to show the
|
||||
output of scripts or [Node-Red](https://nodered.org) server. It runs scripts
|
||||
defined in EmailIt program and shows the output. Scripts from xBar or TextBar
|
||||
can be used, but currently on the TextBar scripts work well. It also displays
|
||||
the output of scripts on your system. ScriptBar doesn't put them in the menubar,
|
||||
but has them all in a convient window for easy viewing. You can have multiple
|
||||
tabs to have many different things show. You can also keep the links to your
|
||||
most visited web sites.
|
||||
34
docs/src/content/docs/community/showcase/snippetexpander.md
Normal file
34
docs/src/content/docs/community/showcase/snippetexpander.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
title: Snippet Expander
|
||||
---
|
||||
|
||||

|
||||
|
||||
Screenshot of Snippet Expander's Select Snippet window
|
||||
|
||||

|
||||
|
||||
Screenshot of Snippet Expander's Add Snippet screen
|
||||
|
||||

|
||||
|
||||
Screenshot of Snippet Expander's Search & Paste window
|
||||
|
||||
[Snippet Expander](https://snippetexpander.org) is "Your little expandable text
|
||||
snippets helper", for Linux.
|
||||
|
||||
Snippet Expander comprises of a GUI application built with Wails for managing
|
||||
snippets and settings, with a Search & Paste window mode for quickly selecting
|
||||
and pasting a snippet.
|
||||
|
||||
The Wails based GUI, go-lang CLI and vala-lang auto expander daemon all
|
||||
communicate with a go-lang daemon via D-Bus. The daemon does the majority of the
|
||||
work, managing the database of snippets and common settings, and providing
|
||||
services for expanding and pasting snippets etc.
|
||||
|
||||
Check out the
|
||||
[source code](https://git.sr.ht/~ianmjones/snippetexpander/tree/trunk/item/cmd/snippetexpandergui/app.go#L38)
|
||||
to see how the Wails app sends messages from the UI to the backend that are then
|
||||
sent to the daemon, and subscribes to a D-Bus event to monitor changes to
|
||||
snippets via another instance of the app or CLI and show them instantly in the
|
||||
UI via a Wails event.
|
||||
9
docs/src/content/docs/community/showcase/surge.md
Normal file
9
docs/src/content/docs/community/showcase/surge.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
title: Surge
|
||||
---
|
||||
|
||||

|
||||
|
||||
[Surge](https://getsurge.io/) is a p2p filesharing app designed to utilize
|
||||
blockchain technologies to enable 100% anonymous file transfers. Surge is
|
||||
end-to-end encrypted, decentralized and open source.
|
||||
12
docs/src/content/docs/community/showcase/tinyrdm.md
Normal file
12
docs/src/content/docs/community/showcase/tinyrdm.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: Tiny RDM
|
||||
---
|
||||
|
||||

|
||||

|
||||
|
||||
The [Tiny RDM](https://redis.tinycraft.cc/) application is an open-source,
|
||||
modern lightweight Redis GUI. It has a beautful UI, intuitive Redis database
|
||||
management, and compatible with Windows, Mac, and Linux. It provides visual
|
||||
key-value data operations, supports various data decoding and viewing options,
|
||||
built-in console for executing commands, slow log queries and more.
|
||||
8
docs/src/content/docs/community/showcase/wailsterm.md
Normal file
8
docs/src/content/docs/community/showcase/wailsterm.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
title: WailsTerm
|
||||
---
|
||||
|
||||

|
||||
|
||||
[WailsTerm](https://github.com/rlshukhov/wailsterm) is a simple translucent
|
||||
terminal app powered by Wails and Xterm.js.
|
||||
10
docs/src/content/docs/community/showcase/wally.md
Normal file
10
docs/src/content/docs/community/showcase/wally.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: Wally
|
||||
---
|
||||
|
||||

|
||||
|
||||
[Wally](https://ergodox-ez.com/pages/wally) is the official firmware flasher for
|
||||
[Ergodox](https://ergodox-ez.com/) keyboards. It looks great and is a fantastic
|
||||
example of what you can achieve with Wails: the ability to combine the power of
|
||||
Go and the rich graphical tools of the web development world.
|
||||
17
docs/src/content/docs/community/showcase/warmine.md
Normal file
17
docs/src/content/docs/community/showcase/warmine.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
title: Minecraft launcher for WarMine
|
||||
---
|
||||
|
||||

|
||||

|
||||
|
||||
[Minecraft launcher for WarMine](https://warmine.ru/) is a Wails application,
|
||||
that allows you to easily join modded game servers and manage your game
|
||||
accounts.
|
||||
|
||||
The Launcher downloads the game files, checks their integrity and launches the
|
||||
game with a wide range of customization options for the launch arguments from
|
||||
the backend.
|
||||
|
||||
Frontend is written in Svelte, whole launcher fits in 9MB and supports Windows
|
||||
7-11.
|
||||
7
docs/src/content/docs/community/showcase/wombat.md
Normal file
7
docs/src/content/docs/community/showcase/wombat.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
title: Wombat
|
||||
---
|
||||
|
||||

|
||||
|
||||
[Wombat](https://github.com/rogchap/wombat) is a cross platform gRPC client.
|
||||
10
docs/src/content/docs/community/showcase/ytd.md
Normal file
10
docs/src/content/docs/community/showcase/ytd.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
title: Ytd
|
||||
---
|
||||
|
||||

|
||||
|
||||
[Ytd](https://github.com/marcio199226/ytd/tree/v2-wails) is an app for
|
||||
downloading tracks from youtube, creating offline playlists and share them with
|
||||
your friends, your friends will be able to playback your playlists or download
|
||||
them for offline listening, has an built-in player.
|
||||
132
docs/src/content/docs/community/templates.md
Normal file
132
docs/src/content/docs/community/templates.md
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
---
|
||||
title: Templates
|
||||
---
|
||||
|
||||
:::caution
|
||||
|
||||
This page might be outdated for Wails v3.
|
||||
|
||||
:::
|
||||
|
||||
<!-- TODO: Update this link -->
|
||||
|
||||
This page serves as a list for community supported templates. To build your own
|
||||
template, please see the [Templates](https://wails.io/docs/guides/templates)
|
||||
guide.
|
||||
|
||||
:::tip[How to Submit a Template]
|
||||
|
||||
You can click `Edit this page` at the bottom to include your templates.
|
||||
|
||||
:::
|
||||
|
||||
To use these templates, run
|
||||
`wails init -n "Your Project Name" -t [the link below[@version]]`
|
||||
|
||||
If there is no version suffix, the main branch code template is used by default.
|
||||
If there is a version suffix, the code template corresponding to the tag of this
|
||||
version is used.
|
||||
|
||||
Example:
|
||||
`wails init -n "Your Project Name" -t https://github.com/misitebao/wails-template-vue`
|
||||
|
||||
:::danger[Attention]
|
||||
|
||||
**The Wails project does not maintain, is not responsible nor liable for 3rd
|
||||
party templates!**
|
||||
|
||||
If you are unsure about a template, inspect `package.json` and `wails.json` for
|
||||
what scripts are run and what packages are installed.
|
||||
|
||||
:::
|
||||
|
||||
## Vue
|
||||
|
||||
- [wails-template-vue](https://github.com/misitebao/wails-template-vue) - Wails
|
||||
template based on Vue ecology (Integrated TypeScript, Dark theme,
|
||||
Internationalization, Single page routing, TailwindCSS)
|
||||
- [wails-template-quasar-js](https://github.com/sgosiaco/wails-template-quasar-js) -
|
||||
A template using JavaScript + Quasar V2 (Vue 3, Vite, Sass, Pinia, ESLint,
|
||||
Prettier)
|
||||
- [wails-template-quasar-ts](https://github.com/sgosiaco/wails-template-quasar-ts) -
|
||||
A template using TypeScript + Quasar V2 (Vue 3, Vite, Sass, Pinia, ESLint,
|
||||
Prettier, Composition API with <script setup>)
|
||||
- [wails-template-naive](https://github.com/tk103331/wails-template-naive) -
|
||||
Wails template based on Naive UI (A Vue 3 Component Library)
|
||||
- [wails-template-nuxt](https://github.com/gornius/wails-template-nuxt) - Wails
|
||||
template using clean Nuxt3 and TypeScript with auto-imports for wails js
|
||||
runtime
|
||||
- [Wails-Tool-Template](https://github.com/xisuo67/Wails-Tool-Template) - Wails
|
||||
template using Vue+TypeScript+Vite+Element-plus(仿网易云)
|
||||
|
||||
## Angular
|
||||
|
||||
- [wails-template-angular](https://github.com/mateothegreat/wails-template-angular) -
|
||||
Angular 15+ action packed & ready to roll to production.
|
||||
- [wails-angular-template](https://github.com/TAINCER/wails-angular-template) -
|
||||
Angular with TypeScript, Sass, Hot-Reload, Code-Splitting and i18n
|
||||
|
||||
## React
|
||||
|
||||
- [wails-react-template](https://github.com/AlienRecall/wails-react-template) -
|
||||
A template using reactjs
|
||||
- [wails-react-template](https://github.com/flin7/wails-react-template) - A
|
||||
minimal template for React that supports live development
|
||||
- [wails-template-nextjs](https://github.com/LGiki/wails-template-nextjs) - A
|
||||
template using Next.js and TypeScript
|
||||
- [wails-template-nextjs-app-router](https://github.com/thisisvk-in/wails-template-nextjs-app-router) -
|
||||
A template using Next.js and TypeScript with App router
|
||||
- [wails-template-nextjs-app-router-src](https://github.com/edai-git/wails-template-nextjs-app-router) -
|
||||
A template using Next.js and TypeScript with App router src + example
|
||||
- [wails-vite-react-ts-tailwind-template](https://github.com/hotafrika/wails-vite-react-ts-tailwind-template) -
|
||||
A template for React + TypeScript + Vite + TailwindCSS
|
||||
- [wails-vite-react-ts-tailwind-shadcnui-template](https://github.com/Mahcks/wails-vite-react-tailwind-shadcnui-ts) -
|
||||
A template with Vite, React, TypeScript, TailwindCSS, and shadcn/ui
|
||||
|
||||
## Svelte
|
||||
|
||||
- [wails-svelte-template](https://github.com/raitonoberu/wails-svelte-template) -
|
||||
A template using Svelte
|
||||
- [wails-vite-svelte-template](https://github.com/BillBuilt/wails-vite-svelte-template) -
|
||||
A template using Svelte and Vite
|
||||
- [wails-vite-svelte-tailwind-template](https://github.com/BillBuilt/wails-vite-svelte-tailwind-template) -
|
||||
A template using Svelte and Vite with TailwindCSS v3
|
||||
- [wails-svelte-tailwind-vite-template](https://github.com/PylotLight/wails-vite-svelte-tailwind-template/tree/master) -
|
||||
An updated template using Svelte v4.2.0 and Vite with TailwindCSS v3.3.3
|
||||
- [wails-sveltekit-template](https://github.com/h8gi/wails-sveltekit-template) -
|
||||
A template using SvelteKit
|
||||
- [wails-template-shadcn-svelte](https://github.com/xijaja/wails-template-shadcn-svelte) -
|
||||
A template using Sveltekit and Shadcn-Svelte
|
||||
|
||||
## Solid
|
||||
|
||||
- [wails-template-vite-solid-ts](https://github.com/xijaja/wails-template-solid-ts) -
|
||||
A template using Solid + Ts + Vite
|
||||
- [wails-template-vite-solid-js](https://github.com/xijaja/wails-template-solid-js) -
|
||||
A template using Solid + Js + Vite
|
||||
|
||||
## Elm
|
||||
|
||||
- [wails-elm-template](https://github.com/benjamin-thomas/wails-elm-template) -
|
||||
Develop your GUI app with functional programming and a **snappy** hot-reload
|
||||
setup :tada: :rocket:
|
||||
- [wails-template-elm-tailwind](https://github.com/rnice01/wails-template-elm-tailwind) -
|
||||
Combine the powers :muscle: of Elm + Tailwind CSS + Wails! Hot reloading
|
||||
supported.
|
||||
|
||||
## HTMX
|
||||
|
||||
- [wails-htmx-templ-chi-tailwind](https://github.com/PylotLight/wails-hmtx-templ-template) -
|
||||
Use a unique combination of pure htmx for interactivity plus templ for
|
||||
creating components and forms
|
||||
|
||||
## Pure JavaScript (Vanilla)
|
||||
|
||||
- [wails-pure-js-template](https://github.com/KiddoV/wails-pure-js-template) - A
|
||||
template with nothing but just basic JavaScript, HTML, and CSS
|
||||
|
||||
## Lit (web components)
|
||||
|
||||
- [wails-lit-shoelace-esbuild-template](https://github.com/Braincompiler/wails-lit-shoelace-esbuild-template) -
|
||||
Wails template providing frontend with lit, Shoelace component library +
|
||||
pre-configured prettier and typescript.
|
||||
655
docs/src/content/docs/concepts/architecture.mdx
Normal file
655
docs/src/content/docs/concepts/architecture.mdx
Normal file
|
|
@ -0,0 +1,655 @@
|
|||
---
|
||||
title: How Wails Works
|
||||
description: Understanding the Wails architecture and how it achieves native performance
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
Wails is a framework for building desktop applications using **Go for the backend** and **web technologies for the frontend**. But unlike Electron, Wails doesn't bundle a browser—it uses the **operating system's native WebView**.
|
||||
|
||||
```d2
|
||||
direction: right
|
||||
|
||||
User: "User" {
|
||||
shape: person
|
||||
style.fill: "#3B82F6"
|
||||
}
|
||||
|
||||
Application: "Your Wails Application" {
|
||||
Frontend: "Frontend\n(HTML/CSS/JS)" {
|
||||
shape: rectangle
|
||||
style.fill: "#8B5CF6"
|
||||
}
|
||||
|
||||
Runtime: "Wails Runtime" {
|
||||
Bridge: "Message Bridge" {
|
||||
shape: diamond
|
||||
style.fill: "#10B981"
|
||||
}
|
||||
|
||||
Bindings: "Type-Safe Bindings" {
|
||||
shape: rectangle
|
||||
style.fill: "#10B981"
|
||||
}
|
||||
}
|
||||
|
||||
Backend: "Go Backend" {
|
||||
Services: "Your Services" {
|
||||
shape: rectangle
|
||||
style.fill: "#00ADD8"
|
||||
}
|
||||
|
||||
NativeAPIs: "OS APIs" {
|
||||
shape: rectangle
|
||||
style.fill: "#00ADD8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OS: "Operating System" {
|
||||
WebView: "Native WebView\n(WebKit/WebView2/WebKitGTK)" {
|
||||
shape: rectangle
|
||||
style.fill: "#6B7280"
|
||||
}
|
||||
|
||||
SystemAPIs: "System APIs\n(Windows/macOS/Linux)" {
|
||||
shape: rectangle
|
||||
style.fill: "#6B7280"
|
||||
}
|
||||
}
|
||||
|
||||
User -> Application.Frontend: "Interacts with UI"
|
||||
Application.Frontend <-> Application.Runtime.Bridge: "JSON messages"
|
||||
Application.Runtime.Bridge <-> Application.Backend.Services: "Direct function calls"
|
||||
Application.Runtime.Bindings -> Application.Frontend: "TypeScript definitions"
|
||||
Application.Frontend -> OS.WebView: "Renders in"
|
||||
Application.Backend.NativeAPIs -> OS.SystemAPIs: "Native calls"
|
||||
```
|
||||
|
||||
**Key differences from Electron:**
|
||||
|
||||
| Aspect | Wails | Electron |
|
||||
|--------|-------|----------|
|
||||
| **Browser** | OS-provided WebView | Bundled Chromium (~100MB) |
|
||||
| **Backend** | Go (compiled) | Node.js (interpreted) |
|
||||
| **Communication** | In-memory bridge | IPC (inter-process) |
|
||||
| **Bundle Size** | ~15MB | ~150MB |
|
||||
| **Memory** | ~10MB | ~100MB+ |
|
||||
| **Startup** | <0.5s | 2-3s |
|
||||
|
||||
## Core Components
|
||||
|
||||
### 1. Native WebView
|
||||
|
||||
Wails uses the operating system's built-in web rendering engine:
|
||||
|
||||
<Tabs syncKey="platform">
|
||||
<TabItem label="Windows" icon="seti:windows">
|
||||
**WebView2** (Microsoft Edge WebView2)
|
||||
- Based on Chromium (same as Edge browser)
|
||||
- Pre-installed on Windows 10/11
|
||||
- Automatic updates via Windows Update
|
||||
- Full modern web standards support
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="macOS" icon="apple">
|
||||
**WebKit** (Safari's rendering engine)
|
||||
- Built into macOS
|
||||
- Same engine as Safari browser
|
||||
- Excellent performance and battery life
|
||||
- Full modern web standards support
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="linux">
|
||||
**WebKitGTK** (GTK port of WebKit)
|
||||
- Installed via package manager
|
||||
- Same engine as GNOME Web (Epiphany)
|
||||
- Good standards support
|
||||
- Lightweight and performant
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
**Why this matters:**
|
||||
- **No bundled browser** → Smaller app size
|
||||
- **OS-native** → Better integration and performance
|
||||
- **Auto-updates** → Security patches from OS updates
|
||||
- **Familiar rendering** → Same as system browser
|
||||
|
||||
### 2. The Wails Bridge
|
||||
|
||||
The bridge is the heart of Wails—it enables **direct communication** between Go and JavaScript.
|
||||
|
||||
```d2
|
||||
direction: down
|
||||
|
||||
Frontend: "Frontend (JavaScript)" {
|
||||
shape: rectangle
|
||||
style.fill: "#8B5CF6"
|
||||
}
|
||||
|
||||
Bridge: "Wails Bridge" {
|
||||
Encoder: "JSON Encoder" {
|
||||
shape: rectangle
|
||||
}
|
||||
|
||||
Router: "Method Router" {
|
||||
shape: diamond
|
||||
style.fill: "#10B981"
|
||||
}
|
||||
|
||||
Decoder: "JSON Decoder" {
|
||||
shape: rectangle
|
||||
}
|
||||
}
|
||||
|
||||
Backend: "Backend (Go)" {
|
||||
Services: "Registered Services" {
|
||||
shape: rectangle
|
||||
style.fill: "#00ADD8"
|
||||
}
|
||||
}
|
||||
|
||||
Frontend -> Bridge.Encoder: "1. Call Go method\nGreet('Alice')"
|
||||
Bridge.Encoder -> Bridge.Router: "2. Encode to JSON\n{method: 'Greet', args: ['Alice']}"
|
||||
Bridge.Router -> Backend.Services: "3. Route to service\nGreetService.Greet('Alice')"
|
||||
Backend.Services -> Bridge.Decoder: "4. Return result\n'Hello, Alice!'"
|
||||
Bridge.Decoder -> Frontend: "5. Decode to JS\nPromise resolves"
|
||||
```
|
||||
|
||||
**How it works:**
|
||||
|
||||
1. **Frontend calls a Go method** (via auto-generated binding)
|
||||
2. **Bridge encodes the call** to JSON (method name + arguments)
|
||||
3. **Router finds the Go method** in registered services
|
||||
4. **Go method executes** and returns a value
|
||||
5. **Bridge decodes the result** and sends back to frontend
|
||||
6. **Promise resolves** in JavaScript with the result
|
||||
|
||||
**Performance characteristics:**
|
||||
- **In-memory**: No network overhead, no HTTP
|
||||
- **Zero-copy** where possible (for large data)
|
||||
- **Async by default**: Non-blocking on both sides
|
||||
- **Type-safe**: TypeScript definitions auto-generated
|
||||
|
||||
### 3. Service System
|
||||
|
||||
Services are the recommended way to expose Go functionality to the frontend.
|
||||
|
||||
```go
|
||||
// Define a service (just a regular Go struct)
|
||||
type GreetService struct {
|
||||
prefix string
|
||||
}
|
||||
|
||||
// Methods with exported names are automatically available
|
||||
func (g *GreetService) Greet(name string) string {
|
||||
return g.prefix + name + "!"
|
||||
}
|
||||
|
||||
func (g *GreetService) GetTime() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
// Register the service
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(&GreetService{prefix: "Hello, "}),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Service discovery:**
|
||||
- Wails **scans your struct** at startup
|
||||
- **Exported methods** become callable from frontend
|
||||
- **Type information** is extracted for TypeScript bindings
|
||||
- **Error handling** is automatic (Go errors → JS exceptions)
|
||||
|
||||
**Generated TypeScript binding:**
|
||||
```typescript
|
||||
// Auto-generated in frontend/bindings/GreetService.ts
|
||||
export function Greet(name: string): Promise<string>
|
||||
export function GetTime(): Promise<Date>
|
||||
```
|
||||
|
||||
**Why services?**
|
||||
- **Type-safe**: Full TypeScript support
|
||||
- **Auto-discovery**: No manual registration of methods
|
||||
- **Organised**: Group related functionality
|
||||
- **Testable**: Services are just Go structs
|
||||
|
||||
[Learn more about services →](/features/bindings/services)
|
||||
|
||||
### 4. Event System
|
||||
|
||||
Events enable **pub/sub communication** between components.
|
||||
|
||||
```d2
|
||||
direction: right
|
||||
|
||||
GoService: "Go Service" {
|
||||
shape: rectangle
|
||||
style.fill: "#00ADD8"
|
||||
}
|
||||
|
||||
EventBus: "Event Bus" {
|
||||
shape: cylinder
|
||||
style.fill: "#10B981"
|
||||
}
|
||||
|
||||
Frontend1: "Window 1" {
|
||||
shape: rectangle
|
||||
style.fill: "#8B5CF6"
|
||||
}
|
||||
|
||||
Frontend2: "Window 2" {
|
||||
shape: rectangle
|
||||
style.fill: "#8B5CF6"
|
||||
}
|
||||
|
||||
GoService -> EventBus: "Emit('data-updated', data)"
|
||||
EventBus -> Frontend1: "Notify subscribers"
|
||||
EventBus -> Frontend2: "Notify subscribers"
|
||||
Frontend1 -> EventBus: "On('data-updated', handler)"
|
||||
Frontend2 -> EventBus: "On('data-updated', handler)"
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- **Window communication**: One window notifies others
|
||||
- **Background tasks**: Go service notifies UI of progress
|
||||
- **State synchronisation**: Keep multiple windows in sync
|
||||
- **Loose coupling**: Components don't need direct references
|
||||
|
||||
**Example:**
|
||||
```go
|
||||
// Go: Emit an event
|
||||
app.Event.Emit("user-logged-in", user)
|
||||
```
|
||||
|
||||
```javascript
|
||||
// JavaScript: Listen for event
|
||||
import { On } from '@wailsio/runtime'
|
||||
|
||||
On('user-logged-in', (user) => {
|
||||
console.log('User logged in:', user)
|
||||
})
|
||||
```
|
||||
|
||||
[Learn more about events →](/features/events/system)
|
||||
|
||||
## Application Lifecycle
|
||||
|
||||
Understanding the lifecycle helps you know when to initialise resources and clean up.
|
||||
|
||||
```d2
|
||||
direction: down
|
||||
|
||||
Start: "Application Start" {
|
||||
shape: oval
|
||||
style.fill: "#10B981"
|
||||
}
|
||||
|
||||
Init: "Initialisation" {
|
||||
Create: "Create Application" {
|
||||
shape: rectangle
|
||||
}
|
||||
|
||||
Register: "Register Services" {
|
||||
shape: rectangle
|
||||
}
|
||||
|
||||
Setup: "Setup Windows/Menus" {
|
||||
shape: rectangle
|
||||
}
|
||||
}
|
||||
|
||||
Run: "Event Loop" {
|
||||
Events: "Process Events" {
|
||||
shape: rectangle
|
||||
}
|
||||
|
||||
Messages: "Handle Messages" {
|
||||
shape: rectangle
|
||||
}
|
||||
|
||||
Render: "Update UI" {
|
||||
shape: rectangle
|
||||
}
|
||||
}
|
||||
|
||||
Shutdown: "Shutdown" {
|
||||
Cleanup: "Cleanup Resources" {
|
||||
shape: rectangle
|
||||
}
|
||||
|
||||
Save: "Save State" {
|
||||
shape: rectangle
|
||||
}
|
||||
}
|
||||
|
||||
End: "Application End" {
|
||||
shape: oval
|
||||
style.fill: "#EF4444"
|
||||
}
|
||||
|
||||
Start -> Init.Create
|
||||
Init.Create -> Init.Register
|
||||
Init.Register -> Init.Setup
|
||||
Init.Setup -> Run.Events
|
||||
Run.Events -> Run.Messages
|
||||
Run.Messages -> Run.Render
|
||||
Run.Render -> Run.Events: "Loop"
|
||||
Run.Events -> Shutdown.Cleanup: "Quit signal"
|
||||
Shutdown.Cleanup -> Shutdown.Save
|
||||
Shutdown.Save -> End
|
||||
```
|
||||
|
||||
**Lifecycle hooks:**
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Name: "My App",
|
||||
|
||||
// Called before windows are created
|
||||
OnStartup: func(ctx context.Context) {
|
||||
// Initialise database, load config, etc.
|
||||
},
|
||||
|
||||
// Called when app is about to quit
|
||||
OnShutdown: func() {
|
||||
// Save state, close connections, etc.
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
[Learn more about lifecycle →](/concepts/lifecycle)
|
||||
|
||||
## Build Process
|
||||
|
||||
Understanding how Wails builds your application:
|
||||
|
||||
```d2
|
||||
direction: down
|
||||
|
||||
Source: "Source Code" {
|
||||
Go: "Go Code\n(main.go, services)" {
|
||||
shape: rectangle
|
||||
style.fill: "#00ADD8"
|
||||
}
|
||||
|
||||
Frontend: "Frontend Code\n(HTML/CSS/JS)" {
|
||||
shape: rectangle
|
||||
style.fill: "#8B5CF6"
|
||||
}
|
||||
}
|
||||
|
||||
Build: "Build Process" {
|
||||
AnalyseGo: "Analyse Go Code" {
|
||||
shape: rectangle
|
||||
}
|
||||
|
||||
GenerateBindings: "Generate Bindings" {
|
||||
shape: rectangle
|
||||
}
|
||||
|
||||
BuildFrontend: "Build Frontend" {
|
||||
shape: rectangle
|
||||
}
|
||||
|
||||
CompileGo: "Compile Go" {
|
||||
shape: rectangle
|
||||
}
|
||||
|
||||
Embed: "Embed Assets" {
|
||||
shape: rectangle
|
||||
}
|
||||
}
|
||||
|
||||
Output: "Output" {
|
||||
Binary: "Native Binary\n(myapp.exe/.app)" {
|
||||
shape: rectangle
|
||||
style.fill: "#10B981"
|
||||
}
|
||||
}
|
||||
|
||||
Source.Go -> Build.AnalyseGo
|
||||
Build.AnalyseGo -> Build.GenerateBindings: "Extract types"
|
||||
Build.GenerateBindings -> Source.Frontend: "TypeScript bindings"
|
||||
Source.Frontend -> Build.BuildFrontend: "Compile (Vite/webpack)"
|
||||
Build.BuildFrontend -> Build.Embed: "Bundled assets"
|
||||
Source.Go -> Build.CompileGo
|
||||
Build.CompileGo -> Build.Embed
|
||||
Build.Embed -> Output.Binary
|
||||
```
|
||||
|
||||
**Build steps:**
|
||||
|
||||
1. **Analyse Go code**
|
||||
- Scan services for exported methods
|
||||
- Extract parameter and return types
|
||||
- Generate method signatures
|
||||
|
||||
2. **Generate TypeScript bindings**
|
||||
- Create `.ts` files for each service
|
||||
- Include full type definitions
|
||||
- Add JSDoc comments
|
||||
|
||||
3. **Build frontend**
|
||||
- Run your bundler (Vite, webpack, etc.)
|
||||
- Minify and optimise
|
||||
- Output to `frontend/dist/`
|
||||
|
||||
4. **Compile Go**
|
||||
- Compile with optimisations (`-ldflags="-s -w"`)
|
||||
- Include build metadata
|
||||
- Platform-specific compilation
|
||||
|
||||
5. **Embed assets**
|
||||
- Embed frontend files into Go binary
|
||||
- Compress assets
|
||||
- Create single executable
|
||||
|
||||
**Result:** A single native executable with everything embedded.
|
||||
|
||||
[Learn more about building →](/guides/build/building)
|
||||
|
||||
## Development vs Production
|
||||
|
||||
Wails behaves differently in development and production:
|
||||
|
||||
<Tabs syncKey="mode">
|
||||
<TabItem label="Development (wails3 dev)" icon="seti:config">
|
||||
**Characteristics:**
|
||||
- **Hot reload**: Frontend changes reload instantly
|
||||
- **Source maps**: Debug with original source
|
||||
- **DevTools**: Browser DevTools available
|
||||
- **Logging**: Verbose logging enabled
|
||||
- **External frontend**: Served from dev server (Vite)
|
||||
|
||||
**How it works:**
|
||||
```d2
|
||||
direction: right
|
||||
|
||||
WailsApp: "Wails App" {
|
||||
shape: rectangle
|
||||
style.fill: "#00ADD8"
|
||||
}
|
||||
|
||||
DevServer: "Vite Dev Server\n(localhost:5173)" {
|
||||
shape: rectangle
|
||||
style.fill: "#8B5CF6"
|
||||
}
|
||||
|
||||
WebView: "WebView" {
|
||||
shape: rectangle
|
||||
style.fill: "#6B7280"
|
||||
}
|
||||
|
||||
WailsApp -> DevServer: "Proxy requests"
|
||||
DevServer -> WebView: "Serve with HMR"
|
||||
WebView -> WailsApp: "Call Go methods"
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Instant feedback on changes
|
||||
- Full debugging capabilities
|
||||
- Faster iteration
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Production (wails3 build)" icon="rocket">
|
||||
**Characteristics:**
|
||||
- **Embedded assets**: Frontend built into binary
|
||||
- **Optimised**: Minified, compressed
|
||||
- **No DevTools**: Disabled by default
|
||||
- **Minimal logging**: Errors only
|
||||
- **Single file**: Everything in one executable
|
||||
|
||||
**How it works:**
|
||||
```d2
|
||||
direction: right
|
||||
|
||||
Binary: "Single Binary\n(myapp.exe)" {
|
||||
GoCode: "Compiled Go" {
|
||||
shape: rectangle
|
||||
style.fill: "#00ADD8"
|
||||
}
|
||||
|
||||
Assets: "Embedded Assets\n(HTML/CSS/JS)" {
|
||||
shape: rectangle
|
||||
style.fill: "#8B5CF6"
|
||||
}
|
||||
}
|
||||
|
||||
WebView: "WebView" {
|
||||
shape: rectangle
|
||||
style.fill: "#6B7280"
|
||||
}
|
||||
|
||||
Binary.Assets -> WebView: "Serve from memory"
|
||||
WebView -> Binary.GoCode: "Call Go methods"
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Single file distribution
|
||||
- Smaller size (minified)
|
||||
- Better performance
|
||||
- No external dependencies
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Memory Model
|
||||
|
||||
Understanding memory usage helps you build efficient applications.
|
||||
|
||||
{/* VISUAL PLACEHOLDER: Memory Diagram
|
||||
Description: Memory layout diagram showing:
|
||||
1. Go Heap (services, application state)
|
||||
2. WebView Memory (DOM, JavaScript heap)
|
||||
3. Shared Memory (bridge communication)
|
||||
4. Arrows showing data flow between regions
|
||||
5. Annotations for zero-copy optimisations
|
||||
Style: Technical diagram with memory regions as boxes, clear labels, size indicators
|
||||
*/}
|
||||
|
||||
**Memory regions:**
|
||||
|
||||
1. **Go Heap**
|
||||
- Your services and application state
|
||||
- Managed by Go garbage collector
|
||||
- Typically 5-10MB for simple apps
|
||||
|
||||
2. **WebView Memory**
|
||||
- DOM, JavaScript heap, CSS
|
||||
- Managed by WebView's engine
|
||||
- Typically 10-20MB for simple apps
|
||||
|
||||
3. **Bridge Memory**
|
||||
- Message buffers for communication
|
||||
- Minimal overhead (\<1MB)
|
||||
- Zero-copy for large data where possible
|
||||
|
||||
**Optimisation tips:**
|
||||
- **Avoid large data transfers**: Pass IDs, fetch details on demand
|
||||
- **Use events for updates**: Don't poll from frontend
|
||||
- **Stream large files**: Don't load entirely into memory
|
||||
- **Clean up listeners**: Remove event listeners when done
|
||||
|
||||
[Learn more about performance →](/guides/advanced/performance)
|
||||
|
||||
## Security Model
|
||||
|
||||
Wails provides a secure-by-default architecture:
|
||||
|
||||
```d2
|
||||
direction: down
|
||||
|
||||
Frontend: "Frontend (Untrusted)" {
|
||||
shape: rectangle
|
||||
style.fill: "#EF4444"
|
||||
}
|
||||
|
||||
Bridge: "Wails Bridge (Validation)" {
|
||||
shape: diamond
|
||||
style.fill: "#F59E0B"
|
||||
}
|
||||
|
||||
Backend: "Backend (Trusted)" {
|
||||
shape: rectangle
|
||||
style.fill: "#10B981"
|
||||
}
|
||||
|
||||
Frontend -> Bridge: "Call method"
|
||||
Bridge -> Bridge: "Validate:\n- Method exists?\n- Types correct?\n- Access allowed?"
|
||||
Bridge -> Backend: "Execute if valid"
|
||||
Backend -> Bridge: "Return result"
|
||||
Bridge -> Frontend: "Send response"
|
||||
```
|
||||
|
||||
**Security features:**
|
||||
|
||||
1. **Method whitelisting**
|
||||
- Only exported methods are callable
|
||||
- Private methods are inaccessible
|
||||
- Explicit service registration required
|
||||
|
||||
2. **Type validation**
|
||||
- Arguments checked against Go types
|
||||
- Invalid types rejected
|
||||
- Prevents injection attacks
|
||||
|
||||
3. **No eval()**
|
||||
- Frontend can't execute arbitrary Go code
|
||||
- Only predefined methods callable
|
||||
- No dynamic code execution
|
||||
|
||||
4. **Context isolation**
|
||||
- Each window has its own context
|
||||
- Services can check caller context
|
||||
- Permissions per window possible
|
||||
|
||||
**Best practices:**
|
||||
- **Validate user input** in Go (don't trust frontend)
|
||||
- **Use context** for authentication/authorisation
|
||||
- **Sanitise file paths** before file operations
|
||||
- **Rate limit** expensive operations
|
||||
|
||||
[Learn more about security →](/guides/advanced/security)
|
||||
|
||||
## Next Steps
|
||||
|
||||
**Application Lifecycle** - Understand startup, shutdown, and lifecycle hooks
|
||||
[Learn More →](/concepts/lifecycle)
|
||||
|
||||
**Go-Frontend Bridge** - Deep dive into how the bridge works
|
||||
[Learn More →](/concepts/bridge)
|
||||
|
||||
**Build System** - Understand how Wails builds your application
|
||||
[Learn More →](/concepts/build-system)
|
||||
|
||||
**Start Building** - Apply what you've learned in a tutorial
|
||||
[Tutorials →](/tutorials/03-notes-vanilla)
|
||||
|
||||
---
|
||||
|
||||
**Questions about architecture?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [API reference](/reference/overview).
|
||||
700
docs/src/content/docs/concepts/bridge.mdx
Normal file
700
docs/src/content/docs/concepts/bridge.mdx
Normal file
|
|
@ -0,0 +1,700 @@
|
|||
---
|
||||
title: Go-Frontend Bridge
|
||||
description: Deep dive into how Wails enables direct communication between Go and JavaScript
|
||||
sidebar:
|
||||
order: 3
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
## Direct Go-JavaScript Communication
|
||||
|
||||
Wails provides a **direct, in-memory bridge** between Go and JavaScript, enabling seamless communication without HTTP overhead, process boundaries, or serialisation bottlenecks.
|
||||
|
||||
## The Big Picture
|
||||
|
||||
```d2
|
||||
direction: right
|
||||
|
||||
Frontend: "Frontend (JavaScript)" {
|
||||
UI: "React/Vue/Vanilla" {
|
||||
shape: rectangle
|
||||
style.fill: "#8B5CF6"
|
||||
}
|
||||
|
||||
Bindings: "Auto-Generated Bindings" {
|
||||
shape: rectangle
|
||||
style.fill: "#A78BFA"
|
||||
}
|
||||
}
|
||||
|
||||
Bridge: "Wails Bridge" {
|
||||
Encoder: "JSON Encoder" {
|
||||
shape: rectangle
|
||||
style.fill: "#10B981"
|
||||
}
|
||||
|
||||
Router: "Method Router" {
|
||||
shape: diamond
|
||||
style.fill: "#10B981"
|
||||
}
|
||||
|
||||
Decoder: "JSON Decoder" {
|
||||
shape: rectangle
|
||||
style.fill: "#10B981"
|
||||
}
|
||||
|
||||
TypeGen: "Type Generator" {
|
||||
shape: rectangle
|
||||
style.fill: "#10B981"
|
||||
}
|
||||
}
|
||||
|
||||
Backend: "Backend (Go)" {
|
||||
Services: "Your Services" {
|
||||
shape: rectangle
|
||||
style.fill: "#00ADD8"
|
||||
}
|
||||
|
||||
Registry: "Service Registry" {
|
||||
shape: rectangle
|
||||
style.fill: "#00ADD8"
|
||||
}
|
||||
}
|
||||
|
||||
Frontend.UI -> Frontend.Bindings: "import { Method }"
|
||||
Frontend.Bindings -> Bridge.Encoder: "Call Method('arg')"
|
||||
Bridge.Encoder -> Bridge.Router: "Encode to JSON"
|
||||
Bridge.Router -> Backend.Registry: "Find service"
|
||||
Backend.Registry -> Backend.Services: "Invoke method"
|
||||
Backend.Services -> Bridge.Decoder: "Return result"
|
||||
Bridge.Decoder -> Frontend.Bindings: "Decode to JS"
|
||||
Frontend.Bindings -> Frontend.UI: "Promise resolves"
|
||||
Bridge.TypeGen -> Frontend.Bindings: "Generate types"
|
||||
```
|
||||
|
||||
**Key insight:** No HTTP, no IPC, no process boundaries. Just **direct function calls** with **type safety**.
|
||||
|
||||
## How It Works: Step by Step
|
||||
|
||||
### 1. Service Registration (Startup)
|
||||
|
||||
When your application starts, Wails scans your services:
|
||||
|
||||
```go
|
||||
type GreetService struct {
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (g *GreetService) Greet(name string) string {
|
||||
return g.prefix + name + "!"
|
||||
}
|
||||
|
||||
func (g *GreetService) Add(a, b int) int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
// Register service
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(&GreetService{prefix: "Hello, "}),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**What Wails does:**
|
||||
1. **Scans the struct** for exported methods
|
||||
2. **Extracts type information** (parameters, return types)
|
||||
3. **Builds a registry** mapping method names to functions
|
||||
4. **Generates TypeScript bindings** with full type definitions
|
||||
|
||||
### 2. Binding Generation (Build Time)
|
||||
|
||||
Wails generates TypeScript bindings automatically:
|
||||
|
||||
```typescript
|
||||
// Auto-generated: frontend/bindings/GreetService.ts
|
||||
export function Greet(name: string): Promise<string>
|
||||
export function Add(a: number, b: number): Promise<number>
|
||||
```
|
||||
|
||||
**Type mapping:**
|
||||
|
||||
| Go Type | TypeScript Type |
|
||||
|---------|-----------------|
|
||||
| `string` | `string` |
|
||||
| `int`, `int32`, `int64` | `number` |
|
||||
| `float32`, `float64` | `number` |
|
||||
| `bool` | `boolean` |
|
||||
| `[]T` | `T[]` |
|
||||
| `map[string]T` | `Record<string, T>` |
|
||||
| `struct` | `interface` |
|
||||
| `time.Time` | `Date` |
|
||||
| `error` | Exception (thrown) |
|
||||
|
||||
### 3. Frontend Call (Runtime)
|
||||
|
||||
Developer calls the Go method from JavaScript:
|
||||
|
||||
```javascript
|
||||
import { Greet, Add } from './bindings/GreetService'
|
||||
|
||||
// Call Go from JavaScript
|
||||
const greeting = await Greet("World")
|
||||
console.log(greeting) // "Hello, World!"
|
||||
|
||||
const sum = await Add(5, 3)
|
||||
console.log(sum) // 8
|
||||
```
|
||||
|
||||
**What happens:**
|
||||
1. **Binding function called** - `Greet("World")`
|
||||
2. **Message created** - `{ service: "GreetService", method: "Greet", args: ["World"] }`
|
||||
3. **Sent to bridge** - Via WebView's JavaScript bridge
|
||||
4. **Promise returned** - Awaits response
|
||||
|
||||
### 4. Bridge Processing (Runtime)
|
||||
|
||||
The bridge receives the message and processes it:
|
||||
|
||||
```d2
|
||||
direction: down
|
||||
|
||||
Receive: "Receive Message" {
|
||||
shape: rectangle
|
||||
style.fill: "#10B981"
|
||||
}
|
||||
|
||||
Parse: "Parse JSON" {
|
||||
shape: rectangle
|
||||
}
|
||||
|
||||
Validate: "Validate" {
|
||||
Check: "Service exists?" {
|
||||
shape: diamond
|
||||
}
|
||||
|
||||
CheckMethod: "Method exists?" {
|
||||
shape: diamond
|
||||
}
|
||||
|
||||
CheckTypes: "Types correct?" {
|
||||
shape: diamond
|
||||
}
|
||||
}
|
||||
|
||||
Invoke: "Invoke Go Method" {
|
||||
shape: rectangle
|
||||
style.fill: "#00ADD8"
|
||||
}
|
||||
|
||||
Encode: "Encode Result" {
|
||||
shape: rectangle
|
||||
}
|
||||
|
||||
Send: "Send Response" {
|
||||
shape: rectangle
|
||||
style.fill: "#10B981"
|
||||
}
|
||||
|
||||
Error: "Send Error" {
|
||||
shape: rectangle
|
||||
style.fill: "#EF4444"
|
||||
}
|
||||
|
||||
Receive -> Parse
|
||||
Parse -> Validate.Check
|
||||
Validate.Check -> Validate.CheckMethod: "Yes"
|
||||
Validate.Check -> Error: "No"
|
||||
Validate.CheckMethod -> Validate.CheckTypes: "Yes"
|
||||
Validate.CheckMethod -> Error: "No"
|
||||
Validate.CheckTypes -> Invoke: "Yes"
|
||||
Validate.CheckTypes -> Error: "No"
|
||||
Invoke -> Encode: "Success"
|
||||
Invoke -> Error: "Error"
|
||||
Encode -> Send
|
||||
```
|
||||
|
||||
**Security:** Only registered services and exported methods are callable.
|
||||
|
||||
### 5. Go Execution (Runtime)
|
||||
|
||||
The Go method executes:
|
||||
|
||||
```go
|
||||
func (g *GreetService) Greet(name string) string {
|
||||
// This runs in Go
|
||||
return g.prefix + name + "!"
|
||||
}
|
||||
```
|
||||
|
||||
**Execution context:**
|
||||
- Runs in a **goroutine** (non-blocking)
|
||||
- Has access to **all Go features** (file system, network, databases)
|
||||
- Can call **other Go code** freely
|
||||
- Returns result or error
|
||||
|
||||
### 6. Response (Runtime)
|
||||
|
||||
Result is sent back to JavaScript:
|
||||
|
||||
```javascript
|
||||
// Promise resolves with result
|
||||
const greeting = await Greet("World")
|
||||
// greeting = "Hello, World!"
|
||||
```
|
||||
|
||||
**Error handling:**
|
||||
|
||||
```go
|
||||
func (g *GreetService) Divide(a, b float64) (float64, error) {
|
||||
if b == 0 {
|
||||
return 0, errors.New("division by zero")
|
||||
}
|
||||
return a / b, nil
|
||||
}
|
||||
```
|
||||
|
||||
```javascript
|
||||
try {
|
||||
const result = await Divide(10, 0)
|
||||
} catch (error) {
|
||||
console.error("Go error:", error) // "division by zero"
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Characteristics
|
||||
|
||||
### Speed
|
||||
|
||||
**Typical call overhead:** <1ms
|
||||
|
||||
```
|
||||
Frontend Call → Bridge → Go Execution → Bridge → Frontend Response
|
||||
↓ ↓ ↓ ↓ ↓
|
||||
<0.1ms <0.1ms [varies] <0.1ms <0.1ms
|
||||
```
|
||||
|
||||
**Compared to alternatives:**
|
||||
- **HTTP/REST:** 5-50ms (network stack, serialisation)
|
||||
- **IPC:** 1-10ms (process boundaries, marshalling)
|
||||
- **Wails Bridge:** <1ms (in-memory, direct call)
|
||||
|
||||
### Memory
|
||||
|
||||
**Per-call overhead:** ~1KB (message buffer)
|
||||
|
||||
**Zero-copy optimisation:** Large data (>1MB) uses shared memory where possible.
|
||||
|
||||
### Concurrency
|
||||
|
||||
**Calls are concurrent:**
|
||||
- Each call runs in its own goroutine
|
||||
- Multiple calls can execute simultaneously
|
||||
- No blocking between calls
|
||||
|
||||
```javascript
|
||||
// These run concurrently
|
||||
const [result1, result2, result3] = await Promise.all([
|
||||
SlowOperation1(),
|
||||
SlowOperation2(),
|
||||
SlowOperation3(),
|
||||
])
|
||||
```
|
||||
|
||||
## Type System
|
||||
|
||||
### Supported Types
|
||||
|
||||
#### Primitives
|
||||
|
||||
```go
|
||||
// Go
|
||||
func Example(
|
||||
s string,
|
||||
i int,
|
||||
f float64,
|
||||
b bool,
|
||||
) (string, int, float64, bool) {
|
||||
return s, i, f, b
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// TypeScript (auto-generated)
|
||||
function Example(
|
||||
s: string,
|
||||
i: number,
|
||||
f: number,
|
||||
b: boolean,
|
||||
): Promise<[string, number, number, boolean]>
|
||||
```
|
||||
|
||||
#### Slices and Arrays
|
||||
|
||||
```go
|
||||
// Go
|
||||
func Sum(numbers []int) int {
|
||||
total := 0
|
||||
for _, n := range numbers {
|
||||
total += n
|
||||
}
|
||||
return total
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// TypeScript
|
||||
function Sum(numbers: number[]): Promise<number>
|
||||
|
||||
// Usage
|
||||
const total = await Sum([1, 2, 3, 4, 5]) // 15
|
||||
```
|
||||
|
||||
#### Maps
|
||||
|
||||
```go
|
||||
// Go
|
||||
func GetConfig() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"theme": "dark",
|
||||
"fontSize": 14,
|
||||
"enabled": true,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// TypeScript
|
||||
function GetConfig(): Promise<Record<string, any>>
|
||||
|
||||
// Usage
|
||||
const config = await GetConfig()
|
||||
console.log(config.theme) // "dark"
|
||||
```
|
||||
|
||||
#### Structs
|
||||
|
||||
```go
|
||||
// Go
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
func GetUser(id int) (*User, error) {
|
||||
return &User{
|
||||
ID: id,
|
||||
Name: "Alice",
|
||||
Email: "alice@example.com",
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// TypeScript (auto-generated)
|
||||
interface User {
|
||||
id: number
|
||||
name: string
|
||||
email: string
|
||||
}
|
||||
|
||||
function GetUser(id: number): Promise<User>
|
||||
|
||||
// Usage
|
||||
const user = await GetUser(1)
|
||||
console.log(user.name) // "Alice"
|
||||
```
|
||||
|
||||
**JSON tags:** Use `json:` tags to control field names in TypeScript.
|
||||
|
||||
#### Time
|
||||
|
||||
```go
|
||||
// Go
|
||||
func GetTimestamp() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// TypeScript
|
||||
function GetTimestamp(): Promise<Date>
|
||||
|
||||
// Usage
|
||||
const timestamp = await GetTimestamp()
|
||||
console.log(timestamp.toISOString())
|
||||
```
|
||||
|
||||
#### Errors
|
||||
|
||||
```go
|
||||
// Go
|
||||
func Validate(input string) error {
|
||||
if input == "" {
|
||||
return errors.New("input cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// TypeScript
|
||||
function Validate(input: string): Promise<void>
|
||||
|
||||
// Usage
|
||||
try {
|
||||
await Validate("")
|
||||
} catch (error) {
|
||||
console.error(error) // "input cannot be empty"
|
||||
}
|
||||
```
|
||||
|
||||
### Unsupported Types
|
||||
|
||||
These types **cannot** be passed across the bridge:
|
||||
|
||||
- **Channels** (`chan T`)
|
||||
- **Functions** (`func()`)
|
||||
- **Interfaces** (except `interface{}` / `any`)
|
||||
- **Pointers** (except to structs)
|
||||
- **Unexported fields** (lowercase)
|
||||
|
||||
**Workaround:** Use IDs or handles:
|
||||
|
||||
```go
|
||||
// ❌ Can't pass file handle
|
||||
func OpenFile(path string) (*os.File, error) {
|
||||
return os.Open(path)
|
||||
}
|
||||
|
||||
// ✅ Return file ID instead
|
||||
var files = make(map[string]*os.File)
|
||||
|
||||
func OpenFile(path string) (string, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
id := generateID()
|
||||
files[id] = file
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func ReadFile(id string) ([]byte, error) {
|
||||
file := files[id]
|
||||
return io.ReadAll(file)
|
||||
}
|
||||
|
||||
func CloseFile(id string) error {
|
||||
file := files[id]
|
||||
delete(files, id)
|
||||
return file.Close()
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Patterns
|
||||
|
||||
### Context Passing
|
||||
|
||||
Services can access the call context:
|
||||
|
||||
```go
|
||||
type UserService struct{}
|
||||
|
||||
func (s *UserService) GetCurrentUser(ctx context.Context) (*User, error) {
|
||||
// Access window that made the call
|
||||
window := application.ContextWindow(ctx)
|
||||
|
||||
// Access application
|
||||
app := application.ContextApplication(ctx)
|
||||
|
||||
// Your logic
|
||||
return getCurrentUser(), nil
|
||||
}
|
||||
```
|
||||
|
||||
**Context provides:**
|
||||
- Window that made the call
|
||||
- Application instance
|
||||
- Request metadata
|
||||
|
||||
### Streaming Data
|
||||
|
||||
For large data, use events instead of return values:
|
||||
|
||||
```go
|
||||
func ProcessLargeFile(path string) error {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
lineNum := 0
|
||||
|
||||
for scanner.Scan() {
|
||||
lineNum++
|
||||
// Emit progress events
|
||||
app.Event.Emit("file-progress", map[string]interface{}{
|
||||
"line": lineNum,
|
||||
"text": scanner.Text(),
|
||||
})
|
||||
}
|
||||
|
||||
return scanner.Err()
|
||||
}
|
||||
```
|
||||
|
||||
```javascript
|
||||
import { On } from '@wailsio/runtime'
|
||||
import { ProcessLargeFile } from './bindings/FileService'
|
||||
|
||||
// Listen for progress
|
||||
On('file-progress', (data) => {
|
||||
console.log(`Line ${data.line}: ${data.text}`)
|
||||
})
|
||||
|
||||
// Start processing
|
||||
await ProcessLargeFile('/path/to/large/file.txt')
|
||||
```
|
||||
|
||||
### Cancellation
|
||||
|
||||
Use context for cancellable operations:
|
||||
|
||||
```go
|
||||
func LongRunningTask(ctx context.Context) error {
|
||||
for i := 0; i < 1000; i++ {
|
||||
// Check if cancelled
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
// Continue work
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** Context cancellation on frontend disconnect is automatic.
|
||||
|
||||
### Batch Operations
|
||||
|
||||
Reduce bridge overhead by batching:
|
||||
|
||||
```go
|
||||
// ❌ Inefficient: N bridge calls
|
||||
for _, item := range items {
|
||||
await ProcessItem(item)
|
||||
}
|
||||
|
||||
// ✅ Efficient: 1 bridge call
|
||||
await ProcessItems(items)
|
||||
```
|
||||
|
||||
```go
|
||||
func ProcessItems(items []Item) ([]Result, error) {
|
||||
results := make([]Result, len(items))
|
||||
for i, item := range items {
|
||||
results[i] = processItem(item)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
```
|
||||
|
||||
## Debugging the Bridge
|
||||
|
||||
### Enable Debug Logging
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Name: "My App",
|
||||
Logger: application.NewDefaultLogger(),
|
||||
LogLevel: logger.DEBUG,
|
||||
})
|
||||
```
|
||||
|
||||
**Output shows:**
|
||||
- Method calls
|
||||
- Parameters
|
||||
- Return values
|
||||
- Errors
|
||||
- Timing information
|
||||
|
||||
### Inspect Generated Bindings
|
||||
|
||||
Check `frontend/bindings/` to see generated TypeScript:
|
||||
|
||||
```typescript
|
||||
// frontend/bindings/MyService.ts
|
||||
export function MyMethod(arg: string): Promise<number> {
|
||||
return window.wails.Call('MyService.MyMethod', arg)
|
||||
}
|
||||
```
|
||||
|
||||
### Test Services Directly
|
||||
|
||||
Test Go services without the frontend:
|
||||
|
||||
```go
|
||||
func TestGreetService(t *testing.T) {
|
||||
service := &GreetService{prefix: "Hello, "}
|
||||
result := service.Greet("Test")
|
||||
if result != "Hello, Test!" {
|
||||
t.Errorf("Expected 'Hello, Test!', got '%s'", result)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Batch operations** - Reduce bridge calls
|
||||
- **Use events for streaming** - Don't return large arrays
|
||||
- **Keep methods fast** - <100ms ideal
|
||||
- **Use goroutines** - For long operations
|
||||
- **Cache on Go side** - Avoid repeated calculations
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Don't make excessive calls** - Batch when possible
|
||||
- **Don't return huge data** - Use pagination or streaming
|
||||
- **Don't block** - Use goroutines for long operations
|
||||
- **Don't pass complex types** - Keep it simple
|
||||
- **Don't ignore errors** - Always handle them
|
||||
|
||||
## Security
|
||||
|
||||
The bridge is secure by default:
|
||||
|
||||
1. **Whitelist only** - Only registered services callable
|
||||
2. **Type validation** - Arguments checked against Go types
|
||||
3. **No eval()** - Frontend can't execute arbitrary Go code
|
||||
4. **No reflection abuse** - Only exported methods accessible
|
||||
|
||||
**Best practices:**
|
||||
- **Validate input** in Go (don't trust frontend)
|
||||
- **Use context** for authentication/authorisation
|
||||
- **Rate limit** expensive operations
|
||||
- **Sanitise** file paths and user input
|
||||
|
||||
## Next Steps
|
||||
|
||||
**Build System** - Learn how Wails builds and bundles your application
|
||||
[Learn More →](/concepts/build-system)
|
||||
|
||||
**Services** - Deep dive into the service system
|
||||
[Learn More →](/features/bindings/services)
|
||||
|
||||
**Events** - Use events for pub/sub communication
|
||||
[Learn More →](/features/events/system)
|
||||
|
||||
---
|
||||
|
||||
**Questions about the bridge?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [binding examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/binding).
|
||||
759
docs/src/content/docs/concepts/build-system.mdx
Normal file
759
docs/src/content/docs/concepts/build-system.mdx
Normal file
|
|
@ -0,0 +1,759 @@
|
|||
---
|
||||
title: Build System
|
||||
description: Understanding how Wails builds and packages your application
|
||||
sidebar:
|
||||
order: 4
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
## Unified Build System
|
||||
|
||||
Wails provides a **unified build system** that compiles Go code, bundles frontend assets, embeds everything into a single executable, and handles platform-specific builds—all with one command.
|
||||
|
||||
```bash
|
||||
wails3 build
|
||||
```
|
||||
|
||||
**Output:** Native executable with everything embedded.
|
||||
|
||||
## Build Process Overview
|
||||
|
||||
```d2
|
||||
direction: down
|
||||
|
||||
Source: "Source Code" {
|
||||
Go: "Go Code\n(main.go, services)" {
|
||||
shape: rectangle
|
||||
style.fill: "#00ADD8"
|
||||
}
|
||||
|
||||
Frontend: "Frontend Code\n(HTML/CSS/JS)" {
|
||||
shape: rectangle
|
||||
style.fill: "#8B5CF6"
|
||||
}
|
||||
}
|
||||
|
||||
Analysis: "Analysis Phase" {
|
||||
ScanGo: "Scan Go Services" {
|
||||
shape: rectangle
|
||||
}
|
||||
|
||||
ExtractTypes: "Extract Types" {
|
||||
shape: rectangle
|
||||
}
|
||||
}
|
||||
|
||||
Generation: "Generation Phase" {
|
||||
GenBindings: "Generate TypeScript Bindings" {
|
||||
shape: rectangle
|
||||
style.fill: "#10B981"
|
||||
}
|
||||
|
||||
BuildFrontend: "Build Frontend\n(Vite/webpack)" {
|
||||
shape: rectangle
|
||||
style.fill: "#8B5CF6"
|
||||
}
|
||||
}
|
||||
|
||||
Compilation: "Compilation Phase" {
|
||||
CompileGo: "Compile Go\n(with optimisations)" {
|
||||
shape: rectangle
|
||||
style.fill: "#00ADD8"
|
||||
}
|
||||
|
||||
EmbedAssets: "Embed Frontend Assets" {
|
||||
shape: rectangle
|
||||
style.fill: "#10B981"
|
||||
}
|
||||
}
|
||||
|
||||
Output: "Output" {
|
||||
Binary: "Native Binary\n(myapp.exe/.app)" {
|
||||
shape: rectangle
|
||||
style.fill: "#10B981"
|
||||
}
|
||||
}
|
||||
|
||||
Source.Go -> Analysis.ScanGo
|
||||
Analysis.ScanGo -> Analysis.ExtractTypes
|
||||
Analysis.ExtractTypes -> Generation.GenBindings
|
||||
Generation.GenBindings -> Source.Frontend: "TypeScript types"
|
||||
Source.Frontend -> Generation.BuildFrontend
|
||||
Generation.BuildFrontend -> Compilation.EmbedAssets: "Bundled assets"
|
||||
Source.Go -> Compilation.CompileGo
|
||||
Compilation.CompileGo -> Compilation.EmbedAssets
|
||||
Compilation.EmbedAssets -> Output.Binary
|
||||
```
|
||||
|
||||
## Build Phases
|
||||
|
||||
### 1. Analysis Phase
|
||||
|
||||
Wails scans your Go code to understand your services:
|
||||
|
||||
```go
|
||||
type GreetService struct {
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (g *GreetService) Greet(name string) string {
|
||||
return g.prefix + name + "!"
|
||||
}
|
||||
```
|
||||
|
||||
**What Wails extracts:**
|
||||
- Service name: `GreetService`
|
||||
- Method name: `Greet`
|
||||
- Parameter types: `string`
|
||||
- Return types: `string`
|
||||
|
||||
**Used for:** Generating TypeScript bindings
|
||||
|
||||
### 2. Generation Phase
|
||||
|
||||
#### TypeScript Bindings
|
||||
|
||||
Wails generates type-safe bindings:
|
||||
|
||||
```typescript
|
||||
// Auto-generated: frontend/bindings/GreetService.ts
|
||||
export function Greet(name: string): Promise<string> {
|
||||
return window.wails.Call('GreetService.Greet', name)
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Full type safety
|
||||
- IDE autocomplete
|
||||
- Compile-time errors
|
||||
- JSDoc comments
|
||||
|
||||
#### Frontend Build
|
||||
|
||||
Your frontend bundler runs (Vite, webpack, etc.):
|
||||
|
||||
```bash
|
||||
# Vite example
|
||||
vite build --outDir dist
|
||||
```
|
||||
|
||||
**What happens:**
|
||||
- JavaScript/TypeScript compiled
|
||||
- CSS processed and minified
|
||||
- Assets optimised
|
||||
- Source maps generated (dev only)
|
||||
- Output to `frontend/dist/`
|
||||
|
||||
### 3. Compilation Phase
|
||||
|
||||
#### Go Compilation
|
||||
|
||||
Go code is compiled with optimisations:
|
||||
|
||||
```bash
|
||||
go build -ldflags="-s -w" -o myapp.exe
|
||||
```
|
||||
|
||||
**Flags:**
|
||||
- `-s`: Strip symbol table
|
||||
- `-w`: Strip DWARF debugging info
|
||||
- Result: Smaller binary (~30% reduction)
|
||||
|
||||
**Platform-specific:**
|
||||
- Windows: `.exe` with icon embedded
|
||||
- macOS: `.app` bundle structure
|
||||
- Linux: ELF binary
|
||||
|
||||
#### Asset Embedding
|
||||
|
||||
Frontend assets are embedded into the Go binary:
|
||||
|
||||
```go
|
||||
//go:embed frontend/dist
|
||||
var assets embed.FS
|
||||
```
|
||||
|
||||
**Result:** Single executable with everything inside.
|
||||
|
||||
### 4. Output
|
||||
|
||||
**Single native binary:**
|
||||
- Windows: `myapp.exe` (~15MB)
|
||||
- macOS: `myapp.app` (~15MB)
|
||||
- Linux: `myapp` (~15MB)
|
||||
|
||||
**No dependencies** (except system WebView).
|
||||
|
||||
## Development vs Production
|
||||
|
||||
<Tabs syncKey="mode">
|
||||
<TabItem label="Development (wails3 dev)" icon="seti:config">
|
||||
**Optimised for speed:**
|
||||
|
||||
```bash
|
||||
wails3 dev
|
||||
```
|
||||
|
||||
**What happens:**
|
||||
1. Starts frontend dev server (Vite on port 5173)
|
||||
2. Compiles Go without optimisations
|
||||
3. Launches app pointing to dev server
|
||||
4. Enables hot reload
|
||||
5. Includes source maps
|
||||
|
||||
**Characteristics:**
|
||||
- **Fast rebuilds** (<1s for frontend changes)
|
||||
- **No asset embedding** (served from dev server)
|
||||
- **Debug symbols** included
|
||||
- **Source maps** enabled
|
||||
- **Verbose logging**
|
||||
|
||||
**File size:** Larger (~50MB with debug symbols)
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Production (wails3 build)" icon="rocket">
|
||||
**Optimised for size and performance:**
|
||||
|
||||
```bash
|
||||
wails3 build
|
||||
```
|
||||
|
||||
**What happens:**
|
||||
1. Builds frontend for production (minified)
|
||||
2. Compiles Go with optimisations
|
||||
3. Strips debug symbols
|
||||
4. Embeds assets
|
||||
5. Creates single binary
|
||||
|
||||
**Characteristics:**
|
||||
- **Optimised code** (minified, tree-shaken)
|
||||
- **Assets embedded** (no external files)
|
||||
- **Debug symbols stripped**
|
||||
- **No source maps**
|
||||
- **Minimal logging**
|
||||
|
||||
**File size:** Smaller (~15MB)
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Build Commands
|
||||
|
||||
### Basic Build
|
||||
|
||||
```bash
|
||||
wails3 build
|
||||
```
|
||||
|
||||
**Output:** `build/bin/myapp[.exe]`
|
||||
|
||||
### Build for Specific Platform
|
||||
|
||||
```bash
|
||||
# Build for Windows (from any OS)
|
||||
wails3 build -platform windows/amd64
|
||||
|
||||
# Build for macOS
|
||||
wails3 build -platform darwin/amd64
|
||||
wails3 build -platform darwin/arm64
|
||||
|
||||
# Build for Linux
|
||||
wails3 build -platform linux/amd64
|
||||
```
|
||||
|
||||
**Cross-compilation:** Build for any platform from any platform.
|
||||
|
||||
### Build with Options
|
||||
|
||||
```bash
|
||||
# Custom output directory
|
||||
wails3 build -o ./dist/myapp
|
||||
|
||||
# Skip frontend build (use existing)
|
||||
wails3 build -skipbindings
|
||||
|
||||
# Clean build (remove cache)
|
||||
wails3 build -clean
|
||||
|
||||
# Verbose output
|
||||
wails3 build -v
|
||||
```
|
||||
|
||||
### Build Modes
|
||||
|
||||
```bash
|
||||
# Debug build (includes symbols)
|
||||
wails3 build -debug
|
||||
|
||||
# Production build (default, optimised)
|
||||
wails3 build
|
||||
|
||||
# Development build (fast, unoptimised)
|
||||
wails3 build -devbuild
|
||||
```
|
||||
|
||||
## Build Configuration
|
||||
|
||||
### Taskfile.yml
|
||||
|
||||
Wails uses [Taskfile](https://taskfile.dev/) for build configuration:
|
||||
|
||||
```yaml
|
||||
# Taskfile.yml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
desc: Build the application
|
||||
cmds:
|
||||
- wails3 build
|
||||
|
||||
build:windows:
|
||||
desc: Build for Windows
|
||||
cmds:
|
||||
- wails3 build -platform windows/amd64
|
||||
|
||||
build:macos:
|
||||
desc: Build for macOS (Universal)
|
||||
cmds:
|
||||
- wails3 build -platform darwin/amd64
|
||||
- wails3 build -platform darwin/arm64
|
||||
- lipo -create -output build/bin/myapp.app build/bin/myapp-amd64.app build/bin/myapp-arm64.app
|
||||
|
||||
build:linux:
|
||||
desc: Build for Linux
|
||||
cmds:
|
||||
- wails3 build -platform linux/amd64
|
||||
```
|
||||
|
||||
**Run tasks:**
|
||||
|
||||
```bash
|
||||
task build:windows
|
||||
task build:macos
|
||||
task build:linux
|
||||
```
|
||||
|
||||
### Build Options File
|
||||
|
||||
Create `build/build.json` for persistent configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "My Application",
|
||||
"version": "1.0.0",
|
||||
"author": "Your Name",
|
||||
"description": "Application description",
|
||||
"icon": "build/appicon.png",
|
||||
"outputFilename": "myapp",
|
||||
"platforms": ["windows/amd64", "darwin/amd64", "linux/amd64"],
|
||||
"frontend": {
|
||||
"dir": "./frontend",
|
||||
"install": "npm install",
|
||||
"build": "npm run build",
|
||||
"dev": "npm run dev"
|
||||
},
|
||||
"go": {
|
||||
"ldflags": "-s -w -X main.version={{.Version}}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Asset Embedding
|
||||
|
||||
### How It Works
|
||||
|
||||
Wails uses Go's `embed` package:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
//go:embed frontend/dist
|
||||
var assets embed.FS
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "My App",
|
||||
Assets: application.AssetOptions{
|
||||
Handler: application.AssetFileServerFS(assets),
|
||||
},
|
||||
})
|
||||
|
||||
app.Window.New()
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
**At build time:**
|
||||
1. Frontend built to `frontend/dist/`
|
||||
2. `//go:embed` directive includes files
|
||||
3. Files compiled into binary
|
||||
4. Binary contains everything
|
||||
|
||||
**At runtime:**
|
||||
1. App starts
|
||||
2. Assets served from memory
|
||||
3. No disk I/O for assets
|
||||
4. Fast loading
|
||||
|
||||
### Custom Assets
|
||||
|
||||
Embed additional files:
|
||||
|
||||
```go
|
||||
//go:embed frontend/dist
|
||||
var frontendAssets embed.FS
|
||||
|
||||
//go:embed data/*.json
|
||||
var dataAssets embed.FS
|
||||
|
||||
//go:embed templates/*.html
|
||||
var templateAssets embed.FS
|
||||
```
|
||||
|
||||
## Build Optimisations
|
||||
|
||||
### Frontend Optimisations
|
||||
|
||||
**Vite (default):**
|
||||
|
||||
```javascript
|
||||
// vite.config.js
|
||||
export default {
|
||||
build: {
|
||||
minify: 'terser',
|
||||
terserOptions: {
|
||||
compress: {
|
||||
drop_console: true, // Remove console.log
|
||||
drop_debugger: true,
|
||||
},
|
||||
},
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
vendor: ['react', 'react-dom'], // Separate vendor bundle
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Results:**
|
||||
- JavaScript minified (~70% reduction)
|
||||
- CSS minified (~60% reduction)
|
||||
- Images optimised
|
||||
- Tree-shaking applied
|
||||
|
||||
### Go Optimisations
|
||||
|
||||
**Compiler flags:**
|
||||
|
||||
```bash
|
||||
-ldflags="-s -w"
|
||||
```
|
||||
|
||||
- `-s`: Strip symbol table (~10% reduction)
|
||||
- `-w`: Strip DWARF debug info (~20% reduction)
|
||||
|
||||
**Additional optimisations:**
|
||||
|
||||
```bash
|
||||
-ldflags="-s -w -X main.version=1.0.0"
|
||||
```
|
||||
|
||||
- `-X`: Set variable values at build time
|
||||
- Useful for version numbers, build dates
|
||||
|
||||
### Binary Compression
|
||||
|
||||
**UPX (optional):**
|
||||
|
||||
```bash
|
||||
# After building
|
||||
upx --best build/bin/myapp.exe
|
||||
```
|
||||
|
||||
**Results:**
|
||||
- ~50% size reduction
|
||||
- Slightly slower startup (~100ms)
|
||||
- Not recommended for macOS (code signing issues)
|
||||
|
||||
## Platform-Specific Builds
|
||||
|
||||
### Windows
|
||||
|
||||
**Output:** `myapp.exe`
|
||||
|
||||
**Includes:**
|
||||
- Application icon
|
||||
- Version information
|
||||
- Manifest (UAC settings)
|
||||
|
||||
**Icon:**
|
||||
|
||||
```bash
|
||||
# Specify icon
|
||||
wails3 build -icon build/appicon.png
|
||||
```
|
||||
|
||||
Wails converts PNG to `.ico` automatically.
|
||||
|
||||
**Manifest:**
|
||||
|
||||
```xml
|
||||
<!-- build/windows/manifest.xml -->
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity version="1.0.0.0" name="MyApp"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
**Output:** `myapp.app` (application bundle)
|
||||
|
||||
**Structure:**
|
||||
|
||||
```
|
||||
myapp.app/
|
||||
├── Contents/
|
||||
│ ├── Info.plist # App metadata
|
||||
│ ├── MacOS/
|
||||
│ │ └── myapp # Binary
|
||||
│ ├── Resources/
|
||||
│ │ └── icon.icns # Icon
|
||||
│ └── _CodeSignature/ # Code signature (if signed)
|
||||
```
|
||||
|
||||
**Info.plist:**
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>My App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.example.myapp</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
```
|
||||
|
||||
**Universal Binary:**
|
||||
|
||||
```bash
|
||||
# Build for both architectures
|
||||
wails3 build -platform darwin/amd64
|
||||
wails3 build -platform darwin/arm64
|
||||
|
||||
# Combine into universal binary
|
||||
lipo -create -output myapp-universal \
|
||||
build/bin/myapp-amd64 \
|
||||
build/bin/myapp-arm64
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
**Output:** `myapp` (ELF binary)
|
||||
|
||||
**Dependencies:**
|
||||
- GTK3
|
||||
- WebKitGTK
|
||||
|
||||
**Desktop file:**
|
||||
|
||||
```ini
|
||||
# myapp.desktop
|
||||
[Desktop Entry]
|
||||
Name=My App
|
||||
Exec=/usr/bin/myapp
|
||||
Icon=myapp
|
||||
Type=Application
|
||||
Categories=Utility;
|
||||
```
|
||||
|
||||
**Installation:**
|
||||
|
||||
```bash
|
||||
# Copy binary
|
||||
sudo cp myapp /usr/bin/
|
||||
|
||||
# Copy desktop file
|
||||
sudo cp myapp.desktop /usr/share/applications/
|
||||
|
||||
# Copy icon
|
||||
sudo cp icon.png /usr/share/icons/hicolor/256x256/apps/myapp.png
|
||||
```
|
||||
|
||||
## Build Performance
|
||||
|
||||
### Typical Build Times
|
||||
|
||||
| Phase | Time | Notes |
|
||||
|-------|------|-------|
|
||||
| Analysis | <1s | Go code scanning |
|
||||
| Binding Generation | <1s | TypeScript generation |
|
||||
| Frontend Build | 5-30s | Depends on project size |
|
||||
| Go Compilation | 2-10s | Depends on code size |
|
||||
| Asset Embedding | <1s | Embedding frontend |
|
||||
| **Total** | **10-45s** | First build |
|
||||
| **Incremental** | **5-15s** | Subsequent builds |
|
||||
|
||||
### Speeding Up Builds
|
||||
|
||||
**1. Use build cache:**
|
||||
|
||||
```bash
|
||||
# Go build cache is automatic
|
||||
# Frontend cache (Vite)
|
||||
npm run build # Uses cache by default
|
||||
```
|
||||
|
||||
**2. Skip unchanged steps:**
|
||||
|
||||
```bash
|
||||
# Skip frontend if unchanged
|
||||
wails3 build -skipbindings
|
||||
```
|
||||
|
||||
**3. Parallel builds:**
|
||||
|
||||
```bash
|
||||
# Build multiple platforms in parallel
|
||||
wails3 build -platform windows/amd64 &
|
||||
wails3 build -platform darwin/amd64 &
|
||||
wails3 build -platform linux/amd64 &
|
||||
wait
|
||||
```
|
||||
|
||||
**4. Use faster tools:**
|
||||
|
||||
```bash
|
||||
# Use esbuild instead of webpack
|
||||
# (Vite uses esbuild by default)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build Fails
|
||||
|
||||
**Symptom:** `wails3 build` exits with error
|
||||
|
||||
**Common causes:**
|
||||
|
||||
1. **Go compilation error**
|
||||
```bash
|
||||
# Check Go code compiles
|
||||
go build
|
||||
```
|
||||
|
||||
2. **Frontend build error**
|
||||
```bash
|
||||
# Check frontend builds
|
||||
cd frontend
|
||||
npm run build
|
||||
```
|
||||
|
||||
3. **Missing dependencies**
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
go mod download
|
||||
```
|
||||
|
||||
### Binary Too Large
|
||||
|
||||
**Symptom:** Binary is >50MB
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Strip debug symbols** (should be automatic)
|
||||
```bash
|
||||
wails3 build # Already includes -ldflags="-s -w"
|
||||
```
|
||||
|
||||
2. **Check embedded assets**
|
||||
```bash
|
||||
# Remove unnecessary files from frontend/dist/
|
||||
# Check for large images, videos, etc.
|
||||
```
|
||||
|
||||
3. **Use UPX compression**
|
||||
```bash
|
||||
upx --best build/bin/myapp.exe
|
||||
```
|
||||
|
||||
### Slow Builds
|
||||
|
||||
**Symptom:** Builds take >1 minute
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Use build cache**
|
||||
- Go cache is automatic
|
||||
- Frontend cache (Vite) is automatic
|
||||
|
||||
2. **Skip unchanged steps**
|
||||
```bash
|
||||
wails3 build -skipbindings
|
||||
```
|
||||
|
||||
3. **Optimise frontend build**
|
||||
```javascript
|
||||
// vite.config.js
|
||||
export default {
|
||||
build: {
|
||||
minify: 'esbuild', // Faster than terser
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Use `wails3 dev` during development** - Fast iteration
|
||||
- **Use `wails3 build` for releases** - Optimised output
|
||||
- **Version your builds** - Use `-ldflags` to embed version
|
||||
- **Test builds on target platforms** - Cross-compilation isn't perfect
|
||||
- **Keep frontend builds fast** - Optimise bundler config
|
||||
- **Use build cache** - Speeds up subsequent builds
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Don't commit `build/` directory** - Add to `.gitignore`
|
||||
- **Don't skip testing builds** - Always test before release
|
||||
- **Don't embed unnecessary assets** - Keep binaries small
|
||||
- **Don't use debug builds for production** - Use optimised builds
|
||||
- **Don't forget code signing** - Required for distribution
|
||||
|
||||
## Next Steps
|
||||
|
||||
**Building Applications** - Detailed guide to building and packaging
|
||||
[Learn More →](/guides/building)
|
||||
|
||||
**Cross-Platform Builds** - Build for all platforms from one machine
|
||||
[Learn More →](/guides/cross-platform)
|
||||
|
||||
**Creating Installers** - Create installers for end users
|
||||
[Learn More →](/guides/installers)
|
||||
|
||||
---
|
||||
|
||||
**Questions about building?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [build examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/build).
|
||||
612
docs/src/content/docs/concepts/lifecycle.mdx
Normal file
612
docs/src/content/docs/concepts/lifecycle.mdx
Normal file
|
|
@ -0,0 +1,612 @@
|
|||
---
|
||||
title: Application Lifecycle
|
||||
description: Understanding the Wails application lifecycle from startup to shutdown
|
||||
sidebar:
|
||||
order: 2
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
## Understanding Application Lifecycle
|
||||
|
||||
Desktop applications have a lifecycle from startup to shutdown. Wails provides hooks at each stage to **initialise resources**, **clean up properly**, **handle errors gracefully**, and **manage multiple windows** effectively.
|
||||
|
||||
## The Lifecycle Stages
|
||||
|
||||
```d2
|
||||
direction: down
|
||||
|
||||
Start: "Application Start" {
|
||||
shape: oval
|
||||
style.fill: "#10B981"
|
||||
}
|
||||
|
||||
PreInit: "Pre-Initialisation" {
|
||||
Parse: "Parse Options" {
|
||||
shape: rectangle
|
||||
}
|
||||
Register: "Register Services" {
|
||||
shape: rectangle
|
||||
}
|
||||
Validate: "Validate Config" {
|
||||
shape: rectangle
|
||||
}
|
||||
}
|
||||
|
||||
OnStartup: "OnStartup Hook" {
|
||||
shape: rectangle
|
||||
style.fill: "#3B82F6"
|
||||
}
|
||||
|
||||
CreateWindows: "Create Windows" {
|
||||
shape: rectangle
|
||||
}
|
||||
|
||||
EventLoop: "Event Loop" {
|
||||
Process: "Process Events" {
|
||||
shape: rectangle
|
||||
}
|
||||
Handle: "Handle Messages" {
|
||||
shape: rectangle
|
||||
}
|
||||
Update: "Update UI" {
|
||||
shape: rectangle
|
||||
}
|
||||
}
|
||||
|
||||
QuitSignal: "Quit Signal" {
|
||||
shape: diamond
|
||||
style.fill: "#F59E0B"
|
||||
}
|
||||
|
||||
OnBeforeClose: "OnBeforeClose Hook" {
|
||||
shape: rectangle
|
||||
style.fill: "#3B82F6"
|
||||
}
|
||||
|
||||
OnShutdown: "OnShutdown Hook" {
|
||||
shape: rectangle
|
||||
style.fill: "#3B82F6"
|
||||
}
|
||||
|
||||
Cleanup: "Cleanup" {
|
||||
Close: "Close Windows" {
|
||||
shape: rectangle
|
||||
}
|
||||
Release: "Release Resources" {
|
||||
shape: rectangle
|
||||
}
|
||||
}
|
||||
|
||||
End: "Application End" {
|
||||
shape: oval
|
||||
style.fill: "#EF4444"
|
||||
}
|
||||
|
||||
Start -> PreInit.Parse
|
||||
PreInit.Parse -> PreInit.Register
|
||||
PreInit.Register -> PreInit.Validate
|
||||
PreInit.Validate -> OnStartup
|
||||
OnStartup -> CreateWindows
|
||||
CreateWindows -> EventLoop.Process
|
||||
EventLoop.Process -> EventLoop.Handle
|
||||
EventLoop.Handle -> EventLoop.Update
|
||||
EventLoop.Update -> EventLoop.Process: "Loop"
|
||||
EventLoop.Process -> QuitSignal: "User quits"
|
||||
QuitSignal -> OnBeforeClose: "Can cancel?"
|
||||
OnBeforeClose -> EventLoop.Process: "Cancelled"
|
||||
OnBeforeClose -> OnShutdown: "Confirmed"
|
||||
OnShutdown -> Cleanup.Close
|
||||
Cleanup.Close -> Cleanup.Release
|
||||
Cleanup.Release -> End
|
||||
```
|
||||
|
||||
### 1. Pre-Initialisation
|
||||
|
||||
Before your code runs, Wails:
|
||||
1. Parses `application.Options`
|
||||
2. Registers services
|
||||
3. Validates configuration
|
||||
4. Sets up the runtime
|
||||
|
||||
**You don't control this phase** - it happens automatically.
|
||||
|
||||
### 2. OnStartup Hook
|
||||
|
||||
Your first opportunity to run code:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Name: "My App",
|
||||
OnStartup: func(ctx context.Context) {
|
||||
// Initialise database
|
||||
db, err := sql.Open("sqlite3", "app.db")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Load configuration
|
||||
config, err := loadConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Store in context for services to access
|
||||
ctx = context.WithValue(ctx, "db", db)
|
||||
ctx = context.WithValue(ctx, "config", config)
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**When it runs:** After Wails initialisation, before windows are created
|
||||
|
||||
**Use it for:**
|
||||
- Database connections
|
||||
- Configuration loading
|
||||
- Resource initialisation
|
||||
- Authentication checks
|
||||
|
||||
**Context:** The `context.Context` is passed to all services and can store shared state.
|
||||
|
||||
### 3. Window Creation
|
||||
|
||||
After `OnStartup`, you create windows:
|
||||
|
||||
```go
|
||||
window := app.Window.New()
|
||||
```
|
||||
|
||||
**What happens:**
|
||||
1. Window is created (but not shown)
|
||||
2. WebView is initialised
|
||||
3. Frontend assets are loaded
|
||||
4. Window is shown (unless `Hidden: true`)
|
||||
|
||||
### 4. Event Loop
|
||||
|
||||
The application enters the event loop:
|
||||
|
||||
```go
|
||||
err := app.Run() // Blocks here until quit
|
||||
```
|
||||
|
||||
**What happens in the loop:**
|
||||
- OS events processed (mouse, keyboard, window events)
|
||||
- Go-to-JS messages handled
|
||||
- JS-to-Go calls executed
|
||||
- UI updates rendered
|
||||
|
||||
**This is where your application spends most of its time.**
|
||||
|
||||
### 5. Quit Signal
|
||||
|
||||
User triggers quit via:
|
||||
- Closing last window (default behaviour)
|
||||
- Cmd+Q / Alt+F4 / File → Quit
|
||||
- Your code calling `app.Quit()`
|
||||
|
||||
### 6. OnBeforeClose Hook
|
||||
|
||||
**Optional hook to prevent quit:**
|
||||
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
OnBeforeClose: func() bool {
|
||||
// Return false to cancel quit
|
||||
// Return true to allow quit
|
||||
|
||||
if hasUnsavedChanges() {
|
||||
result := showConfirmDialog("Unsaved changes. Quit anyway?")
|
||||
return result == "yes"
|
||||
}
|
||||
return true
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Confirm quit with unsaved changes
|
||||
- Prevent accidental closure
|
||||
- Save state before quitting
|
||||
|
||||
**Important:** Only works for window close events, not `app.Quit()`.
|
||||
|
||||
### 7. OnShutdown Hook
|
||||
|
||||
Your last opportunity to run code:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
OnShutdown: func() {
|
||||
// Save application state
|
||||
saveState()
|
||||
|
||||
// Close database
|
||||
db.Close()
|
||||
|
||||
// Release resources
|
||||
cleanup()
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**When it runs:** After quit confirmed, before application exits
|
||||
|
||||
**Use it for:**
|
||||
- Saving state
|
||||
- Closing connections
|
||||
- Releasing resources
|
||||
- Final cleanup
|
||||
|
||||
**Important:** Keep it fast (<1 second). OS may force-kill if too slow.
|
||||
|
||||
### 8. Cleanup & Exit
|
||||
|
||||
Wails automatically:
|
||||
1. Closes all windows
|
||||
2. Releases WebView resources
|
||||
3. Exits the process
|
||||
|
||||
## Lifecycle Hooks Reference
|
||||
|
||||
| Hook | When | Can Cancel? | Use For |
|
||||
|------|------|-------------|---------|
|
||||
| `OnStartup` | Before windows created | No | Initialisation |
|
||||
| `OnBeforeClose` | Window closing | Yes | Confirm quit |
|
||||
| `OnShutdown` | After quit confirmed | No | Cleanup |
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Pattern 1: Database Lifecycle
|
||||
|
||||
```go
|
||||
var db *sql.DB
|
||||
|
||||
app := application.New(application.Options{
|
||||
OnStartup: func(ctx context.Context) {
|
||||
var err error
|
||||
db, err = sql.Open("sqlite3", "app.db")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Run migrations
|
||||
if err := runMigrations(db); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
OnShutdown: func() {
|
||||
if db != nil {
|
||||
db.Close()
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Pattern 2: Configuration Management
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Theme string
|
||||
Language string
|
||||
WindowPos Point
|
||||
}
|
||||
|
||||
var config *Config
|
||||
|
||||
app := application.New(application.Options{
|
||||
OnStartup: func(ctx context.Context) {
|
||||
config = loadConfig() // Load from disk
|
||||
},
|
||||
OnShutdown: func() {
|
||||
saveConfig(config) // Save to disk
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Pattern 3: Confirm Quit with Unsaved Changes
|
||||
|
||||
```go
|
||||
type AppState struct {
|
||||
hasUnsavedChanges bool
|
||||
}
|
||||
|
||||
var state AppState
|
||||
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
OnBeforeClose: func() bool {
|
||||
if state.hasUnsavedChanges {
|
||||
// Show dialog (blocks until user responds)
|
||||
result := showConfirmdialog("Unsaved changes. Quit anyway?")
|
||||
return result == "yes"
|
||||
}
|
||||
return true
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Pattern 4: Background Tasks
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
OnStartup: func(ctx context.Context) {
|
||||
// Start background task
|
||||
go func() {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
performBackgroundSync()
|
||||
case <-ctx.Done():
|
||||
// Context cancelled, quit
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Important:** Use `ctx.Done()` to know when to stop background tasks.
|
||||
|
||||
## Window Lifecycle
|
||||
|
||||
Each window has its own lifecycle:
|
||||
|
||||
```d2
|
||||
direction: down
|
||||
|
||||
Create: "Create Window" {
|
||||
shape: oval
|
||||
style.fill: "#10B981"
|
||||
}
|
||||
|
||||
Load: "Load Frontend" {
|
||||
shape: rectangle
|
||||
}
|
||||
|
||||
Show: "Show Window" {
|
||||
shape: rectangle
|
||||
}
|
||||
|
||||
Active: "Window Active" {
|
||||
Events: "Handle Events" {
|
||||
shape: rectangle
|
||||
}
|
||||
}
|
||||
|
||||
CloseRequest: "Close Request" {
|
||||
shape: diamond
|
||||
style.fill: "#F59E0B"
|
||||
}
|
||||
|
||||
OnBeforeClose: "OnBeforeClose" {
|
||||
shape: rectangle
|
||||
style.fill: "#3B82F6"
|
||||
}
|
||||
|
||||
Destroy: "Destroy Window" {
|
||||
shape: rectangle
|
||||
}
|
||||
|
||||
End: "Window Closed" {
|
||||
shape: oval
|
||||
style.fill: "#EF4444"
|
||||
}
|
||||
|
||||
Create -> Load
|
||||
Load -> Show
|
||||
Show -> Active.Events
|
||||
Active.Events -> Active.Events: "Loop"
|
||||
Active.Events -> CloseRequest: "User closes"
|
||||
CloseRequest -> OnBeforeClose
|
||||
OnBeforeClose -> Active.Events: "Cancelled"
|
||||
OnBeforeClose -> Destroy: "Confirmed"
|
||||
Destroy -> End
|
||||
```
|
||||
|
||||
**Key points:**
|
||||
- Each window is independent
|
||||
- Closing last window quits app (by default)
|
||||
- Windows can prevent their own closure
|
||||
|
||||
## Multi-Window Lifecycle
|
||||
|
||||
With multiple windows:
|
||||
|
||||
```go
|
||||
// Create main window
|
||||
mainWindow := app.Window.New()
|
||||
|
||||
// Create secondary window
|
||||
secondaryWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Settings",
|
||||
Width: 400,
|
||||
Height: 600,
|
||||
})
|
||||
|
||||
// Closing secondary window doesn't quit app
|
||||
// Closing main window quits app (closes all windows)
|
||||
```
|
||||
|
||||
**Default behaviour:**
|
||||
- Closing any window closes just that window
|
||||
- Closing the **last** window quits the application
|
||||
|
||||
**Custom behaviour:**
|
||||
|
||||
```go
|
||||
// Prevent app quit when last window closes
|
||||
app := application.New(application.Options{
|
||||
Mac: application.MacOptions{
|
||||
ApplicationShouldTerminateAfterLastWindowClosed: false,
|
||||
},
|
||||
})
|
||||
|
||||
// Now app stays running even with no windows
|
||||
// Useful for menu bar / system tray apps
|
||||
```
|
||||
|
||||
## Error Handling During Lifecycle
|
||||
|
||||
### Startup Errors
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
OnStartup: func(ctx context.Context) {
|
||||
if err := initialise(); err != nil {
|
||||
// Show error dialog
|
||||
showErrordialog("Initialisation failed: " + err.Error())
|
||||
|
||||
// Quit application
|
||||
app.Quit()
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Shutdown Errors
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
OnShutdown: func() {
|
||||
if err := saveState(); err != nil {
|
||||
// Log error (can't show dialog, app is quitting)
|
||||
log.Printf("Failed to save state: %v", err)
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Important:** `OnShutdown` runs during quit - don't show dialogs or try to cancel.
|
||||
|
||||
## Platform Differences
|
||||
|
||||
### macOS
|
||||
|
||||
- **Application menu** persists even with no windows
|
||||
- **Cmd+Q** always quits (can't be prevented)
|
||||
- **Dock icon** remains unless hidden
|
||||
|
||||
### Windows
|
||||
|
||||
- **No application menu** without a window
|
||||
- **Alt+F4** closes window (can be prevented)
|
||||
- **System tray** can keep app running
|
||||
|
||||
### Linux
|
||||
|
||||
- **Behaviour varies** by desktop environment
|
||||
- **Generally similar to Windows**
|
||||
|
||||
## Debugging Lifecycle Issues
|
||||
|
||||
### Problem: Resources Not Cleaned Up
|
||||
|
||||
**Symptom:** Database connections left open, files not closed
|
||||
|
||||
**Solution:** Use `OnShutdown`:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
OnShutdown: func() {
|
||||
log.Println("Cleaning up...")
|
||||
// Your cleanup code
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Problem: Application Won't Quit
|
||||
|
||||
**Symptom:** App hangs when trying to quit
|
||||
|
||||
**Causes:**
|
||||
1. `OnBeforeClose` returning `false`
|
||||
2. `OnShutdown` taking too long
|
||||
3. Background goroutines not stopping
|
||||
|
||||
**Solution:**
|
||||
|
||||
```go
|
||||
// 1. Check OnBeforeClose logic
|
||||
OnBeforeClose: func() bool {
|
||||
log.Println("OnBeforeClose called")
|
||||
return true // Allow quit
|
||||
}
|
||||
|
||||
// 2. Keep OnShutdown fast
|
||||
OnShutdown: func() {
|
||||
log.Println("OnShutdown started")
|
||||
// Fast cleanup only
|
||||
log.Println("OnShutdown finished")
|
||||
}
|
||||
|
||||
// 3. Stop background tasks
|
||||
OnStartup: func(ctx context.Context) {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Println("Background task stopped")
|
||||
return
|
||||
default:
|
||||
// Work
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
```
|
||||
|
||||
### Problem: Initialisation Fails Silently
|
||||
|
||||
**Symptom:** App starts but doesn't work correctly
|
||||
|
||||
**Solution:** Check errors in `OnStartup`:
|
||||
|
||||
```go
|
||||
OnStartup: func(ctx context.Context) {
|
||||
if err := initialise(); err != nil {
|
||||
log.Fatal("Initialisation failed:", err)
|
||||
// Or show dialog and quit
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Initialise in OnStartup** - Database, config, resources
|
||||
- **Clean up in OnShutdown** - Close connections, save state
|
||||
- **Keep OnShutdown fast** - <1 second
|
||||
- **Use context for cancellation** - Stop background tasks
|
||||
- **Handle errors gracefully** - Show dialogs, log errors
|
||||
- **Test quit scenarios** - Unsaved changes, background tasks
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Don't block OnStartup** - Keep it fast (<2 seconds)
|
||||
- **Don't show dialogs in OnShutdown** - App is quitting
|
||||
- **Don't ignore errors** - Log or show them
|
||||
- **Don't leak resources** - Always clean up
|
||||
- **Don't forget background tasks** - Stop them on quit
|
||||
|
||||
## Next Steps
|
||||
|
||||
**Go-Frontend Bridge** - Understand how Go and JavaScript communicate
|
||||
[Learn More →](/concepts/bridge)
|
||||
|
||||
**Build System** - Learn how Wails builds your application
|
||||
[Learn More →](/concepts/build-system)
|
||||
|
||||
**Events System** - Use events for communication between components
|
||||
[Learn More →](/features/events/system)
|
||||
|
||||
**Window Management** - Create and manage multiple windows
|
||||
[Learn More →](/features/windows/basics)
|
||||
|
||||
---
|
||||
|
||||
**Questions about lifecycle?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).
|
||||
266
docs/src/content/docs/concepts/manager-api.mdx
Normal file
266
docs/src/content/docs/concepts/manager-api.mdx
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
---
|
||||
title: Manager API
|
||||
description: Organized API structure with focused manager interfaces
|
||||
sidebar:
|
||||
order: 2
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
The Wails v3 Manager API provides an organized and discoverable way to access application functionality through focused manager structs. This new API structure groups related methods together while maintaining full backward compatibility with the traditional App API.
|
||||
|
||||
## Overview
|
||||
|
||||
The Manager API organizes application functionality into eleven focused areas:
|
||||
|
||||
- **`app.Window`** - Window creation, management, and callbacks
|
||||
- **`app.ContextMenu`** - Context menu registration and management
|
||||
- **`app.KeyBinding`** - Global key binding management
|
||||
- **`app.Browser`** - Browser integration (opening URLs and files)
|
||||
- **`app.Env`** - Environment information and system state
|
||||
- **`app.Dialog`** - File and message dialog operations
|
||||
- **`app.Event`** - Custom event handling and application events
|
||||
- **`app.Menu`** - Application menu management
|
||||
- **`app.Screen`** - Screen management and coordinate transformations
|
||||
- **`app.Clipboard`** - Clipboard text operations
|
||||
- **`app.SystemTray`** - System tray icon creation and management
|
||||
|
||||
## Benefits
|
||||
|
||||
- **Better discoverability** - IDE autocomplete shows organized API surface
|
||||
- **Improved code organization** - Related methods grouped together
|
||||
- **Enhanced maintainability** - Separation of concerns across managers
|
||||
- **Future extensibility** - Easier to add new features to specific areas
|
||||
|
||||
## Usage
|
||||
|
||||
The Manager API provides organized access to all application functionality:
|
||||
|
||||
```go
|
||||
// Events and custom event handling
|
||||
app.Event.Emit("custom", data)
|
||||
app.Event.On("custom", func(e *CustomEvent) { ... })
|
||||
|
||||
// Window management
|
||||
window, _ := app.Window.GetByName("main")
|
||||
app.Window.OnCreate(func(window Window) { ... })
|
||||
|
||||
// Browser integration
|
||||
app.Browser.OpenURL("https://wails.io")
|
||||
|
||||
// Menu management
|
||||
menu := app.Menu.New()
|
||||
app.Menu.Set(menu)
|
||||
|
||||
// System tray
|
||||
systray := app.SystemTray.New()
|
||||
```
|
||||
|
||||
## Manager Reference
|
||||
|
||||
### Window Manager
|
||||
|
||||
Manages window creation, retrieval, and lifecycle callbacks.
|
||||
|
||||
```go
|
||||
// Create windows
|
||||
window := app.Window.New()
|
||||
window := app.Window.NewWithOptions(options)
|
||||
current := app.Window.Current()
|
||||
|
||||
// Find windows
|
||||
window, exists := app.Window.GetByName("main")
|
||||
windows := app.Window.GetAll()
|
||||
|
||||
// Window callbacks
|
||||
app.Window.OnCreate(func(window Window) {
|
||||
// Handle window creation
|
||||
})
|
||||
```
|
||||
|
||||
### Event Manager
|
||||
|
||||
Handles custom events and application event listening.
|
||||
|
||||
```go
|
||||
// Custom events
|
||||
app.Event.Emit("userAction", data)
|
||||
cancelFunc := app.Event.On("userAction", func(e *CustomEvent) {
|
||||
// Handle event
|
||||
})
|
||||
app.Event.Off("userAction")
|
||||
app.Event.Reset() // Remove all listeners
|
||||
|
||||
// Application events
|
||||
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *ApplicationEvent) {
|
||||
// Handle system theme change
|
||||
})
|
||||
```
|
||||
|
||||
### Browser Manager
|
||||
|
||||
Provides browser integration for opening URLs and files.
|
||||
|
||||
```go
|
||||
// Open URLs and files in default browser
|
||||
err := app.Browser.OpenURL("https://wails.io")
|
||||
err := app.Browser.OpenFile("/path/to/document.pdf")
|
||||
```
|
||||
|
||||
### Environment Manager
|
||||
|
||||
Access to system environment information.
|
||||
|
||||
```go
|
||||
// Get environment info
|
||||
env := app.Env.Info()
|
||||
fmt.Printf("OS: %s, Arch: %s\n", env.OS, env.Arch)
|
||||
|
||||
// Check system theme
|
||||
if app.Env.IsDarkMode() {
|
||||
// Dark mode is active
|
||||
}
|
||||
|
||||
// Open file manager
|
||||
err := app.Env.OpenFileManager("/path/to/folder", false)
|
||||
```
|
||||
|
||||
### Dialog Manager
|
||||
|
||||
Organized access to file and message dialogs.
|
||||
|
||||
```go
|
||||
// File dialogs
|
||||
result, err := app.Dialog.OpenFile().
|
||||
AddFilter("Text Files", "*.txt").
|
||||
PromptForSingleSelection()
|
||||
|
||||
result, err = app.Dialog.SaveFile().
|
||||
SetDefaultFilename("document.txt").
|
||||
PromptForSingleSelection()
|
||||
|
||||
// Message dialogs
|
||||
app.Dialog.Info().
|
||||
SetTitle("Information").
|
||||
SetMessage("Operation completed successfully").
|
||||
Show()
|
||||
|
||||
app.Dialog.Error().
|
||||
SetTitle("Error").
|
||||
SetMessage("An error occurred").
|
||||
Show()
|
||||
```
|
||||
|
||||
### Menu Manager
|
||||
|
||||
Application menu creation and management.
|
||||
|
||||
```go
|
||||
// Create and set application menu
|
||||
menu := app.Menu.New()
|
||||
fileMenu := menu.AddSubmenu("File")
|
||||
fileMenu.Add("New").OnClick(func(ctx *Context) {
|
||||
// Handle menu click
|
||||
})
|
||||
|
||||
app.Menu.Set(menu)
|
||||
|
||||
// Show about dialog
|
||||
app.Menu.ShowAbout()
|
||||
```
|
||||
|
||||
### Key Binding Manager
|
||||
|
||||
Dynamic management of global key bindings.
|
||||
|
||||
```go
|
||||
// Add key bindings
|
||||
app.KeyBinding.Add("ctrl+n", func(window application.Window) {
|
||||
// Handle Ctrl+N
|
||||
})
|
||||
|
||||
app.KeyBinding.Add("ctrl+q", func(window application.Window) {
|
||||
app.Quit()
|
||||
})
|
||||
|
||||
// Remove key bindings
|
||||
app.KeyBinding.Remove("ctrl+n")
|
||||
|
||||
// Get all bindings
|
||||
bindings := app.KeyBinding.GetAll()
|
||||
```
|
||||
|
||||
### Context Menu Manager
|
||||
|
||||
Advanced context menu management (for library authors).
|
||||
|
||||
```go
|
||||
// Create and register context menu
|
||||
menu := app.ContextMenu.New()
|
||||
app.ContextMenu.Add("myMenu", menu)
|
||||
|
||||
// Retrieve context menu
|
||||
menu, exists := app.ContextMenu.Get("myMenu")
|
||||
|
||||
// Remove context menu
|
||||
app.ContextMenu.Remove("myMenu")
|
||||
```
|
||||
|
||||
### Screen Manager
|
||||
|
||||
Screen management and coordinate transformations for multi-monitor setups.
|
||||
|
||||
```go
|
||||
// Get screen information
|
||||
screens := app.Screen.GetAll()
|
||||
primary := app.Screen.GetPrimary()
|
||||
|
||||
// Coordinate transformations
|
||||
physicalPoint := app.Screen.DipToPhysicalPoint(logicalPoint)
|
||||
logicalPoint := app.Screen.PhysicalToDipPoint(physicalPoint)
|
||||
|
||||
// Screen detection
|
||||
screen := app.Screen.ScreenNearestDipPoint(point)
|
||||
screen = app.Screen.ScreenNearestDipRect(rect)
|
||||
```
|
||||
|
||||
### Clipboard Manager
|
||||
|
||||
Clipboard operations for reading and writing text.
|
||||
|
||||
```go
|
||||
// Set text to clipboard
|
||||
success := app.Clipboard.SetText("Hello World")
|
||||
if !success {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Get text from clipboard
|
||||
text, ok := app.Clipboard.Text()
|
||||
if !ok {
|
||||
// Handle error
|
||||
} else {
|
||||
// Use the text
|
||||
}
|
||||
```
|
||||
|
||||
### SystemTray Manager
|
||||
|
||||
System tray icon creation and management.
|
||||
|
||||
```go
|
||||
// Create system tray
|
||||
systray := app.SystemTray.New()
|
||||
systray.SetLabel("My App")
|
||||
systray.SetIcon(iconBytes)
|
||||
|
||||
// Add menu to system tray
|
||||
menu := app.Menu.New()
|
||||
menu.Add("Open").OnClick(func(ctx *Context) {
|
||||
// Handle click
|
||||
})
|
||||
systray.SetMenu(menu)
|
||||
|
||||
// Destroy system tray when done
|
||||
systray.Destroy()
|
||||
```
|
||||
275
docs/src/content/docs/contributing.mdx
Normal file
275
docs/src/content/docs/contributing.mdx
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
---
|
||||
title: Contributing
|
||||
description: Contribute to Wails
|
||||
sidebar:
|
||||
order: 100
|
||||
---
|
||||
|
||||
import { Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Welcome Contributors!
|
||||
|
||||
We welcome contributions to Wails! Whether you're fixing bugs, adding features, or improving documentation, your help is appreciated.
|
||||
|
||||
## Ways to Contribute
|
||||
|
||||
### 1. Report Issues
|
||||
|
||||
Found a bug? [Open an issue](https://github.com/wailsapp/wails/issues/new) with:
|
||||
- Clear description
|
||||
- Steps to reproduce
|
||||
- Expected vs actual behaviour
|
||||
- System information
|
||||
- Code samples
|
||||
|
||||
### 2. Improve Documentation
|
||||
|
||||
Documentation improvements are always welcome:
|
||||
- Fix typos and errors
|
||||
- Add examples
|
||||
- Clarify explanations
|
||||
- Translate content
|
||||
|
||||
### 3. Submit Code
|
||||
|
||||
Contribute code through pull requests:
|
||||
- Bug fixes
|
||||
- New features
|
||||
- Performance improvements
|
||||
- Tests
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Fork and Clone
|
||||
|
||||
```bash
|
||||
# Fork the repository on GitHub
|
||||
# Then clone your fork
|
||||
git clone https://github.com/YOUR_USERNAME/wails.git
|
||||
cd wails
|
||||
|
||||
# Add upstream remote
|
||||
git remote add upstream https://github.com/wailsapp/wails.git
|
||||
```
|
||||
|
||||
### Build from Source
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
go mod download
|
||||
|
||||
# Build Wails CLI
|
||||
cd v3/cmd/wails3
|
||||
go build
|
||||
|
||||
# Test your build
|
||||
./wails3 version
|
||||
```
|
||||
|
||||
### Run Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
go test ./...
|
||||
|
||||
# Run specific package tests
|
||||
go test ./v3/pkg/application
|
||||
|
||||
# Run with coverage
|
||||
go test -cover ./...
|
||||
```
|
||||
|
||||
## Making Changes
|
||||
|
||||
### Create a Branch
|
||||
|
||||
```bash
|
||||
# Update main
|
||||
git checkout main
|
||||
git pull upstream main
|
||||
|
||||
# Create feature branch
|
||||
git checkout -b feature/my-feature
|
||||
```
|
||||
|
||||
### Make Your Changes
|
||||
|
||||
1. **Write code** following Go conventions
|
||||
2. **Add tests** for new functionality
|
||||
3. **Update documentation** if needed
|
||||
4. **Run tests** to ensure nothing breaks
|
||||
5. **Commit changes** with clear messages
|
||||
|
||||
### Commit Guidelines
|
||||
|
||||
```bash
|
||||
# Good commit messages
|
||||
git commit -m "fix: resolve window focus issue on macOS"
|
||||
git commit -m "feat: add support for custom window chrome"
|
||||
git commit -m "docs: improve bindings documentation"
|
||||
|
||||
# Use conventional commits:
|
||||
# - feat: New feature
|
||||
# - fix: Bug fix
|
||||
# - docs: Documentation
|
||||
# - test: Tests
|
||||
# - refactor: Code refactoring
|
||||
# - chore: Maintenance
|
||||
```
|
||||
|
||||
### Submit Pull Request
|
||||
|
||||
```bash
|
||||
# Push to your fork
|
||||
git push origin feature/my-feature
|
||||
|
||||
# Open pull request on GitHub
|
||||
# Provide clear description
|
||||
# Reference related issues
|
||||
```
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
### Good PR Description
|
||||
|
||||
```markdown
|
||||
## Description
|
||||
Brief description of changes
|
||||
|
||||
## Changes
|
||||
- Added feature X
|
||||
- Fixed bug Y
|
||||
- Updated documentation
|
||||
|
||||
## Testing
|
||||
- Tested on macOS 14
|
||||
- Tested on Windows 11
|
||||
- All tests passing
|
||||
|
||||
## Related Issues
|
||||
Fixes #123
|
||||
```
|
||||
|
||||
### PR Checklist
|
||||
|
||||
- [ ] Code follows Go conventions
|
||||
- [ ] Tests added/updated
|
||||
- [ ] Documentation updated
|
||||
- [ ] All tests passing
|
||||
- [ ] No breaking changes (or documented)
|
||||
- [ ] Commit messages clear
|
||||
|
||||
## Code Guidelines
|
||||
|
||||
### Go Code Style
|
||||
|
||||
```go
|
||||
// ✅ Good: Clear, documented, tested
|
||||
// ProcessData processes the input data and returns the result.
|
||||
// It returns an error if the data is invalid.
|
||||
func ProcessData(data string) (string, error) {
|
||||
if data == "" {
|
||||
return "", errors.New("data cannot be empty")
|
||||
}
|
||||
|
||||
result := process(data)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ❌ Bad: No docs, no error handling
|
||||
func ProcessData(data string) string {
|
||||
return process(data)
|
||||
}
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```go
|
||||
func TestProcessData(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{"valid input", "test", "processed", false},
|
||||
{"empty input", "", "", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ProcessData(tt.input)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ProcessData() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("ProcessData() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
### Writing Docs
|
||||
|
||||
Documentation uses Starlight (Astro):
|
||||
|
||||
```bash
|
||||
cd docs
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Documentation Style
|
||||
|
||||
- Use International English spelling
|
||||
- Start with the problem
|
||||
- Provide working examples
|
||||
- Include troubleshooting
|
||||
- Cross-reference related content
|
||||
|
||||
## Community
|
||||
|
||||
### Get Help
|
||||
|
||||
- **Discord:** [Join our community](https://discord.gg/JDdSxwjhGf)
|
||||
- **GitHub Discussions:** Ask questions
|
||||
- **GitHub Issues:** Report bugs
|
||||
|
||||
### Code of Conduct
|
||||
|
||||
Be respectful, inclusive, and professional. We're all here to build great software together.
|
||||
|
||||
## Recognition
|
||||
|
||||
Contributors are recognised in:
|
||||
- Release notes
|
||||
- Contributors list
|
||||
- GitHub insights
|
||||
|
||||
Thank you for contributing to Wails! 🎉
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGrid>
|
||||
<Card title="GitHub Repository" icon="github">
|
||||
Visit the Wails repository.
|
||||
|
||||
[View on GitHub →](https://github.com/wailsapp/wails)
|
||||
</Card>
|
||||
|
||||
<Card title="Discord Community" icon="discord">
|
||||
Join the community.
|
||||
|
||||
[Join Discord →](https://discord.gg/JDdSxwjhGf)
|
||||
</Card>
|
||||
|
||||
<Card title="Documentation" icon="open-book">
|
||||
Read the docs.
|
||||
|
||||
[Browse Docs →](/quick-start/why-wails)
|
||||
</Card>
|
||||
</CardGrid>
|
||||
9
docs/src/content/docs/contributing/_category_.json
Normal file
9
docs/src/content/docs/contributing/_category_.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"label": "Technical Documentation",
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Deep dive into Wails v3 internals for developers who want to understand or contribute to the codebase."
|
||||
},
|
||||
"order": 50,
|
||||
"collapsed": false
|
||||
}
|
||||
170
docs/src/content/docs/contributing/architecture.mdx
Normal file
170
docs/src/content/docs/contributing/architecture.mdx
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
---
|
||||
title: Wails v3 Architecture
|
||||
description: Deep-dive diagrams and explanations of every moving part inside Wails v3
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
|
||||
Wails v3 is a **full-stack desktop framework** consisting of a Go runtime,
|
||||
a JavaScript bridge, a task-driven tool-chain and a collection of templates that
|
||||
let you ship native applications powered by modern web tech.
|
||||
|
||||
This page presents the *big picture* in four diagrams:
|
||||
|
||||
1. **Overall Architecture** – how every subsystem connects
|
||||
2. **Runtime Flow** – what happens when JS calls Go and vice-versa
|
||||
3. **Development vs Production** – two modes of the asset server
|
||||
4. **Platform Implementations** – where OS-specific code lives
|
||||
|
||||
---
|
||||
|
||||
## 1 · Overall Architecture
|
||||
|
||||
**Wails v3 – High-Level Stack**
|
||||
|
||||
```d2
|
||||
direction: right
|
||||
Developer: "wails3 CLI"
|
||||
Build: {
|
||||
label: "Build-time Tool-chain"
|
||||
GEN: "Binding Generator
|
||||
(static analysis)"
|
||||
TMP: "Template Engine"
|
||||
ASSETDEV: "Asset Server (dev)"
|
||||
PKG: "Cross-compilation & Packaging"
|
||||
}
|
||||
Runtime: {
|
||||
label: "Native Runtime"
|
||||
RT: "Desktop Runtime
|
||||
(window, dialogs, tray, …)"
|
||||
BRIDGE: "Message Bridge
|
||||
(JSON channel)"
|
||||
}
|
||||
App: {
|
||||
label: "Your Application"
|
||||
BACKEND: "Go Backend"
|
||||
FRONTEND: "Web Frontend
|
||||
(React/Vue/…)"
|
||||
}
|
||||
Developer -> TMP: "init"
|
||||
Developer -> GEN: "generate"
|
||||
Developer -> ASSETDEV: "dev"
|
||||
Developer -> PKG: "build / package"
|
||||
GEN -> BACKEND: "Go & TS stubs"
|
||||
GEN -> FRONTEND: "bindings.json"
|
||||
ASSETDEV <-> FRONTEND: "HTTP"
|
||||
BACKEND <-> BRIDGE: "calls / events"
|
||||
FRONTEND <-> BRIDGE: "invoke / emit"
|
||||
BRIDGE <-> RT: "native API"
|
||||
RT -> ASSETDEV: "serve assets"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2 · Runtime Call Flow
|
||||
|
||||
**Runtime – JavaScript ⇄ Go Calling Path**
|
||||
|
||||
```d2
|
||||
direction: right
|
||||
JS: "JavaScript
|
||||
(frontend)"
|
||||
Bridge: "Bridge
|
||||
(WebView callback)"
|
||||
MP: "Message Processor
|
||||
(Go)"
|
||||
GoNode: "Bound Go Function"
|
||||
JS -> Bridge: "invoke(\"Greet\",\"Alice\")"
|
||||
Bridge -> MP: "JSON call {t:c,id:42,…}"
|
||||
MP -> GoNode: "call Greet(\"Alice\")"
|
||||
GoNode -> MP: "\"Hello Alice\""
|
||||
MP -> Bridge: "JSON {t:r,id:42,result:\"Hello Alice\"}"
|
||||
Bridge -> JS: "Promise.resolve(\"Hello Alice\")"
|
||||
```
|
||||
|
||||
Key points:
|
||||
|
||||
* **No HTTP / IPC** – the bridge uses the native WebView’s in-memory channel
|
||||
* **Method IDs** – deterministic FNV-hash enables O(1) lookup in Go
|
||||
* **Promises** – errors propagate as rejections with stack & code
|
||||
|
||||
---
|
||||
|
||||
## 3 · Development vs Production Asset Flow
|
||||
|
||||
**Dev ↔ Prod Asset Server**
|
||||
|
||||
```d2
|
||||
direction: right
|
||||
Dev: {
|
||||
label: "`wails3 dev`"
|
||||
VITE: "Framework Dev Server
|
||||
(port 5173)"
|
||||
ASDEV: "Asset Server (dev)
|
||||
(proxy + disk)"
|
||||
FRONTENDDEV: "Browser"
|
||||
}
|
||||
Prod: {
|
||||
label: "`wails3 build`"
|
||||
EMBED: "Embedded FS
|
||||
(go:embed)"
|
||||
ASPROD: "Asset Server (prod)
|
||||
(read-only)"
|
||||
FRONTENDPROD: "WebView Window"
|
||||
}
|
||||
VITE <-> ASDEV: "proxy / HMR"
|
||||
ASDEV <-> FRONTENDDEV: "HTTP"
|
||||
EMBED -> ASPROD: "serve assets"
|
||||
ASPROD <-> FRONTENDPROD: "in-memory"
|
||||
```
|
||||
|
||||
* In **dev** the server proxies unknown paths to the framework’s live-reload
|
||||
server and serves static assets from disk.
|
||||
* In **prod** the same API is backed by `go:embed`, producing a zero-dependency
|
||||
binary.
|
||||
|
||||
---
|
||||
|
||||
## 4 · Platform-Specific Runtime Split
|
||||
|
||||
**Per-OS Runtime Files**
|
||||
|
||||
```d2
|
||||
direction: right
|
||||
Window: "runtime::Window
|
||||
Shared interface (pkg/application)"
|
||||
WindowDarwin: "Window_darwin
|
||||
//go:build darwin
|
||||
Objective-C (cgo)
|
||||
NSWindow* ptr"
|
||||
WindowLinux: "Window_linux
|
||||
//go:build linux
|
||||
Pure Go GTK calls
|
||||
GtkWindow* ptr"
|
||||
WindowWindows: "Window_windows
|
||||
//go:build windows
|
||||
Win32 API via syscall
|
||||
HWND ptr"
|
||||
Window -> WindowDarwin
|
||||
Window -> WindowLinux
|
||||
Window -> WindowWindows
|
||||
```
|
||||
|
||||
Every feature follows this pattern:
|
||||
|
||||
1. **Common interface** in `pkg/application`
|
||||
2. **Message processor** entry in `pkg/application/messageprocessor_*.go`
|
||||
3. **Implementation** per OS under `internal/runtime/*.go` guarded by build tags
|
||||
|
||||
Missing functionality on an OS should return `ErrCapability` and register
|
||||
availability via `internal/capabilities`.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
These diagrams outline **where the code lives**, **how data moves**, and
|
||||
**which layers own which responsibilities**.
|
||||
Keep them handy while exploring the detailed pages that follow – they are your
|
||||
map to the Wails v3 source tree.
|
||||
156
docs/src/content/docs/contributing/architecture/bindings.mdx
Normal file
156
docs/src/content/docs/contributing/architecture/bindings.mdx
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
---
|
||||
title: Binding System
|
||||
description: How the binding system collects, processes, and generates JavaScript/TypeScript code
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { FileTree } from "@astrojs/starlight/components";
|
||||
|
||||
This guide explains how the Wails binding system works internally, providing insights for developers who want to understand the mechanics behind the automatic code generation.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The Wails binding system consists of three main components:
|
||||
|
||||
1. **Collection**: Analyzes Go code to extract information about services, models, and other declarations
|
||||
2. **Configuration**: Manages settings and options for the binding generation process
|
||||
3. **Rendering**: Generates JavaScript/TypeScript code based on the collected information
|
||||
|
||||
<FileTree>
|
||||
- internal/generator/
|
||||
- collect/ # Package analysis and information extraction
|
||||
- config/ # Configuration structures and interfaces
|
||||
- render/ # Code generation for JS/TS
|
||||
</FileTree>
|
||||
|
||||
## Collection Process
|
||||
|
||||
The collection process is responsible for analyzing Go packages and extracting information about services, models, and other declarations. This is handled by the `collect` package.
|
||||
|
||||
### Key Components
|
||||
|
||||
- **Collector**: Manages package information and caches collected data
|
||||
- **Package**: Represents a Go package being analyzed and stores collected services, models, and directives
|
||||
- **Service**: Collects information about service types and their methods
|
||||
- **Model**: Collects detailed information about model types, including fields, values, and type parameters
|
||||
- **Directive**: Parses and interprets `//wails:` directives in Go source code
|
||||
|
||||
### Collection Flow
|
||||
|
||||
1. The collector scans the Go packages specified in the project
|
||||
2. It identifies service types (structs with methods that will be exposed to the frontend)
|
||||
3. For each service, it collects information about its methods
|
||||
4. It identifies model types (structs used as parameters or return values in service methods)
|
||||
5. For each model, it collects information about its fields and type parameters
|
||||
6. It processes any `//wails:` directives found in the code
|
||||
|
||||
## Rendering Process
|
||||
|
||||
The rendering process is responsible for generating JavaScript/TypeScript code based on the collected information. This is handled by the `render` package.
|
||||
|
||||
### Key Components
|
||||
|
||||
- **Renderer**: Orchestrates the rendering of service, model, and index files
|
||||
- **Module**: Represents a single generated JavaScript/TypeScript module
|
||||
- **Templates**: Text templates used for code generation
|
||||
|
||||
### Rendering Flow
|
||||
|
||||
1. For each service, the renderer generates a JavaScript/TypeScript file with functions that mirror the service methods
|
||||
2. For each model, the renderer generates a JavaScript/TypeScript class that mirrors the model struct
|
||||
3. The renderer generates index files that re-export all services and models
|
||||
4. The renderer applies any custom code injections specified by `//wails:inject` directives
|
||||
|
||||
## Type Mapping
|
||||
|
||||
One of the most important aspects of the binding system is how Go types are mapped to JavaScript/TypeScript types. Here's a summary of the mapping:
|
||||
|
||||
| Go Type | JavaScript Type | TypeScript Type |
|
||||
|---------|----------------|----------------|
|
||||
| `bool` | `boolean` | `boolean` |
|
||||
| `int`, `int8`, `int16`, `int32`, `int64`, `uint`, `uint8`, `uint16`, `uint32`, `uint64`, `float32`, `float64` | `number` | `number` |
|
||||
| `string` | `string` | `string` |
|
||||
| `[]byte` | `Uint8Array` | `Uint8Array` |
|
||||
| `[]T` | `Array<T>` | `T[]` |
|
||||
| `map[K]V` | `Object` | `Record<K, V>` |
|
||||
| `struct` | `Object` | Custom class |
|
||||
| `interface{}` | `any` | `any` |
|
||||
| `*T` | `T \| null` | `T \| null` |
|
||||
| `func` | Not supported | Not supported |
|
||||
| `chan` | Not supported | Not supported |
|
||||
|
||||
## Directives System
|
||||
|
||||
The binding system supports several directives that can be used to customize the generated code. These directives are added as comments in your Go code.
|
||||
|
||||
### Available Directives
|
||||
|
||||
- `//wails:inject`: Injects custom JavaScript/TypeScript code into the generated bindings
|
||||
- `//wails:include`: Includes additional files with the generated bindings
|
||||
- `//wails:internal`: Marks a type or method as internal, preventing it from being exported to the frontend
|
||||
- `//wails:ignore`: Completely ignores a method during binding generation
|
||||
- `//wails:id`: Specifies a custom ID for a method, overriding the default hash-based ID
|
||||
|
||||
### Directive Processing
|
||||
|
||||
1. During the collection phase, the collector identifies and parses directives in the Go code
|
||||
2. The directives are stored with the corresponding declarations (services, methods, models, etc.)
|
||||
3. During the rendering phase, the renderer applies the directives to customize the generated code
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Conditional Code Generation
|
||||
|
||||
The binding system supports conditional code generation using a two-character condition prefix for `include` and `inject` directives:
|
||||
|
||||
```
|
||||
<language><style>:<content>
|
||||
```
|
||||
|
||||
Where:
|
||||
- `<language>` can be:
|
||||
- `*` - Both JavaScript and TypeScript
|
||||
- `j` - JavaScript only
|
||||
- `t` - TypeScript only
|
||||
|
||||
- `<style>` can be:
|
||||
- `*` - Both classes and interfaces
|
||||
- `c` - Classes only
|
||||
- `i` - Interfaces only
|
||||
|
||||
For example:
|
||||
```go
|
||||
//wails:inject j*:console.log("JavaScript only");
|
||||
//wails:inject t*:console.log("TypeScript only");
|
||||
```
|
||||
|
||||
### Custom Method IDs
|
||||
|
||||
By default, methods are identified by a hash-based ID. However, you can specify a custom ID using the `//wails:id` directive:
|
||||
|
||||
```go
|
||||
//wails:id 42
|
||||
func (s *Service) CustomIDMethod() {}
|
||||
```
|
||||
|
||||
This can be useful for maintaining compatibility when refactoring code.
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
The binding generator is designed to be efficient, but there are a few things to keep in mind:
|
||||
|
||||
1. The first run will be slower as it builds up a cache of packages to scan
|
||||
2. Subsequent runs will be faster as they use the cached information
|
||||
3. The generator processes all packages in the project, which can be time-consuming for large projects
|
||||
4. You can use the `-clean` flag to clean the output directory before generation
|
||||
|
||||
## Debugging
|
||||
|
||||
If you encounter issues with the binding generation, you can use the `-v` flag to enable debug output:
|
||||
|
||||
```bash
|
||||
wails3 generate bindings -v
|
||||
```
|
||||
|
||||
This will provide detailed information about the collection and rendering process, which can help identify the source of the issue.
|
||||
203
docs/src/content/docs/contributing/asset-server.mdx
Normal file
203
docs/src/content/docs/contributing/asset-server.mdx
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
---
|
||||
title: Asset Server
|
||||
description: How Wails v3 serves and embeds your web assets in development and production
|
||||
sidebar:
|
||||
order: 4
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Every Wails application ships a **single native executable** that combines:
|
||||
|
||||
1. Your *Go* backend
|
||||
2. A *Web* frontend (HTML + JS + CSS)
|
||||
|
||||
The **Asset Server** is the glue that makes this possible.
|
||||
It has **two operating modes** that are selected at compile-time via Go build
|
||||
tags:
|
||||
|
||||
| Mode | Tag | Purpose |
|
||||
|------|-----|---------|
|
||||
| **Development** | `//go:build dev` | Fast iteration with hot-reload |
|
||||
| **Production** | `//go:build !dev` | Zero-dependency, embedded assets |
|
||||
|
||||
The implementation lives in
|
||||
`v3/internal/assetserver/` with clear file splits:
|
||||
|
||||
```
|
||||
assetserver_dev.go # ⬅️ runtime dev server
|
||||
assetserver_production.go # ⬅️ embedded server
|
||||
assetserver_darwin.go # OS-specific helpers (same for linux/windows)
|
||||
asset_fileserver.go # Shared static file logic
|
||||
content_type_sniffer.go # MIME type detection
|
||||
ringqueue.go # Tiny LRU for mime cache
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Development Mode
|
||||
|
||||
### Lifecycle
|
||||
|
||||
1. `wails3 dev` boots and **spawns your frontend dev server**
|
||||
(Vite, SvelteKit, React-SWC …) by running the task defined in
|
||||
`frontend/Taskfile.yml` (usually `npm run dev`).
|
||||
2. Wails starts the **Dev Asset Server** listening on `localhost:<random>` and
|
||||
tells the Go runtime to load `http://<host>:<port>` as the window URL.
|
||||
3. Incoming requests are handled by `assetserver_dev.go`:
|
||||
|
||||
```
|
||||
┌─────────┐ /runtime/... ┌─────────────┐
|
||||
│ Browser │ ── native bridge ───▶ │ Runtime │
|
||||
├─────────┤ └─────────────┘
|
||||
│ JS │ / (index.html) proxy / -> Vite
|
||||
└─────────┘ ◀─────────────┐
|
||||
AssetServer │
|
||||
▼
|
||||
┌────────────┐
|
||||
│ Vite Dev │
|
||||
│ Server │
|
||||
└────────────┘
|
||||
```
|
||||
|
||||
4. Static files (`/assets/logo.svg`) are **served directly from disk** via
|
||||
`asset_fileserver.go` (for speed) while anything unknown is **proxied** to
|
||||
the framework dev server, giving you *instant* hot-module replacement.
|
||||
|
||||
### Features
|
||||
|
||||
* **Live Reload** – Vite/Snowpack/… injects HMR WebSocket; Wails only has to
|
||||
proxy it.
|
||||
* **Source Map Support** – because assets are not bundled, your browser devtools
|
||||
map errors back to original source.
|
||||
* **No Go Re-compile** – Only the frontend rebuilds; Go code stays running until
|
||||
you change `.go` files.
|
||||
|
||||
### Switching Frameworks
|
||||
|
||||
The dev proxy is **framework-agnostic**:
|
||||
|
||||
* The `wails.json` template injects two env vars:
|
||||
`FRONTEND_DEV_HOST` & `FRONTEND_DEV_PORT`.
|
||||
* Taskfiles for each template emit those vars before running their dev servers.
|
||||
* `assetserver_dev.go` simply proxies to that target.
|
||||
|
||||
Add a new template → define its dev task → Asset Server just works.
|
||||
|
||||
---
|
||||
|
||||
## Production Mode
|
||||
|
||||
When you run `wails3 build` the pipeline:
|
||||
|
||||
1. Runs the frontend **production build** (`npm run build`) producing
|
||||
`/frontend/dist/**`.
|
||||
2. **Embeds** that folder into `go:embed` FS at compile time (see
|
||||
`bundled_assetserver.go` generated file).
|
||||
3. Compiles the Go binary with `-tags production` (implicit).
|
||||
|
||||
### Request Handling
|
||||
|
||||
```go
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// 1. Try embedded static assets (exact path)
|
||||
// 2. Fallback to index.html for SPA routing
|
||||
// 3. Sniff content-type if extension unknown
|
||||
// 4. Set strong cache headers
|
||||
}
|
||||
```
|
||||
|
||||
* **MIME Detection** – If the build tool produced extension-less files (e.g.
|
||||
`/assets/manifest`) `content_type_sniffer.go` inspects the first 512 bytes and
|
||||
caches the result in a tiny lock-free LRU.
|
||||
* **Ring Queue Caching** – Frequently accessed assets (logo, CSS) are kept
|
||||
in-memory for the lifetime of the app, removing the need for disk or embed FS
|
||||
lookups.
|
||||
* **Security Headers** – Disallows `file://` navigation, enables `nosniff`.
|
||||
|
||||
Because everything is embedded, the shipped binary has **no external
|
||||
dependencies** (even on Windows).
|
||||
|
||||
---
|
||||
|
||||
## Bridging Dev ↔ Prod
|
||||
|
||||
Both modes expose the **same public interface**:
|
||||
|
||||
```go
|
||||
type AssetServer interface {
|
||||
URL() string // dev: http://localhost:34115, prod: wails://app
|
||||
Open() error // start listening
|
||||
Close() // graceful shutdown
|
||||
}
|
||||
```
|
||||
|
||||
`pkg/application` happily uses whichever implementation was compiled in, meaning
|
||||
**your application code does not change** between `dev` and `build`.
|
||||
|
||||
---
|
||||
|
||||
## How Frontend Frameworks Integrate
|
||||
|
||||
### Templates
|
||||
|
||||
Each official template (React, Vue, Svelte, Solid…) contains:
|
||||
|
||||
* `frontend/Taskfile.yml`
|
||||
* `frontend/vite.config.ts` (or equivalent)
|
||||
|
||||
They export two tasks:
|
||||
|
||||
| Task | Purpose |
|
||||
|------|---------|
|
||||
| `dev` | Starts the framework dev server on a **random free port** and prints it to stdout (`PORT=5173`). |
|
||||
| `build` | Produces static assets into `dist/` + manifest for cache-busting. |
|
||||
|
||||
`internal/commands/dev.go` parses that stdout, sets `FRONTEND_DEV_*` env vars
|
||||
and launches the **Dev Asset Server**.
|
||||
|
||||
Frameworks remain fully decoupled from Go:
|
||||
|
||||
* No need to import Wails JS SDK at build time – the runtime injects it at
|
||||
window creation.
|
||||
* Any framework with an HTTP dev server can plug in.
|
||||
|
||||
---
|
||||
|
||||
## Extending / Customising
|
||||
|
||||
Need custom headers, auth, or gzip?
|
||||
|
||||
1. Implement `type Middleware func(http.Handler) http.Handler`
|
||||
2. Register via `internal/assetserver/options.go`
|
||||
3. For prod, remember to add the same middleware in `assetserver_production.go`.
|
||||
|
||||
---
|
||||
|
||||
## Key Source Files
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `assetserver_dev.go` | Reverse proxy + disk file server |
|
||||
| `assetserver_production.go` | Embedded FS handler |
|
||||
| `options.go` | Config struct parsed from `pkg/options/assetserver` |
|
||||
| `build_dev.go` / `build_production.go` | Build-tag wrappers selecting correct implementation |
|
||||
| `bundled_assetserver.go` | Generated embed data (only present in production builds) |
|
||||
|
||||
---
|
||||
|
||||
## Gotchas & Debugging
|
||||
|
||||
* **White Screen in Prod** – usually SPA routing: ensure `History API Fallback`
|
||||
is enabled in dev and `index.html` fallback works in prod.
|
||||
* **404 in Dev** – mis-matched `FRONTEND_DEV_PORT`; run with
|
||||
`WAILSDEV_VERBOSE=1` to print every proxied request.
|
||||
* **Large Assets** – they are embedded; consider
|
||||
[`assetserver.WithExternalDir("/path")`](https://pkg.go.dev) to load from disk.
|
||||
|
||||
---
|
||||
|
||||
You now know how the Wails **Asset Server** feeds your web code to the native
|
||||
window in both **development** and **production**.
|
||||
Master this layer and you can debug loading issues, add middlewares, or even
|
||||
swap in a completely different frontend tool-chain with confidence.
|
||||
240
docs/src/content/docs/contributing/binding-system.mdx
Normal file
240
docs/src/content/docs/contributing/binding-system.mdx
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
---
|
||||
title: Binding System
|
||||
description: How Wails v3 lets Go and JavaScript call each other with zero boilerplate
|
||||
sidebar:
|
||||
order: 5
|
||||
---
|
||||
|
||||
> “Bindings” are the **type-safe contract** that lets you write:
|
||||
|
||||
```go
|
||||
msg, err := chatService.Send("Hello")
|
||||
```
|
||||
|
||||
in Go *and*
|
||||
|
||||
```ts
|
||||
const msg = await window.backend.ChatService.Send("Hello")
|
||||
```
|
||||
|
||||
in TypeScript **without manual glue code**.
|
||||
This document breaks down *how* that magic happens, from **static analysis** at
|
||||
build time, through **code generation**, to the **runtime bridge** that moves
|
||||
bytes over the WebView.
|
||||
|
||||
---
|
||||
|
||||
## 1. 30-Second Overview
|
||||
|
||||
| Stage | Component | Output |
|
||||
|-------|-----------|--------|
|
||||
| **Analysis** | `internal/generator/analyse.go` | In-memory AST of exported Go structs, methods, params, return types |
|
||||
| **Generation** | `internal/generator/render/*.tmpl` | • `pkg/application/bindings.go` (Go)<br />• `frontend/src/wailsjs/**.ts` (TS defs)<br />• `runtime/desktop/@wailsio/runtime/internal/bindings.json` |
|
||||
| **Runtime** | `pkg/application/messageprocessor_call.go` + JS runtime (`invoke.ts`) | JSON messages over WebView native bridge |
|
||||
|
||||
The flow is orchestrated by the `wails3` CLI:
|
||||
|
||||
```
|
||||
wails3 generate ─┬─> generator.Collect() // parse Go packages
|
||||
├─> generator.Analyse() // build semantic model
|
||||
└─> generator.Render() // write files + `go fmt`
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Static Analysis
|
||||
|
||||
### Entry Point
|
||||
|
||||
```
|
||||
internal/generator/analyse.go
|
||||
```
|
||||
|
||||
`Analyse(cfg generator.Config)`:
|
||||
|
||||
1. Recursively loads provided Go packages with `go/packages`.
|
||||
2. Walks the *type* and *value* specs of every AST file.
|
||||
3. Filters **exported** symbols (capitalised) outside `internal/` packages.
|
||||
4. Records:
|
||||
* Receiver type (`struct`, `interface`)
|
||||
* Method name
|
||||
* Parameter list (name, type, pointer, variadic)
|
||||
* Return tuple (values + error presence)
|
||||
5. Normalises types to a **serialisable set**
|
||||
(`int`, `float64`, `string`, `[]byte`, slices, maps, structs, pointers).
|
||||
|
||||
Unsupported types produce a **compile-time error** so mistakes are caught early.
|
||||
|
||||
### Model
|
||||
|
||||
```go
|
||||
type Method struct {
|
||||
ID uint32 // stable hash for runtime lookup
|
||||
Name string
|
||||
Params []Param
|
||||
Results []Result
|
||||
Receiver *Struct // nil for package funcs
|
||||
}
|
||||
```
|
||||
|
||||
A deterministic **FNV-1a** hash (`internal/hash/fnv.go`) of
|
||||
`<Receiver>.<Name>(<Signature>)` becomes the *method ID* used at runtime.
|
||||
|
||||
---
|
||||
|
||||
## 3. Code Generation
|
||||
|
||||
### Templates
|
||||
|
||||
`internal/generator/render/` contains text/template files:
|
||||
|
||||
| Template | Target |
|
||||
|----------|--------|
|
||||
| `go_bindings.tmpl` | `pkg/application/bindings.go` |
|
||||
| `ts_bindings.tmpl` | `frontend/wailsjs/go/models.d.ts` |
|
||||
| `ts_index.tmpl` | `frontend/wailsjs/index.ts` |
|
||||
|
||||
Add or adjust templates here to customise generation.
|
||||
|
||||
### Go Output
|
||||
|
||||
`bindings.go` registers a lookup table:
|
||||
|
||||
```go
|
||||
var bindings = []application.BoundMethod{
|
||||
{ID: 0x7A1201D3, Name: "ChatService.Send", Call: chatServiceSend},
|
||||
}
|
||||
|
||||
func chatServiceSend(ctx context.Context, in []byte) ([]byte, error) {
|
||||
var req struct{ Msg string }
|
||||
if err := json.Unmarshal(in, &req); err != nil { return nil, err }
|
||||
res, err := chatService.Send(req.Msg)
|
||||
return json.Marshal(res), err
|
||||
}
|
||||
```
|
||||
|
||||
Key points:
|
||||
|
||||
* **Zero reflection** at runtime → performance.
|
||||
* Marshal/Unmarshal is **per-method** with generated struct wrappers.
|
||||
|
||||
### TypeScript Output
|
||||
|
||||
```ts
|
||||
export namespace backend {
|
||||
export namespace ChatService {
|
||||
function Send(msg: string): Promise<string>;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* Emitted as **ES modules** so any bundler can tree-shake.
|
||||
* Method IDs are embedded in an auto-generated `bindings.json` for the JS
|
||||
runtime.
|
||||
|
||||
---
|
||||
|
||||
## 4. Runtime Invocation Protocol
|
||||
|
||||
### JavaScript Side
|
||||
|
||||
```ts
|
||||
import { invoke } from "@wailsio/runtime";
|
||||
|
||||
await invoke(0x7a1201d3 /* ChatService.Send */, ["Hello"]);
|
||||
```
|
||||
|
||||
Implementation: `runtime/desktop/@wailsio/runtime/invoke.ts`
|
||||
|
||||
1. Packs `{t:"c", id:<methodID>, p:[...args]}` into JSON.
|
||||
2. Calls `window.external.invoke(payload)` (WebView2) or equivalent.
|
||||
3. Returns a `Promise` that resolves/rejects based on the reply.
|
||||
|
||||
### Go Side
|
||||
|
||||
1. `messageprocessor_call.go` receives the JSON.
|
||||
2. Looks up `methodID` in the `bindings` slice.
|
||||
3. Executes the generated stub (`chatServiceSend`).
|
||||
4. Serialises `{result, error}` back to JS.
|
||||
|
||||
### Error Mapping
|
||||
|
||||
| Go | JavaScript |
|
||||
|----|------------|
|
||||
| `error == nil` | `Promise` resolves with result |
|
||||
| `errors.New(...)` | `Promise` rejects with `{message, stack, code}` |
|
||||
|
||||
The mapping code lives in `runtime/desktop/@wailsio/runtime/errors.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Calling JavaScript from Go
|
||||
|
||||
*Browser → Go* is covered above.
|
||||
*Go → Browser* uses **Events** or **Eval**:
|
||||
|
||||
```go
|
||||
window.Eval(`alert("Hi")`)
|
||||
app.Publish("chat:new-message", msg)
|
||||
```
|
||||
|
||||
Binding generation is one-way (Go methods).
|
||||
For JS-exposed functions use `runtime.EventsOn` in JS and `application.Publish`
|
||||
from Go.
|
||||
|
||||
---
|
||||
|
||||
## 6. Extending & Troubleshooting
|
||||
|
||||
### Adding Custom Serialisers
|
||||
|
||||
* Implement `generator.TypeConverter` interface.
|
||||
* Register in `generator.Config.Converters`.
|
||||
* Update JS runtime deserialisation if needed.
|
||||
|
||||
### Unsupported Type Error
|
||||
|
||||
```
|
||||
error: field "Client" uses unsupported type: chan struct{}
|
||||
```
|
||||
|
||||
→ wrap the channel in a struct with an exposed API or redesign.
|
||||
|
||||
### Version Skew
|
||||
|
||||
Bindings are regenerated on **every** `wails3 dev` / `wails3 build`.
|
||||
If IDE intellisense shows stale stubs, delete `frontend/wailsjs` and rebuild.
|
||||
|
||||
### Performance Tips
|
||||
|
||||
* Prefer **value** receivers for small structs to reduce allocations.
|
||||
* Avoid large byte slices over the bridge; use `application.FileServer` instead.
|
||||
* Batch multiple quick calls into one method to minimise bridge latency.
|
||||
|
||||
---
|
||||
|
||||
## 7. Key Files Map
|
||||
|
||||
| Concern | File |
|
||||
|---------|------|
|
||||
| Static analysis entry | `internal/generator/analyse.go` |
|
||||
| Render pipeline | `internal/generator/generate.go` |
|
||||
| Template assets | `internal/generator/render/*.tmpl` |
|
||||
| Go binding table | `pkg/application/bindings.go` (generated) |
|
||||
| Call processor | `pkg/application/messageprocessor_call.go` |
|
||||
| JS runtime | `runtime/desktop/@wailsio/runtime/invoke.ts` |
|
||||
| Errors mapping | `runtime/desktop/@wailsio/runtime/errors.ts` |
|
||||
|
||||
Keep this cheat-sheet handy when you trace a bridge bug.
|
||||
|
||||
---
|
||||
|
||||
## 8. Recap
|
||||
|
||||
1. **Generator** scans your Go code → semantic model.
|
||||
2. **Templates** emit **Go stubs** + **TypeScript definitions**.
|
||||
3. **Message Processor** executes stubs at runtime.
|
||||
4. **JS Runtime** wraps it all in idiomatic promises.
|
||||
|
||||
All without reflection, without IPC servers, and without you writing a single
|
||||
line of boilerplate. That’s the Wails v3 binding system. Go forth and bind!
|
||||
211
docs/src/content/docs/contributing/build-packaging.mdx
Normal file
211
docs/src/content/docs/contributing/build-packaging.mdx
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
---
|
||||
title: Build & Packaging Pipeline
|
||||
description: What happens under the hood when you run `wails3 build`, how cross-platform binaries are produced, and how installers are generated for each OS.
|
||||
sidebar:
|
||||
order: 6
|
||||
---
|
||||
|
||||
`wails3 build` is a **single command** that drives a _multi-stage_ pipeline:
|
||||
|
||||
1. **Frontend production build** (Vite / React / …)
|
||||
2. **Asset embedding** into Go sources
|
||||
3. **Native compilation** for the target OS/arch
|
||||
4. **Post-processing** (icon injection, version info, codesign)
|
||||
5. **Packaging** into a distributable artefact (AppImage, DMG, MSI, …)
|
||||
|
||||
This document follows every stage, shows where the code lives, and explains how
|
||||
to customise or debug it.
|
||||
|
||||
---
|
||||
|
||||
## 1. Entry Point: `internal/commands/build-assets.go`
|
||||
|
||||
```
|
||||
wails3 build # cmd/wails3/main.go
|
||||
└─ internal/commands.Build() # build-assets.go
|
||||
```
|
||||
|
||||
High-level tasks are delegated to **Taskfile** targets so the same logic runs in
|
||||
CI or locally.
|
||||
|
||||
| Stage | Taskfile target | Go implementation |
|
||||
|-------|-----------------|-------------------|
|
||||
| Frontend build | `frontend:build` | `internal/commands/task.go` |
|
||||
| Embed assets | `embed:assets` | generated `bundled_assetserver.go` |
|
||||
| Go compile | `build:go` | `tool_buildinfo.go`, `tool_package.go` |
|
||||
| Package | `package:*` | `internal/packager`, `internal/commands/*` |
|
||||
|
||||
---
|
||||
|
||||
## 2. Frontend Production Build
|
||||
|
||||
1. The CLI changes into `frontend/` and executes the `build` task found in the
|
||||
project `Taskfile.yml` (`npm run build` by default).
|
||||
2. Output is written to `frontend/dist/`.
|
||||
3. A **content hash** manifest is produced (`manifest.json`) so cache-busting
|
||||
works out of the box.
|
||||
|
||||
If the task fails the pipeline aborts early, returning the exit code of your
|
||||
build tool.
|
||||
|
||||
---
|
||||
|
||||
## 3. Embedding Assets
|
||||
|
||||
Implemented in `internal/assetserver/build_production.go`:
|
||||
|
||||
* Walks `frontend/dist` and generates a `go:embed` file
|
||||
`bundled_assetserver.go` (ignored by git).
|
||||
* Adds the `production` build tag.
|
||||
|
||||
Result: the final binary contains every HTML/JS/CSS byte, so the executable is
|
||||
self-contained.
|
||||
|
||||
---
|
||||
|
||||
## 4. Native Compilation
|
||||
|
||||
### Build Flags
|
||||
|
||||
| Flag | Purpose |
|
||||
|------|---------|
|
||||
| `-tags production` | Select prod asset server & runtime stubs |
|
||||
| `-ldflags "-s -w"` | Strip symbol table & DWARF (smaller binaries) |
|
||||
| `-X internal/buildinfo.BuildTime=$(date)` | Embed reproducible metadata |
|
||||
|
||||
`internal/commands/tool_buildinfo.go` collects version, git commit, and build
|
||||
time then injects them using `go build -ldflags`.
|
||||
|
||||
### Cross Compilation Matrix
|
||||
|
||||
| OS | Arch | Build Tag | CGO | Notes |
|
||||
|----|------|-----------|-----|-------|
|
||||
| Windows | amd64 / arm64 | `windows` | cgo enabled for WebView2 | Generates `.syso` via `internal/commands/syso.go` |
|
||||
| macOS | amd64 / arm64 | `darwin` | cgo enabled (Objective-C) | Codesign & notarisation tasks available |
|
||||
| Linux | amd64 / arm64 | `linux` | pure Go (webkit option) | GTK/WebKitGTK headers required for CGO build |
|
||||
|
||||
`wails3 build -platform darwin/arm64` overrides GOOS/GOARCH.
|
||||
If CGO is needed on the host that can't compile the target (e.g. building
|
||||
Windows from Linux), the CLI falls back to **Docker** images (`ghcr.io/wailsapp/cross-compiler`).
|
||||
|
||||
---
|
||||
|
||||
## 5. Post-Processing
|
||||
|
||||
### Icons & Resources
|
||||
|
||||
* **Windows** – `syso.go` merges your PNG/ICO into a `.syso` that `go build`
|
||||
links automatically. Also writes `manifest.xml` enabling High-DPI support.
|
||||
* **macOS** – `icons.go` turns `icon.png` → `.icns`, injects into
|
||||
`MyApp.app/Contents/Resources`.
|
||||
* **Linux** – `.desktop` files are generated in `/usr/share/applications`
|
||||
by each packager backend.
|
||||
|
||||
### Code Signing (Optional)
|
||||
|
||||
* macOS: `codesign` + `xcrun altool --notarize-app`
|
||||
* Windows: `signtool.exe`
|
||||
* Linux: Not common (repository GPG handled externally)
|
||||
|
||||
Task targets: `sign:mac`, `sign:windows`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Packaging Back-Ends
|
||||
|
||||
### Linux
|
||||
|
||||
| Artefact | Code Path | Tooling |
|
||||
|----------|-----------|---------|
|
||||
| **AppImage** (default) | `internal/commands/appimage.go` | `linuxdeploy` + `linuxdeploy-plugin-gtk` |
|
||||
| **deb / rpm / pacman** | `internal/packager` | `fpm` (invoked via Go) |
|
||||
|
||||
Flags:
|
||||
|
||||
```
|
||||
wails3 build -package deb # produces myapp_1.0.0_amd64.deb
|
||||
wails3 build -package rpm
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
* **DMG** – `internal/commands/packager.go` calls `appdmg` to generate a
|
||||
drag-&-drop installer.
|
||||
* **ZIP** – fallback if `appdmg` is missing.
|
||||
* Sets CFBundle identifiers and version plist automatically.
|
||||
|
||||
### Windows
|
||||
|
||||
* **MSI** – `internal/commands/packager.go` wraps **WiX** candle & light tools.
|
||||
Heat autogenerates the component tree from the built `.exe`.
|
||||
|
||||
Extra resources:
|
||||
|
||||
* `internal/commands/windows_resources/` contains templated **.wxs** fragments.
|
||||
* Version info injected via `rsrc` utility.
|
||||
|
||||
---
|
||||
|
||||
## 7. Build Artefact Layout
|
||||
|
||||
After a successful build the CLI prints:
|
||||
|
||||
```
|
||||
build/bin/
|
||||
├── myapp-linux-amd64
|
||||
└── myapp-linux-amd64.AppImage
|
||||
|
||||
build/package/
|
||||
└── myapp_1.0.0_amd64.deb
|
||||
```
|
||||
|
||||
The exact files depend on `-platform` and `-package` flags.
|
||||
|
||||
---
|
||||
|
||||
## 8. Customising the Pipeline
|
||||
|
||||
| Need | Approach |
|
||||
|------|----------|
|
||||
| Run a linter before build | Add a `lint` task in **Taskfile.yml** and make `build` depend on it. |
|
||||
| Extra linker flags | `wails3 build -ldflags "-H windowsgui"` |
|
||||
| Skip packaging | `wails3 build -skip-package` (keeps raw binary). |
|
||||
| Bring your own packager | Implement `internal/packager/<name>.go`, register in `packager.go`. |
|
||||
|
||||
All Taskfile targets use environment variables exported by the CLI, so you can
|
||||
reference values like `$WAILS_VERSION` or `$WAILS_PLATFORM` inside custom tasks.
|
||||
|
||||
---
|
||||
|
||||
## 9. Troubleshooting
|
||||
|
||||
| Symptom | Likely Cause | Fix |
|
||||
|---------|--------------|-----|
|
||||
| **`ld: framework not found WebKit` (mac)** | Xcode CLI tools missing | `xcode-select --install` |
|
||||
| **Blank window in prod build** | Frontend build failed or SPA routing | Check `frontend/dist/index.html` exists and `History API Fallback` is set. |
|
||||
| **`wixl` not found** (Linux MSI cross-build) | WiX toolset not installed in Docker image | Use `--docker` build or install WiX manually. |
|
||||
| **Duplicated symbol `_WinMain`** | Mixed `windowsgui` flag and syso | Remove custom `-ldflags` or let Wails manage resources. |
|
||||
|
||||
Verbose mode: `wails3 build -verbose` dumps every command executed.
|
||||
|
||||
---
|
||||
|
||||
## 10. Key Source Map
|
||||
|
||||
| Concern | File |
|
||||
|---------|------|
|
||||
| Task runner glue | `internal/commands/task_wrapper.go` |
|
||||
| Build dispatcher | `internal/commands/build-assets.go` |
|
||||
| AppImage builder | `internal/commands/appimage.go` |
|
||||
| Generic packager interface | `internal/packager/packager.go` |
|
||||
| Windows resource generator | `internal/commands/syso.go` |
|
||||
| Build info injector | `internal/commands/tool_buildinfo.go` |
|
||||
| Version constants | `internal/version/version.go` |
|
||||
|
||||
Keep this table handy when you trace a build failure.
|
||||
|
||||
---
|
||||
|
||||
You now have the full picture from **source code** to **installer**. Armed with
|
||||
this knowledge you can tweak the pipeline, add new packaging targets, or debug
|
||||
cross-compilation issues without fear. Happy shipping!
|
||||
216
docs/src/content/docs/contributing/codebase-layout.mdx
Normal file
216
docs/src/content/docs/contributing/codebase-layout.mdx
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
---
|
||||
title: Codebase Layout
|
||||
description: How the Wails v3 repository is organised and how the pieces fit together
|
||||
sidebar:
|
||||
order: 2
|
||||
---
|
||||
|
||||
Wails v3 lives in a **monorepo** that contains the framework runtime, CLI,
|
||||
examples, documentation, and build tool-chain.
|
||||
This page walks through the *directory structure* that matters to anyone digging
|
||||
into the internals.
|
||||
|
||||
## Top-Level View
|
||||
|
||||
```
|
||||
wails/
|
||||
├── v3/ # ⬅️ Everything specific to Wails v3 lives here
|
||||
├── v2/ # Legacy v2 implementation (can be ignored for v3 work)
|
||||
├── docs/ # Astro-powered v3 docs site (this page!)
|
||||
├── website/ # Docusaurus v2 site and marketing pages (main site)
|
||||
├── scripts/ # Misc helper scripts (e.g. sponsor image generator)
|
||||
├── mkdocs-website/ # Deprecated v3 docs prototype
|
||||
└── *.md # Project-wide meta files (CHANGELOG, LICENSE, …)
|
||||
```
|
||||
|
||||
From here on, we zoom into the **`v3/`** tree.
|
||||
|
||||
## `v3/` Root
|
||||
|
||||
```
|
||||
v3/
|
||||
├── cmd/ # Compilable commands (currently only wails3 CLI)
|
||||
├── examples/ # Hands-on sample apps testing every feature
|
||||
├── internal/ # Framework implementation (not public API)
|
||||
├── pkg/ # Public Go packages ‑ the API surface
|
||||
├── tasks/ # Taskfile-based release utilities
|
||||
├── templates/ # RFC-style proposals (WEP) + common assets
|
||||
├── go.mod
|
||||
└── go.sum
|
||||
```
|
||||
|
||||
### Mental Model
|
||||
|
||||
1. **`pkg/`** exposes *what application developers import*
|
||||
2. **`internal/`** contains *how the magic is implemented*
|
||||
3. **`cmd/wails3`** drives *project lifecycle & builds*
|
||||
4. **`examples/`** double as *living tests* and *reference code*
|
||||
|
||||
Everything else supports those three pillars.
|
||||
|
||||
---
|
||||
|
||||
## `cmd/` – Commands
|
||||
|
||||
| Path | Notes |
|
||||
|------|-------|
|
||||
| `v3/cmd/wails3` | The **CLI entrypoint**. A tiny `main.go` delegates all logic to packages in `internal/commands`. |
|
||||
| `internal/commands/*` | Sub-commands (init, dev, build, doctor, …). Each lives in its own file for easy discoverability. |
|
||||
| `internal/commands/task_wrapper.go` | Bridges between CLI flags and the Taskfile build pipeline. |
|
||||
|
||||
The CLI owns:
|
||||
|
||||
* **Project scaffolding** (`init`, template generation)
|
||||
* **Dev server orchestration** (`dev`, live-reload)
|
||||
* **Production builds & packaging** (`build`, `package`, platform wrappers)
|
||||
* **Diagnostics** (`doctor`)
|
||||
|
||||
---
|
||||
|
||||
## `internal/` – The Engine Room
|
||||
|
||||
```
|
||||
internal/
|
||||
├── assetserver/ # Serving & embedding web assets
|
||||
├── buildinfo/ # Reproducible build metadata
|
||||
├── commands/ # CLI mechanics (see above)
|
||||
├── runtime/ # Desktop runtime (per-platform)
|
||||
├── generator/ # Static analysis & binding generator
|
||||
├── templates/ # Project templates (frontend stacks)
|
||||
├── packager/ # Linux AppImage/deb/rpm, Windows MSI, macOS DMG
|
||||
├── capabilities/ # Host OS capability probing
|
||||
├── dbus/ # Linux system tray integration
|
||||
├── service/ # IPC micro-services framework
|
||||
└── ... # [other helper sub-packages]
|
||||
```
|
||||
|
||||
### Key Sub-Packages
|
||||
|
||||
| Package | Responsibility | Where It Connects |
|
||||
|---------|----------------|-------------------|
|
||||
| `runtime` | Window/event loop, clipboard, dialogs, system tray. One file per OS with build-tags (`*_darwin.go`, `*_linux.go`, …). | Called by `pkg/application` and message processor. |
|
||||
| `assetserver` | Dual-mode file server:<br />• Dev: serves from disk & proxies Vite<br />• Prod: embeds assets via `go:embed` | Initialized by `pkg/application` during startup. |
|
||||
| `generator` | Parses Go source to build **binding metadata** which later produces TypeScript stub files and Go marshal/unmarshal code. | Triggered by `wails3 init` / `wails3 generate`. |
|
||||
| `packager` | Wraps platform-specific packaging CLIs (eg. `electron-builder` equivalents) into Go for cross-platform automation. | Invoked by `wails3 package`. |
|
||||
|
||||
Supporting utilities (eg. `s/`, `hash/`, `flags/`) keep internal concerns decoupled.
|
||||
|
||||
---
|
||||
|
||||
## `pkg/` – Public API
|
||||
|
||||
```
|
||||
pkg/
|
||||
├── application/ # Core API: windows, menus, dialogs, events, …
|
||||
├── runtime/ # JS-side helpers (bridge, events, screen, ...)
|
||||
├── options/ # Strongly-typed configuration structs
|
||||
├── menu/ # Declarative menu DSL
|
||||
└── ... # Platform helpers (mac/, windows/, assetserver/, …)
|
||||
```
|
||||
|
||||
`pkg/application` bootstraps a Wails program:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "MyApp",
|
||||
Assets: application.NewAssetsFS(assetsFS),
|
||||
})
|
||||
window := app.Window.New(&application.WebviewWindowOptions{
|
||||
Title: "Hello",
|
||||
Width: 1024,
|
||||
Height: 768,
|
||||
})
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
Under the hood it:
|
||||
|
||||
1. Spins up `internal.runtime.*`
|
||||
2. Sets up an `assetserver`
|
||||
3. Registers bindings generated by `internal.generator`
|
||||
4. Enters the OS main thread
|
||||
|
||||
---
|
||||
|
||||
## `examples/` – Living Specs
|
||||
|
||||
Each sub-folder is a **self-contained application** exercising one feature.
|
||||
Patterns you’ll see mirrored in real code:
|
||||
|
||||
* Binding services (`examples/binding/`)
|
||||
* Multi-window (`examples/window/`)
|
||||
* System tray (`examples/systray-*`)
|
||||
* Packaging (`examples/file-association/`)
|
||||
|
||||
When in doubt, start here and drill into implementation.
|
||||
|
||||
---
|
||||
|
||||
## `templates/` – Scaffolding Blueprints
|
||||
|
||||
`internal/templates` ships **base templates** (Go layout) and **frontend skins**
|
||||
(React, Svelte, Vue, …).
|
||||
At `wails3 init -t react`, the CLI:
|
||||
|
||||
1. Copies `_common` Go files
|
||||
2. Merges desired frontend pack
|
||||
3. Runs `npm install` + `task deps`
|
||||
|
||||
Editing templates **does not** affect existing apps, only future `init`s.
|
||||
|
||||
---
|
||||
|
||||
## `tasks/` – Release Automation
|
||||
|
||||
Taskfiles wrap complex cross-compilation, version bumping, and changelog
|
||||
generation. They are consumed programmatically by `internal/commands/task.go`
|
||||
so the same logic powers **CLI** and **CI**.
|
||||
|
||||
---
|
||||
|
||||
## How the Pieces Interact
|
||||
|
||||
```d2
|
||||
direction: down
|
||||
CLI: "wails3 CLI"
|
||||
Generator: "internal/generator"
|
||||
AssetDev: "assetserver (dev)"
|
||||
Packager: "internal/packager"
|
||||
AppRuntime: {
|
||||
label: "App runtime"
|
||||
ApplicationPkg: "pkg.application"
|
||||
InternalRuntime: "internal.runtime"
|
||||
OSAPIs: "OS APIs"
|
||||
}
|
||||
CLI -> Generator: "build / generate"
|
||||
CLI -> AssetDev: "dev"
|
||||
CLI -> Packager: "package"
|
||||
Generator -> ApplicationPkg: "bindings"
|
||||
ApplicationPkg -> InternalRuntime
|
||||
InternalRuntime -> OSAPIs
|
||||
ApplicationPkg -> AssetDev
|
||||
```
|
||||
|
||||
*CLI → generator → runtime* forms the core path from **source** to **running
|
||||
desktop app**.
|
||||
|
||||
---
|
||||
|
||||
## Orientation Tips
|
||||
|
||||
| Need to understand… | Look at… |
|
||||
|---------------------|----------|
|
||||
| Platform shims | `internal/runtime/*_DARWIN.go`, `*_windows.go`, … |
|
||||
| Bridge protocol | `pkg/application/messageprocessor_*.go` |
|
||||
| Asset workflow | `internal/assetserver`, `v3/templates/base/assets` |
|
||||
| Packaging flow | `internal/commands/appimage.go`, `internal/packager` |
|
||||
| Template engine | `internal/templates/templates.go` |
|
||||
| Static analysis | `internal/generator/*` |
|
||||
|
||||
---
|
||||
|
||||
You now have a **mental map** of the repository.
|
||||
Use it with `ripgrep`, your IDE’s “Go to File/Symbol”, and the example apps to
|
||||
navigate deeper into any feature. Happy hacking!
|
||||
246
docs/src/content/docs/contributing/extending-wails.mdx
Normal file
246
docs/src/content/docs/contributing/extending-wails.mdx
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
---
|
||||
title: Extending Wails
|
||||
description: Practical guide to adding new features and platforms to Wails v3
|
||||
sidebar:
|
||||
order: 8
|
||||
---
|
||||
|
||||
> Wails is designed to be **hackable**.
|
||||
> Every major subsystem lives in Go code you can read, modify, and ship.
|
||||
> This page shows _where_ to start and _how_ to stay cross-platform when you:
|
||||
|
||||
* Add a **Service** (file server, KV store, custom IPC…)
|
||||
* Create a **new CLI command** (`wails3 <foo>`)
|
||||
* Extend the **Runtime** (window API, dialogs, events)
|
||||
* Introduce a **Platform capability** (Wayland, Apple Vision OS…)
|
||||
* Keep **cross-platform compatibility** without drowning in `//go:build` tags
|
||||
|
||||
---
|
||||
|
||||
## 1. Adding a Service
|
||||
|
||||
Services are background Go components exposed to apps via the `application.Context`.
|
||||
|
||||
```
|
||||
internal/service/
|
||||
├── template/ # Boilerplate for new services
|
||||
│ ├── template.go
|
||||
│ └── README.md
|
||||
└── service.go # Registration + lifecycle interfaces
|
||||
```
|
||||
|
||||
### 1.1 Define the Service
|
||||
|
||||
```go
|
||||
package chat
|
||||
|
||||
type Service struct {
|
||||
messages []string
|
||||
}
|
||||
|
||||
func New() *Service { return &Service{} }
|
||||
|
||||
func (s *Service) Send(msg string) string {
|
||||
s.messages = append(s.messages, msg)
|
||||
return "ok"
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 Implement `service.Service`
|
||||
|
||||
```go
|
||||
func (s *Service) Init(ctx *application.Context) error { return nil }
|
||||
func (s *Service) Shutdown() error { return nil }
|
||||
```
|
||||
|
||||
### 1.3 Register Generator
|
||||
|
||||
*Add to* `internal/generator/collect/services.go`
|
||||
|
||||
```go
|
||||
services.Register("chat", chat.New)
|
||||
```
|
||||
|
||||
> Now `wails3 generate` emits bindings so JS can call
|
||||
> `window.backend.chat.Send("hi")`.
|
||||
|
||||
### 1.4 Ship an Example
|
||||
|
||||
Copy `v3/examples/services/` and adjust.
|
||||
|
||||
---
|
||||
|
||||
## 2. Writing a New CLI Command
|
||||
|
||||
CLI logic lives under `internal/commands/`.
|
||||
Each command is **one file** that mounts itself in `init()`.
|
||||
|
||||
### 2.1 Create the File
|
||||
|
||||
`v3/internal/commands/hello.go`
|
||||
|
||||
```go
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Add(&cobra.Command{
|
||||
Use: "hello",
|
||||
Short: "Prints Hello World",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cmd.Println("Hello Wails!")
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
`Add()` registers with the root in `cmd/wails3/main.go`.
|
||||
|
||||
### 2.2 Re-compile CLI
|
||||
|
||||
```
|
||||
go install ./v3/cmd/wails3
|
||||
wails3 hello
|
||||
```
|
||||
|
||||
If your command needs **Taskfile** integration, reuse helpers in
|
||||
`internal/commands/task_wrapper.go`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Modifying the Runtime
|
||||
|
||||
Common reasons:
|
||||
|
||||
* New window feature (`SetOpacity`, `Shake`, …)
|
||||
* Extra dialog (`ColorPicker`)
|
||||
* System-level API (screen brightness)
|
||||
|
||||
### 3.1 Public API
|
||||
|
||||
Add to `pkg/application/window.go`:
|
||||
|
||||
```go
|
||||
func (w *WebviewWindow) SetOpacity(o float32) {
|
||||
w.ctx.Call("window:setOpacity", o)
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Message Processor
|
||||
|
||||
Create `messageprocessor_window_opacity.go`:
|
||||
|
||||
```go
|
||||
const MsgSetOpacity = "window:setOpacity"
|
||||
|
||||
func init() { register(MsgSetOpacity, handleSetOpacity) }
|
||||
|
||||
func handleSetOpacity(ctx *Context, params json.RawMessage) ([]byte, error) {
|
||||
var req struct {
|
||||
ID uint64 `json:"id"`
|
||||
Opacity float32 `json:"o"`
|
||||
}
|
||||
json.Unmarshal(params, &req)
|
||||
return nil, runtime.SetOpacity(req.ID, req.Opacity)
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 Platform Implementation
|
||||
|
||||
```
|
||||
internal/runtime/
|
||||
webview_window_darwin.go
|
||||
webview_window_linux.go
|
||||
webview_window_windows.go
|
||||
```
|
||||
|
||||
Add `SetOpacity()` to each, guarded by build tags.
|
||||
If a platform can’t support it, return `ErrCapability`.
|
||||
|
||||
### 3.4 Capability Flag
|
||||
|
||||
Expose via `internal/capabilities`.
|
||||
|
||||
```go
|
||||
const CapOpacity = "window:opacity"
|
||||
```
|
||||
|
||||
Runtime files `*_darwin.go` etc. should `registerCapability(CapOpacity)` when
|
||||
successfully initialised.
|
||||
|
||||
Apps can query:
|
||||
|
||||
```go
|
||||
if application.HasCapability(application.CapOpacity) { ... }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Adding New Platform Capabilities
|
||||
|
||||
Example: **Wayland** clipboard on Linux.
|
||||
|
||||
1. Fork `internal/runtime/clipboard_linux.go`.
|
||||
2. Split file:
|
||||
* `clipboard_linux_x11.go` → `//go:build linux && !wayland`
|
||||
* `clipboard_linux_wayland.go` → `//go:build linux && wayland`
|
||||
3. Add CLI flag `--tags wayland` in `internal/commands/dev.go`
|
||||
(`goEnv.BuildTags += ",wayland"`).
|
||||
4. Document in **Asset Server** & README.
|
||||
|
||||
> Keep default build tags minimal; reserve opt-in tags for niche features.
|
||||
|
||||
---
|
||||
|
||||
## 5. Cross-Platform Compatibility Checklist
|
||||
|
||||
| ✅ Step | Why |
|
||||
|---------|-----|
|
||||
| Provide **every** public method in all platform files (even if stub) | Keeps build green on all OS/arch |
|
||||
| Gate platform specifics behind **capabilities** | Let apps degrade gracefully |
|
||||
| Use **pure Go** first, CGO only when needed | Simplifies cross-compiles |
|
||||
| Test with `task matrix:test` | Runs `go test ./...` on linux/mac/windows in Docker |
|
||||
| Document new build tags in `docs/getting-started/installation.mdx` | Users must know flags |
|
||||
|
||||
---
|
||||
|
||||
## 6. Debug Builds & Iteration Speed
|
||||
|
||||
* `wails3 dev -tags debug` adds extra log hooks (`logger_dev*.go`).
|
||||
* Use `task reload` to rebuild runtime without restarting the whole app.
|
||||
* Race detector: `wails3 dev -race` (see `pkg/application/RACE.md`).
|
||||
|
||||
---
|
||||
|
||||
## 7. Upstream Contributions
|
||||
|
||||
1. Open an **issue** to discuss the idea & design.
|
||||
2. Follow the techniques above to implement.
|
||||
3. Add:
|
||||
* Unit tests (`*_test.go`)
|
||||
* Example (`v3/examples/<feat>/`)
|
||||
* Docs (this file or API reference)
|
||||
4. Ensure `go vet`, `golangci-lint`, and CI matrix pass.
|
||||
|
||||
---
|
||||
|
||||
### Quick Links
|
||||
|
||||
| Area | Location |
|
||||
|------|----------|
|
||||
| Services framework | `internal/service/` |
|
||||
| CLI core | `internal/commands/` |
|
||||
| Runtime per-OS | `internal/runtime/` |
|
||||
| Capability helpers| `internal/capabilities/` |
|
||||
| Taskfile DSL | `tasks/Taskfile.yml` |
|
||||
| Dev matrix tests | `tasks/events/generate.go` |
|
||||
|
||||
---
|
||||
|
||||
You now have a **roadmap** for bending Wails to your will—add services, sprinkle
|
||||
CLI magic, hack the runtime, or bring entirely new OS features.
|
||||
Happy extending!
|
||||
369
docs/src/content/docs/contributing/getting-started.mdx
Normal file
369
docs/src/content/docs/contributing/getting-started.mdx
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
---
|
||||
title: Getting Started
|
||||
description: How to start contributing to Wails v3
|
||||
---
|
||||
|
||||
import { Steps, Tabs, TabItem } from '@astrojs/starlight/components';
|
||||
|
||||
## Welcome, Contributor!
|
||||
|
||||
Thank you for your interest in contributing to Wails! This guide will help you make your first contribution.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you begin, ensure you have:
|
||||
|
||||
- **Go 1.25+** installed ([download](https://go.dev/dl/))
|
||||
- **Node.js 20+** and **npm** ([download](https://nodejs.org/))
|
||||
- **Git** configured with your GitHub account
|
||||
- Basic familiarity with Go and JavaScript/TypeScript
|
||||
|
||||
### Platform-Specific Requirements
|
||||
|
||||
**macOS:**
|
||||
- Xcode Command Line Tools: `xcode-select --install`
|
||||
|
||||
**Windows:**
|
||||
- MSYS2 or similar Unix-like environment recommended
|
||||
- WebView2 runtime (usually pre-installed on Windows 11)
|
||||
|
||||
**Linux:**
|
||||
- `gcc`, `pkg-config`, `libgtk-3-dev`, `libwebkit2gtk-4.0-dev`
|
||||
- Install via: `sudo apt install build-essential pkg-config libgtk-3-dev libwebkit2gtk-4.0-dev` (Debian/Ubuntu)
|
||||
|
||||
## Contribution Process Overview
|
||||
|
||||
The typical contribution workflow follows these steps:
|
||||
|
||||
1. **Fork & Clone** - Create your own copy of the Wails repository
|
||||
2. **Setup** - Build the Wails CLI and verify your environment
|
||||
3. **Branch** - Create a feature branch for your changes
|
||||
4. **Develop** - Make your changes following our coding standards
|
||||
5. **Test** - Run tests to ensure everything works
|
||||
6. **Commit** - Commit with clear, conventional commit messages
|
||||
7. **Submit** - Open a pull request for review
|
||||
8. **Iterate** - Respond to feedback and make adjustments
|
||||
9. **Merge** - Once approved, your changes become part of Wails!
|
||||
|
||||
## Step-by-Step Guide
|
||||
|
||||
Choose your contribution type:
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Bug Fix">
|
||||
|
||||
<Steps>
|
||||
|
||||
1. **Find or Report the Bug**
|
||||
|
||||
- Check if the bug is already reported in [GitHub Issues](https://github.com/wailsapp/wails/issues)
|
||||
- If not, create a new issue with steps to reproduce
|
||||
- Wait for confirmation before starting work
|
||||
|
||||
2. **Fork and Clone**
|
||||
|
||||
Fork the repository at [github.com/wailsapp/wails/fork](https://github.com/wailsapp/wails/fork)
|
||||
|
||||
Clone your fork:
|
||||
```bash
|
||||
git clone https://github.com/YOUR_USERNAME/wails.git
|
||||
cd wails
|
||||
git remote add upstream https://github.com/wailsapp/wails.git
|
||||
```
|
||||
|
||||
3. **Build and Verify**
|
||||
|
||||
Build Wails and verify you can reproduce the bug:
|
||||
```bash
|
||||
cd v3
|
||||
go build -o ../wails3 ./cmd/wails3
|
||||
|
||||
# Reproduce the bug to understand it
|
||||
```
|
||||
|
||||
4. **Create a Bug Fix Branch**
|
||||
|
||||
Create a branch for your fix:
|
||||
```bash
|
||||
git checkout -b fix/issue-123-window-crash
|
||||
```
|
||||
|
||||
5. **Fix the Bug**
|
||||
|
||||
- Make the minimal changes needed to fix the bug
|
||||
- Don't refactor unrelated code
|
||||
- Add or update tests to prevent regression
|
||||
|
||||
```bash
|
||||
# Make your changes
|
||||
# Add tests in *_test.go files
|
||||
```
|
||||
|
||||
6. **Test Your Fix**
|
||||
|
||||
Run tests to ensure the fix works:
|
||||
```bash
|
||||
go test ./...
|
||||
|
||||
# Test the specific package
|
||||
go test ./pkg/application -v
|
||||
|
||||
# Run with race detector
|
||||
go test ./... -race
|
||||
```
|
||||
|
||||
7. **Commit Your Fix**
|
||||
|
||||
Commit with a clear message:
|
||||
```bash
|
||||
git commit -m "fix: prevent window crash when closing during initialization
|
||||
|
||||
Fixes #123"
|
||||
```
|
||||
|
||||
8. **Submit Pull Request**
|
||||
|
||||
Push and create PR:
|
||||
```bash
|
||||
git push origin fix/issue-123-window-crash
|
||||
```
|
||||
|
||||
In your PR description:
|
||||
- Explain the bug and root cause
|
||||
- Describe your fix
|
||||
- Reference the issue: "Fixes #123"
|
||||
- Include before/after behavior
|
||||
|
||||
9. **Respond to Feedback**
|
||||
|
||||
Address review comments and update your PR as needed.
|
||||
|
||||
</Steps>
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Enhancements">
|
||||
|
||||
<Steps>
|
||||
|
||||
1. **Discuss the Feature**
|
||||
|
||||
- Open a [GitHub Discussion](https://github.com/wailsapp/wails/discussions) or issue
|
||||
- Describe what you want to add and why
|
||||
- Wait for maintainer feedback before implementing
|
||||
- Ensure it aligns with Wails' goals
|
||||
|
||||
2. **Fork and Clone**
|
||||
|
||||
Fork the repository at [github.com/wailsapp/wails/fork](https://github.com/wailsapp/wails/fork)
|
||||
|
||||
Clone your fork:
|
||||
```bash
|
||||
git clone https://github.com/YOUR_USERNAME/wails.git
|
||||
cd wails
|
||||
git remote add upstream https://github.com/wailsapp/wails.git
|
||||
```
|
||||
|
||||
3. **Setup Development Environment**
|
||||
|
||||
Build Wails and verify your environment:
|
||||
```bash
|
||||
cd v3
|
||||
go build -o ../wails3 ./cmd/wails3
|
||||
|
||||
# Run tests to ensure everything works
|
||||
go test ./...
|
||||
```
|
||||
|
||||
4. **Create a Feature Branch**
|
||||
|
||||
Create a descriptive branch:
|
||||
```bash
|
||||
git checkout -b feat/window-transparency-support
|
||||
```
|
||||
|
||||
5. **Implement the Feature**
|
||||
|
||||
- Follow our [Coding Standards](/contributing/standards)
|
||||
- Keep changes focused on the feature
|
||||
- Write clean, documented code
|
||||
- Add comprehensive tests
|
||||
|
||||
```bash
|
||||
# Example: Adding a new window method
|
||||
# 1. Add to window.go interface
|
||||
# 2. Implement in platform files (darwin, windows, linux)
|
||||
# 3. Add tests
|
||||
# 4. Update documentation
|
||||
```
|
||||
|
||||
6. **Test Thoroughly**
|
||||
|
||||
Test your feature:
|
||||
```bash
|
||||
# Unit tests
|
||||
go test ./pkg/application -v
|
||||
|
||||
# Integration test - create a test app
|
||||
cd ..
|
||||
./wails3 init -n feature-test
|
||||
cd feature-test
|
||||
# Add code using your new feature
|
||||
../wails3 dev
|
||||
```
|
||||
|
||||
7. **Document Your Feature**
|
||||
|
||||
- Add docstrings to all public APIs
|
||||
- Update relevant documentation in `/docs`
|
||||
- Add examples if applicable
|
||||
|
||||
8. **Commit with Convention**
|
||||
|
||||
Use conventional commits:
|
||||
```bash
|
||||
git commit -m "feat: add window transparency support
|
||||
|
||||
- Add SetTransparent() method to Window API
|
||||
- Implement for macOS, Windows, and Linux
|
||||
- Add tests and documentation
|
||||
|
||||
Closes #456"
|
||||
```
|
||||
|
||||
9. **Submit Pull Request**
|
||||
|
||||
Push and create PR:
|
||||
```bash
|
||||
git push origin feat/window-transparency-support
|
||||
```
|
||||
|
||||
In your PR:
|
||||
- Describe the feature and use cases
|
||||
- Show examples or screenshots
|
||||
- List any breaking changes
|
||||
- Reference the discussion/issue
|
||||
|
||||
10. **Iterate Based on Review**
|
||||
|
||||
Maintainers may request changes. Be patient and collaborative.
|
||||
|
||||
</Steps>
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Documentation">
|
||||
|
||||
<Steps>
|
||||
|
||||
1. **Identify Documentation Needs**
|
||||
|
||||
- Found outdated docs while using Wails?
|
||||
- Notice missing examples or explanations?
|
||||
- Want to fix typos or improve clarity?
|
||||
- Check [documentation issues](https://github.com/wailsapp/wails/labels/documentation)
|
||||
|
||||
2. **Fork and Clone**
|
||||
|
||||
Fork the repository at [github.com/wailsapp/wails/fork](https://github.com/wailsapp/wails/fork)
|
||||
|
||||
Clone your fork:
|
||||
```bash
|
||||
git clone https://github.com/YOUR_USERNAME/wails.git
|
||||
cd wails
|
||||
git remote add upstream https://github.com/wailsapp/wails.git
|
||||
```
|
||||
|
||||
3. **Setup Documentation Environment**
|
||||
|
||||
The docs are in `/docs` and built with Astro:
|
||||
```bash
|
||||
cd docs
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open http://localhost:4321/ to preview changes live.
|
||||
|
||||
4. **Create a Documentation Branch**
|
||||
|
||||
Create a branch for your changes:
|
||||
```bash
|
||||
git checkout -b docs/improve-window-api-examples
|
||||
```
|
||||
|
||||
5. **Make Your Changes**
|
||||
|
||||
Documentation files are in `/docs/src/content/docs/`:
|
||||
```bash
|
||||
# Edit MDX files
|
||||
# Check the preview in your browser
|
||||
# Ensure formatting is correct
|
||||
```
|
||||
|
||||
**Best Practices:**
|
||||
- Use clear, concise language
|
||||
- Include practical code examples
|
||||
- Add links to related sections
|
||||
- Check spelling and grammar
|
||||
- Test all code examples
|
||||
|
||||
6. **Verify Your Changes**
|
||||
|
||||
Check the live preview and ensure:
|
||||
- Links work correctly
|
||||
- Code examples are accurate
|
||||
- Formatting renders properly
|
||||
- No broken images or references
|
||||
|
||||
7. **Commit Documentation Changes**
|
||||
|
||||
Commit with clear message:
|
||||
```bash
|
||||
git commit -m "docs: add practical examples to Window API guide
|
||||
|
||||
- Add window positioning examples
|
||||
- Include common patterns section
|
||||
- Fix broken links to Event API"
|
||||
```
|
||||
|
||||
8. **Submit Pull Request**
|
||||
|
||||
Push and create PR:
|
||||
```bash
|
||||
git push origin docs/improve-window-api-examples
|
||||
```
|
||||
|
||||
In your PR:
|
||||
- Describe what docs you improved
|
||||
- Explain why the change helps users
|
||||
- Include screenshots if visual changes
|
||||
|
||||
9. **Address Review Feedback**
|
||||
|
||||
Documentation PRs are usually quick to review and merge!
|
||||
|
||||
</Steps>
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Finding Issues to Work On
|
||||
|
||||
- Look for [`good first issue`](https://github.com/wailsapp/wails/labels/good%20first%20issue) labels
|
||||
- Check [`help wanted`](https://github.com/wailsapp/wails/labels/help%20wanted) issues
|
||||
- Browse [open issues](https://github.com/wailsapp/wails/issues) and ask to be assigned
|
||||
|
||||
## Getting Help
|
||||
|
||||
- **Discord:** Join [Wails Discord](https://discord.gg/JDdSxwjhGf)
|
||||
- **Discussions:** Post in [GitHub Discussions](https://github.com/wailsapp/wails/discussions)
|
||||
- **Issues:** Open an issue if you find a bug or have a question
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Be respectful, constructive, and welcoming. We're building a friendly community focused on creating great software together.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Set up your [Development Environment](/contributing/setup)
|
||||
- Review our [Coding Standards](/contributing/standards)
|
||||
- Explore the [Technical Documentation](/contributing)
|
||||
127
docs/src/content/docs/contributing/index.mdx
Normal file
127
docs/src/content/docs/contributing/index.mdx
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
---
|
||||
title: Technical Overview
|
||||
description: High-level architecture and roadmap to the Wails v3 codebase
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Welcome to the Wails v3 Technical Documentation
|
||||
|
||||
This section is **not** about community guidelines or how to open a pull-request.
|
||||
Instead, it dives into **how Wails v3 is built** so that you can quickly orient
|
||||
yourself in the codebase and start hacking with confidence.
|
||||
|
||||
Whether you plan to patch the runtime, extend the CLI, craft new templates, or
|
||||
simply understand the internals, the pages that follow provide the technical
|
||||
context you need.
|
||||
|
||||
---
|
||||
|
||||
## High-Level Architecture
|
||||
|
||||
<CardGrid>
|
||||
<Card title="Go Backend" icon="seti:go">
|
||||
The heart of every Wails app is Go code compiled into a native executable.
|
||||
It owns application logic, system integration and performance-critical
|
||||
operations.
|
||||
</Card>
|
||||
|
||||
<Card title="Web Frontend" icon="seti:html">
|
||||
UI is written with standard web tech (React, Vue, Svelte, Vanilla, …)
|
||||
rendered by a lightweight system WebView (WebKit on Linux/macOS, WebView2 on
|
||||
Windows).
|
||||
</Card>
|
||||
|
||||
<Card title="Bridging Layer" icon="puzzle">
|
||||
A zero-copy, in-memory bridge enables **Go⇄JavaScript** calls with automatic
|
||||
type conversion, event propagation and error forwarding.
|
||||
</Card>
|
||||
|
||||
<Card title="CLI & Tooling" icon="terminal">
|
||||
`wails3` orchestrates project creation, live-reload dev server, asset
|
||||
bundling, cross-compilation and packaging (deb, rpm, AppImage, msi, dmg…).
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
---
|
||||
|
||||
## Architectural Overview
|
||||
|
||||
**Wails v3 – End-to-End Flow**
|
||||
|
||||
```d2
|
||||
direction: right
|
||||
Developer: "wails3 CLI
|
||||
Init · Dev · Build · Package"
|
||||
Build: {
|
||||
label: "Build-Time"
|
||||
GEN: "Binding System
|
||||
(Static Analysis & Codegen)"
|
||||
ASSET: "Asset Server
|
||||
(Dev Proxy · Embed FS)"
|
||||
PKG: "Build & Packaging
|
||||
Pipeline"
|
||||
}
|
||||
Runtime: {
|
||||
label: "Runtime"
|
||||
RUNTIME: "Desktop Runtime
|
||||
(Window · Events · Dialogs)"
|
||||
BRIDGE: "Bridge
|
||||
(Message Processor)"
|
||||
}
|
||||
Application: {
|
||||
label: "Application"
|
||||
GO: "Go Backend
|
||||
(App Logic)"
|
||||
WEB: "Web Frontend
|
||||
(React/Vue/...)"
|
||||
}
|
||||
Developer -> GEN: "generate"
|
||||
Developer -> ASSET: "dev / build"
|
||||
Developer -> PKG: "compile & package"
|
||||
GEN -> GO: "Code Stubs + TS"
|
||||
GEN -> WEB: "Bindings JSON"
|
||||
PKG -> GO: "Final Binary + Installer"
|
||||
GO -> BRIDGE: "Function Calls"
|
||||
WEB -> BRIDGE: "Invoke / Events"
|
||||
BRIDGE <-> RUNTIME: "native messages"
|
||||
RUNTIME -> ASSET: "Serve Assets"
|
||||
WEB <-> ASSET: "HTTP / In-Memory"
|
||||
```
|
||||
|
||||
The diagram shows the **end-to-end flow**:
|
||||
|
||||
1. **CLI** drives generation, dev server, compilation, and packaging.
|
||||
2. **Binding System** produces glue code that lets the **Web Frontend** call into the **Go Backend**.
|
||||
3. During development the **Asset Server** proxies to the framework dev server; in production it serves embedded files.
|
||||
4. At runtime the **Desktop Runtime** manages windows and OS APIs, while the **Bridge** shuttles messages between Go and JavaScript.
|
||||
|
||||
---
|
||||
|
||||
## What This Documentation Covers
|
||||
|
||||
| Topic | Why It Matters |
|
||||
| ----- | -------------- |
|
||||
| **Codebase Layout** | Map of `/v3` directories and how modules interact. |
|
||||
| **Runtime Internals** | Window management, system APIs, message processor, and platform shims. |
|
||||
| **Asset & Dev Server** | How web assets are served in dev and embedded in production. |
|
||||
| **Build & Packaging Pipeline** | Taskfile-based workflow, cross-platform compilation, and installer generation. |
|
||||
| **Binding System** | Static analysis pipeline that generates type-safe Go⇄TS bindings. |
|
||||
| **Template System** | Generator architecture that powers `wails init -t <framework>`. |
|
||||
| **Testing & CI** | Unit/integration test harness, GitHub Actions, race detector guidance. |
|
||||
| **Extending Wails** | Adding services, templates, or CLI sub-commands. |
|
||||
|
||||
Each subsequent page drills into these areas with concrete code samples,
|
||||
diagrams, and references to the relevant source files.
|
||||
|
||||
---
|
||||
|
||||
:::note
|
||||
Prerequisites: You should be comfortable with **Go 1.25+**, basic TypeScript,
|
||||
and modern frontend build tools. If you are new to Go, consider skimming the
|
||||
official tour first.
|
||||
:::
|
||||
|
||||
Happy exploring — and welcome to the Wails v3 internals!
|
||||
175
docs/src/content/docs/contributing/runtime-internals.mdx
Normal file
175
docs/src/content/docs/contributing/runtime-internals.mdx
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
---
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
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!
|
||||
297
docs/src/content/docs/contributing/setup.mdx
Normal file
297
docs/src/content/docs/contributing/setup.mdx
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
---
|
||||
title: Development Setup
|
||||
description: Set up your development environment for Wails v3 development
|
||||
---
|
||||
|
||||
## Development Environment Setup
|
||||
|
||||
This guide walks you through setting up a complete development environment for working on Wails v3.
|
||||
|
||||
## Required Tools
|
||||
|
||||
### Go Development
|
||||
|
||||
1. **Install Go 1.25 or later:**
|
||||
```bash
|
||||
# Download from https://go.dev/dl/
|
||||
go version # Verify installation
|
||||
```
|
||||
|
||||
2. **Configure Go environment:**
|
||||
```bash
|
||||
# Add to your shell profile (.bashrc, .zshrc, etc.)
|
||||
export GOPATH=$HOME/go
|
||||
export PATH=$PATH:$GOPATH/bin
|
||||
```
|
||||
|
||||
3. **Install useful Go tools:**
|
||||
```bash
|
||||
go install golang.org/x/tools/cmd/goimports@latest
|
||||
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
```
|
||||
|
||||
### Node.js and npm
|
||||
|
||||
Required for building documentation and testing frontend integrations.
|
||||
|
||||
```bash
|
||||
# Install Node.js 20+ and npm
|
||||
node --version # Should be 20+
|
||||
npm --version
|
||||
```
|
||||
|
||||
### Platform-Specific Dependencies
|
||||
|
||||
**macOS:**
|
||||
|
||||
```bash
|
||||
# Install Xcode Command Line Tools
|
||||
xcode-select --install
|
||||
|
||||
# Verify installation
|
||||
xcode-select -p # Should output a path
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
|
||||
1. Install [MSYS2](https://www.msys2.org/) for a Unix-like environment
|
||||
2. WebView2 Runtime (pre-installed on Windows 11, [download](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) for Windows 10)
|
||||
3. Optional: Install [Git for Windows](https://git-scm.com/download/win)
|
||||
|
||||
**Linux (Debian/Ubuntu):**
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install build-essential pkg-config libgtk-3-dev libwebkit2gtk-4.0-dev
|
||||
```
|
||||
|
||||
**Linux (Fedora/RHEL):**
|
||||
|
||||
```bash
|
||||
sudo dnf install gcc pkg-config gtk3-devel webkit2gtk3-devel
|
||||
```
|
||||
|
||||
**Linux (Arch):**
|
||||
|
||||
```bash
|
||||
sudo pacman -S base-devel gtk3 webkit2gtk
|
||||
```
|
||||
|
||||
## Repository Setup
|
||||
|
||||
### Clone and Configure
|
||||
|
||||
```bash
|
||||
# Clone your fork
|
||||
git clone https://github.com/YOUR_USERNAME/wails.git
|
||||
cd wails
|
||||
|
||||
# Add upstream remote
|
||||
git remote add upstream https://github.com/wailsapp/wails.git
|
||||
|
||||
# Verify remotes
|
||||
git remote -v
|
||||
```
|
||||
|
||||
### Build the Wails CLI
|
||||
|
||||
```bash
|
||||
# Navigate to v3 directory
|
||||
cd v3
|
||||
|
||||
# Build the CLI
|
||||
go build -o ../wails3 ./cmd/wails3
|
||||
|
||||
# Test the build
|
||||
cd ..
|
||||
./wails3 version
|
||||
```
|
||||
|
||||
### Add to PATH (Optional)
|
||||
|
||||
**Linux/macOS:**
|
||||
|
||||
```bash
|
||||
# Add to ~/.bashrc or ~/.zshrc
|
||||
export PATH=$PATH:/path/to/wails
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
|
||||
Add the Wails directory to your PATH environment variable through System Properties.
|
||||
|
||||
## IDE Setup
|
||||
|
||||
### VS Code (Recommended)
|
||||
|
||||
1. **Install VS Code:** [Download](https://code.visualstudio.com/)
|
||||
|
||||
2. **Install extensions:**
|
||||
- Go (by Go Team at Google)
|
||||
- ESLint
|
||||
- Prettier
|
||||
- MDX (for documentation)
|
||||
|
||||
3. **Configure workspace settings** (`.vscode/settings.json`):
|
||||
```json
|
||||
{
|
||||
"go.useLanguageServer": true,
|
||||
"go.lintTool": "golangci-lint",
|
||||
"go.lintOnSave": "workspace",
|
||||
"editor.formatOnSave": true,
|
||||
"go.formatTool": "goimports"
|
||||
}
|
||||
```
|
||||
|
||||
### GoLand
|
||||
|
||||
1. **Install GoLand:** [Download](https://www.jetbrains.com/go/)
|
||||
|
||||
2. **Configure:**
|
||||
- Enable Go modules support
|
||||
- Set up file watchers for `goimports`
|
||||
- Configure code style to match project conventions
|
||||
|
||||
## Verify Your Setup
|
||||
|
||||
Run these commands to verify everything is working:
|
||||
|
||||
```bash
|
||||
# Go version check
|
||||
go version
|
||||
|
||||
# Build Wails
|
||||
cd v3
|
||||
go build ./cmd/wails3
|
||||
|
||||
# Run tests
|
||||
go test ./pkg/...
|
||||
|
||||
# Create a test app
|
||||
cd ..
|
||||
./wails3 init -n mytest -t vanilla
|
||||
cd mytest
|
||||
../wails3 dev
|
||||
```
|
||||
|
||||
If the test app builds and runs, your environment is ready!
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Unit Tests
|
||||
|
||||
```bash
|
||||
cd v3
|
||||
go test ./...
|
||||
```
|
||||
|
||||
### Specific Package Tests
|
||||
|
||||
```bash
|
||||
go test ./pkg/application
|
||||
go test ./pkg/events -v # Verbose output
|
||||
```
|
||||
|
||||
### Run with Coverage
|
||||
|
||||
```bash
|
||||
go test ./... -coverprofile=coverage.out
|
||||
go tool cover -html=coverage.out
|
||||
```
|
||||
|
||||
### Run with Race Detector
|
||||
|
||||
```bash
|
||||
go test ./... -race
|
||||
```
|
||||
|
||||
## Working with Documentation
|
||||
|
||||
The Wails documentation is built with Astro and Starlight.
|
||||
|
||||
```bash
|
||||
cd docs
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Start dev server
|
||||
npm run dev
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
```
|
||||
|
||||
Documentation will be available at `http://localhost:4321/`
|
||||
|
||||
## Debugging
|
||||
|
||||
### Debugging Go Code
|
||||
|
||||
**VS Code:**
|
||||
|
||||
Create `.vscode/launch.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug Wails CLI",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${workspaceFolder}/v3/cmd/wails3",
|
||||
"args": ["dev"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Command Line:**
|
||||
|
||||
```bash
|
||||
# Use Delve debugger
|
||||
go install github.com/go-delve/delve/cmd/dlv@latest
|
||||
dlv debug ./cmd/wails3 -- dev
|
||||
```
|
||||
|
||||
### Debugging Platform Code
|
||||
|
||||
Platform-specific debugging requires platform tools:
|
||||
|
||||
- **macOS:** Xcode Instruments
|
||||
- **Windows:** Visual Studio Debugger
|
||||
- **Linux:** GDB
|
||||
|
||||
## Common Issues
|
||||
|
||||
### "command not found: wails3"
|
||||
|
||||
Add the Wails directory to your PATH or use `./wails3` from the project root.
|
||||
|
||||
### "webkit2gtk not found" (Linux)
|
||||
|
||||
Install WebKit2GTK development packages:
|
||||
|
||||
```bash
|
||||
sudo apt install libwebkit2gtk-4.0-dev # Debian/Ubuntu
|
||||
```
|
||||
|
||||
### Build fails with Go module errors
|
||||
|
||||
```bash
|
||||
cd v3
|
||||
go mod tidy
|
||||
go mod download
|
||||
```
|
||||
|
||||
### "CGO_ENABLED" errors on Windows
|
||||
|
||||
Ensure you have a C compiler (MinGW-w64 via MSYS2) in your PATH.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Review [Coding Standards](/contributing/standards)
|
||||
- Explore the [Technical Documentation](/contributing)
|
||||
- Find an issue to work on: [Good First Issues](https://github.com/wailsapp/wails/labels/good%20first%20issue)
|
||||
465
docs/src/content/docs/contributing/standards.mdx
Normal file
465
docs/src/content/docs/contributing/standards.mdx
Normal file
|
|
@ -0,0 +1,465 @@
|
|||
---
|
||||
title: Coding Standards
|
||||
description: Code style, conventions, and best practices for Wails v3
|
||||
---
|
||||
|
||||
## Code Style and Conventions
|
||||
|
||||
Following consistent coding standards makes the codebase easier to read, maintain, and contribute to.
|
||||
|
||||
## Go Code Standards
|
||||
|
||||
### Code Formatting
|
||||
|
||||
Use standard Go formatting tools:
|
||||
|
||||
```bash
|
||||
# Format all code
|
||||
gofmt -w .
|
||||
|
||||
# Use goimports for import organization
|
||||
goimports -w .
|
||||
```
|
||||
|
||||
**Required:** All Go code must pass `gofmt` and `goimports` before committing.
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
**Packages:**
|
||||
- Lowercase, single word when possible
|
||||
- `package application`, `package events`
|
||||
- Avoid underscores or mixed caps
|
||||
|
||||
**Exported Names:**
|
||||
- PascalCase for types, functions, constants
|
||||
- `type WebviewWindow struct`, `func NewApplication()`
|
||||
|
||||
**Unexported Names:**
|
||||
- camelCase for internal types, functions, variables
|
||||
- `type windowImpl struct`, `func createWindow()`
|
||||
|
||||
**Interfaces:**
|
||||
- Name by behavior: `Reader`, `Writer`, `Handler`
|
||||
- Single-method interfaces: name with `-er` suffix
|
||||
|
||||
```go
|
||||
// Good
|
||||
type Closer interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Avoid
|
||||
type CloseInterface interface {
|
||||
Close() error
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
**Always check errors:**
|
||||
|
||||
```go
|
||||
// Good
|
||||
result, err := doSomething()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to do something: %w", err)
|
||||
}
|
||||
|
||||
// Bad - ignoring errors
|
||||
result, _ := doSomething()
|
||||
```
|
||||
|
||||
**Use error wrapping:**
|
||||
|
||||
```go
|
||||
// Wrap errors to provide context
|
||||
if err := validate(); err != nil {
|
||||
return fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
**Create custom error types when needed:**
|
||||
|
||||
```go
|
||||
type ValidationError struct {
|
||||
Field string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (e *ValidationError) Error() string {
|
||||
return fmt.Sprintf("invalid value %q for field %q", e.Value, e.Field)
|
||||
}
|
||||
```
|
||||
|
||||
### Comments and Documentation
|
||||
|
||||
**Package comments:**
|
||||
|
||||
```go
|
||||
// Package application provides the core Wails application runtime.
|
||||
//
|
||||
// It handles window management, event dispatching, and service lifecycle.
|
||||
package application
|
||||
```
|
||||
|
||||
**Exported declarations:**
|
||||
|
||||
```go
|
||||
// NewApplication creates a new Wails application with the given options.
|
||||
//
|
||||
// The application must be started with Run() or RunWithContext().
|
||||
func NewApplication(opts Options) *Application {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation comments:**
|
||||
|
||||
```go
|
||||
// processEvent handles incoming events from the runtime.
|
||||
// It dispatches to registered handlers and manages event lifecycle.
|
||||
func (a *Application) processEvent(event *Event) {
|
||||
// Validate event before processing
|
||||
if event == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Find and invoke handlers
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Function and Method Structure
|
||||
|
||||
**Keep functions focused:**
|
||||
|
||||
```go
|
||||
// Good - single responsibility
|
||||
func (w *Window) setTitle(title string) {
|
||||
w.title = title
|
||||
w.updateNativeTitle()
|
||||
}
|
||||
|
||||
// Bad - doing too much
|
||||
func (w *Window) updateEverything() {
|
||||
w.setTitle(w.title)
|
||||
w.setSize(w.width, w.height)
|
||||
w.setPosition(w.x, w.y)
|
||||
// ... 20 more operations
|
||||
}
|
||||
```
|
||||
|
||||
**Use early returns:**
|
||||
|
||||
```go
|
||||
// Good
|
||||
func validate(input string) error {
|
||||
if input == "" {
|
||||
return errors.New("empty input")
|
||||
}
|
||||
|
||||
if len(input) > 100 {
|
||||
return errors.New("input too long")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Avoid deep nesting
|
||||
```
|
||||
|
||||
### Concurrency
|
||||
|
||||
**Use context for cancellation:**
|
||||
|
||||
```go
|
||||
func (a *Application) RunWithContext(ctx context.Context) error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-a.done:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Protect shared state with mutexes:**
|
||||
|
||||
```go
|
||||
type SafeCounter struct {
|
||||
mu sync.Mutex
|
||||
count int
|
||||
}
|
||||
|
||||
func (c *SafeCounter) Increment() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.count++
|
||||
}
|
||||
```
|
||||
|
||||
**Avoid goroutine leaks:**
|
||||
|
||||
```go
|
||||
// Good - goroutine has exit condition
|
||||
func (a *Application) startWorker(ctx context.Context) {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return // Clean exit
|
||||
case work := <-a.workChan:
|
||||
a.process(work)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
**Test file naming:**
|
||||
|
||||
```go
|
||||
// Implementation: window.go
|
||||
// Tests: window_test.go
|
||||
```
|
||||
|
||||
**Table-driven tests:**
|
||||
|
||||
```go
|
||||
func TestValidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantErr bool
|
||||
}{
|
||||
{"empty input", "", true},
|
||||
{"valid input", "hello", false},
|
||||
{"too long", strings.Repeat("a", 101), true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := validate(tt.input)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## JavaScript/TypeScript Standards
|
||||
|
||||
### Code Formatting
|
||||
|
||||
Use Prettier for consistent formatting:
|
||||
|
||||
```json
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
**Variables and functions:**
|
||||
- camelCase: `const userName = "John"`
|
||||
|
||||
**Classes and types:**
|
||||
- PascalCase: `class WindowManager`
|
||||
|
||||
**Constants:**
|
||||
- UPPER_SNAKE_CASE: `const MAX_RETRIES = 3`
|
||||
|
||||
### TypeScript
|
||||
|
||||
**Use explicit types:**
|
||||
|
||||
```typescript
|
||||
// Good
|
||||
function greet(name: string): string {
|
||||
return `Hello, ${name}`
|
||||
}
|
||||
|
||||
// Avoid implicit any
|
||||
function process(data) { // Bad
|
||||
return data
|
||||
}
|
||||
```
|
||||
|
||||
**Define interfaces:**
|
||||
|
||||
```typescript
|
||||
interface WindowOptions {
|
||||
title: string
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
function createWindow(options: WindowOptions): void {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Commit Message Format
|
||||
|
||||
Use [Conventional Commits](https://www.conventionalcommits.org/):
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
<body>
|
||||
|
||||
<footer>
|
||||
```
|
||||
|
||||
**Types:**
|
||||
- `feat`: New feature
|
||||
- `fix`: Bug fix
|
||||
- `docs`: Documentation changes
|
||||
- `refactor`: Code refactoring
|
||||
- `test`: Adding or updating tests
|
||||
- `chore`: Maintenance tasks
|
||||
|
||||
**Examples:**
|
||||
|
||||
```
|
||||
feat(window): add SetAlwaysOnTop method
|
||||
|
||||
Implement SetAlwaysOnTop for keeping windows above others.
|
||||
Adds platform implementations for macOS, Windows, and Linux.
|
||||
|
||||
Closes #123
|
||||
```
|
||||
|
||||
```
|
||||
fix(events): prevent event handler memory leak
|
||||
|
||||
Event listeners were not being properly cleaned up when
|
||||
windows were closed. This adds explicit cleanup in the
|
||||
window destructor.
|
||||
```
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
### Before Submitting
|
||||
|
||||
- [ ] Code passes `gofmt` and `goimports`
|
||||
- [ ] All tests pass (`go test ./...`)
|
||||
- [ ] New code has tests
|
||||
- [ ] Documentation updated if needed
|
||||
- [ ] Commit messages follow conventions
|
||||
- [ ] No merge conflicts with `master`
|
||||
|
||||
### PR Description Template
|
||||
|
||||
```markdown
|
||||
## Description
|
||||
Brief description of what this PR does.
|
||||
|
||||
## Type of Change
|
||||
- [ ] Bug fix
|
||||
- [ ] New feature
|
||||
- [ ] Breaking change
|
||||
- [ ] Documentation update
|
||||
|
||||
## Testing
|
||||
How was this tested?
|
||||
|
||||
## Checklist
|
||||
- [ ] Tests pass
|
||||
- [ ] Documentation updated
|
||||
- [ ] No breaking changes (or documented)
|
||||
```
|
||||
|
||||
## Code Review Process
|
||||
|
||||
### As a Reviewer
|
||||
|
||||
- Be constructive and respectful
|
||||
- Focus on code quality, not personal preferences
|
||||
- Explain why changes are suggested
|
||||
- Approve once satisfied
|
||||
|
||||
### As an Author
|
||||
|
||||
- Respond to all comments
|
||||
- Ask for clarification if needed
|
||||
- Make requested changes or explain why not
|
||||
- Be open to feedback
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Performance
|
||||
|
||||
- Avoid premature optimization
|
||||
- Profile before optimizing
|
||||
- Use benchmarks for performance-critical code
|
||||
|
||||
```go
|
||||
func BenchmarkProcess(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
process(testData)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Security
|
||||
|
||||
- Validate all user input
|
||||
- Sanitize data before display
|
||||
- Use `crypto/rand` for random data
|
||||
- Never log sensitive information
|
||||
|
||||
### Documentation
|
||||
|
||||
- Document exported APIs
|
||||
- Include examples in documentation
|
||||
- Update docs when changing APIs
|
||||
- Keep README files current
|
||||
|
||||
## Platform-Specific Code
|
||||
|
||||
### File Naming
|
||||
|
||||
```
|
||||
window.go // Common interface
|
||||
window_darwin.go // macOS implementation
|
||||
window_windows.go // Windows implementation
|
||||
window_linux.go // Linux implementation
|
||||
```
|
||||
|
||||
### Build Tags
|
||||
|
||||
```go
|
||||
//go:build darwin
|
||||
|
||||
package application
|
||||
|
||||
// macOS-specific code
|
||||
```
|
||||
|
||||
## Linting
|
||||
|
||||
Run linters before committing:
|
||||
|
||||
```bash
|
||||
# golangci-lint (recommended)
|
||||
golangci-lint run
|
||||
|
||||
# Individual linters
|
||||
go vet ./...
|
||||
staticcheck ./...
|
||||
```
|
||||
|
||||
## Questions?
|
||||
|
||||
If you're unsure about any standards:
|
||||
- Check existing code for examples
|
||||
- Ask in [Discord](https://discord.gg/JDdSxwjhGf)
|
||||
- Open a discussion on GitHub
|
||||
222
docs/src/content/docs/contributing/template-system.mdx
Normal file
222
docs/src/content/docs/contributing/template-system.mdx
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
---
|
||||
title: Template System
|
||||
description: How Wails v3 scaffolds new projects, how templates are organised, and how to build your own.
|
||||
sidebar:
|
||||
order: 7
|
||||
---
|
||||
|
||||
Wails ships with a **template system** that lets `wails3 init` produce a ready-to-run
|
||||
project for **any** modern frontend stack (React, Svelte, Vue, Solid, Vanilla
|
||||
JS …).
|
||||
|
||||
This page covers:
|
||||
|
||||
1. Template directory layout
|
||||
2. How the CLI chooses & renders templates
|
||||
3. Creating a new template step-by-step
|
||||
4. Updating or overriding existing templates
|
||||
5. Troubleshooting and best practices
|
||||
|
||||
---
|
||||
|
||||
## 1. Where Templates Live
|
||||
|
||||
```
|
||||
v3/
|
||||
└── internal/templates/
|
||||
├── _common/ # Files copied into EVERY project (main.go, .gitignore)
|
||||
├── base/ # Backend-only “plain Go” template
|
||||
├── react/ # React + Vite
|
||||
├── react-ts/
|
||||
├── svelte/
|
||||
├── vue/
|
||||
├── ... (others)
|
||||
└── templates.go # Registry + helper functions
|
||||
```
|
||||
|
||||
* **`_common/`** – universal boilerplate (Go modules, Taskfile, README stub,
|
||||
VS Code settings).
|
||||
* **Framework folders** – contain **frontend/** and any stack-specific config.
|
||||
* Folder names match the **template ID** you pass to the CLI
|
||||
(`wails3 init -t react-ts`).
|
||||
|
||||
> The whole directory is compiled into the CLI binary via `go:embed`, so users
|
||||
> can scaffold projects offline.
|
||||
|
||||
---
|
||||
|
||||
## 2. How `wails3 init` Uses Templates
|
||||
|
||||
Call chain:
|
||||
|
||||
```
|
||||
cmd/wails3/init.go
|
||||
│
|
||||
▼
|
||||
internal/commands/init.go ← parses flags, target dir
|
||||
│
|
||||
▼
|
||||
internal/templates.Load() ← templates.go
|
||||
│
|
||||
▼
|
||||
Template.CopyTo(dest) ← filesystem copy + text substitutions
|
||||
```
|
||||
|
||||
### Substitutions
|
||||
|
||||
Placeholders wrapped in `{{ }}` are replaced at copy time:
|
||||
|
||||
| Placeholder | Example | Source |
|
||||
|-------------|---------|--------|
|
||||
| `{{ProjectName}}` | `myapp` | CLI flag/dir name |
|
||||
| `{{ModulePath}}` | `github.com/me/myapp` | if `--module` provided |
|
||||
| `{{WailsVersion}}`| `v3.0.0` | compiled constant |
|
||||
|
||||
You can add **any** placeholder; just ensure it gets a value in
|
||||
`internal/commands/init.go`.
|
||||
|
||||
### Post-Copy Hooks
|
||||
|
||||
Each template may ship a **Taskfile** with a `deps` task.
|
||||
After copy, the CLI runs:
|
||||
|
||||
```
|
||||
task --taskfile Taskfile.yml deps
|
||||
```
|
||||
|
||||
Typical work:
|
||||
|
||||
* `go mod tidy`
|
||||
* `npm install`
|
||||
* git init & initial commit (optional)
|
||||
|
||||
Hook behaviour defined in `_common/Taskfile.yml` but override-able per template.
|
||||
|
||||
---
|
||||
|
||||
## 3. Creating a New Template
|
||||
|
||||
> Example: Add **Qwik-TS** template
|
||||
|
||||
### 3.1 Folder & ID
|
||||
|
||||
```
|
||||
internal/templates/qwik-ts/
|
||||
```
|
||||
|
||||
The folder name = template ID. Keep it **kebab-case**.
|
||||
|
||||
### 3.2 Minimal File Set
|
||||
|
||||
```
|
||||
qwik-ts/
|
||||
├── README.md # Shown in CLI with -list
|
||||
├── frontend/ # Your web project
|
||||
│ ├── src/
|
||||
│ ├── package.json
|
||||
│ └── vite.config.ts
|
||||
├── Taskfile.yml # deps & dev tasks
|
||||
└── go/ # Optional extra Go files
|
||||
```
|
||||
|
||||
Start by copying `react-ts` and pruning files.
|
||||
|
||||
### 3.3 Update Placeholders
|
||||
|
||||
Search/replace:
|
||||
|
||||
* `myapp` → `{{ProjectName}}`
|
||||
* `github.com/you/myapp` → `{{ModulePath}}`
|
||||
|
||||
### 3.4 Register (optional)
|
||||
|
||||
If the folder exists, **templates.go auto-detects it** via `embed`.
|
||||
Only add explicit code if you need **custom validation**:
|
||||
|
||||
```go
|
||||
func (t *Template) Validate() error {
|
||||
// check Node ≥ 20 for this template
|
||||
}
|
||||
```
|
||||
|
||||
### 3.5 Test
|
||||
|
||||
```bash
|
||||
wails3 init -n demo -t qwik-ts
|
||||
cd demo
|
||||
wails3 dev
|
||||
```
|
||||
|
||||
Ensure:
|
||||
|
||||
* Dev server starts
|
||||
* `wailsjs` bindings generate
|
||||
* Hot-reload works
|
||||
|
||||
---
|
||||
|
||||
## 4. Modifying Existing Templates
|
||||
|
||||
1. Edit files under `internal/templates/<id>/`.
|
||||
2. Run `go generate ./v3/...` **or** just `go run ./v3/cmd/wails3 –help`; the
|
||||
embed directive recompiles automatically.
|
||||
3. Bump any **dependency versions** in `frontend/package.json` & `Taskfile.yml`.
|
||||
4. Update `README.md` inside the template — users see it via
|
||||
`wails3 init -t react --help`.
|
||||
|
||||
### Common Tweaks
|
||||
|
||||
| Task | Where |
|
||||
|------|-------|
|
||||
| Change dev server port | `frontend/Taskfile.yml` (`vite dev --port {{DevPort}}`) |
|
||||
| Add environment variables | same Taskfile or `.env` |
|
||||
| Replace JS package manager | Switch `npm` → `pnpm` in Taskfile |
|
||||
|
||||
---
|
||||
|
||||
## 5. Template Authoring Tips
|
||||
|
||||
* **Keep frontend generic** – avoid referencing Wails-specific globals; runtime
|
||||
is injected automatically.
|
||||
* **No compiled artefacts** – exclude `node_modules`, `dist`, `.DS_Store` via
|
||||
`.tmplignore`.
|
||||
* **Document prerequisites** – Node version, extra CLI tools, etc.
|
||||
* **Avoid breaking changes** – create a *new* template ID if overhaul is big.
|
||||
|
||||
---
|
||||
|
||||
## 6. Troubleshooting
|
||||
|
||||
| Symptom | Cause | Fix |
|
||||
|---------|-------|-----|
|
||||
| `unknown template id` | Folder name mismatch | Check `wails3 init -list` |
|
||||
| Placeholders not replaced | Missing entry in `Data` map | Edit `internal/commands/init.go` |
|
||||
| Dev server opens blank page | Wrong `DevPort` env | Ensure Taskfile echoes port to stdout |
|
||||
| Frontend fails to build in prod | Forgotten vite `base` path | Set `base: "./"` in `vite.config.ts` |
|
||||
|
||||
Add `--verbose` to `wails3 init` to print every copied file and substitution.
|
||||
|
||||
---
|
||||
|
||||
## 7. Key Source File Map
|
||||
|
||||
| File | Responsibility |
|
||||
|------|----------------|
|
||||
| `internal/templates/templates.go` | Embeds template FS, returns `[]Template` |
|
||||
| `internal/templates/<id>/**` | Actual template content |
|
||||
| `internal/commands/init.go` | CLI glue: picks template, fills placeholders |
|
||||
| `internal/commands/generate_template.go` | Utility to *export* a live project back into a template (handy for updates) |
|
||||
|
||||
---
|
||||
|
||||
## 8. Recap
|
||||
|
||||
* Templates live in **`internal/templates/`** and are baked into the CLI.
|
||||
* `wails3 init -t <id>` copies `_common` + selected template, performs
|
||||
substitutions, then runs a **deps** Taskfile hook.
|
||||
* Creating a template is as simple as **making a folder**, adding files, and
|
||||
using placeholders.
|
||||
* The system is **extensible** and **self-contained** — perfect for sharing
|
||||
custom stacks with your team or the community.
|
||||
|
||||
Happy templating!
|
||||
216
docs/src/content/docs/contributing/testing-ci.mdx
Normal file
216
docs/src/content/docs/contributing/testing-ci.mdx
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
---
|
||||
title: Testing & Continuous Integration
|
||||
description: How Wails v3 ensures quality with unit tests, integration suites, race detection, and GitHub Actions CI.
|
||||
sidebar:
|
||||
order: 9
|
||||
---
|
||||
|
||||
Robust desktop frameworks demand rock-solid testing.
|
||||
Wails v3 employs a **layered strategy**:
|
||||
|
||||
| Layer | Goal | Tooling |
|
||||
|-------|------|---------|
|
||||
| Unit tests | Fast feedback for isolated functions | `go test ./...` |
|
||||
| Integration / Example tests | Validate whole features across packages | Example apps + headless assertions |
|
||||
| Race detection | Catch data races in the runtime & bridge | `go test -race`, `wails3 dev -race` |
|
||||
| CI matrix | Cross-OS confidence on every PR | GitHub Actions |
|
||||
|
||||
This document explains **where tests live**, **how to run them**, and **what happens in CI**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Directory Conventions
|
||||
|
||||
```
|
||||
v3/
|
||||
├── internal/.../_test.go # Unit tests for internal packages
|
||||
├── pkg/.../_test.go # Public API tests
|
||||
├── examples/... # Example apps double as integration tests
|
||||
├── tasks/events/... # Generated code tests run in CI
|
||||
└── pkg/application/RACE.md # Race test guidance & known safe suppressions
|
||||
```
|
||||
|
||||
Guidelines:
|
||||
|
||||
* **Keep unit tests next to code** (`foo.go` ↔ `foo_test.go`)
|
||||
* Use the **black-box style** for `pkg/` packages (`package application_test`)
|
||||
* Integration helpers go in `internal/testutil/` (shared mocks, fixtures)
|
||||
|
||||
---
|
||||
|
||||
## 2. Unit Tests
|
||||
|
||||
### Writing Tests
|
||||
|
||||
```go
|
||||
func TestColourParseHex(t *testing.T) {
|
||||
c, err := colour.Parse("#FF00FF")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 255, c.R)
|
||||
}
|
||||
```
|
||||
|
||||
Recommendations:
|
||||
|
||||
* Use [`stretchr/testify`](https://github.com/stretchr/testify) – already vendor-imported.
|
||||
* Follow **table-driven** style for branches.
|
||||
* Stub platform quirks behind build tags (`_test_windows.go` etc.) when needed.
|
||||
|
||||
### Running Locally
|
||||
|
||||
```bash
|
||||
cd v3
|
||||
go test ./... -cover
|
||||
```
|
||||
|
||||
Taskfile shortcut:
|
||||
|
||||
```
|
||||
task test # same as above + vet + lint
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Integration & Example Tests
|
||||
|
||||
Every folder under `v3/examples/` is a **self-contained app**.
|
||||
Three techniques turn them into tests:
|
||||
|
||||
| Technique | File | What it checks |
|
||||
|-----------|------|----------------|
|
||||
| `go test ./...` inside example | `*_test.go` spins up `wails dev` in headless mode | Build & startup |
|
||||
| CLI golden tests | `internal/commands/appimage_test.go` | Generated artefact hashes |
|
||||
| Scripted e2e | `tasks/events/generate.go` | Emits Go that launches example, asserts runtime logs |
|
||||
|
||||
Run all with:
|
||||
|
||||
```
|
||||
task integration
|
||||
```
|
||||
|
||||
> Headless mode uses **Xvfb** on Linux runners; macOS & Windows start minimized.
|
||||
|
||||
---
|
||||
|
||||
## 4. Race Detection
|
||||
|
||||
Data races are fatal in GUI runtimes.
|
||||
|
||||
### Dedicated Doc
|
||||
|
||||
See `v3/pkg/application/RACE.md` for:
|
||||
|
||||
* Known benign races and suppression rationale
|
||||
* How to interpret stack-traces crossing Cgo boundaries
|
||||
|
||||
### Local Race Suite
|
||||
|
||||
```
|
||||
go test -race ./...
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
wails3 dev -race
|
||||
```
|
||||
|
||||
*The latter rebuilds runtime with race flags and launches a demo window.
|
||||
Close the window; any races print after exit.*
|
||||
|
||||
CI runs the race suite on **linux/amd64** (fastest) for every PR.
|
||||
|
||||
---
|
||||
|
||||
## 5. GitHub Actions Workflows
|
||||
|
||||
### 5.1 `build-and-test-v3.yml`
|
||||
|
||||
Location: `.github/workflows/build-and-test-v3.yml`
|
||||
|
||||
Key features:
|
||||
|
||||
* **Matrix** over `os: [ubuntu-latest, windows-latest, macos-latest]`
|
||||
* Caches Go & npm deps for speed
|
||||
* Steps:
|
||||
1. `setup-go` @ 1.25
|
||||
2. `go vet ./...`
|
||||
3. `go test ./... -v -coverprofile=cover.out`
|
||||
4. Build CLI + sample app (`wails3 build -skip-package`)
|
||||
5. Upload coverage to Codecov
|
||||
* Conditional **race** job on Ubuntu:
|
||||
```yaml
|
||||
- name: Race Detector
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: go test -race ./...
|
||||
```
|
||||
|
||||
### 5.2 Static Analysis
|
||||
|
||||
* `semgrep.yml` – security/lint scanning (`.github/workflows/semgrep.yml`)
|
||||
* `qodana.yaml` – JetBrains Qodana cloud inspection (optional in PR)
|
||||
|
||||
### 5.3 Release Pipeline
|
||||
|
||||
`runtime.yml` builds and publishes the JS runtime to npm on tag push; out of testing scope but referenced by build matrix.
|
||||
|
||||
---
|
||||
|
||||
## 6. Local CI Parity
|
||||
|
||||
Run the **exact** CI task set via Taskfile:
|
||||
|
||||
```
|
||||
task ci
|
||||
```
|
||||
|
||||
It executes:
|
||||
|
||||
1. `lint` (golangci-lint, govet)
|
||||
2. `test` (unit + race)
|
||||
3. `integration`
|
||||
4. `build` (for host platform)
|
||||
|
||||
---
|
||||
|
||||
## 7. Troubleshooting Failing Tests
|
||||
|
||||
| Symptom | Likely Cause | Fix |
|
||||
|---------|--------------|-----|
|
||||
| **Race in `runtime_darwin.go`** | Forgot to lock `mainthread.Call` | Use `mainthread.Sync` helper |
|
||||
| **Windows headless hangs** | Console window waiting for input | Pass `-verbose` and ensure no dialogs open |
|
||||
| **CI example build fails** | Template changed without bumping example deps | `task update-examples` regenerates lockfiles |
|
||||
| **Coverpkg errors** | Integration test importing `main` | Switch to build tags `//go:build integration` |
|
||||
|
||||
---
|
||||
|
||||
## 8. Adding New Tests
|
||||
|
||||
1. **Unit** – create `*_test.go`, run `go test ./...`
|
||||
2. **Integration** – update or add example app + test harness
|
||||
3. **CI** – commit; the workflow auto-discovers tests
|
||||
If OS-specific, add skip tags:
|
||||
|
||||
```go
|
||||
//go:build !windows
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Key File Map
|
||||
|
||||
| What | Path |
|
||||
|------|------|
|
||||
| Unit test example | `internal/colour/colour_test.go` |
|
||||
| Integration harness | `internal/commands/appimage_test.go` |
|
||||
| Race guide | `pkg/application/RACE.md` |
|
||||
| Taskfile test targets | `v3/Taskfile.yaml` |
|
||||
| CI workflow | `.github/workflows/build-and-test-v3.yml` |
|
||||
|
||||
---
|
||||
|
||||
Quality isn’t an afterthought in Wails v3.
|
||||
With unit tests, integration suites, race detection, and a cross-platform CI
|
||||
matrix you can contribute confidently, knowing your changes run green on every
|
||||
OS we support.
|
||||
Happy testing!
|
||||
62
docs/src/content/docs/credits.mdx
Normal file
62
docs/src/content/docs/credits.mdx
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
---
|
||||
title: Credits
|
||||
---
|
||||
|
||||
{/* Import the auto-generated contributors file */}
|
||||
|
||||
import Contributors from "../../assets/contributors.html";
|
||||
|
||||
- [Lea Anthony](https://github.com/leaanthony) - Project owner, lead developer
|
||||
- [Stffabi](https://github.com/stffabi) - Technical lead, developer and
|
||||
maintainer
|
||||
- [Travis McLane](https://github.com/tmclane) - Cross-compilation work, MacOS
|
||||
testing
|
||||
- [Atterpac](https://github.com/atterpac) - Developer, support guru, powerhouse
|
||||
- [Simon Thomas](mailto:enquiries@wails.io) - Growth Hacker
|
||||
- [Lyimmi](https://github.com/Lyimmi) - All things Linux
|
||||
- [fbbdev](https://github.com/fbbdev) - Bindings Generator guru & core contributor
|
||||
|
||||
## Sponsors
|
||||
<br/>
|
||||
<a href="https://zsa.io/">
|
||||
<img
|
||||
src="/sponsors/zsa.png"
|
||||
style={{ margin: "auto", width: "400px" }}
|
||||
alt="ZSA"
|
||||
/>
|
||||
</a>
|
||||
<img
|
||||
src="https://wails.io/img/sponsors.svg"
|
||||
style={{ margin: "auto", width: "100%", "max-width": "1600px;" }}
|
||||
alt="Sponsors"
|
||||
/>
|
||||
Special thanks:
|
||||
<img
|
||||
src="/sponsors/jetbrains-grayscale.webp"
|
||||
style={{ margin: "auto", width: "100px" }}
|
||||
alt="JetBrains"
|
||||
/>
|
||||
|
||||
## Contributors
|
||||
|
||||
<Contributors />
|
||||
|
||||
## Special Mentions
|
||||
|
||||
- [John Chadwick](https://github.com/jchv) - His amazing work on
|
||||
[go-webview2](https://github.com/jchv/go-webview2) and
|
||||
[go-winloader](https://github.com/jchv/go-winloader) have made the Windows
|
||||
version possible.
|
||||
- [Tad Vizbaras](https://github.com/tadvi) - His winc project was the first step
|
||||
down the path to a pure Go Wails.
|
||||
- [Mat Ryer](https://github.com/matryer) - For advice, support and bants.
|
||||
- [Byron Chris](https://github.com/bh90210) - For his long term contributions to
|
||||
this project.
|
||||
- [Dustin Krysak](https://wiki.ubuntu.com/bashfulrobot) - His support and
|
||||
feedback has been invaluable.
|
||||
- [Justen Walker](https://github.com/justenwalker/) - For helping wrangle COM
|
||||
issues which got v2 over the line.
|
||||
- [Wang, Chi](https://github.com/patr0nus/) - The DeskGap project was a huge
|
||||
influence on the direction of Wails v2.
|
||||
- [Serge Zaitsev](https://github.com/zserge) - Whilst Wails does not use the
|
||||
Webview project, it is still a source of inspiration.
|
||||
260
docs/src/content/docs/faq.mdx
Normal file
260
docs/src/content/docs/faq.mdx
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
---
|
||||
title: Frequently Asked Questions
|
||||
description: Common questions about Wails
|
||||
sidebar:
|
||||
order: 99
|
||||
---
|
||||
|
||||
## General
|
||||
|
||||
### What is Wails?
|
||||
|
||||
Wails is a framework for building desktop applications using Go and web technologies. It provides native OS integration whilst allowing you to build your UI with HTML, CSS, and JavaScript.
|
||||
|
||||
### How does Wails compare to Electron?
|
||||
|
||||
**Wails:**
|
||||
- ~10MB memory usage
|
||||
- ~15MB binary size
|
||||
- Native performance
|
||||
- Go backend
|
||||
|
||||
**Electron:**
|
||||
- ~100MB+ memory usage
|
||||
- ~150MB+ binary size
|
||||
- Chromium overhead
|
||||
- Node.js backend
|
||||
|
||||
### Is Wails production-ready?
|
||||
|
||||
Yes! Wails v3 is suitable for production applications. Many companies use Wails for their desktop applications.
|
||||
|
||||
### What platforms does Wails support?
|
||||
|
||||
- Windows (7+)
|
||||
- macOS (10.13+)
|
||||
- Linux (GTK3)
|
||||
|
||||
## Development
|
||||
|
||||
### Do I need to know Go?
|
||||
|
||||
Basic Go knowledge is helpful but not required. You can start with simple services and learn as you go.
|
||||
|
||||
### Can I use my favourite frontend framework?
|
||||
|
||||
Yes! Wails works with:
|
||||
- Vanilla JavaScript
|
||||
- React
|
||||
- Vue
|
||||
- Svelte
|
||||
- Any framework that builds to HTML/CSS/JS
|
||||
|
||||
### How do I call Go functions from JavaScript?
|
||||
|
||||
Use bindings:
|
||||
|
||||
```go
|
||||
// Go
|
||||
func (s *Service) Greet(name string) string {
|
||||
return "Hello " + name
|
||||
}
|
||||
```
|
||||
|
||||
```javascript
|
||||
// JavaScript
|
||||
import { Greet } from './bindings/myapp/service'
|
||||
const message = await Greet("World")
|
||||
```
|
||||
|
||||
### Can I use TypeScript?
|
||||
|
||||
Yes! Wails generates TypeScript definitions automatically.
|
||||
|
||||
### How do I debug my application?
|
||||
|
||||
Use your browser's dev tools:
|
||||
- Right-click → Inspect Element
|
||||
- Or enable dev tools in window options
|
||||
|
||||
## Building & Distribution
|
||||
|
||||
### How do I build for production?
|
||||
|
||||
```bash
|
||||
wails3 build
|
||||
```
|
||||
|
||||
Your application will be in `build/bin/`.
|
||||
|
||||
### Can I cross-compile?
|
||||
|
||||
Yes! Build for other platforms:
|
||||
|
||||
```bash
|
||||
wails3 build -platform windows/amd64
|
||||
wails3 build -platform darwin/universal
|
||||
wails3 build -platform linux/amd64
|
||||
```
|
||||
|
||||
### How do I create an installer?
|
||||
|
||||
Use platform-specific tools:
|
||||
- Windows: NSIS or WiX
|
||||
- macOS: Create DMG
|
||||
- Linux: DEB/RPM packages
|
||||
|
||||
See the [Installers Guide](/guides/installers).
|
||||
|
||||
### How do I code sign my application?
|
||||
|
||||
**macOS:**
|
||||
```bash
|
||||
codesign --deep --force --sign "Developer ID" MyApp.app
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
Use SignTool with your certificate.
|
||||
|
||||
## Features
|
||||
|
||||
### Can I create multiple windows?
|
||||
|
||||
Yes! Wails v3 has native multi-window support:
|
||||
|
||||
```go
|
||||
window1 := app.Window.New()
|
||||
window2 := app.Window.New()
|
||||
```
|
||||
|
||||
### Does Wails support system tray?
|
||||
|
||||
Yes! Create system tray applications:
|
||||
|
||||
```go
|
||||
tray := app.SystemTray.New()
|
||||
tray.SetIcon(iconBytes)
|
||||
tray.SetMenu(menu)
|
||||
```
|
||||
|
||||
### Can I use native dialogs?
|
||||
|
||||
Yes! Wails provides native dialogs:
|
||||
|
||||
```go
|
||||
path, _ := app.OpenFileDialog().
|
||||
SetTitle("Select File").
|
||||
PromptForSingleSelection()
|
||||
```
|
||||
|
||||
### Does Wails support auto-updates?
|
||||
|
||||
Wails doesn't include auto-update functionality, but you can implement it yourself or use third-party libraries.
|
||||
|
||||
## Performance
|
||||
|
||||
### Why is my binary large?
|
||||
|
||||
Go binaries include the runtime. Reduce size:
|
||||
|
||||
```bash
|
||||
wails3 build -ldflags "-s -w"
|
||||
```
|
||||
|
||||
### How do I improve performance?
|
||||
|
||||
- Paginate large datasets
|
||||
- Cache expensive operations
|
||||
- Use events for updates
|
||||
- Optimise frontend bundle
|
||||
- Profile your code
|
||||
|
||||
See the [Performance Guide](/guides/performance).
|
||||
|
||||
### Does Wails support hot reload?
|
||||
|
||||
Yes! Use dev mode:
|
||||
|
||||
```bash
|
||||
wails3 dev
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### My bindings aren't working
|
||||
|
||||
Regenerate bindings:
|
||||
|
||||
```bash
|
||||
wails3 generate bindings
|
||||
```
|
||||
|
||||
### Window doesn't appear
|
||||
|
||||
Check if you called `Show()`:
|
||||
|
||||
```go
|
||||
window := app.Window.New()
|
||||
window.Show() // Don't forget this!
|
||||
```
|
||||
|
||||
### Events not firing
|
||||
|
||||
Ensure event names match exactly:
|
||||
|
||||
```go
|
||||
// Go
|
||||
app.Event.Emit("my-event", data)
|
||||
|
||||
// JavaScript
|
||||
OnEvent("my-event", handler) // Must match
|
||||
```
|
||||
|
||||
### Build fails
|
||||
|
||||
Common fixes:
|
||||
- Run `go mod tidy`
|
||||
- Check `wails.json` configuration
|
||||
- Verify frontend builds: `cd frontend && npm run build`
|
||||
- Update Wails: `go install github.com/wailsapp/wails/v3/cmd/wails3@latest`
|
||||
|
||||
## Migration
|
||||
|
||||
### Should I migrate from v2 to v3?
|
||||
|
||||
v3 offers:
|
||||
- Better performance
|
||||
- Multi-window support
|
||||
- Improved API
|
||||
- Better developer experience
|
||||
|
||||
See the [Migration Guide](/migration/v2-to-v3).
|
||||
|
||||
### Will v2 be maintained?
|
||||
|
||||
Yes, v2 will receive critical updates.
|
||||
|
||||
### Can I run v2 and v3 side by side?
|
||||
|
||||
Yes, they use different import paths.
|
||||
|
||||
## Community
|
||||
|
||||
### How do I get help?
|
||||
|
||||
- [Discord Community](https://discord.gg/JDdSxwjhGf)
|
||||
- [GitHub Discussions](https://github.com/wailsapp/wails/discussions)
|
||||
- [GitHub Issues](https://github.com/wailsapp/wails/issues)
|
||||
|
||||
### How do I contribute?
|
||||
|
||||
See the [Contributing Guide](/contributing).
|
||||
|
||||
### Where can I find examples?
|
||||
|
||||
- [Official Examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples)
|
||||
- [Community Examples](https://github.com/topics/wails)
|
||||
|
||||
## Still Have Questions?
|
||||
|
||||
Ask in [Discord](https://discord.gg/JDdSxwjhGf) or [open a discussion](https://github.com/wailsapp/wails/discussions).
|
||||
343
docs/src/content/docs/features/bindings/advanced.mdx
Normal file
343
docs/src/content/docs/features/bindings/advanced.mdx
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
---
|
||||
title: Advanced Binding
|
||||
description: Advanced binding techniques including directives, code injection, and custom IDs
|
||||
sidebar:
|
||||
order: 3
|
||||
---
|
||||
|
||||
import { FileTree } from "@astrojs/starlight/components";
|
||||
|
||||
This guide covers advanced techniques for customizing and optimizing the binding generation process in Wails v3.
|
||||
|
||||
## Customizing Generated Code with Directives
|
||||
|
||||
### Injecting Custom Code
|
||||
|
||||
The `//wails:inject` directive allows you to inject custom JavaScript/TypeScript code into the generated bindings:
|
||||
|
||||
```go
|
||||
//wails:inject console.log("Hello from Wails!");
|
||||
type MyService struct {}
|
||||
|
||||
func (s *MyService) Greet(name string) string {
|
||||
return "Hello, " + name
|
||||
}
|
||||
```
|
||||
|
||||
This will inject the specified code into the generated JavaScript/TypeScript file for the `MyService` service.
|
||||
|
||||
You can also use conditional injection to target specific output formats:
|
||||
|
||||
```go
|
||||
//wails:inject j*:console.log("Hello JS!"); // JavaScript only
|
||||
//wails:inject t*:console.log("Hello TS!"); // TypeScript only
|
||||
```
|
||||
|
||||
### Including Additional Files
|
||||
|
||||
The `//wails:include` directive allows you to include additional files with the generated bindings:
|
||||
|
||||
```go
|
||||
//wails:include js/*.js
|
||||
package mypackage
|
||||
```
|
||||
|
||||
This directive is typically used in package documentation comments to include additional JavaScript/TypeScript files with the generated bindings.
|
||||
|
||||
### Marking Internal Types and Methods
|
||||
|
||||
The `//wails:internal` directive marks a type or method as internal, preventing it from being exported to the frontend:
|
||||
|
||||
```go
|
||||
//wails:internal
|
||||
type InternalModel struct {
|
||||
Field string
|
||||
}
|
||||
|
||||
//wails:internal
|
||||
func (s *MyService) InternalMethod() {}
|
||||
```
|
||||
|
||||
This is useful for types and methods that are only used internally by your Go code and should not be exposed to the frontend.
|
||||
|
||||
### Ignoring Methods
|
||||
|
||||
The `//wails:ignore` directive completely ignores a method during binding generation:
|
||||
|
||||
```go
|
||||
//wails:ignore
|
||||
func (s *MyService) IgnoredMethod() {}
|
||||
```
|
||||
|
||||
This is similar to `//wails:internal`, but it completely ignores the method rather than marking it as internal.
|
||||
|
||||
### Custom Method IDs
|
||||
|
||||
The `//wails:id` directive specifies a custom ID for a method, overriding the default hash-based ID:
|
||||
|
||||
```go
|
||||
//wails:id 42
|
||||
func (s *MyService) CustomIDMethod() {}
|
||||
```
|
||||
|
||||
This can be useful for maintaining compatibility when refactoring code.
|
||||
|
||||
## Working with Complex Types
|
||||
|
||||
### Nested Structs
|
||||
|
||||
The binding generator handles nested structs automatically:
|
||||
|
||||
```go
|
||||
type Address struct {
|
||||
Street string
|
||||
City string
|
||||
State string
|
||||
Zip string
|
||||
}
|
||||
|
||||
type Person struct {
|
||||
Name string
|
||||
Address Address
|
||||
}
|
||||
|
||||
func (s *MyService) GetPerson() Person {
|
||||
return Person{
|
||||
Name: "John Doe",
|
||||
Address: Address{
|
||||
Street: "123 Main St",
|
||||
City: "Anytown",
|
||||
State: "CA",
|
||||
Zip: "12345",
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The generated JavaScript/TypeScript code will include classes for both `Person` and `Address`.
|
||||
|
||||
### Maps and Slices
|
||||
|
||||
Maps and slices are also handled automatically:
|
||||
|
||||
```go
|
||||
type Person struct {
|
||||
Name string
|
||||
Attributes map[string]string
|
||||
Friends []string
|
||||
}
|
||||
|
||||
func (s *MyService) GetPerson() Person {
|
||||
return Person{
|
||||
Name: "John Doe",
|
||||
Attributes: map[string]string{
|
||||
"hair": "brown",
|
||||
"eyes": "blue",
|
||||
},
|
||||
Friends: []string{"Jane", "Bob", "Alice"},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In JavaScript, maps are represented as objects and slices as arrays. In TypeScript, maps are represented as `Record<K, V>` and slices as `T[]`.
|
||||
|
||||
### Generic Types
|
||||
|
||||
The binding generator supports generic types:
|
||||
|
||||
```go
|
||||
type Result[T any] struct {
|
||||
Data T
|
||||
Error string
|
||||
}
|
||||
|
||||
func (s *MyService) GetResult() Result[string] {
|
||||
return Result[string]{
|
||||
Data: "Hello, World!",
|
||||
Error: "",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The generated TypeScript code will include a generic class for `Result`:
|
||||
|
||||
```typescript
|
||||
export class Result<T> {
|
||||
"Data": T;
|
||||
"Error": string;
|
||||
|
||||
constructor(source: Partial<Result<T>> = {}) {
|
||||
if (!("Data" in source)) {
|
||||
this["Data"] = null as any;
|
||||
}
|
||||
if (!("Error" in source)) {
|
||||
this["Error"] = "";
|
||||
}
|
||||
|
||||
Object.assign(this, source);
|
||||
}
|
||||
|
||||
static createFrom<T>(source: string | object = {}): Result<T> {
|
||||
let parsedSource = typeof source === "string" ? JSON.parse(source) : source;
|
||||
return new Result<T>(parsedSource as Partial<Result<T>>);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Interfaces
|
||||
|
||||
The binding generator can generate TypeScript interfaces instead of classes using the `-i` flag:
|
||||
|
||||
```bash
|
||||
wails3 generate bindings -ts -i
|
||||
```
|
||||
|
||||
This will generate TypeScript interfaces for all models:
|
||||
|
||||
```typescript
|
||||
export interface Person {
|
||||
Name: string;
|
||||
Attributes: Record<string, string>;
|
||||
Friends: string[];
|
||||
}
|
||||
```
|
||||
|
||||
## Optimizing Binding Generation
|
||||
|
||||
### Using Names Instead of IDs
|
||||
|
||||
By default, the binding generator uses hash-based IDs for method calls. You can use the `-names` flag to use names instead:
|
||||
|
||||
```bash
|
||||
wails3 generate bindings -names
|
||||
```
|
||||
|
||||
This will generate code that uses method names instead of IDs:
|
||||
|
||||
```javascript
|
||||
export function Greet(name) {
|
||||
let $resultPromise = $Call.ByName("Greet", name);
|
||||
return $resultPromise;
|
||||
}
|
||||
```
|
||||
|
||||
This can make the generated code more readable and easier to debug, but it may be slightly less efficient.
|
||||
|
||||
### Bundling the Runtime
|
||||
|
||||
By default, the generated code imports the Wails runtime from the `@wailsio/runtime` npm package. You can use the `-b` flag to bundle the runtime with the generated code:
|
||||
|
||||
```bash
|
||||
wails3 generate bindings -b
|
||||
```
|
||||
|
||||
This will include the runtime code directly in the generated files, eliminating the need for the npm package.
|
||||
|
||||
### Disabling Index Files
|
||||
|
||||
If you don't need the index files, you can use the `-noindex` flag to disable their generation:
|
||||
|
||||
```bash
|
||||
wails3 generate bindings -noindex
|
||||
```
|
||||
|
||||
This can be useful if you prefer to import services and models directly from their respective files.
|
||||
|
||||
## Real-World Examples
|
||||
|
||||
### Authentication Service
|
||||
|
||||
Here's an example of an authentication service with custom directives:
|
||||
|
||||
```go
|
||||
package auth
|
||||
|
||||
//wails:inject console.log("Auth service initialized");
|
||||
type AuthService struct {
|
||||
// Private fields
|
||||
users map[string]User
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Username string
|
||||
Email string
|
||||
Role string
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
Success bool
|
||||
User User
|
||||
Token string
|
||||
Error string
|
||||
}
|
||||
|
||||
// Login authenticates a user
|
||||
func (s *AuthService) Login(req LoginRequest) LoginResponse {
|
||||
// Implementation...
|
||||
}
|
||||
|
||||
// GetCurrentUser returns the current user
|
||||
func (s *AuthService) GetCurrentUser() User {
|
||||
// Implementation...
|
||||
}
|
||||
|
||||
// Internal helper method
|
||||
//wails:internal
|
||||
func (s *AuthService) validateCredentials(username, password string) bool {
|
||||
// Implementation...
|
||||
}
|
||||
```
|
||||
|
||||
### Data Processing Service
|
||||
|
||||
Here's an example of a data processing service with generic types:
|
||||
|
||||
```go
|
||||
package data
|
||||
|
||||
type ProcessingResult[T any] struct {
|
||||
Data T
|
||||
Error string
|
||||
}
|
||||
|
||||
type DataService struct {}
|
||||
|
||||
// Process processes data and returns a result
|
||||
func (s *DataService) Process(data string) ProcessingResult[map[string]int] {
|
||||
// Implementation...
|
||||
}
|
||||
|
||||
// ProcessBatch processes multiple data items
|
||||
func (s *DataService) ProcessBatch(data []string) ProcessingResult[[]map[string]int] {
|
||||
// Implementation...
|
||||
}
|
||||
|
||||
// Internal helper method
|
||||
//wails:internal
|
||||
func (s *DataService) parseData(data string) (map[string]int, error) {
|
||||
// Implementation...
|
||||
}
|
||||
```
|
||||
|
||||
### Conditional Code Injection
|
||||
|
||||
Here's an example of conditional code injection for different output formats:
|
||||
|
||||
```go
|
||||
//wails:inject j*:/**
|
||||
//wails:inject j*: * @param {string} arg
|
||||
//wails:inject j*: * @returns {Promise<void>}
|
||||
//wails:inject j*: */
|
||||
//wails:inject j*:export async function CustomMethod(arg) {
|
||||
//wails:inject t*:export async function CustomMethod(arg: string): Promise<void> {
|
||||
//wails:inject await InternalMethod("Hello " + arg + "!");
|
||||
//wails:inject }
|
||||
type Service struct{}
|
||||
```
|
||||
|
||||
This injects different code for JavaScript and TypeScript outputs, providing appropriate type annotations for each language.
|
||||
688
docs/src/content/docs/features/bindings/best-practices.mdx
Normal file
688
docs/src/content/docs/features/bindings/best-practices.mdx
Normal file
|
|
@ -0,0 +1,688 @@
|
|||
---
|
||||
title: Bindings Best Practices
|
||||
description: Design patterns and best practices for Go-JavaScript bindings
|
||||
sidebar:
|
||||
order: 4
|
||||
---
|
||||
|
||||
## Bindings Best Practices
|
||||
|
||||
Follow **proven patterns** for binding design to create clean, performant, and secure bindings. This guide covers API design principles, performance optimisation, security patterns, error handling, and testing strategies for maintainable applications.
|
||||
|
||||
## API Design Principles
|
||||
|
||||
### 1. Single Responsibility
|
||||
|
||||
Each service should have one clear purpose:
|
||||
|
||||
```go
|
||||
// ❌ Bad: God object
|
||||
type AppService struct {
|
||||
// Does everything
|
||||
}
|
||||
|
||||
func (a *AppService) SaveFile(path string, data []byte) error
|
||||
func (a *AppService) GetUser(id int) (*User, error)
|
||||
func (a *AppService) SendEmail(to, subject, body string) error
|
||||
func (a *AppService) ProcessPayment(amount float64) error
|
||||
|
||||
// ✅ Good: Focused services
|
||||
type FileService struct{}
|
||||
func (f *FileService) Save(path string, data []byte) error
|
||||
|
||||
type UserService struct{}
|
||||
func (u *UserService) GetByID(id int) (*User, error)
|
||||
|
||||
type EmailService struct{}
|
||||
func (e *EmailService) Send(to, subject, body string) error
|
||||
|
||||
type PaymentService struct{}
|
||||
func (p *PaymentService) Process(amount float64) error
|
||||
```
|
||||
|
||||
### 2. Clear Method Names
|
||||
|
||||
Use descriptive, action-oriented names:
|
||||
|
||||
```go
|
||||
// ❌ Bad: Unclear names
|
||||
func (s *Service) Do(x string) error
|
||||
func (s *Service) Handle(data interface{}) interface{}
|
||||
func (s *Service) Process(input map[string]interface{}) bool
|
||||
|
||||
// ✅ Good: Clear names
|
||||
func (s *FileService) SaveDocument(path string, content string) error
|
||||
func (s *UserService) AuthenticateUser(email, password string) (*User, error)
|
||||
func (s *OrderService) CreateOrder(items []Item) (*Order, error)
|
||||
```
|
||||
|
||||
### 3. Consistent Return Types
|
||||
|
||||
Always return errors explicitly:
|
||||
|
||||
```go
|
||||
// ❌ Bad: Inconsistent error handling
|
||||
func (s *Service) GetData() interface{} // How to handle errors?
|
||||
func (s *Service) SaveData(data string) // Silent failures?
|
||||
|
||||
// ✅ Good: Explicit errors
|
||||
func (s *Service) GetData() (Data, error)
|
||||
func (s *Service) SaveData(data string) error
|
||||
```
|
||||
|
||||
### 4. Input Validation
|
||||
|
||||
Validate all input on the Go side:
|
||||
|
||||
```go
|
||||
// ❌ Bad: No validation
|
||||
func (s *UserService) CreateUser(email, password string) (*User, error) {
|
||||
user := &User{Email: email, Password: password}
|
||||
return s.db.Create(user)
|
||||
}
|
||||
|
||||
// ✅ Good: Validate first
|
||||
func (s *UserService) CreateUser(email, password string) (*User, error) {
|
||||
// Validate email
|
||||
if !isValidEmail(email) {
|
||||
return nil, errors.New("invalid email address")
|
||||
}
|
||||
|
||||
// Validate password
|
||||
if len(password) < 8 {
|
||||
return nil, errors.New("password must be at least 8 characters")
|
||||
}
|
||||
|
||||
// Hash password
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user := &User{
|
||||
Email: email,
|
||||
PasswordHash: string(hash),
|
||||
}
|
||||
|
||||
return s.db.Create(user)
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Patterns
|
||||
|
||||
### 1. Batch Operations
|
||||
|
||||
Reduce bridge calls by batching:
|
||||
|
||||
```go
|
||||
// ❌ Bad: N calls
|
||||
// JavaScript
|
||||
for (const item of items) {
|
||||
await ProcessItem(item) // N bridge calls
|
||||
}
|
||||
|
||||
// ✅ Good: 1 call
|
||||
// Go
|
||||
func (s *Service) ProcessItems(items []Item) ([]Result, error) {
|
||||
results := make([]Result, len(items))
|
||||
for i, item := range items {
|
||||
results[i] = s.processItem(item)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// JavaScript
|
||||
const results = await ProcessItems(items) // 1 bridge call
|
||||
```
|
||||
|
||||
### 2. Pagination
|
||||
|
||||
Don't return huge datasets:
|
||||
|
||||
```go
|
||||
// ❌ Bad: Returns everything
|
||||
func (s *Service) GetAllUsers() ([]User, error) {
|
||||
return s.db.FindAll() // Could be millions
|
||||
}
|
||||
|
||||
// ✅ Good: Paginated
|
||||
type PageRequest struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}
|
||||
|
||||
type PageResponse struct {
|
||||
Items []User `json:"items"`
|
||||
TotalItems int `json:"totalItems"`
|
||||
TotalPages int `json:"totalPages"`
|
||||
Page int `json:"page"`
|
||||
}
|
||||
|
||||
func (s *Service) GetUsers(req PageRequest) (*PageResponse, error) {
|
||||
// Validate
|
||||
if req.Page < 1 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.PageSize < 1 || req.PageSize > 100 {
|
||||
req.PageSize = 20
|
||||
}
|
||||
|
||||
// Get total
|
||||
total, err := s.db.Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get page
|
||||
offset := (req.Page - 1) * req.PageSize
|
||||
users, err := s.db.Find(offset, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &PageResponse{
|
||||
Items: users,
|
||||
TotalItems: total,
|
||||
TotalPages: (total + req.PageSize - 1) / req.PageSize,
|
||||
Page: req.Page,
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Caching
|
||||
|
||||
Cache expensive operations:
|
||||
|
||||
```go
|
||||
type CachedService struct {
|
||||
cache map[string]interface{}
|
||||
mu sync.RWMutex
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
func (s *CachedService) GetData(key string) (interface{}, error) {
|
||||
// Check cache
|
||||
s.mu.RLock()
|
||||
if data, ok := s.cache[key]; ok {
|
||||
s.mu.RUnlock()
|
||||
return data, nil
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
|
||||
// Fetch data
|
||||
data, err := s.fetchData(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Cache it
|
||||
s.mu.Lock()
|
||||
s.cache[key] = data
|
||||
s.mu.Unlock()
|
||||
|
||||
// Schedule expiry
|
||||
go func() {
|
||||
time.Sleep(s.ttl)
|
||||
s.mu.Lock()
|
||||
delete(s.cache, key)
|
||||
s.mu.Unlock()
|
||||
}()
|
||||
|
||||
return data, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Streaming with Events
|
||||
|
||||
Use events for streaming data:
|
||||
|
||||
```go
|
||||
// ❌ Bad: Polling
|
||||
func (s *Service) GetProgress() int {
|
||||
return s.progress
|
||||
}
|
||||
|
||||
// JavaScript polls
|
||||
setInterval(async () => {
|
||||
const progress = await GetProgress()
|
||||
updateUI(progress)
|
||||
}, 100)
|
||||
|
||||
// ✅ Good: Events
|
||||
func (s *Service) ProcessLargeFile(path string) error {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
total := 0
|
||||
processed := 0
|
||||
|
||||
// Count lines
|
||||
for scanner.Scan() {
|
||||
total++
|
||||
}
|
||||
|
||||
// Process
|
||||
file.Seek(0, 0)
|
||||
scanner = bufio.NewScanner(file)
|
||||
|
||||
for scanner.Scan() {
|
||||
s.processLine(scanner.Text())
|
||||
processed++
|
||||
|
||||
// Emit progress
|
||||
s.app.Event.Emit("progress", map[string]interface{}{
|
||||
"processed": processed,
|
||||
"total": total,
|
||||
"percent": int(float64(processed) / float64(total) * 100),
|
||||
})
|
||||
}
|
||||
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
// JavaScript listens
|
||||
OnEvent("progress", (data) => {
|
||||
updateProgress(data.percent)
|
||||
})
|
||||
```
|
||||
|
||||
## Security Patterns
|
||||
|
||||
### 1. Input Sanitisation
|
||||
|
||||
Always sanitise user input:
|
||||
|
||||
```go
|
||||
import (
|
||||
"html"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (s *Service) SaveComment(text string) error {
|
||||
// Sanitise
|
||||
text = strings.TrimSpace(text)
|
||||
text = html.EscapeString(text)
|
||||
|
||||
// Validate length
|
||||
if len(text) == 0 {
|
||||
return errors.New("comment cannot be empty")
|
||||
}
|
||||
if len(text) > 1000 {
|
||||
return errors.New("comment too long")
|
||||
}
|
||||
|
||||
return s.db.SaveComment(text)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Authentication
|
||||
|
||||
Protect sensitive operations:
|
||||
|
||||
```go
|
||||
type AuthService struct {
|
||||
sessions map[string]*Session
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (a *AuthService) Login(email, password string) (string, error) {
|
||||
user, err := a.db.FindByEmail(email)
|
||||
if err != nil {
|
||||
return "", errors.New("invalid credentials")
|
||||
}
|
||||
|
||||
if !a.verifyPassword(user.PasswordHash, password) {
|
||||
return "", errors.New("invalid credentials")
|
||||
}
|
||||
|
||||
// Create session
|
||||
token := generateToken()
|
||||
a.mu.Lock()
|
||||
a.sessions[token] = &Session{
|
||||
UserID: user.ID,
|
||||
ExpiresAt: time.Now().Add(24 * time.Hour),
|
||||
}
|
||||
a.mu.Unlock()
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (a *AuthService) requireAuth(token string) (*Session, error) {
|
||||
a.mu.RLock()
|
||||
session, ok := a.sessions[token]
|
||||
a.mu.RUnlock()
|
||||
|
||||
if !ok {
|
||||
return nil, errors.New("not authenticated")
|
||||
}
|
||||
|
||||
if time.Now().After(session.ExpiresAt) {
|
||||
return nil, errors.New("session expired")
|
||||
}
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
// Protected method
|
||||
func (a *AuthService) DeleteAccount(token string) error {
|
||||
session, err := a.requireAuth(token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return a.db.DeleteUser(session.UserID)
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Rate Limiting
|
||||
|
||||
Prevent abuse:
|
||||
|
||||
```go
|
||||
type RateLimiter struct {
|
||||
requests map[string][]time.Time
|
||||
mu sync.Mutex
|
||||
limit int
|
||||
window time.Duration
|
||||
}
|
||||
|
||||
func (r *RateLimiter) Allow(key string) bool {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
// Clean old requests
|
||||
if requests, ok := r.requests[key]; ok {
|
||||
var recent []time.Time
|
||||
for _, t := range requests {
|
||||
if now.Sub(t) < r.window {
|
||||
recent = append(recent, t)
|
||||
}
|
||||
}
|
||||
r.requests[key] = recent
|
||||
}
|
||||
|
||||
// Check limit
|
||||
if len(r.requests[key]) >= r.limit {
|
||||
return false
|
||||
}
|
||||
|
||||
// Add request
|
||||
r.requests[key] = append(r.requests[key], now)
|
||||
return true
|
||||
}
|
||||
|
||||
// Usage
|
||||
func (s *Service) SendEmail(to, subject, body string) error {
|
||||
if !s.rateLimiter.Allow(to) {
|
||||
return errors.New("rate limit exceeded")
|
||||
}
|
||||
|
||||
return s.emailer.Send(to, subject, body)
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling Patterns
|
||||
|
||||
### 1. Descriptive Errors
|
||||
|
||||
Provide context in errors:
|
||||
|
||||
```go
|
||||
// ❌ Bad: Generic errors
|
||||
func (s *Service) LoadFile(path string) ([]byte, error) {
|
||||
return os.ReadFile(path) // "no such file or directory"
|
||||
}
|
||||
|
||||
// ✅ Good: Contextual errors
|
||||
func (s *Service) LoadFile(path string) ([]byte, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load file %s: %w", path, err)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Error Types
|
||||
|
||||
Use typed errors for specific handling:
|
||||
|
||||
```go
|
||||
type ValidationError struct {
|
||||
Field string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *ValidationError) Error() string {
|
||||
return fmt.Sprintf("%s: %s", e.Field, e.Message)
|
||||
}
|
||||
|
||||
type NotFoundError struct {
|
||||
Resource string
|
||||
ID interface{}
|
||||
}
|
||||
|
||||
func (e *NotFoundError) Error() string {
|
||||
return fmt.Sprintf("%s not found: %v", e.Resource, e.ID)
|
||||
}
|
||||
|
||||
// Usage
|
||||
func (s *UserService) GetUser(id int) (*User, error) {
|
||||
if id <= 0 {
|
||||
return nil, &ValidationError{
|
||||
Field: "id",
|
||||
Message: "must be positive",
|
||||
}
|
||||
}
|
||||
|
||||
user, err := s.db.Find(id)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, &NotFoundError{
|
||||
Resource: "User",
|
||||
ID: id,
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("database error: %w", err)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Error Recovery
|
||||
|
||||
Handle errors gracefully:
|
||||
|
||||
```go
|
||||
func (s *Service) ProcessWithRetry(data string) error {
|
||||
maxRetries := 3
|
||||
|
||||
for attempt := 1; attempt <= maxRetries; attempt++ {
|
||||
err := s.process(data)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Log attempt
|
||||
s.app.Logger.Warn("Process failed",
|
||||
"attempt", attempt,
|
||||
"error", err)
|
||||
|
||||
// Don't retry on validation errors
|
||||
if _, ok := err.(*ValidationError); ok {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait before retry
|
||||
if attempt < maxRetries {
|
||||
time.Sleep(time.Duration(attempt) * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed after %d attempts", maxRetries)
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Patterns
|
||||
|
||||
### 1. Unit Testing
|
||||
|
||||
Test services in isolation:
|
||||
|
||||
```go
|
||||
func TestUserService_CreateUser(t *testing.T) {
|
||||
// Setup
|
||||
db := &MockDB{}
|
||||
service := &UserService{db: db}
|
||||
|
||||
// Test valid input
|
||||
user, err := service.CreateUser("test@example.com", "password123")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if user.Email != "test@example.com" {
|
||||
t.Errorf("expected email test@example.com, got %s", user.Email)
|
||||
}
|
||||
|
||||
// Test invalid email
|
||||
_, err = service.CreateUser("invalid", "password123")
|
||||
if err == nil {
|
||||
t.Error("expected error for invalid email")
|
||||
}
|
||||
|
||||
// Test short password
|
||||
_, err = service.CreateUser("test@example.com", "short")
|
||||
if err == nil {
|
||||
t.Error("expected error for short password")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Integration Testing
|
||||
|
||||
Test with real dependencies:
|
||||
|
||||
```go
|
||||
func TestUserService_Integration(t *testing.T) {
|
||||
// Setup real database
|
||||
db, err := sql.Open("sqlite3", ":memory:")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Create schema
|
||||
_, err = db.Exec(`CREATE TABLE users (...)`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test service
|
||||
service := &UserService{db: db}
|
||||
|
||||
user, err := service.CreateUser("test@example.com", "password123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify in database
|
||||
var count int
|
||||
db.QueryRow("SELECT COUNT(*) FROM users WHERE email = ?",
|
||||
user.Email).Scan(&count)
|
||||
|
||||
if count != 1 {
|
||||
t.Errorf("expected 1 user, got %d", count)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Mock Services
|
||||
|
||||
Create testable interfaces:
|
||||
|
||||
```go
|
||||
type UserRepository interface {
|
||||
Create(user *User) error
|
||||
FindByEmail(email string) (*User, error)
|
||||
Update(user *User) error
|
||||
Delete(id int) error
|
||||
}
|
||||
|
||||
type UserService struct {
|
||||
repo UserRepository
|
||||
}
|
||||
|
||||
// Mock for testing
|
||||
type MockUserRepository struct {
|
||||
users map[string]*User
|
||||
}
|
||||
|
||||
func (m *MockUserRepository) Create(user *User) error {
|
||||
m.users[user.Email] = user
|
||||
return nil
|
||||
}
|
||||
|
||||
// Test with mock
|
||||
func TestUserService_WithMock(t *testing.T) {
|
||||
mock := &MockUserRepository{
|
||||
users: make(map[string]*User),
|
||||
}
|
||||
|
||||
service := &UserService{repo: mock}
|
||||
|
||||
// Test
|
||||
user, err := service.CreateUser("test@example.com", "password123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify mock was called
|
||||
if len(mock.users) != 1 {
|
||||
t.Error("expected 1 user in mock")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices Summary
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Single responsibility** - One service, one purpose
|
||||
- **Clear naming** - Descriptive method names
|
||||
- **Validate input** - Always on Go side
|
||||
- **Return errors** - Explicit error handling
|
||||
- **Batch operations** - Reduce bridge calls
|
||||
- **Use events** - For streaming data
|
||||
- **Sanitise input** - Prevent injection
|
||||
- **Test thoroughly** - Unit and integration tests
|
||||
- **Document methods** - Comments become JSDoc
|
||||
- **Version your API** - Plan for changes
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Don't create god objects** - Keep services focused
|
||||
- **Don't trust frontend** - Validate everything
|
||||
- **Don't return huge datasets** - Use pagination
|
||||
- **Don't block** - Use goroutines for long operations
|
||||
- **Don't ignore errors** - Handle all error cases
|
||||
- **Don't skip testing** - Test early and often
|
||||
- **Don't hardcode** - Use configuration
|
||||
- **Don't expose internals** - Keep implementation private
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Methods](/features/bindings/methods) - Learn method binding basics
|
||||
- [Services](/features/bindings/services) - Understand service architecture
|
||||
- [Models](/features/bindings/models) - Bind complex data structures
|
||||
- [Events](/features/events/system) - Use events for pub/sub
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [binding examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/binding).
|
||||
643
docs/src/content/docs/features/bindings/methods.mdx
Normal file
643
docs/src/content/docs/features/bindings/methods.mdx
Normal file
|
|
@ -0,0 +1,643 @@
|
|||
---
|
||||
title: Method Bindings
|
||||
description: Call Go methods from JavaScript with type safety
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { FileTree, Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Type-Safe Go-JavaScript Bindings
|
||||
|
||||
Wails **automatically generates type-safe JavaScript/TypeScript bindings** for your Go methods. Write Go code, run one command, and get fully-typed frontend functions with no HTTP overhead, no manual work, and zero boilerplate.
|
||||
|
||||
## Quick Start
|
||||
|
||||
**1. Write Go service:**
|
||||
|
||||
```go
|
||||
type GreetService struct{}
|
||||
|
||||
func (g *GreetService) Greet(name string) string {
|
||||
return "Hello, " + name + "!"
|
||||
}
|
||||
```
|
||||
|
||||
**2. Register service:**
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(&GreetService{}),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**3. Generate bindings:**
|
||||
|
||||
```bash
|
||||
wails3 generate bindings
|
||||
```
|
||||
|
||||
**4. Use in JavaScript:**
|
||||
|
||||
```javascript
|
||||
import { Greet } from './bindings/myapp/greetservice'
|
||||
|
||||
const message = await Greet("World")
|
||||
console.log(message) // "Hello, World!"
|
||||
```
|
||||
|
||||
**That's it!** Type-safe Go-to-JavaScript calls.
|
||||
|
||||
## Creating Services
|
||||
|
||||
### Basic Service
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "github.com/wailsapp/wails/v3/pkg/application"
|
||||
|
||||
type CalculatorService struct{}
|
||||
|
||||
func (c *CalculatorService) Add(a, b int) int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
func (c *CalculatorService) Subtract(a, b int) int {
|
||||
return a - b
|
||||
}
|
||||
|
||||
func (c *CalculatorService) Multiply(a, b int) int {
|
||||
return a * b
|
||||
}
|
||||
|
||||
func (c *CalculatorService) Divide(a, b float64) (float64, error) {
|
||||
if b == 0 {
|
||||
return 0, errors.New("division by zero")
|
||||
}
|
||||
return a / b, nil
|
||||
}
|
||||
```
|
||||
|
||||
**Register:**
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(&CalculatorService{}),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Key points:**
|
||||
- Only **exported methods** (PascalCase) are bound
|
||||
- Methods can return values or `(value, error)`
|
||||
- Services are **singletons** (one instance per application)
|
||||
|
||||
### Service with State
|
||||
|
||||
```go
|
||||
type CounterService struct {
|
||||
count int
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (c *CounterService) Increment() int {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.count++
|
||||
return c.count
|
||||
}
|
||||
|
||||
func (c *CounterService) Decrement() int {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.count--
|
||||
return c.count
|
||||
}
|
||||
|
||||
func (c *CounterService) GetCount() int {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return c.count
|
||||
}
|
||||
|
||||
func (c *CounterService) Reset() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.count = 0
|
||||
}
|
||||
```
|
||||
|
||||
**Important:** Services are shared across all windows. Use mutexes for thread safety.
|
||||
|
||||
### Service with Dependencies
|
||||
|
||||
```go
|
||||
type DatabaseService struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewDatabaseService(db *sql.DB) *DatabaseService {
|
||||
return &DatabaseService{db: db}
|
||||
}
|
||||
|
||||
func (d *DatabaseService) GetUser(id int) (*User, error) {
|
||||
var user User
|
||||
err := d.db.QueryRow("SELECT * FROM users WHERE id = ?", id).Scan(&user)
|
||||
return &user, err
|
||||
}
|
||||
```
|
||||
|
||||
**Register with dependencies:**
|
||||
|
||||
```go
|
||||
db, _ := sql.Open("sqlite3", "app.db")
|
||||
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(NewDatabaseService(db)),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Generating Bindings
|
||||
|
||||
### Basic Generation
|
||||
|
||||
```bash
|
||||
wails3 generate bindings
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```
|
||||
INFO 347 Packages, 3 Services, 12 Methods, 0 Enums, 0 Models in 1.98s
|
||||
INFO Output directory: /myproject/frontend/bindings
|
||||
```
|
||||
|
||||
**Generated structure:**
|
||||
|
||||
<FileTree>
|
||||
- frontend/bindings
|
||||
- myapp
|
||||
- calculatorservice.js
|
||||
- counterservice.js
|
||||
- databaseservice.js
|
||||
- index.js
|
||||
</FileTree>
|
||||
|
||||
### TypeScript Generation
|
||||
|
||||
```bash
|
||||
wails3 generate bindings -ts
|
||||
```
|
||||
|
||||
**Generates `.ts` files** with full TypeScript types.
|
||||
|
||||
### Custom Output Directory
|
||||
|
||||
```bash
|
||||
wails3 generate bindings -o ./src/bindings
|
||||
```
|
||||
|
||||
### Watch Mode (Development)
|
||||
|
||||
```bash
|
||||
wails3 dev
|
||||
```
|
||||
|
||||
**Automatically regenerates bindings** when Go code changes.
|
||||
|
||||
## Using Bindings
|
||||
|
||||
### JavaScript
|
||||
|
||||
**Generated binding:**
|
||||
|
||||
```javascript
|
||||
// frontend/bindings/myapp/calculatorservice.js
|
||||
|
||||
/**
|
||||
* @param {number} a
|
||||
* @param {number} b
|
||||
* @returns {Promise<number>}
|
||||
*/
|
||||
export function Add(a, b) {
|
||||
return window.wails.Call('CalculatorService.Add', a, b)
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```javascript
|
||||
import { Add, Subtract, Multiply, Divide } from './bindings/myapp/calculatorservice'
|
||||
|
||||
// Simple calls
|
||||
const sum = await Add(5, 3) // 8
|
||||
const diff = await Subtract(10, 4) // 6
|
||||
const product = await Multiply(7, 6) // 42
|
||||
|
||||
// Error handling
|
||||
try {
|
||||
const result = await Divide(10, 0)
|
||||
} catch (error) {
|
||||
console.error("Error:", error) // "division by zero"
|
||||
}
|
||||
```
|
||||
|
||||
### TypeScript
|
||||
|
||||
**Generated binding:**
|
||||
|
||||
```typescript
|
||||
// frontend/bindings/myapp/calculatorservice.ts
|
||||
|
||||
export function Add(a: number, b: number): Promise<number>
|
||||
export function Subtract(a: number, b: number): Promise<number>
|
||||
export function Multiply(a: number, b: number): Promise<number>
|
||||
export function Divide(a: number, b: number): Promise<number>
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```typescript
|
||||
import { Add, Divide } from './bindings/myapp/calculatorservice'
|
||||
|
||||
const sum: number = await Add(5, 3)
|
||||
|
||||
try {
|
||||
const result = await Divide(10, 0)
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(error.message)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Full type checking
|
||||
- IDE autocomplete
|
||||
- Compile-time errors
|
||||
- Better refactoring
|
||||
|
||||
### Index Files
|
||||
|
||||
**Generated index:**
|
||||
|
||||
```javascript
|
||||
// frontend/bindings/myapp/index.js
|
||||
|
||||
export * as CalculatorService from './calculatorservice.js'
|
||||
export * as CounterService from './counterservice.js'
|
||||
export * as DatabaseService from './databaseservice.js'
|
||||
```
|
||||
|
||||
**Simplified imports:**
|
||||
|
||||
```javascript
|
||||
import { CalculatorService } from './bindings/myapp'
|
||||
|
||||
const sum = await CalculatorService.Add(5, 3)
|
||||
```
|
||||
|
||||
## Type Mapping
|
||||
|
||||
### Primitive Types
|
||||
|
||||
| Go Type | JavaScript/TypeScript |
|
||||
|---------|----------------------|
|
||||
| `string` | `string` |
|
||||
| `bool` | `boolean` |
|
||||
| `int`, `int8`, `int16`, `int32`, `int64` | `number` |
|
||||
| `uint`, `uint8`, `uint16`, `uint32`, `uint64` | `number` |
|
||||
| `float32`, `float64` | `number` |
|
||||
| `byte` | `number` |
|
||||
| `rune` | `number` |
|
||||
|
||||
### Complex Types
|
||||
|
||||
| Go Type | JavaScript/TypeScript |
|
||||
|---------|----------------------|
|
||||
| `[]T` | `T[]` |
|
||||
| `[N]T` | `T[]` |
|
||||
| `map[string]T` | `Record<string, T>` |
|
||||
| `map[K]V` | `Map<K, V>` |
|
||||
| `struct` | `class` (with fields) |
|
||||
| `time.Time` | `Date` |
|
||||
| `*T` | `T` (pointers transparent) |
|
||||
| `interface{}` | `any` |
|
||||
| `error` | Exception (thrown) |
|
||||
|
||||
### Unsupported Types
|
||||
|
||||
These types **cannot** be passed across the bridge:
|
||||
- `chan T` (channels)
|
||||
- `func()` (functions)
|
||||
- Complex interfaces (except `interface{}`)
|
||||
- Unexported fields (lowercase)
|
||||
|
||||
**Workaround:** Use IDs or handles:
|
||||
|
||||
```go
|
||||
// ❌ Can't return file handle
|
||||
func OpenFile(path string) (*os.File, error)
|
||||
|
||||
// ✅ Return file ID instead
|
||||
var files = make(map[string]*os.File)
|
||||
|
||||
func OpenFile(path string) (string, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
id := generateID()
|
||||
files[id] = file
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func ReadFile(id string) ([]byte, error) {
|
||||
file := files[id]
|
||||
return io.ReadAll(file)
|
||||
}
|
||||
|
||||
func CloseFile(id string) error {
|
||||
file := files[id]
|
||||
delete(files, id)
|
||||
return file.Close()
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Go Side
|
||||
|
||||
```go
|
||||
func (d *DatabaseService) GetUser(id int) (*User, error) {
|
||||
if id <= 0 {
|
||||
return nil, errors.New("invalid user ID")
|
||||
}
|
||||
|
||||
var user User
|
||||
err := d.db.QueryRow("SELECT * FROM users WHERE id = ?", id).Scan(&user)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, fmt.Errorf("user %d not found", id)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("database error: %w", err)
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
```
|
||||
|
||||
### JavaScript Side
|
||||
|
||||
```javascript
|
||||
import { GetUser } from './bindings/myapp/databaseservice'
|
||||
|
||||
try {
|
||||
const user = await GetUser(123)
|
||||
console.log("User:", user)
|
||||
} catch (error) {
|
||||
console.error("Error:", error)
|
||||
// Error: "user 123 not found"
|
||||
}
|
||||
```
|
||||
|
||||
**Error types:**
|
||||
- Go `error` → JavaScript exception
|
||||
- Error message preserved
|
||||
- Stack trace available
|
||||
|
||||
## Performance
|
||||
|
||||
### Call Overhead
|
||||
|
||||
**Typical call:** <1ms
|
||||
|
||||
```
|
||||
JavaScript → Bridge → Go → Bridge → JavaScript
|
||||
↓ ↓ ↓ ↓ ↓
|
||||
<0.1ms <0.1ms [varies] <0.1ms <0.1ms
|
||||
```
|
||||
|
||||
**Compared to alternatives:**
|
||||
- HTTP/REST: 5-50ms
|
||||
- IPC: 1-10ms
|
||||
- Wails: <1ms
|
||||
|
||||
### Optimisation Tips
|
||||
|
||||
**✅ Batch operations:**
|
||||
|
||||
```javascript
|
||||
// ❌ Slow: N calls
|
||||
for (const item of items) {
|
||||
await ProcessItem(item)
|
||||
}
|
||||
|
||||
// ✅ Fast: 1 call
|
||||
await ProcessItems(items)
|
||||
```
|
||||
|
||||
**✅ Cache results:**
|
||||
|
||||
```javascript
|
||||
// ❌ Repeated calls
|
||||
const config1 = await GetConfig()
|
||||
const config2 = await GetConfig()
|
||||
|
||||
// ✅ Cache
|
||||
const config = await GetConfig()
|
||||
// Use config multiple times
|
||||
```
|
||||
|
||||
**✅ Use events for streaming:**
|
||||
|
||||
```go
|
||||
func ProcessLargeFile(path string) error {
|
||||
// Emit progress events
|
||||
for line := range lines {
|
||||
app.Event.Emit("progress", line)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
**Go:**
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
type TodoService struct {
|
||||
todos []Todo
|
||||
}
|
||||
|
||||
type Todo struct {
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Completed bool `json:"completed"`
|
||||
}
|
||||
|
||||
func (t *TodoService) GetAll() []Todo {
|
||||
return t.todos
|
||||
}
|
||||
|
||||
func (t *TodoService) Add(title string) Todo {
|
||||
todo := Todo{
|
||||
ID: len(t.todos) + 1,
|
||||
Title: title,
|
||||
Completed: false,
|
||||
}
|
||||
t.todos = append(t.todos, todo)
|
||||
return todo
|
||||
}
|
||||
|
||||
func (t *TodoService) Toggle(id int) error {
|
||||
for i := range t.todos {
|
||||
if t.todos[i].ID == id {
|
||||
t.todos[i].Completed = !t.todos[i].Completed
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("todo %d not found", id)
|
||||
}
|
||||
|
||||
func (t *TodoService) Delete(id int) error {
|
||||
for i := range t.todos {
|
||||
if t.todos[i].ID == id {
|
||||
t.todos = append(t.todos[:i], t.todos[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("todo %d not found", id)
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(&TodoService{}),
|
||||
},
|
||||
})
|
||||
|
||||
app.Window.New()
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
**JavaScript:**
|
||||
|
||||
```javascript
|
||||
import { GetAll, Add, Toggle, Delete } from './bindings/myapp/todoservice'
|
||||
|
||||
class TodoApp {
|
||||
async loadTodos() {
|
||||
const todos = await GetAll()
|
||||
this.renderTodos(todos)
|
||||
}
|
||||
|
||||
async addTodo(title) {
|
||||
try {
|
||||
const todo = await Add(title)
|
||||
this.loadTodos()
|
||||
} catch (error) {
|
||||
console.error("Failed to add todo:", error)
|
||||
}
|
||||
}
|
||||
|
||||
async toggleTodo(id) {
|
||||
try {
|
||||
await Toggle(id)
|
||||
this.loadTodos()
|
||||
} catch (error) {
|
||||
console.error("Failed to toggle todo:", error)
|
||||
}
|
||||
}
|
||||
|
||||
async deleteTodo(id) {
|
||||
try {
|
||||
await Delete(id)
|
||||
this.loadTodos()
|
||||
} catch (error) {
|
||||
console.error("Failed to delete todo:", error)
|
||||
}
|
||||
}
|
||||
|
||||
renderTodos(todos) {
|
||||
const list = document.getElementById('todo-list')
|
||||
list.innerHTML = todos.map(todo => `
|
||||
<div class="todo ${todo.Completed ? 'completed' : ''}">
|
||||
<input type="checkbox"
|
||||
${todo.Completed ? 'checked' : ''}
|
||||
onchange="app.toggleTodo(${todo.ID})">
|
||||
<span>${todo.Title}</span>
|
||||
<button onclick="app.deleteTodo(${todo.ID})">Delete</button>
|
||||
</div>
|
||||
`).join('')
|
||||
}
|
||||
}
|
||||
|
||||
const app = new TodoApp()
|
||||
app.loadTodos()
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Keep methods simple** - Single responsibility
|
||||
- **Return errors** - Don't panic
|
||||
- **Use thread-safe state** - Mutexes for shared data
|
||||
- **Batch operations** - Reduce bridge calls
|
||||
- **Cache on Go side** - Avoid repeated work
|
||||
- **Document methods** - Comments become JSDoc
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Don't block** - Use goroutines for long operations
|
||||
- **Don't return channels** - Use events instead
|
||||
- **Don't return functions** - Not supported
|
||||
- **Don't ignore errors** - Always handle them
|
||||
- **Don't use unexported fields** - Won't be bound
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGrid>
|
||||
<Card title="Services" icon="puzzle">
|
||||
Deep dive into the service system.
|
||||
|
||||
[Learn More →](/features/bindings/services)
|
||||
</Card>
|
||||
|
||||
<Card title="Models" icon="document">
|
||||
Bind complex data structures.
|
||||
|
||||
[Learn More →](/features/bindings/models)
|
||||
</Card>
|
||||
|
||||
<Card title="Go-Frontend Bridge" icon="rocket">
|
||||
Understand the bridge mechanism.
|
||||
|
||||
[Learn More →](/concepts/bridge)
|
||||
</Card>
|
||||
|
||||
<Card title="Events" icon="star">
|
||||
Use events for pub/sub communication.
|
||||
|
||||
[Learn More →](/features/events/system)
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [binding examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/binding).
|
||||
741
docs/src/content/docs/features/bindings/models.mdx
Normal file
741
docs/src/content/docs/features/bindings/models.mdx
Normal file
|
|
@ -0,0 +1,741 @@
|
|||
---
|
||||
title: Data Models
|
||||
description: Bind complex data structures between Go and JavaScript
|
||||
sidebar:
|
||||
order: 3
|
||||
---
|
||||
|
||||
import { Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Data Model Bindings
|
||||
|
||||
Wails **automatically generates JavaScript/TypeScript classes** from Go structs, providing full type safety when passing complex data between backend and frontend. Write Go structs, generate bindings, and get fully-typed frontend models complete with constructors, type annotations, and JSDoc comments.
|
||||
|
||||
## Quick Start
|
||||
|
||||
**Go struct:**
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
func (s *UserService) GetUser(id int) (*User, error) {
|
||||
// Return user
|
||||
}
|
||||
```
|
||||
|
||||
**Generate:**
|
||||
|
||||
```bash
|
||||
wails3 generate bindings
|
||||
```
|
||||
|
||||
**JavaScript:**
|
||||
|
||||
```javascript
|
||||
import { GetUser } from './bindings/myapp/userservice'
|
||||
import { User } from './bindings/myapp/models'
|
||||
|
||||
const user = await GetUser(1)
|
||||
console.log(user.Name) // Type-safe!
|
||||
```
|
||||
|
||||
**That's it!** Full type safety across the bridge.
|
||||
|
||||
## Defining Models
|
||||
|
||||
### Basic Struct
|
||||
|
||||
```go
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
```
|
||||
|
||||
**Generated JavaScript:**
|
||||
|
||||
```javascript
|
||||
export class Person {
|
||||
/** @type {string} */
|
||||
Name = ""
|
||||
|
||||
/** @type {number} */
|
||||
Age = 0
|
||||
|
||||
constructor(source = {}) {
|
||||
Object.assign(this, source)
|
||||
}
|
||||
|
||||
static createFrom(source = {}) {
|
||||
return new Person(source)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### With JSON Tags
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
```
|
||||
|
||||
**Generated JavaScript:**
|
||||
|
||||
```javascript
|
||||
export class User {
|
||||
/** @type {number} */
|
||||
id = 0
|
||||
|
||||
/** @type {string} */
|
||||
name = ""
|
||||
|
||||
/** @type {string} */
|
||||
email = ""
|
||||
|
||||
/** @type {Date} */
|
||||
createdAt = new Date()
|
||||
|
||||
constructor(source = {}) {
|
||||
Object.assign(this, source)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**JSON tags control field names** in JavaScript.
|
||||
|
||||
### With Comments
|
||||
|
||||
```go
|
||||
// User represents an application user
|
||||
type User struct {
|
||||
// Unique identifier
|
||||
ID int `json:"id"`
|
||||
|
||||
// Full name of the user
|
||||
Name string `json:"name"`
|
||||
|
||||
// Email address (must be unique)
|
||||
Email string `json:"email"`
|
||||
}
|
||||
```
|
||||
|
||||
**Generated JavaScript:**
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* User represents an application user
|
||||
*/
|
||||
export class User {
|
||||
/**
|
||||
* Unique identifier
|
||||
* @type {number}
|
||||
*/
|
||||
id = 0
|
||||
|
||||
/**
|
||||
* Full name of the user
|
||||
* @type {string}
|
||||
*/
|
||||
name = ""
|
||||
|
||||
/**
|
||||
* Email address (must be unique)
|
||||
* @type {string}
|
||||
*/
|
||||
email = ""
|
||||
}
|
||||
```
|
||||
|
||||
**Comments become JSDoc!** Your IDE shows them.
|
||||
|
||||
### Nested Structs
|
||||
|
||||
```go
|
||||
type Address struct {
|
||||
Street string `json:"street"`
|
||||
City string `json:"city"`
|
||||
Country string `json:"country"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Address Address `json:"address"`
|
||||
}
|
||||
```
|
||||
|
||||
**Generated JavaScript:**
|
||||
|
||||
```javascript
|
||||
export class Address {
|
||||
/** @type {string} */
|
||||
street = ""
|
||||
|
||||
/** @type {string} */
|
||||
city = ""
|
||||
|
||||
/** @type {string} */
|
||||
country = ""
|
||||
}
|
||||
|
||||
export class User {
|
||||
/** @type {number} */
|
||||
id = 0
|
||||
|
||||
/** @type {string} */
|
||||
name = ""
|
||||
|
||||
/** @type {Address} */
|
||||
address = new Address()
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```javascript
|
||||
const user = new User({
|
||||
id: 1,
|
||||
name: "Alice",
|
||||
address: new Address({
|
||||
street: "123 Main St",
|
||||
city: "Springfield",
|
||||
country: "USA"
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Arrays and Slices
|
||||
|
||||
```go
|
||||
type Team struct {
|
||||
Name string `json:"name"`
|
||||
Members []string `json:"members"`
|
||||
}
|
||||
```
|
||||
|
||||
**Generated JavaScript:**
|
||||
|
||||
```javascript
|
||||
export class Team {
|
||||
/** @type {string} */
|
||||
name = ""
|
||||
|
||||
/** @type {string[]} */
|
||||
members = []
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```javascript
|
||||
const team = new Team({
|
||||
name: "Engineering",
|
||||
members: ["Alice", "Bob", "Charlie"]
|
||||
})
|
||||
```
|
||||
|
||||
### Maps
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Settings map[string]string `json:"settings"`
|
||||
}
|
||||
```
|
||||
|
||||
**Generated JavaScript:**
|
||||
|
||||
```javascript
|
||||
export class Config {
|
||||
/** @type {Record<string, string>} */
|
||||
settings = {}
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```javascript
|
||||
const config = new Config({
|
||||
settings: {
|
||||
theme: "dark",
|
||||
language: "en"
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Type Mapping
|
||||
|
||||
### Primitive Types
|
||||
|
||||
| Go Type | JavaScript Type |
|
||||
|---------|----------------|
|
||||
| `string` | `string` |
|
||||
| `bool` | `boolean` |
|
||||
| `int`, `int8`, `int16`, `int32`, `int64` | `number` |
|
||||
| `uint`, `uint8`, `uint16`, `uint32`, `uint64` | `number` |
|
||||
| `float32`, `float64` | `number` |
|
||||
| `byte` | `number` |
|
||||
| `rune` | `number` |
|
||||
|
||||
### Special Types
|
||||
|
||||
| Go Type | JavaScript Type |
|
||||
|---------|----------------|
|
||||
| `time.Time` | `Date` |
|
||||
| `[]byte` | `Uint8Array` |
|
||||
| `*T` | `T` (pointers transparent) |
|
||||
| `interface{}` | `any` |
|
||||
|
||||
### Collections
|
||||
|
||||
| Go Type | JavaScript Type |
|
||||
|---------|----------------|
|
||||
| `[]T` | `T[]` |
|
||||
| `[N]T` | `T[]` |
|
||||
| `map[string]T` | `Record<string, T>` |
|
||||
| `map[K]V` | `Map<K, V>` |
|
||||
|
||||
### Unsupported Types
|
||||
|
||||
- `chan T` (channels)
|
||||
- `func()` (functions)
|
||||
- Complex interfaces (except `interface{}`)
|
||||
- Unexported fields (lowercase)
|
||||
|
||||
## Using Models
|
||||
|
||||
### Creating Instances
|
||||
|
||||
```javascript
|
||||
import { User } from './bindings/myapp/models'
|
||||
|
||||
// Empty instance
|
||||
const user1 = new User()
|
||||
|
||||
// With data
|
||||
const user2 = new User({
|
||||
id: 1,
|
||||
name: "Alice",
|
||||
email: "alice@example.com"
|
||||
})
|
||||
|
||||
// From JSON string
|
||||
const user3 = User.createFrom('{"id":1,"name":"Alice"}')
|
||||
|
||||
// From object
|
||||
const user4 = User.createFrom({ id: 1, name: "Alice" })
|
||||
```
|
||||
|
||||
### Passing to Go
|
||||
|
||||
```javascript
|
||||
import { CreateUser } from './bindings/myapp/userservice'
|
||||
import { User } from './bindings/myapp/models'
|
||||
|
||||
const user = new User({
|
||||
name: "Bob",
|
||||
email: "bob@example.com"
|
||||
})
|
||||
|
||||
const created = await CreateUser(user)
|
||||
console.log("Created user:", created.id)
|
||||
```
|
||||
|
||||
### Receiving from Go
|
||||
|
||||
```javascript
|
||||
import { GetUser } from './bindings/myapp/userservice'
|
||||
|
||||
const user = await GetUser(1)
|
||||
|
||||
// user is already a User instance
|
||||
console.log(user.name)
|
||||
console.log(user.email)
|
||||
console.log(user.createdAt.toISOString())
|
||||
```
|
||||
|
||||
### Updating Models
|
||||
|
||||
```javascript
|
||||
import { GetUser, UpdateUser } from './bindings/myapp/userservice'
|
||||
|
||||
// Get user
|
||||
const user = await GetUser(1)
|
||||
|
||||
// Modify
|
||||
user.name = "Alice Smith"
|
||||
user.email = "alice.smith@example.com"
|
||||
|
||||
// Save
|
||||
await UpdateUser(user)
|
||||
```
|
||||
|
||||
## TypeScript Support
|
||||
|
||||
### Generated TypeScript
|
||||
|
||||
```bash
|
||||
wails3 generate bindings -ts
|
||||
```
|
||||
|
||||
**Generated:**
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* User represents an application user
|
||||
*/
|
||||
export class User {
|
||||
/**
|
||||
* Unique identifier
|
||||
*/
|
||||
id: number = 0
|
||||
|
||||
/**
|
||||
* Full name of the user
|
||||
*/
|
||||
name: string = ""
|
||||
|
||||
/**
|
||||
* Email address (must be unique)
|
||||
*/
|
||||
email: string = ""
|
||||
|
||||
/**
|
||||
* Account creation date
|
||||
*/
|
||||
createdAt: Date = new Date()
|
||||
|
||||
constructor(source: Partial<User> = {}) {
|
||||
Object.assign(this, source)
|
||||
}
|
||||
|
||||
static createFrom(source: string | Partial<User> = {}): User {
|
||||
const parsedSource = typeof source === 'string'
|
||||
? JSON.parse(source)
|
||||
: source
|
||||
return new User(parsedSource)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Usage in TypeScript
|
||||
|
||||
```typescript
|
||||
import { GetUser, CreateUser } from './bindings/myapp/userservice'
|
||||
import { User } from './bindings/myapp/models'
|
||||
|
||||
async function example() {
|
||||
// Create user
|
||||
const newUser = new User({
|
||||
name: "Alice",
|
||||
email: "alice@example.com"
|
||||
})
|
||||
|
||||
const created: User = await CreateUser(newUser)
|
||||
|
||||
// Get user
|
||||
const user: User = await GetUser(created.id)
|
||||
|
||||
// Type-safe access
|
||||
console.log(user.name.toUpperCase()) // ✅ string method
|
||||
console.log(user.id + 1) // ✅ number operation
|
||||
console.log(user.createdAt.getTime()) // ✅ Date method
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Patterns
|
||||
|
||||
### Optional Fields
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Nickname *string `json:"nickname,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
**JavaScript:**
|
||||
|
||||
```javascript
|
||||
const user = new User({
|
||||
id: 1,
|
||||
name: "Alice",
|
||||
nickname: "Ally" // Optional
|
||||
})
|
||||
|
||||
// Check if set
|
||||
if (user.nickname) {
|
||||
console.log("Nickname:", user.nickname)
|
||||
}
|
||||
```
|
||||
|
||||
### Enums
|
||||
|
||||
```go
|
||||
type UserRole string
|
||||
|
||||
const (
|
||||
RoleAdmin UserRole = "admin"
|
||||
RoleUser UserRole = "user"
|
||||
RoleGuest UserRole = "guest"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Role UserRole `json:"role"`
|
||||
}
|
||||
```
|
||||
|
||||
**Generated:**
|
||||
|
||||
```javascript
|
||||
export const UserRole = {
|
||||
Admin: "admin",
|
||||
User: "user",
|
||||
Guest: "guest"
|
||||
}
|
||||
|
||||
export class User {
|
||||
/** @type {string} */
|
||||
role = UserRole.User
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```javascript
|
||||
import { User, UserRole } from './bindings/myapp/models'
|
||||
|
||||
const admin = new User({
|
||||
name: "Admin",
|
||||
role: UserRole.Admin
|
||||
})
|
||||
```
|
||||
|
||||
### Validation
|
||||
|
||||
```javascript
|
||||
class User {
|
||||
validate() {
|
||||
if (!this.name) {
|
||||
throw new Error("Name is required")
|
||||
}
|
||||
if (!this.email.includes('@')) {
|
||||
throw new Error("Invalid email")
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Use
|
||||
const user = new User({ name: "Alice", email: "alice@example.com" })
|
||||
user.validate() // ✅
|
||||
|
||||
const invalid = new User({ name: "", email: "invalid" })
|
||||
invalid.validate() // ❌ Throws
|
||||
```
|
||||
|
||||
### Serialisation
|
||||
|
||||
```javascript
|
||||
// To JSON
|
||||
const json = JSON.stringify(user)
|
||||
|
||||
// From JSON
|
||||
const user = User.createFrom(json)
|
||||
|
||||
// To plain object
|
||||
const obj = { ...user }
|
||||
|
||||
// From plain object
|
||||
const user2 = new User(obj)
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
**Go:**
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
type Address struct {
|
||||
Street string `json:"street"`
|
||||
City string `json:"city"`
|
||||
Country string `json:"country"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Address Address `json:"address"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type UserService struct {
|
||||
users []User
|
||||
}
|
||||
|
||||
func (s *UserService) GetAll() []User {
|
||||
return s.users
|
||||
}
|
||||
|
||||
func (s *UserService) GetByID(id int) (*User, error) {
|
||||
for _, user := range s.users {
|
||||
if user.ID == id {
|
||||
return &user, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("user %d not found", id)
|
||||
}
|
||||
|
||||
func (s *UserService) Create(user User) User {
|
||||
user.ID = len(s.users) + 1
|
||||
user.CreatedAt = time.Now()
|
||||
s.users = append(s.users, user)
|
||||
return user
|
||||
}
|
||||
|
||||
func (s *UserService) Update(user User) error {
|
||||
for i, u := range s.users {
|
||||
if u.ID == user.ID {
|
||||
s.users[i] = user
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("user %d not found", user.ID)
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(&UserService{}),
|
||||
},
|
||||
})
|
||||
|
||||
app.Window.New()
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
**JavaScript:**
|
||||
|
||||
```javascript
|
||||
import { GetAll, GetByID, Create, Update } from './bindings/myapp/userservice'
|
||||
import { User, Address } from './bindings/myapp/models'
|
||||
|
||||
class UserManager {
|
||||
async loadUsers() {
|
||||
const users = await GetAll()
|
||||
this.renderUsers(users)
|
||||
}
|
||||
|
||||
async createUser(name, email, address) {
|
||||
const user = new User({
|
||||
name,
|
||||
email,
|
||||
address: new Address(address)
|
||||
})
|
||||
|
||||
try {
|
||||
const created = await Create(user)
|
||||
console.log("Created user:", created.id)
|
||||
this.loadUsers()
|
||||
} catch (error) {
|
||||
console.error("Failed to create user:", error)
|
||||
}
|
||||
}
|
||||
|
||||
async updateUser(id, updates) {
|
||||
try {
|
||||
const user = await GetByID(id)
|
||||
Object.assign(user, updates)
|
||||
await Update(user)
|
||||
this.loadUsers()
|
||||
} catch (error) {
|
||||
console.error("Failed to update user:", error)
|
||||
}
|
||||
}
|
||||
|
||||
renderUsers(users) {
|
||||
const list = document.getElementById('users')
|
||||
list.innerHTML = users.map(user => `
|
||||
<div class="user">
|
||||
<h3>${user.name}</h3>
|
||||
<p>${user.email}</p>
|
||||
<p>${user.address.city}, ${user.address.country}</p>
|
||||
<small>Created: ${user.createdAt.toLocaleDateString()}</small>
|
||||
</div>
|
||||
`).join('')
|
||||
}
|
||||
}
|
||||
|
||||
const manager = new UserManager()
|
||||
manager.loadUsers()
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Use JSON tags** - Control field names
|
||||
- **Add comments** - They become JSDoc
|
||||
- **Use time.Time** - Converts to Date
|
||||
- **Validate on Go side** - Don't trust frontend
|
||||
- **Keep models simple** - Data containers only
|
||||
- **Use pointers for optional** - `*string` for nullable
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Don't add methods to Go structs** - Keep them as data
|
||||
- **Don't use unexported fields** - Won't be bound
|
||||
- **Don't use complex interfaces** - Not supported
|
||||
- **Don't forget JSON tags** - Field names matter
|
||||
- **Don't nest too deeply** - Keep it simple
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGrid>
|
||||
<Card title="Method Bindings" icon="rocket">
|
||||
Learn how to bind Go methods.
|
||||
|
||||
[Learn More →](/features/bindings/methods)
|
||||
</Card>
|
||||
|
||||
<Card title="Services" icon="puzzle">
|
||||
Organise code with services.
|
||||
|
||||
[Learn More →](/features/bindings/services)
|
||||
</Card>
|
||||
|
||||
<Card title="Best Practices" icon="approve-check">
|
||||
Binding design patterns.
|
||||
|
||||
[Learn More →](/features/bindings/best-practices)
|
||||
</Card>
|
||||
|
||||
<Card title="Go-Frontend Bridge" icon="star">
|
||||
Understand the bridge mechanism.
|
||||
|
||||
[Learn More →](/concepts/bridge)
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [binding examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/binding).
|
||||
787
docs/src/content/docs/features/bindings/services.mdx
Normal file
787
docs/src/content/docs/features/bindings/services.mdx
Normal file
|
|
@ -0,0 +1,787 @@
|
|||
---
|
||||
title: Services
|
||||
description: Build modular, reusable application components with services
|
||||
sidebar:
|
||||
order: 2
|
||||
---
|
||||
|
||||
## Service Architecture
|
||||
|
||||
Wails **services** provide a structured way to organise application logic with modular, self-contained components. Services are lifecycle-aware with startup and shutdown hooks, automatically bound to the frontend, dependency-injectable, and fully testable in isolation.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
type GreetService struct {
|
||||
prefix string
|
||||
}
|
||||
|
||||
func NewGreetService(prefix string) *GreetService {
|
||||
return &GreetService{prefix: prefix}
|
||||
}
|
||||
|
||||
func (g *GreetService) Greet(name string) string {
|
||||
return g.prefix + name + "!"
|
||||
}
|
||||
|
||||
// Register
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(NewGreetService("Hello, ")),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**That's it!** `Greet` is now callable from JavaScript.
|
||||
|
||||
## Creating Services
|
||||
|
||||
### Basic Service
|
||||
|
||||
```go
|
||||
type CalculatorService struct{}
|
||||
|
||||
func (c *CalculatorService) Add(a, b int) int {
|
||||
return a + b
|
||||
}
|
||||
|
||||
func (c *CalculatorService) Subtract(a, b int) int {
|
||||
return a - b
|
||||
}
|
||||
```
|
||||
|
||||
**Register:**
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(&CalculatorService{}),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Key points:**
|
||||
- Only **exported methods** (PascalCase) are bound
|
||||
- Services are **singletons** (one instance)
|
||||
- Methods can return `(value, error)`
|
||||
|
||||
### Service with State
|
||||
|
||||
```go
|
||||
type CounterService struct {
|
||||
count int
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (c *CounterService) Increment() int {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.count++
|
||||
return c.count
|
||||
}
|
||||
|
||||
func (c *CounterService) GetCount() int {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return c.count
|
||||
}
|
||||
|
||||
func (c *CounterService) Reset() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.count = 0
|
||||
}
|
||||
```
|
||||
|
||||
**Important:** Services are shared across all windows. Always use mutexes for thread safety.
|
||||
|
||||
### Service with Dependencies
|
||||
|
||||
```go
|
||||
type UserService struct {
|
||||
db *sql.DB
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func NewUserService(db *sql.DB, logger *slog.Logger) *UserService {
|
||||
return &UserService{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserService) GetUser(id int) (*User, error) {
|
||||
u.logger.Info("Getting user", "id", id)
|
||||
|
||||
var user User
|
||||
err := u.db.QueryRow("SELECT * FROM users WHERE id = ?", id).Scan(&user)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get user: %w", err)
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
```
|
||||
|
||||
**Register with dependencies:**
|
||||
|
||||
```go
|
||||
db, _ := sql.Open("sqlite3", "app.db")
|
||||
logger := slog.Default()
|
||||
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(NewUserService(db, logger)),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Service Lifecycle
|
||||
|
||||
### ServiceStartup
|
||||
|
||||
Called when application starts:
|
||||
|
||||
```go
|
||||
func (u *UserService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
||||
u.logger.Info("UserService starting up")
|
||||
|
||||
// Initialise resources
|
||||
if err := u.db.Ping(); err != nil {
|
||||
return fmt.Errorf("database not available: %w", err)
|
||||
}
|
||||
|
||||
// Run migrations
|
||||
if err := u.runMigrations(); err != nil {
|
||||
return fmt.Errorf("migrations failed: %w", err)
|
||||
}
|
||||
|
||||
// Start background tasks
|
||||
go u.backgroundSync(ctx)
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Initialise resources
|
||||
- Validate configuration
|
||||
- Run migrations
|
||||
- Start background tasks
|
||||
- Connect to external services
|
||||
|
||||
**Important:**
|
||||
- Services start in **registration order**
|
||||
- Return error to **prevent app startup**
|
||||
- Use `ctx` for cancellation
|
||||
|
||||
### ServiceShutdown
|
||||
|
||||
Called when application shuts down:
|
||||
|
||||
```go
|
||||
func (u *UserService) ServiceShutdown() error {
|
||||
u.logger.Info("UserService shutting down")
|
||||
|
||||
// Close database
|
||||
if err := u.db.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close database: %w", err)
|
||||
}
|
||||
|
||||
// Cleanup resources
|
||||
u.cleanup()
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Close connections
|
||||
- Save state
|
||||
- Cleanup resources
|
||||
- Flush buffers
|
||||
- Cancel background tasks
|
||||
|
||||
**Important:**
|
||||
- Services shutdown in **reverse order**
|
||||
- Application context already cancelled
|
||||
- Return error to **log warning** (doesn't prevent shutdown)
|
||||
|
||||
### Complete Lifecycle Example
|
||||
|
||||
```go
|
||||
type DatabaseService struct {
|
||||
db *sql.DB
|
||||
logger *slog.Logger
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func NewDatabaseService(logger *slog.Logger) *DatabaseService {
|
||||
return &DatabaseService{logger: logger}
|
||||
}
|
||||
|
||||
func (d *DatabaseService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
||||
d.logger.Info("Starting database service")
|
||||
|
||||
// Open database
|
||||
db, err := sql.Open("sqlite3", "app.db")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open database: %w", err)
|
||||
}
|
||||
d.db = db
|
||||
|
||||
// Test connection
|
||||
if err := db.Ping(); err != nil {
|
||||
return fmt.Errorf("database not available: %w", err)
|
||||
}
|
||||
|
||||
// Start background cleanup
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
d.cancel = cancel
|
||||
go d.periodicCleanup(ctx)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DatabaseService) ServiceShutdown() error {
|
||||
d.logger.Info("Shutting down database service")
|
||||
|
||||
// Cancel background tasks
|
||||
if d.cancel != nil {
|
||||
d.cancel()
|
||||
}
|
||||
|
||||
// Close database
|
||||
if d.db != nil {
|
||||
if err := d.db.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close database: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DatabaseService) periodicCleanup(ctx context.Context) {
|
||||
ticker := time.NewTicker(1 * time.Hour)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
d.cleanup()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Service Options
|
||||
|
||||
### Custom Name
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewServiceWithOptions(&MyService{}, application.ServiceOptions{
|
||||
Name: "CustomServiceName",
|
||||
}),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Multiple instances of same service type
|
||||
- Clearer logging
|
||||
- Better debugging
|
||||
|
||||
### HTTP Routes
|
||||
|
||||
Services can handle HTTP requests:
|
||||
|
||||
```go
|
||||
type FileService struct {
|
||||
root string
|
||||
}
|
||||
|
||||
func (f *FileService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Serve files from root directory
|
||||
http.FileServer(http.Dir(f.root)).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// Register with route
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewServiceWithOptions(&FileService{root: "./files"}, application.ServiceOptions{
|
||||
Route: "/files",
|
||||
}),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Access:** `http://wails.localhost/files/...`
|
||||
|
||||
**Use cases:**
|
||||
- File serving
|
||||
- Custom APIs
|
||||
- WebSocket endpoints
|
||||
- Media streaming
|
||||
|
||||
## Service Patterns
|
||||
|
||||
### Repository Pattern
|
||||
|
||||
```go
|
||||
type UserRepository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetByID(id int) (*User, error) {
|
||||
// Database query
|
||||
}
|
||||
|
||||
func (r *UserRepository) Create(user *User) error {
|
||||
// Insert user
|
||||
}
|
||||
|
||||
func (r *UserRepository) Update(user *User) error {
|
||||
// Update user
|
||||
}
|
||||
|
||||
func (r *UserRepository) Delete(id int) error {
|
||||
// Delete user
|
||||
}
|
||||
```
|
||||
|
||||
### Service Layer Pattern
|
||||
|
||||
```go
|
||||
type UserService struct {
|
||||
repo *UserRepository
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func (s *UserService) RegisterUser(email, password string) (*User, error) {
|
||||
// Validate
|
||||
if !isValidEmail(email) {
|
||||
return nil, errors.New("invalid email")
|
||||
}
|
||||
|
||||
// Hash password
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create user
|
||||
user := &User{
|
||||
Email: email,
|
||||
PasswordHash: string(hash),
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if err := s.repo.Create(user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.Info("User registered", "email", email)
|
||||
return user, nil
|
||||
}
|
||||
```
|
||||
|
||||
### Factory Pattern
|
||||
|
||||
```go
|
||||
type ServiceFactory struct {
|
||||
db *sql.DB
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func (f *ServiceFactory) CreateUserService() *UserService {
|
||||
return &UserService{
|
||||
repo: &UserRepository{db: f.db},
|
||||
logger: f.logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *ServiceFactory) CreateOrderService() *OrderService {
|
||||
return &OrderService{
|
||||
repo: &OrderRepository{db: f.db},
|
||||
logger: f.logger,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Event-Driven Pattern
|
||||
|
||||
```go
|
||||
type OrderService struct {
|
||||
app *application.App
|
||||
}
|
||||
|
||||
func (o *OrderService) CreateOrder(items []Item) (*Order, error) {
|
||||
order := &Order{
|
||||
Items: items,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Save order
|
||||
if err := o.saveOrder(order); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Emit event
|
||||
o.app.Event.Emit("order-created", order)
|
||||
|
||||
return order, nil
|
||||
}
|
||||
```
|
||||
|
||||
## Dependency Injection
|
||||
|
||||
### Constructor Injection
|
||||
|
||||
```go
|
||||
type EmailService struct {
|
||||
smtp *smtp.Client
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func NewEmailService(smtp *smtp.Client, logger *slog.Logger) *EmailService {
|
||||
return &EmailService{
|
||||
smtp: smtp,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Register
|
||||
smtpClient := createSMTPClient()
|
||||
logger := slog.Default()
|
||||
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(NewEmailService(smtpClient, logger)),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Application Injection
|
||||
|
||||
```go
|
||||
type NotificationService struct {
|
||||
app *application.App
|
||||
}
|
||||
|
||||
func NewNotificationService(app *application.App) *NotificationService {
|
||||
return &NotificationService{app: app}
|
||||
}
|
||||
|
||||
func (n *NotificationService) Notify(message string) {
|
||||
// Use application to emit events
|
||||
n.app.Event.Emit("notification", message)
|
||||
}
|
||||
|
||||
// Register after app creation
|
||||
app := application.New(application.Options{})
|
||||
app.RegisterService(application.NewService(NewNotificationService(app)))
|
||||
```
|
||||
|
||||
### Service-to-Service Dependencies
|
||||
|
||||
```go
|
||||
type OrderService struct {
|
||||
userService *UserService
|
||||
emailService *EmailService
|
||||
}
|
||||
|
||||
func NewOrderService(userService *UserService, emailService *EmailService) *OrderService {
|
||||
return &OrderService{
|
||||
userService: userService,
|
||||
emailService: emailService,
|
||||
}
|
||||
}
|
||||
|
||||
// Register in order
|
||||
userService := &UserService{}
|
||||
emailService := &EmailService{}
|
||||
orderService := NewOrderService(userService, emailService)
|
||||
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(userService),
|
||||
application.NewService(emailService),
|
||||
application.NewService(orderService),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Testing Services
|
||||
|
||||
### Unit Testing
|
||||
|
||||
```go
|
||||
func TestCalculatorService_Add(t *testing.T) {
|
||||
calc := &CalculatorService{}
|
||||
|
||||
result := calc.Add(2, 3)
|
||||
|
||||
if result != 5 {
|
||||
t.Errorf("expected 5, got %d", result)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Testing with Dependencies
|
||||
|
||||
```go
|
||||
func TestUserService_GetUser(t *testing.T) {
|
||||
// Create mock database
|
||||
db, mock, _ := sqlmock.New()
|
||||
defer db.Close()
|
||||
|
||||
// Set expectations
|
||||
rows := sqlmock.NewRows([]string{"id", "name"}).
|
||||
AddRow(1, "Alice")
|
||||
mock.ExpectQuery("SELECT").WillReturnRows(rows)
|
||||
|
||||
// Create service
|
||||
service := NewUserService(db, slog.Default())
|
||||
|
||||
// Test
|
||||
user, err := service.GetUser(1)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if user.Name != "Alice" {
|
||||
t.Errorf("expected Alice, got %s", user.Name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Testing Lifecycle
|
||||
|
||||
```go
|
||||
func TestDatabaseService_Lifecycle(t *testing.T) {
|
||||
service := NewDatabaseService(slog.Default())
|
||||
|
||||
// Test startup
|
||||
ctx := context.Background()
|
||||
err := service.ServiceStartup(ctx, application.ServiceOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("startup failed: %v", err)
|
||||
}
|
||||
|
||||
// Test functionality
|
||||
// ...
|
||||
|
||||
// Test shutdown
|
||||
err = service.ServiceShutdown()
|
||||
if err != nil {
|
||||
t.Fatalf("shutdown failed: %v", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Single responsibility** - One service, one purpose
|
||||
- **Constructor injection** - Pass dependencies explicitly
|
||||
- **Thread-safe state** - Use mutexes
|
||||
- **Return errors** - Don't panic
|
||||
- **Log important events** - Use structured logging
|
||||
- **Test in isolation** - Mock dependencies
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Don't use global state** - Pass dependencies
|
||||
- **Don't block startup** - Keep ServiceStartup fast
|
||||
- **Don't ignore shutdown** - Always cleanup
|
||||
- **Don't create circular dependencies** - Design carefully
|
||||
- **Don't expose internal methods** - Keep them private
|
||||
- **Don't forget thread safety** - Services are shared
|
||||
|
||||
## Complete Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type UserService struct {
|
||||
db *sql.DB
|
||||
logger *slog.Logger
|
||||
cache map[int]*User
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewUserService(logger *slog.Logger) *UserService {
|
||||
return &UserService{
|
||||
logger: logger,
|
||||
cache: make(map[int]*User),
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
||||
u.logger.Info("Starting UserService")
|
||||
|
||||
// Open database
|
||||
db, err := sql.Open("sqlite3", "users.db")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open database: %w", err)
|
||||
}
|
||||
u.db = db
|
||||
|
||||
// Create table
|
||||
_, err = db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create table: %w", err)
|
||||
}
|
||||
|
||||
// Preload cache
|
||||
if err := u.loadCache(); err != nil {
|
||||
return fmt.Errorf("failed to load cache: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserService) ServiceShutdown() error {
|
||||
u.logger.Info("Shutting down UserService")
|
||||
|
||||
if u.db != nil {
|
||||
return u.db.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserService) GetUser(id int) (*User, error) {
|
||||
// Check cache first
|
||||
u.mu.RLock()
|
||||
if user, ok := u.cache[id]; ok {
|
||||
u.mu.RUnlock()
|
||||
return user, nil
|
||||
}
|
||||
u.mu.RUnlock()
|
||||
|
||||
// Query database
|
||||
var user User
|
||||
err := u.db.QueryRow(
|
||||
"SELECT id, name, email, created_at FROM users WHERE id = ?",
|
||||
id,
|
||||
).Scan(&user.ID, &user.Name, &user.Email, &user.CreatedAt)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, fmt.Errorf("user %d not found", id)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("database error: %w", err)
|
||||
}
|
||||
|
||||
// Update cache
|
||||
u.mu.Lock()
|
||||
u.cache[id] = &user
|
||||
u.mu.Unlock()
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (u *UserService) CreateUser(name, email string) (*User, error) {
|
||||
result, err := u.db.Exec(
|
||||
"INSERT INTO users (name, email) VALUES (?, ?)",
|
||||
name, email,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create user: %w", err)
|
||||
}
|
||||
|
||||
id, _ := result.LastInsertId()
|
||||
|
||||
user := &User{
|
||||
ID: int(id),
|
||||
Name: name,
|
||||
Email: email,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Update cache
|
||||
u.mu.Lock()
|
||||
u.cache[int(id)] = user
|
||||
u.mu.Unlock()
|
||||
|
||||
u.logger.Info("User created", "id", id, "email", email)
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (u *UserService) loadCache() error {
|
||||
rows, err := u.db.Query("SELECT id, name, email, created_at FROM users")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
u.mu.Lock()
|
||||
defer u.mu.Unlock()
|
||||
|
||||
for rows.Next() {
|
||||
var user User
|
||||
if err := rows.Scan(&user.ID, &user.Name, &user.Email, &user.CreatedAt); err != nil {
|
||||
return err
|
||||
}
|
||||
u.cache[user.ID] = &user
|
||||
}
|
||||
|
||||
return rows.Err()
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "User Management",
|
||||
Services: []application.Service{
|
||||
application.NewService(NewUserService(slog.Default())),
|
||||
},
|
||||
})
|
||||
|
||||
app.Window.New()
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Method Bindings](/features/bindings/methods) - Learn how to bind Go methods to JavaScript
|
||||
- [Models](/features/bindings/models) - Bind complex data structures
|
||||
- [Events](/features/events/system) - Use events for pub/sub communication
|
||||
- [Best Practices](/features/bindings/best-practices) - Service design patterns and best practices
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [service examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).
|
||||
511
docs/src/content/docs/features/browser/integration.mdx
Normal file
511
docs/src/content/docs/features/browser/integration.mdx
Normal file
|
|
@ -0,0 +1,511 @@
|
|||
---
|
||||
title: Browser Integration
|
||||
description: Open URLs and files in the user's default web browser
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
Wails provides simple browser integration through the BrowserManager API, allowing your application to open URLs and files in the user's default web browser. This is useful for opening external links, documentation, or files that should be handled by the browser.
|
||||
|
||||
## Accessing the Browser Manager
|
||||
|
||||
The browser manager is accessed through the `Browser` property on your application instance:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Name: "Browser Integration Demo",
|
||||
})
|
||||
|
||||
// Access the browser manager
|
||||
browser := app.Browser
|
||||
```
|
||||
|
||||
## Opening URLs
|
||||
|
||||
### Open Web URLs
|
||||
|
||||
Open URLs in the user's default web browser:
|
||||
|
||||
```go
|
||||
// Open a website
|
||||
err := app.Browser.OpenURL("https://wails.io")
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open URL", "error", err)
|
||||
}
|
||||
|
||||
// Open specific pages
|
||||
err = app.Browser.OpenURL("https://github.com/wailsapp/wails")
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open GitHub", "error", err)
|
||||
}
|
||||
```
|
||||
|
||||
### Open Local URLs
|
||||
|
||||
Open local development servers or local network resources:
|
||||
|
||||
```go
|
||||
// Open local development server
|
||||
err := app.Browser.OpenURL("http://localhost:3000")
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open local server", "error", err)
|
||||
}
|
||||
|
||||
// Open network resource
|
||||
err = app.Browser.OpenURL("http://192.168.1.100:8080")
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open network resource", "error", err)
|
||||
}
|
||||
```
|
||||
|
||||
## Opening Files
|
||||
|
||||
### Open HTML Files
|
||||
|
||||
Open local HTML files in the browser:
|
||||
|
||||
```go
|
||||
// Open an HTML file
|
||||
err := app.Browser.OpenFile("/path/to/documentation.html")
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open HTML file", "error", err)
|
||||
}
|
||||
|
||||
// Open generated reports
|
||||
reportPath := "/tmp/report.html"
|
||||
err = app.Browser.OpenFile(reportPath)
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open report", "error", err)
|
||||
}
|
||||
```
|
||||
|
||||
### Open Other File Types
|
||||
|
||||
Open various file types that browsers can handle:
|
||||
|
||||
```go
|
||||
// Open PDF files
|
||||
err := app.Browser.OpenFile("/path/to/document.pdf")
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open PDF", "error", err)
|
||||
}
|
||||
|
||||
// Open image files
|
||||
err = app.Browser.OpenFile("/path/to/image.png")
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open image", "error", err)
|
||||
}
|
||||
|
||||
// Open text files
|
||||
err = app.Browser.OpenFile("/path/to/readme.txt")
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open text file", "error", err)
|
||||
}
|
||||
```
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### Help and Documentation
|
||||
|
||||
Provide easy access to help resources:
|
||||
|
||||
```go
|
||||
// Create help menu
|
||||
func setupHelpMenu(app *application.App) {
|
||||
menu := app.Menu.New()
|
||||
helpMenu := menu.AddSubmenu("Help")
|
||||
|
||||
helpMenu.Add("Online Documentation").OnClick(func(ctx *application.Context) {
|
||||
err := app.Browser.OpenURL("https://docs.yourapp.com")
|
||||
if err != nil {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Error").
|
||||
SetMessage("Could not open documentation").
|
||||
Show()
|
||||
}
|
||||
})
|
||||
|
||||
helpMenu.Add("GitHub Repository").OnClick(func(ctx *application.Context) {
|
||||
app.Browser.OpenURL("https://github.com/youruser/yourapp")
|
||||
})
|
||||
|
||||
helpMenu.Add("Report Issue").OnClick(func(ctx *application.Context) {
|
||||
app.Browser.OpenURL("https://github.com/youruser/yourapp/issues/new")
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### External Links in Content
|
||||
|
||||
Handle external links from your application content:
|
||||
|
||||
```go
|
||||
func handleExternalLink(app *application.App, url string) {
|
||||
// Validate the URL before opening
|
||||
if !isValidURL(url) {
|
||||
app.Logger.Warn("Invalid URL", "url", url)
|
||||
return
|
||||
}
|
||||
|
||||
// Optionally confirm with user
|
||||
dialog := app.Dialog.Question()
|
||||
dialog.SetTitle("Open External Link")
|
||||
dialog.SetMessage(fmt.Sprintf("Open %s in your browser?", url))
|
||||
|
||||
dialog.AddButton("Open").OnClick(func() {
|
||||
err := app.Browser.OpenURL(url)
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open URL", "url", url, "error", err)
|
||||
}
|
||||
})
|
||||
|
||||
dialog.AddButton("Cancel")
|
||||
dialog.Show()
|
||||
}
|
||||
|
||||
func isValidURL(url string) bool {
|
||||
parsed, err := url.Parse(url)
|
||||
return err == nil && (parsed.Scheme == "http" || parsed.Scheme == "https")
|
||||
}
|
||||
```
|
||||
|
||||
### Export and View Reports
|
||||
|
||||
Generate and open reports in the browser:
|
||||
|
||||
```go
|
||||
import (
|
||||
"html/template"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func generateAndOpenReport(app *application.App, data interface{}) error {
|
||||
// Create temporary file for the report
|
||||
tmpDir := os.TempDir()
|
||||
reportPath := filepath.Join(tmpDir, "report.html")
|
||||
|
||||
// Generate HTML report
|
||||
tmpl := `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Application Report</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; }
|
||||
.header { border-bottom: 2px solid #333; padding-bottom: 10px; }
|
||||
.data { margin-top: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>Application Report</h1>
|
||||
<p>Generated: {{.Timestamp}}</p>
|
||||
</div>
|
||||
<div class="data">
|
||||
<!-- Report content here -->
|
||||
{{range .Items}}
|
||||
<p>{{.}}</p>
|
||||
{{end}}
|
||||
</div>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
// Write report to file
|
||||
file, err := os.Create(reportPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
t, err := template.New("report").Parse(tmpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = t.Execute(file, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Open in browser
|
||||
return app.Browser.OpenFile(reportPath)
|
||||
}
|
||||
```
|
||||
|
||||
### Development Tools
|
||||
|
||||
Open development resources during development:
|
||||
|
||||
```go
|
||||
func setupDevelopmentMenu(app *application.App) {
|
||||
if !app.Env.Info().Debug {
|
||||
return // Only show in debug mode
|
||||
}
|
||||
|
||||
menu := app.Menu.New()
|
||||
devMenu := menu.AddSubmenu("Development")
|
||||
|
||||
devMenu.Add("Open DevTools").OnClick(func(ctx *application.Context) {
|
||||
// This would open browser devtools if available
|
||||
window := app.Window.Current()
|
||||
if window != nil {
|
||||
window.OpenDevTools()
|
||||
}
|
||||
})
|
||||
|
||||
devMenu.Add("View Source").OnClick(func(ctx *application.Context) {
|
||||
// Open source code repository
|
||||
app.Browser.OpenURL("https://github.com/youruser/yourapp")
|
||||
})
|
||||
|
||||
devMenu.Add("API Documentation").OnClick(func(ctx *application.Context) {
|
||||
// Open local API docs
|
||||
app.Browser.OpenURL("http://localhost:8080/docs")
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Graceful Error Handling
|
||||
|
||||
Always handle potential errors when opening URLs or files:
|
||||
|
||||
```go
|
||||
func openURLWithFallback(app *application.App, url string, fallbackMessage string) {
|
||||
err := app.Browser.OpenURL(url)
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open URL", "url", url, "error", err)
|
||||
|
||||
// Show fallback dialog with URL
|
||||
dialog := app.Dialog.Info()
|
||||
dialog.SetTitle("Unable to Open Link")
|
||||
dialog.SetMessage(fmt.Sprintf("%s\n\nURL: %s", fallbackMessage, url))
|
||||
dialog.Show()
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
openURLWithFallback(app,
|
||||
"https://docs.example.com",
|
||||
"Please open the following URL manually in your browser:")
|
||||
```
|
||||
|
||||
### User Feedback
|
||||
|
||||
Provide feedback when operations succeed or fail:
|
||||
|
||||
```go
|
||||
func openURLWithFeedback(app *application.App, url string) {
|
||||
err := app.Browser.OpenURL(url)
|
||||
if err != nil {
|
||||
// Show error dialog
|
||||
app.Dialog.Error().
|
||||
SetTitle("Browser Error").
|
||||
SetMessage(fmt.Sprintf("Could not open URL: %s", err.Error())).
|
||||
Show()
|
||||
} else {
|
||||
// Optionally show success notification
|
||||
app.Logger.Info("URL opened successfully", "url", url)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
On macOS:
|
||||
|
||||
- Uses the `open` command to launch the default browser
|
||||
- Respects user's default browser setting in System Preferences
|
||||
- May prompt for permission if the application is sandboxed
|
||||
- Handles `file://` URLs correctly for local files
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
|
||||
On Windows:
|
||||
|
||||
- Uses Windows Shell API to open URLs
|
||||
- Respects default browser setting in Windows Settings
|
||||
- Handles Windows path formats correctly
|
||||
- May show security warnings for untrusted URLs
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
|
||||
On Linux:
|
||||
|
||||
- Attempts to use `xdg-open` first, falls back to other methods
|
||||
- Behavior varies by desktop environment
|
||||
- Respects `BROWSER` environment variable if set
|
||||
- May require additional packages in minimal installations
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always Handle Errors**: Browser operations can fail for various reasons:
|
||||
```go
|
||||
if err := app.Browser.OpenURL(url); err != nil {
|
||||
app.Logger.Error("Failed to open browser", "error", err)
|
||||
// Provide fallback or user notification
|
||||
}
|
||||
```
|
||||
|
||||
2. **Validate URLs**: Ensure URLs are well-formed before opening:
|
||||
```go
|
||||
func isValidHTTPURL(str string) bool {
|
||||
u, err := url.Parse(str)
|
||||
return err == nil && (u.Scheme == "http" || u.Scheme == "https")
|
||||
}
|
||||
```
|
||||
|
||||
3. **User Confirmation**: For external links, consider asking user permission:
|
||||
```go
|
||||
// Show confirmation dialog before opening external links
|
||||
confirmAndOpen(app, "https://external-site.com")
|
||||
```
|
||||
|
||||
4. **Secure File Paths**: When opening files, ensure paths are safe:
|
||||
```go
|
||||
func openSafeFile(app *application.App, filename string) error {
|
||||
// Ensure file exists and is readable
|
||||
if _, err := os.Stat(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
return app.Browser.OpenFile(filename)
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a complete example showing various browser integration patterns:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Browser Integration Demo",
|
||||
})
|
||||
|
||||
// Setup menu with browser actions
|
||||
setupMenu(app)
|
||||
|
||||
// Create main window
|
||||
window := app.Window.New()
|
||||
window.SetTitle("Browser Integration")
|
||||
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func setupMenu(app *application.App) {
|
||||
menu := app.Menu.New()
|
||||
|
||||
// File menu
|
||||
fileMenu := menu.AddSubmenu("File")
|
||||
fileMenu.Add("Generate Report").OnClick(func(ctx *application.Context) {
|
||||
generateHTMLReport(app)
|
||||
})
|
||||
|
||||
// Help menu
|
||||
helpMenu := menu.AddSubmenu("Help")
|
||||
helpMenu.Add("Documentation").OnClick(func(ctx *application.Context) {
|
||||
openWithConfirmation(app, "https://docs.example.com")
|
||||
})
|
||||
helpMenu.Add("Support").OnClick(func(ctx *application.Context) {
|
||||
openWithConfirmation(app, "https://support.example.com")
|
||||
})
|
||||
|
||||
app.Menu.Set(menu)
|
||||
}
|
||||
|
||||
func openWithConfirmation(app *application.App, url string) {
|
||||
dialog := app.Dialog.Question()
|
||||
dialog.SetTitle("Open External Link")
|
||||
dialog.SetMessage(fmt.Sprintf("Open %s in your browser?", url))
|
||||
|
||||
dialog.AddButton("Open").OnClick(func() {
|
||||
if err := app.Browser.OpenURL(url); err != nil {
|
||||
showError(app, "Failed to open URL", err)
|
||||
}
|
||||
})
|
||||
|
||||
dialog.AddButton("Cancel")
|
||||
dialog.Show()
|
||||
}
|
||||
|
||||
func generateHTMLReport(app *application.App) {
|
||||
// Create temporary HTML file
|
||||
tmpDir := os.TempDir()
|
||||
reportPath := filepath.Join(tmpDir, "demo_report.html")
|
||||
|
||||
html := `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Demo Report</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; }
|
||||
.header { color: #333; border-bottom: 1px solid #ccc; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>Application Report</h1>
|
||||
<p>This is a sample report generated by the application.</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h2>Report Details</h2>
|
||||
<p>This report was generated to demonstrate browser integration.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
err := os.WriteFile(reportPath, []byte(html), 0644)
|
||||
if err != nil {
|
||||
showError(app, "Failed to create report", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Open in browser
|
||||
err = app.Browser.OpenFile(reportPath)
|
||||
if err != nil {
|
||||
showError(app, "Failed to open report", err)
|
||||
}
|
||||
}
|
||||
|
||||
func showError(app *application.App, message string, err error) {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Error").
|
||||
SetMessage(fmt.Sprintf("%s: %v", message, err)).
|
||||
Show()
|
||||
}
|
||||
```
|
||||
|
||||
:::tip[Pro Tip]
|
||||
Consider providing fallback mechanisms for when browser operations fail, such as copying URLs to clipboard or showing them in a dialog for manual opening.
|
||||
:::
|
||||
|
||||
:::danger[Warning]
|
||||
Always validate URLs and file paths before opening them to prevent security issues. Be cautious about opening user-provided URLs without validation.
|
||||
:::
|
||||
493
docs/src/content/docs/features/clipboard/basics.mdx
Normal file
493
docs/src/content/docs/features/clipboard/basics.mdx
Normal file
|
|
@ -0,0 +1,493 @@
|
|||
---
|
||||
title: Clipboard Operations
|
||||
description: Copy and paste text with the system clipboard
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Clipboard Operations
|
||||
|
||||
Wails provides a **unified clipboard API** that works across all platforms. Copy and paste text with simple, consistent methods on Windows, macOS, and Linux.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
// Copy text to clipboard
|
||||
app.Clipboard.SetText("Hello, World!")
|
||||
|
||||
// Get text from clipboard
|
||||
text, ok := app.Clipboard.Text()
|
||||
if ok {
|
||||
fmt.Println("Clipboard:", text)
|
||||
}
|
||||
```
|
||||
|
||||
**That's it!** Cross-platform clipboard access.
|
||||
|
||||
## Copying Text
|
||||
|
||||
### Basic Copy
|
||||
|
||||
```go
|
||||
success := app.Clipboard.SetText("Text to copy")
|
||||
if !success {
|
||||
app.Logger.Error("Failed to copy to clipboard")
|
||||
}
|
||||
```
|
||||
|
||||
**Returns:** `bool` - `true` if successful, `false` otherwise
|
||||
|
||||
### Copy from Service
|
||||
|
||||
```go
|
||||
type ClipboardService struct {
|
||||
app *application.App
|
||||
}
|
||||
|
||||
func (c *ClipboardService) CopyToClipboard(text string) bool {
|
||||
return c.app.Clipboard.SetText(text)
|
||||
}
|
||||
```
|
||||
|
||||
**Call from JavaScript:**
|
||||
|
||||
```javascript
|
||||
import { CopyToClipboard } from './bindings/myapp/clipboardservice'
|
||||
|
||||
await CopyToClipboard("Text to copy")
|
||||
```
|
||||
|
||||
### Copy with Feedback
|
||||
|
||||
```go
|
||||
func copyWithFeedback(text string) {
|
||||
if app.Clipboard.SetText(text) {
|
||||
app.Dialog.Info().
|
||||
SetTitle("Copied").
|
||||
SetMessage("Text copied to clipboard!").
|
||||
Show()
|
||||
} else {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Copy Failed").
|
||||
SetMessage("Failed to copy to clipboard.").
|
||||
Show()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Pasting Text
|
||||
|
||||
### Basic Paste
|
||||
|
||||
```go
|
||||
text, ok := app.Clipboard.Text()
|
||||
if !ok {
|
||||
app.Logger.Error("Failed to read clipboard")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Clipboard text:", text)
|
||||
```
|
||||
|
||||
**Returns:** `(string, bool)` - Text and success flag
|
||||
|
||||
### Paste from Service
|
||||
|
||||
```go
|
||||
func (c *ClipboardService) PasteFromClipboard() string {
|
||||
text, ok := c.app.Clipboard.Text()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return text
|
||||
}
|
||||
```
|
||||
|
||||
**Call from JavaScript:**
|
||||
|
||||
```javascript
|
||||
import { PasteFromClipboard } from './bindings/myapp/clipboardservice'
|
||||
|
||||
const text = await PasteFromClipboard()
|
||||
console.log("Pasted:", text)
|
||||
```
|
||||
|
||||
### Paste with Validation
|
||||
|
||||
```go
|
||||
func pasteText() (string, error) {
|
||||
text, ok := app.Clipboard.Text()
|
||||
if !ok {
|
||||
return "", errors.New("clipboard empty or unavailable")
|
||||
}
|
||||
|
||||
// Validate
|
||||
if len(text) == 0 {
|
||||
return "", errors.New("clipboard is empty")
|
||||
}
|
||||
|
||||
if len(text) > 10000 {
|
||||
return "", errors.New("clipboard text too large")
|
||||
}
|
||||
|
||||
return text, nil
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Copy Button
|
||||
|
||||
**Go:**
|
||||
|
||||
```go
|
||||
type TextService struct {
|
||||
app *application.App
|
||||
}
|
||||
|
||||
func (t *TextService) CopyText(text string) error {
|
||||
if !t.app.Clipboard.SetText(text) {
|
||||
return errors.New("failed to copy")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
**JavaScript:**
|
||||
|
||||
```javascript
|
||||
import { CopyText } from './bindings/myapp/textservice'
|
||||
|
||||
async function copyToClipboard(text) {
|
||||
try {
|
||||
await CopyText(text)
|
||||
showNotification("Copied to clipboard!")
|
||||
} catch (error) {
|
||||
showError("Failed to copy: " + error)
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
document.getElementById('copy-btn').addEventListener('click', () => {
|
||||
const text = document.getElementById('text').value
|
||||
copyToClipboard(text)
|
||||
})
|
||||
```
|
||||
|
||||
### Paste and Process
|
||||
|
||||
**Go:**
|
||||
|
||||
```go
|
||||
type DataService struct {
|
||||
app *application.App
|
||||
}
|
||||
|
||||
func (d *DataService) PasteAndProcess() (string, error) {
|
||||
// Get clipboard text
|
||||
text, ok := d.app.Clipboard.Text()
|
||||
if !ok {
|
||||
return "", errors.New("clipboard unavailable")
|
||||
}
|
||||
|
||||
// Process text
|
||||
processed := strings.TrimSpace(text)
|
||||
processed = strings.ToUpper(processed)
|
||||
|
||||
return processed, nil
|
||||
}
|
||||
```
|
||||
|
||||
**JavaScript:**
|
||||
|
||||
```javascript
|
||||
import { PasteAndProcess } from './bindings/myapp/dataservice'
|
||||
|
||||
async function pasteAndProcess() {
|
||||
try {
|
||||
const result = await PasteAndProcess()
|
||||
document.getElementById('output').value = result
|
||||
} catch (error) {
|
||||
showError("Failed to paste: " + error)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Copy Multiple Formats
|
||||
|
||||
```go
|
||||
type CopyService struct {
|
||||
app *application.App
|
||||
}
|
||||
|
||||
func (c *CopyService) CopyAsPlainText(text string) bool {
|
||||
return c.app.Clipboard.SetText(text)
|
||||
}
|
||||
|
||||
func (c *CopyService) CopyAsJSON(data interface{}) bool {
|
||||
jsonBytes, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return c.app.Clipboard.SetText(string(jsonBytes))
|
||||
}
|
||||
|
||||
func (c *CopyService) CopyAsCSV(rows [][]string) bool {
|
||||
var buf bytes.Buffer
|
||||
writer := csv.NewWriter(&buf)
|
||||
|
||||
for _, row := range rows {
|
||||
if err := writer.Write(row); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
writer.Flush()
|
||||
return c.app.Clipboard.SetText(buf.String())
|
||||
}
|
||||
```
|
||||
|
||||
### Clipboard Monitor
|
||||
|
||||
```go
|
||||
type ClipboardMonitor struct {
|
||||
app *application.App
|
||||
lastText string
|
||||
ticker *time.Ticker
|
||||
stopChan chan bool
|
||||
}
|
||||
|
||||
func NewClipboardMonitor(app *application.App) *ClipboardMonitor {
|
||||
return &ClipboardMonitor{
|
||||
app: app,
|
||||
stopChan: make(chan bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ClipboardMonitor) Start() {
|
||||
cm.ticker = time.NewTicker(1 * time.Second)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-cm.ticker.C:
|
||||
cm.checkClipboard()
|
||||
case <-cm.stopChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (cm *ClipboardMonitor) Stop() {
|
||||
if cm.ticker != nil {
|
||||
cm.ticker.Stop()
|
||||
}
|
||||
cm.stopChan <- true
|
||||
}
|
||||
|
||||
func (cm *ClipboardMonitor) checkClipboard() {
|
||||
text, ok := cm.app.Clipboard.Text()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if text != cm.lastText {
|
||||
cm.lastText = text
|
||||
cm.app.Event.Emit("clipboard-changed", text)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Copy with History
|
||||
|
||||
```go
|
||||
type ClipboardHistory struct {
|
||||
app *application.App
|
||||
history []string
|
||||
maxSize int
|
||||
}
|
||||
|
||||
func NewClipboardHistory(app *application.App) *ClipboardHistory {
|
||||
return &ClipboardHistory{
|
||||
app: app,
|
||||
history: make([]string, 0),
|
||||
maxSize: 10,
|
||||
}
|
||||
}
|
||||
|
||||
func (ch *ClipboardHistory) Copy(text string) bool {
|
||||
if !ch.app.Clipboard.SetText(text) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Add to history
|
||||
ch.history = append([]string{text}, ch.history...)
|
||||
|
||||
// Limit size
|
||||
if len(ch.history) > ch.maxSize {
|
||||
ch.history = ch.history[:ch.maxSize]
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (ch *ClipboardHistory) GetHistory() []string {
|
||||
return ch.history
|
||||
}
|
||||
|
||||
func (ch *ClipboardHistory) RestoreFromHistory(index int) bool {
|
||||
if index < 0 || index >= len(ch.history) {
|
||||
return false
|
||||
}
|
||||
|
||||
return ch.app.Clipboard.SetText(ch.history[index])
|
||||
}
|
||||
```
|
||||
|
||||
## Frontend Integration
|
||||
|
||||
### Using Browser Clipboard API
|
||||
|
||||
For simple text, you can use the browser's clipboard API:
|
||||
|
||||
```javascript
|
||||
// Copy
|
||||
async function copyText(text) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
console.log("Copied!")
|
||||
} catch (error) {
|
||||
console.error("Copy failed:", error)
|
||||
}
|
||||
}
|
||||
|
||||
// Paste
|
||||
async function pasteText() {
|
||||
try {
|
||||
const text = await navigator.clipboard.readText()
|
||||
return text
|
||||
} catch (error) {
|
||||
console.error("Paste failed:", error)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** Browser clipboard API requires HTTPS or localhost, and user permission.
|
||||
|
||||
### Using Wails Clipboard
|
||||
|
||||
For system-wide clipboard access:
|
||||
|
||||
```javascript
|
||||
import { CopyToClipboard, PasteFromClipboard } from './bindings/myapp/clipboardservice'
|
||||
|
||||
// Copy
|
||||
async function copy(text) {
|
||||
const success = await CopyToClipboard(text)
|
||||
if (success) {
|
||||
console.log("Copied!")
|
||||
}
|
||||
}
|
||||
|
||||
// Paste
|
||||
async function paste() {
|
||||
const text = await PasteFromClipboard()
|
||||
return text
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Check return values** - Handle failures gracefully
|
||||
- **Provide feedback** - Let users know copy succeeded
|
||||
- **Validate pasted text** - Check format and size
|
||||
- **Use appropriate method** - Browser API vs Wails API
|
||||
- **Handle empty clipboard** - Check before using
|
||||
- **Trim whitespace** - Clean pasted text
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Don't ignore failures** - Always check success
|
||||
- **Don't copy sensitive data** - Clipboard is shared
|
||||
- **Don't assume format** - Validate pasted data
|
||||
- **Don't poll too frequently** - If monitoring clipboard
|
||||
- **Don't copy large data** - Use files instead
|
||||
- **Don't forget security** - Sanitise pasted content
|
||||
|
||||
## Platform Differences
|
||||
|
||||
### macOS
|
||||
|
||||
- Uses NSPasteboard
|
||||
- Supports rich text (future)
|
||||
- System-wide clipboard
|
||||
- Clipboard history (system feature)
|
||||
|
||||
### Windows
|
||||
|
||||
- Uses Windows Clipboard API
|
||||
- Supports multiple formats (future)
|
||||
- System-wide clipboard
|
||||
- Clipboard history (Windows 10+)
|
||||
|
||||
### Linux
|
||||
|
||||
- Uses X11/Wayland clipboard
|
||||
- Primary and clipboard selections
|
||||
- Varies by desktop environment
|
||||
- May require clipboard manager
|
||||
|
||||
## Limitations
|
||||
|
||||
### Current Version
|
||||
|
||||
- **Text only** - Images not yet supported
|
||||
- **No format detection** - Plain text only
|
||||
- **No clipboard events** - Must poll for changes
|
||||
- **No clipboard history** - Implement yourself
|
||||
|
||||
### Future Features
|
||||
|
||||
- Image support
|
||||
- Rich text support
|
||||
- Multiple formats
|
||||
- Clipboard change events
|
||||
- Clipboard history API
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGrid>
|
||||
<Card title="Bindings" icon="rocket">
|
||||
Call Go functions from JavaScript.
|
||||
|
||||
[Learn More →](/features/bindings/methods)
|
||||
</Card>
|
||||
|
||||
<Card title="Events" icon="star">
|
||||
Use events for clipboard notifications.
|
||||
|
||||
[Learn More →](/features/events/system)
|
||||
</Card>
|
||||
|
||||
<Card title="dialogs" icon="information">
|
||||
Show copy/paste feedback.
|
||||
|
||||
[Learn More →](/features/dialogs/message)
|
||||
</Card>
|
||||
|
||||
<Card title="Services" icon="puzzle">
|
||||
Organise clipboard code.
|
||||
|
||||
[Learn More →](/features/bindings/services)
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [clipboard examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).
|
||||
626
docs/src/content/docs/features/dialogs/custom.mdx
Normal file
626
docs/src/content/docs/features/dialogs/custom.mdx
Normal file
|
|
@ -0,0 +1,626 @@
|
|||
---
|
||||
title: Custom dialogs
|
||||
sidebar:
|
||||
order: 4
|
||||
---
|
||||
|
||||
import { Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Custom dialogs
|
||||
|
||||
Create **custom dialog windows** using regular Wails windows with dialog-like behaviour. Build custom forms, complex input validation, branded appearance, and rich content (images, videos) whilst maintaining familiar dialog patterns.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
// Create custom dialog window
|
||||
dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Custom dialog",
|
||||
Width: 400,
|
||||
Height: 300,
|
||||
AlwaysOnTop: true,
|
||||
Frameless: true,
|
||||
Hidden: true,
|
||||
})
|
||||
|
||||
// Load custom UI
|
||||
dialog.SetURL("http://wails.localhost/dialog.html")
|
||||
|
||||
// Show as modal
|
||||
dialog.Show()
|
||||
dialog.Focus()
|
||||
```
|
||||
|
||||
**That's it!** Custom UI with dialog behaviour.
|
||||
|
||||
## Creating Custom dialogs
|
||||
|
||||
### Basic Custom dialog
|
||||
|
||||
```go
|
||||
type Customdialog struct {
|
||||
window *application.WebviewWindow
|
||||
result chan string
|
||||
}
|
||||
|
||||
func NewCustomdialog(app *application.App) *Customdialog {
|
||||
dialog := &Customdialog{
|
||||
result: make(chan string, 1),
|
||||
}
|
||||
|
||||
dialog.window = app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Custom dialog",
|
||||
Width: 400,
|
||||
Height: 300,
|
||||
AlwaysOnTop: true,
|
||||
Resizable: false,
|
||||
Hidden: true,
|
||||
})
|
||||
|
||||
return dialog
|
||||
}
|
||||
|
||||
func (d *Customdialog) Show() string {
|
||||
d.window.Show()
|
||||
d.window.Focus()
|
||||
|
||||
// Wait for result
|
||||
return <-d.result
|
||||
}
|
||||
|
||||
func (d *Customdialog) Close(result string) {
|
||||
d.result <- result
|
||||
d.window.Close()
|
||||
}
|
||||
```
|
||||
|
||||
### Modal dialog
|
||||
|
||||
```go
|
||||
func ShowModaldialog(parent *application.WebviewWindow, title string) string {
|
||||
// Create dialog
|
||||
dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: title,
|
||||
Width: 400,
|
||||
Height: 200,
|
||||
Parent: parent,
|
||||
AlwaysOnTop: true,
|
||||
Resizable: false,
|
||||
})
|
||||
|
||||
// Disable parent
|
||||
parent.SetEnabled(false)
|
||||
|
||||
// Re-enable parent on close
|
||||
dialog.OnClose(func() bool {
|
||||
parent.SetEnabled(true)
|
||||
parent.Focus()
|
||||
return true
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
|
||||
return waitForResult(dialog)
|
||||
}
|
||||
```
|
||||
|
||||
### Form dialog
|
||||
|
||||
```go
|
||||
type Formdialog struct {
|
||||
window *application.WebviewWindow
|
||||
data map[string]interface{}
|
||||
done chan bool
|
||||
}
|
||||
|
||||
func NewFormdialog(app *application.App) *Formdialog {
|
||||
fd := &Formdialog{
|
||||
data: make(map[string]interface{}),
|
||||
done: make(chan bool, 1),
|
||||
}
|
||||
|
||||
fd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Enter Information",
|
||||
Width: 500,
|
||||
Height: 400,
|
||||
Frameless: true,
|
||||
Hidden: true,
|
||||
})
|
||||
|
||||
return fd
|
||||
}
|
||||
|
||||
func (fd *Formdialog) Show() (map[string]interface{}, bool) {
|
||||
fd.window.Show()
|
||||
fd.window.Focus()
|
||||
|
||||
ok := <-fd.done
|
||||
return fd.data, ok
|
||||
}
|
||||
|
||||
func (fd *Formdialog) Submit(data map[string]interface{}) {
|
||||
fd.data = data
|
||||
fd.done <- true
|
||||
fd.window.Close()
|
||||
}
|
||||
|
||||
func (fd *Formdialog) Cancel() {
|
||||
fd.done <- false
|
||||
fd.window.Close()
|
||||
}
|
||||
```
|
||||
|
||||
## dialog Patterns
|
||||
|
||||
### Confirmation dialog
|
||||
|
||||
```go
|
||||
func ShowConfirmdialog(message string) bool {
|
||||
dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Confirm",
|
||||
Width: 400,
|
||||
Height: 150,
|
||||
AlwaysOnTop: true,
|
||||
Frameless: true,
|
||||
})
|
||||
|
||||
// Pass message to dialog
|
||||
dialog.OnReady(func() {
|
||||
dialog.EmitEvent("set-message", message)
|
||||
})
|
||||
|
||||
result := make(chan bool, 1)
|
||||
|
||||
// Handle responses
|
||||
app.Event.On("confirm-yes", func(e *application.CustomEvent) {
|
||||
result <- true
|
||||
dialog.Close()
|
||||
})
|
||||
|
||||
app.Event.On("confirm-no", func(e *application.CustomEvent) {
|
||||
result <- false
|
||||
dialog.Close()
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
return <-result
|
||||
}
|
||||
```
|
||||
|
||||
**Frontend (HTML/JS):**
|
||||
|
||||
```html
|
||||
<div class="dialog">
|
||||
<h2 id="message"></h2>
|
||||
<div class="buttons">
|
||||
<button onclick="confirm(true)">Yes</button>
|
||||
<button onclick="confirm(false)">No</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import { OnEvent, Emit } from '@wailsio/runtime'
|
||||
|
||||
OnEvent("set-message", (message) => {
|
||||
document.getElementById("message").textContent = message
|
||||
})
|
||||
|
||||
function confirm(result) {
|
||||
Emit(result ? "confirm-yes" : "confirm-no")
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### Input dialog
|
||||
|
||||
```go
|
||||
func ShowInputdialog(prompt string, defaultValue string) (string, bool) {
|
||||
dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Input",
|
||||
Width: 400,
|
||||
Height: 150,
|
||||
Frameless: true,
|
||||
})
|
||||
|
||||
result := make(chan struct {
|
||||
value string
|
||||
ok bool
|
||||
}, 1)
|
||||
|
||||
dialog.OnReady(func() {
|
||||
dialog.EmitEvent("set-prompt", map[string]string{
|
||||
"prompt": prompt,
|
||||
"default": defaultValue,
|
||||
})
|
||||
})
|
||||
|
||||
app.Event.On("input-submit", func(e *application.CustomEvent) {
|
||||
result <- struct {
|
||||
value string
|
||||
ok bool
|
||||
}{e.Data.(string), true}
|
||||
dialog.Close()
|
||||
})
|
||||
|
||||
app.Event.On("input-cancel", func(e *application.CustomEvent) {
|
||||
result <- struct {
|
||||
value string
|
||||
ok bool
|
||||
}{"", false}
|
||||
dialog.Close()
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
r := <-result
|
||||
return r.value, r.ok
|
||||
}
|
||||
```
|
||||
|
||||
### Progress dialog
|
||||
|
||||
```go
|
||||
type Progressdialog struct {
|
||||
window *application.WebviewWindow
|
||||
}
|
||||
|
||||
func NewProgressdialog(title string) *Progressdialog {
|
||||
pd := &Progressdialog{}
|
||||
|
||||
pd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: title,
|
||||
Width: 400,
|
||||
Height: 150,
|
||||
Frameless: true,
|
||||
})
|
||||
|
||||
return pd
|
||||
}
|
||||
|
||||
func (pd *Progressdialog) Show() {
|
||||
pd.window.Show()
|
||||
}
|
||||
|
||||
func (pd *Progressdialog) UpdateProgress(current, total int, message string) {
|
||||
pd.window.EmitEvent("progress-update", map[string]interface{}{
|
||||
"current": current,
|
||||
"total": total,
|
||||
"message": message,
|
||||
})
|
||||
}
|
||||
|
||||
func (pd *Progressdialog) Close() {
|
||||
pd.window.Close()
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```go
|
||||
func processFiles(files []string) {
|
||||
progress := NewProgressdialog("Processing Files")
|
||||
progress.Show()
|
||||
|
||||
for i, file := range files {
|
||||
progress.UpdateProgress(i+1, len(files),
|
||||
fmt.Sprintf("Processing %s...", filepath.Base(file)))
|
||||
|
||||
processFile(file)
|
||||
}
|
||||
|
||||
progress.Close()
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Login dialog
|
||||
|
||||
**Go:**
|
||||
|
||||
```go
|
||||
type Logindialog struct {
|
||||
window *application.WebviewWindow
|
||||
result chan struct {
|
||||
username string
|
||||
password string
|
||||
ok bool
|
||||
}
|
||||
}
|
||||
|
||||
func NewLogindialog(app *application.App) *Logindialog {
|
||||
ld := &Logindialog{
|
||||
result: make(chan struct {
|
||||
username string
|
||||
password string
|
||||
ok bool
|
||||
}, 1),
|
||||
}
|
||||
|
||||
ld.window = app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Login",
|
||||
Width: 400,
|
||||
Height: 250,
|
||||
Frameless: true,
|
||||
})
|
||||
|
||||
return ld
|
||||
}
|
||||
|
||||
func (ld *Logindialog) Show() (string, string, bool) {
|
||||
ld.window.Show()
|
||||
ld.window.Focus()
|
||||
|
||||
r := <-ld.result
|
||||
return r.username, r.password, r.ok
|
||||
}
|
||||
|
||||
func (ld *Logindialog) Submit(username, password string) {
|
||||
ld.result <- struct {
|
||||
username string
|
||||
password string
|
||||
ok bool
|
||||
}{username, password, true}
|
||||
ld.window.Close()
|
||||
}
|
||||
|
||||
func (ld *Logindialog) Cancel() {
|
||||
ld.result <- struct {
|
||||
username string
|
||||
password string
|
||||
ok bool
|
||||
}{"", "", false}
|
||||
ld.window.Close()
|
||||
}
|
||||
```
|
||||
|
||||
**Frontend:**
|
||||
|
||||
```html
|
||||
<div class="login-dialog">
|
||||
<h2>Login</h2>
|
||||
<form id="login-form">
|
||||
<input type="text" id="username" placeholder="Username" required>
|
||||
<input type="password" id="password" placeholder="Password" required>
|
||||
<div class="buttons">
|
||||
<button type="submit">Login</button>
|
||||
<button type="button" onclick="cancel()">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import { Emit } from '@wailsio/runtime'
|
||||
|
||||
document.getElementById('login-form').addEventListener('submit', (e) => {
|
||||
e.preventDefault()
|
||||
const username = document.getElementById('username').value
|
||||
const password = document.getElementById('password').value
|
||||
Emit('login-submit', { username, password })
|
||||
})
|
||||
|
||||
function cancel() {
|
||||
Emit('login-cancel')
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### Settings dialog
|
||||
|
||||
**Go:**
|
||||
|
||||
```go
|
||||
type Settingsdialog struct {
|
||||
window *application.WebviewWindow
|
||||
settings map[string]interface{}
|
||||
done chan bool
|
||||
}
|
||||
|
||||
func NewSettingsdialog(app *application.App, current map[string]interface{}) *Settingsdialog {
|
||||
sd := &Settingsdialog{
|
||||
settings: current,
|
||||
done: make(chan bool, 1),
|
||||
}
|
||||
|
||||
sd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Settings",
|
||||
Width: 600,
|
||||
Height: 500,
|
||||
})
|
||||
|
||||
sd.window.OnReady(func() {
|
||||
sd.window.EmitEvent("load-settings", current)
|
||||
})
|
||||
|
||||
return sd
|
||||
}
|
||||
|
||||
func (sd *Settingsdialog) Show() (map[string]interface{}, bool) {
|
||||
sd.window.Show()
|
||||
|
||||
ok := <-sd.done
|
||||
return sd.settings, ok
|
||||
}
|
||||
|
||||
func (sd *Settingsdialog) Save(settings map[string]interface{}) {
|
||||
sd.settings = settings
|
||||
sd.done <- true
|
||||
sd.window.Close()
|
||||
}
|
||||
|
||||
func (sd *Settingsdialog) Cancel() {
|
||||
sd.done <- false
|
||||
sd.window.Close()
|
||||
}
|
||||
```
|
||||
|
||||
### Wizard dialog
|
||||
|
||||
```go
|
||||
type Wizarddialog struct {
|
||||
window *application.WebviewWindow
|
||||
currentStep int
|
||||
data map[string]interface{}
|
||||
done chan bool
|
||||
}
|
||||
|
||||
func NewWizarddialog(app *application.App) *Wizarddialog {
|
||||
wd := &Wizarddialog{
|
||||
currentStep: 0,
|
||||
data: make(map[string]interface{}),
|
||||
done: make(chan bool, 1),
|
||||
}
|
||||
|
||||
wd.window = app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Setup Wizard",
|
||||
Width: 600,
|
||||
Height: 400,
|
||||
Resizable: false,
|
||||
})
|
||||
|
||||
return wd
|
||||
}
|
||||
|
||||
func (wd *Wizarddialog) Show() (map[string]interface{}, bool) {
|
||||
wd.window.Show()
|
||||
|
||||
ok := <-wd.done
|
||||
return wd.data, ok
|
||||
}
|
||||
|
||||
func (wd *Wizarddialog) NextStep(stepData map[string]interface{}) {
|
||||
// Merge step data
|
||||
for k, v := range stepData {
|
||||
wd.data[k] = v
|
||||
}
|
||||
|
||||
wd.currentStep++
|
||||
wd.window.EmitEvent("next-step", wd.currentStep)
|
||||
}
|
||||
|
||||
func (wd *Wizarddialog) PreviousStep() {
|
||||
if wd.currentStep > 0 {
|
||||
wd.currentStep--
|
||||
wd.window.EmitEvent("previous-step", wd.currentStep)
|
||||
}
|
||||
}
|
||||
|
||||
func (wd *Wizarddialog) Finish(finalData map[string]interface{}) {
|
||||
for k, v := range finalData {
|
||||
wd.data[k] = v
|
||||
}
|
||||
|
||||
wd.done <- true
|
||||
wd.window.Close()
|
||||
}
|
||||
|
||||
func (wd *Wizarddialog) Cancel() {
|
||||
wd.done <- false
|
||||
wd.window.Close()
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do
|
||||
|
||||
- **Use appropriate window options** - AlwaysOnTop, Frameless, etc.
|
||||
- **Handle cancellation** - Always provide a way to cancel
|
||||
- **Validate input** - Check data before accepting
|
||||
- **Provide feedback** - Loading states, errors
|
||||
- **Use events for communication** - Clean separation
|
||||
- **Clean up resources** - Close windows, remove listeners
|
||||
|
||||
### Don't
|
||||
|
||||
- **Don't block the main thread** - Use channels for results
|
||||
- **Don't forget to close** - Memory leaks
|
||||
- **Don't skip validation** - Always validate input
|
||||
- **Don't ignore errors** - Handle all error cases
|
||||
- **Don't make it too complex** - Keep dialogs simple
|
||||
- **Don't forget accessibility** - Keyboard navigation
|
||||
|
||||
## Styling Custom dialogs
|
||||
|
||||
### Modern dialog Style
|
||||
|
||||
```css
|
||||
.dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background: white;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
--wails-draggable: drag;
|
||||
padding: 16px;
|
||||
background: #f5f5f5;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
padding: 16px;
|
||||
background: #f5f5f5;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
button.primary {
|
||||
background: #007aff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
button.secondary {
|
||||
background: #e0e0e0;
|
||||
color: #333;
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGrid>
|
||||
<Card title="Message dialogs" icon="information">
|
||||
Standard info, warning, error dialogs.
|
||||
|
||||
[Learn More →](/features/dialogs/message)
|
||||
</Card>
|
||||
|
||||
<Card title="File dialogs" icon="document">
|
||||
Open, save, folder selection.
|
||||
|
||||
[Learn More →](/features/dialogs/file)
|
||||
</Card>
|
||||
|
||||
<Card title="Windows" icon="laptop">
|
||||
Learn about window management.
|
||||
|
||||
[Learn More →](/features/windows/basics)
|
||||
</Card>
|
||||
|
||||
<Card title="Events" icon="star">
|
||||
Use events for dialog communication.
|
||||
|
||||
[Learn More →](/features/events/system)
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [custom dialog examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/dialogs).
|
||||
591
docs/src/content/docs/features/dialogs/file.mdx
Normal file
591
docs/src/content/docs/features/dialogs/file.mdx
Normal file
|
|
@ -0,0 +1,591 @@
|
|||
---
|
||||
title: File dialogs
|
||||
description: Open, save, and folder selection dialogs
|
||||
sidebar:
|
||||
order: 3
|
||||
---
|
||||
|
||||
import { Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## File dialogs
|
||||
|
||||
Wails provides **native file dialogs** with platform-appropriate appearance for opening files, saving files, and selecting folders. Simple API with file type filtering, multiple selection support, and default locations.
|
||||
## Open File dialog
|
||||
|
||||
Select files to open:
|
||||
|
||||
```go
|
||||
path, err := app.Dialog.OpenFile().
|
||||
SetTitle("Select Image").
|
||||
AddFilter("Images", "*.png;*.jpg;*.gif").
|
||||
AddFilter("All Files", "*.*").
|
||||
PromptForSingleSelection()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
openFile(path)
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Open documents
|
||||
- Import files
|
||||
- Load images
|
||||
- Select configuration files
|
||||
|
||||
### Single File Selection
|
||||
|
||||
```go
|
||||
path, err := app.Dialog.OpenFile().
|
||||
SetTitle("Open Document").
|
||||
AddFilter("Text Files", "*.txt").
|
||||
AddFilter("All Files", "*.*").
|
||||
PromptForSingleSelection()
|
||||
|
||||
if err != nil {
|
||||
// User cancelled or error occurred
|
||||
return
|
||||
}
|
||||
|
||||
// Use selected file
|
||||
data, _ := os.ReadFile(path)
|
||||
```
|
||||
|
||||
### Multiple File Selection
|
||||
|
||||
```go
|
||||
paths, err := app.Dialog.OpenFile().
|
||||
SetTitle("Select Images").
|
||||
AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif").
|
||||
PromptForMultipleSelection()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Process all selected files
|
||||
for _, path := range paths {
|
||||
processFile(path)
|
||||
}
|
||||
```
|
||||
|
||||
### With Default Directory
|
||||
|
||||
```go
|
||||
path, err := app.Dialog.OpenFile().
|
||||
SetTitle("Open File").
|
||||
SetDirectory("/Users/me/Documents").
|
||||
PromptForSingleSelection()
|
||||
```
|
||||
|
||||
## Save File dialog
|
||||
|
||||
Choose where to save:
|
||||
|
||||
```go
|
||||
path, err := app.Dialog.SaveFile().
|
||||
SetTitle("Save Document").
|
||||
SetFilename("document.txt").
|
||||
AddFilter("Text Files", "*.txt").
|
||||
AddFilter("All Files", "*.*").
|
||||
PromptForSingleSelection()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
saveFile(path, data)
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Save documents
|
||||
- Export data
|
||||
- Create new files
|
||||
- Save as...
|
||||
|
||||
### With Default Filename
|
||||
|
||||
```go
|
||||
path, err := app.Dialog.SaveFile().
|
||||
SetTitle("Export Data").
|
||||
SetFilename("export.csv").
|
||||
AddFilter("CSV Files", "*.csv").
|
||||
PromptForSingleSelection()
|
||||
```
|
||||
|
||||
### With Default Directory
|
||||
|
||||
```go
|
||||
path, err := app.Dialog.SaveFile().
|
||||
SetTitle("Save File").
|
||||
SetDirectory("/Users/me/Documents").
|
||||
SetFilename("untitled.txt").
|
||||
PromptForSingleSelection()
|
||||
```
|
||||
|
||||
### Overwrite Confirmation
|
||||
|
||||
```go
|
||||
path, err := app.Dialog.SaveFile().
|
||||
SetTitle("Save File").
|
||||
SetFilename("document.txt").
|
||||
PromptForSingleSelection()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if file exists
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
dialog := app.Dialog.Question().
|
||||
SetTitle("Confirm Overwrite").
|
||||
SetMessage("File already exists. Overwrite?")
|
||||
|
||||
overwriteBtn := dialog.AddButton("Overwrite")
|
||||
cancelBtn := dialog.AddButton("Cancel")
|
||||
cancelBtn.SetAsDefault()
|
||||
cancelBtn.SetAsCancel()
|
||||
|
||||
overwriteBtn.OnClick(func() {
|
||||
saveFile(path, data)
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
return
|
||||
}
|
||||
|
||||
saveFile(path, data)
|
||||
```
|
||||
|
||||
## Select Folder dialog
|
||||
|
||||
Choose a directory:
|
||||
|
||||
```go
|
||||
path, err := app.Dialog.OpenFile().
|
||||
CanChooseDirectories(true).
|
||||
CanChooseFiles(false).
|
||||
SetTitle("Select Output Folder").
|
||||
PromptForSingleSelection()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
exportToFolder(path)
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Choose output directory
|
||||
- Select workspace
|
||||
- Pick backup location
|
||||
- Choose installation directory
|
||||
|
||||
### With Default Directory
|
||||
|
||||
```go
|
||||
path, err := app.Dialog.OpenFile().
|
||||
CanChooseDirectories(true).
|
||||
CanChooseFiles(false).
|
||||
SetTitle("Select Folder").
|
||||
SetDirectory("/Users/me/Documents").
|
||||
PromptForSingleSelection()
|
||||
```
|
||||
|
||||
## File Filters
|
||||
|
||||
### Basic Filters
|
||||
|
||||
```go
|
||||
path, _ := app.Dialog.OpenFile().
|
||||
AddFilter("Text Files", "*.txt").
|
||||
AddFilter("All Files", "*.*").
|
||||
PromptForSingleSelection()
|
||||
```
|
||||
|
||||
### Multiple Extensions
|
||||
|
||||
```go
|
||||
path, _ := app.Dialog.OpenFile().
|
||||
AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif;*.bmp").
|
||||
AddFilter("Documents", "*.txt;*.doc;*.docx;*.pdf").
|
||||
AddFilter("All Files", "*.*").
|
||||
PromptForSingleSelection()
|
||||
```
|
||||
|
||||
### Platform-Specific Patterns
|
||||
|
||||
**Windows:**
|
||||
```go
|
||||
// Semicolon-separated
|
||||
// Pattern: "*.png;*.jpg;*.gif"
|
||||
```
|
||||
|
||||
**macOS/Linux:**
|
||||
```go
|
||||
// Space or comma-separated (both work)
|
||||
// Pattern: "*.png *.jpg *.gif"
|
||||
// or
|
||||
// Pattern: "*.png,*.jpg,*.gif"
|
||||
```
|
||||
|
||||
**Cross-platform (recommended):**
|
||||
```go
|
||||
// Use semicolons (works on all platforms)
|
||||
// Pattern: "*.png;*.jpg;*.gif"
|
||||
```
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Open Image File
|
||||
|
||||
```go
|
||||
func openImage() (image.Image, error) {
|
||||
path, err := app.Dialog.OpenFile().
|
||||
SetTitle("Select Image").
|
||||
AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif;*.bmp").
|
||||
PromptForSingleSelection()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Open and decode image
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Open Failed").
|
||||
SetMessage(err.Error()).
|
||||
Show()
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
img, _, err := image.Decode(file)
|
||||
if err != nil {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Invalid Image").
|
||||
SetMessage("Could not decode image file.").
|
||||
Show()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return img, nil
|
||||
}
|
||||
```
|
||||
|
||||
### Save Document with Validation
|
||||
|
||||
```go
|
||||
func saveDocument(content string) error {
|
||||
path, err := app.Dialog.SaveFile().
|
||||
SetTitle("Save Document").
|
||||
SetFilename("document.txt").
|
||||
AddFilter("Text Files", "*.txt").
|
||||
AddFilter("Markdown Files", "*.md").
|
||||
AddFilter("All Files", "*.*").
|
||||
PromptForSingleSelection()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate extension
|
||||
ext := filepath.Ext(path)
|
||||
if ext != ".txt" && ext != ".md" {
|
||||
dialog := app.Dialog.Question().
|
||||
SetTitle("Confirm Extension").
|
||||
SetMessage(fmt.Sprintf("Save as %s file?", ext))
|
||||
|
||||
saveBtn := dialog.AddButton("Save")
|
||||
cancelBtn := dialog.AddButton("Cancel")
|
||||
cancelBtn.SetAsCancel()
|
||||
|
||||
confirmed := make(chan bool, 1)
|
||||
saveBtn.OnClick(func() {
|
||||
confirmed <- true
|
||||
})
|
||||
cancelBtn.OnClick(func() {
|
||||
confirmed <- false
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
|
||||
if !<-confirmed {
|
||||
return errors.New("cancelled")
|
||||
}
|
||||
}
|
||||
|
||||
// Save file
|
||||
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Save Failed").
|
||||
SetMessage(err.Error()).
|
||||
Show()
|
||||
return err
|
||||
}
|
||||
|
||||
app.Dialog.Info().
|
||||
SetTitle("Saved").
|
||||
SetMessage("Document saved successfully!").
|
||||
Show()
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### Batch File Processing
|
||||
|
||||
```go
|
||||
func processMultipleFiles() {
|
||||
paths, err := app.Dialog.OpenFile().
|
||||
SetTitle("Select Files to Process").
|
||||
AddFilter("Images", "*.png;*.jpg").
|
||||
PromptForMultipleSelection()
|
||||
|
||||
if err != nil || len(paths) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Confirm processing
|
||||
dialog := app.Dialog.Question().
|
||||
SetTitle("Confirm Processing").
|
||||
SetMessage(fmt.Sprintf("Process %d file(s)?", len(paths)))
|
||||
|
||||
processBtn := dialog.AddButton("Process")
|
||||
cancelBtn := dialog.AddButton("Cancel")
|
||||
cancelBtn.SetAsCancel()
|
||||
|
||||
processBtn.OnClick(func() {
|
||||
// Process files
|
||||
var errs []error
|
||||
for i, path := range paths {
|
||||
if err := processFile(path); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
// Update progress
|
||||
app.Event.Emit("progress", map[string]interface{}{
|
||||
"current": i + 1,
|
||||
"total": len(paths),
|
||||
})
|
||||
}
|
||||
|
||||
// Show results
|
||||
if len(errs) > 0 {
|
||||
app.Dialog.Warning().
|
||||
SetTitle("Processing Complete").
|
||||
SetMessage(fmt.Sprintf("Processed %d files with %d errors.",
|
||||
len(paths), len(errs))).
|
||||
Show()
|
||||
} else {
|
||||
app.Dialog.Info().
|
||||
SetTitle("Success").
|
||||
SetMessage(fmt.Sprintf("Processed %d files successfully!", len(paths))).
|
||||
Show()
|
||||
}
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
}
|
||||
```
|
||||
|
||||
### Export with Folder Selection
|
||||
|
||||
```go
|
||||
func exportData(data []byte) {
|
||||
// Select output folder
|
||||
folder, err := app.Dialog.OpenFile().
|
||||
CanChooseDirectories(true).
|
||||
CanChooseFiles(false).
|
||||
SetTitle("Select Export Folder").
|
||||
SetDirectory(getDefaultExportFolder()).
|
||||
PromptForSingleSelection()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Generate filename
|
||||
filename := fmt.Sprintf("export_%s.csv",
|
||||
time.Now().Format("2006-01-02_15-04-05"))
|
||||
path := filepath.Join(folder, filename)
|
||||
|
||||
// Save file
|
||||
if err := os.WriteFile(path, data, 0644); err != nil {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Export Failed").
|
||||
SetMessage(err.Error()).
|
||||
Show()
|
||||
return
|
||||
}
|
||||
|
||||
// Show success with option to open folder
|
||||
dialog := app.Dialog.Question().
|
||||
SetTitle("Export Complete").
|
||||
SetMessage(fmt.Sprintf("Exported to %s", filename))
|
||||
|
||||
openFolderBtn := dialog.AddButton("Open Folder")
|
||||
okBtn := dialog.AddButton("OK")
|
||||
okBtn.SetAsDefault()
|
||||
|
||||
openFolderBtn.OnClick(func() {
|
||||
openFolder(folder)
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
}
|
||||
```
|
||||
|
||||
### Import with Validation
|
||||
|
||||
```go
|
||||
func importConfiguration() error {
|
||||
path, err := app.Dialog.OpenFile().
|
||||
SetTitle("Import Configuration").
|
||||
AddFilter("JSON Files", "*.json").
|
||||
AddFilter("YAML Files", "*.yaml;*.yml").
|
||||
PromptForSingleSelection()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read file
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Read Failed").
|
||||
SetMessage(err.Error()).
|
||||
Show()
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate configuration
|
||||
config, err := parseConfig(data)
|
||||
if err != nil {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Invalid Configuration").
|
||||
SetMessage("File is not a valid configuration.").
|
||||
Show()
|
||||
return err
|
||||
}
|
||||
|
||||
// Confirm import
|
||||
dialog := app.Dialog.Question().
|
||||
SetTitle("Confirm Import").
|
||||
SetMessage("Import this configuration?")
|
||||
|
||||
importBtn := dialog.AddButton("Import")
|
||||
cancelBtn := dialog.AddButton("Cancel")
|
||||
cancelBtn.SetAsCancel()
|
||||
|
||||
importResult := make(chan bool, 1)
|
||||
importBtn.OnClick(func() {
|
||||
importResult <- true
|
||||
})
|
||||
cancelBtn.OnClick(func() {
|
||||
importResult <- false
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
|
||||
if !<-importResult {
|
||||
return errors.New("cancelled")
|
||||
}
|
||||
|
||||
// Apply configuration
|
||||
if err := applyConfig(config); err != nil {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Import Failed").
|
||||
SetMessage(err.Error()).
|
||||
Show()
|
||||
return err
|
||||
}
|
||||
|
||||
app.Dialog.Info().
|
||||
SetTitle("Success").
|
||||
SetMessage("Configuration imported successfully!").
|
||||
Show()
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do
|
||||
|
||||
- **Provide file filters** - Help users find files
|
||||
- **Set appropriate titles** - Clear context
|
||||
- **Use default directories** - Start in logical location
|
||||
- **Validate selections** - Check file types
|
||||
- **Handle cancellation** - User might cancel
|
||||
- **Show confirmation** - For destructive actions
|
||||
- **Provide feedback** - Success/error messages
|
||||
|
||||
### Don't
|
||||
|
||||
- **Don't skip validation** - Check file types
|
||||
- **Don't ignore errors** - Handle cancellation
|
||||
- **Don't use generic filters** - Be specific
|
||||
- **Don't forget "All Files"** - Always include as option
|
||||
- **Don't hardcode paths** - Use user's home directory
|
||||
- **Don't assume file exists** - Check before opening
|
||||
|
||||
## Platform Differences
|
||||
|
||||
### macOS
|
||||
|
||||
- Native NSOpenPanel/NSSavePanel
|
||||
- Sheet-style when attached to window
|
||||
- Follows system theme
|
||||
- Supports Quick Look preview
|
||||
- Tags and favourites integration
|
||||
|
||||
### Windows
|
||||
|
||||
- Native File Open/Save dialogs
|
||||
- Follows system theme
|
||||
- Recent files integration
|
||||
- Network location support
|
||||
|
||||
### Linux
|
||||
|
||||
- GTK file chooser
|
||||
- Varies by desktop environment
|
||||
- Follows desktop theme
|
||||
- Recent files support
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGrid>
|
||||
<Card title="Message dialogs" icon="information">
|
||||
Info, warning, and error dialogs.
|
||||
|
||||
[Learn More →](/features/dialogs/message)
|
||||
</Card>
|
||||
|
||||
<Card title="Custom dialogs" icon="puzzle">
|
||||
Create custom dialog windows.
|
||||
|
||||
[Learn More →](/features/dialogs/custom)
|
||||
</Card>
|
||||
|
||||
<Card title="Bindings" icon="rocket">
|
||||
Call Go functions from JavaScript.
|
||||
|
||||
[Learn More →](/features/bindings/methods)
|
||||
</Card>
|
||||
|
||||
<Card title="Events" icon="star">
|
||||
Use events for progress updates.
|
||||
|
||||
[Learn More →](/features/events/system)
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [file dialog examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/dialogs).
|
||||
518
docs/src/content/docs/features/dialogs/message.mdx
Normal file
518
docs/src/content/docs/features/dialogs/message.mdx
Normal file
|
|
@ -0,0 +1,518 @@
|
|||
---
|
||||
title: Message dialogs
|
||||
description: Display information, warnings, errors, and questions
|
||||
sidebar:
|
||||
order: 2
|
||||
---
|
||||
|
||||
import { Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Message dialogs
|
||||
|
||||
Wails provides **native message dialogs** with platform-appropriate appearance: info, warning, error, and question dialogs with customisable titles, messages, and buttons. Simple API, native behaviour, accessible by default.
|
||||
## Information dialog
|
||||
|
||||
Display informational messages:
|
||||
|
||||
```go
|
||||
app.Dialog.Info().
|
||||
SetTitle("Success").
|
||||
SetMessage("File saved successfully!").
|
||||
Show()
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Success confirmations
|
||||
- Completion notices
|
||||
- Informational messages
|
||||
- Status updates
|
||||
|
||||
**Example - Save confirmation:**
|
||||
|
||||
```go
|
||||
func saveFile(path string, data []byte) error {
|
||||
if err := os.WriteFile(path, data, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
app.Dialog.Info().
|
||||
SetTitle("File Saved").
|
||||
SetMessage(fmt.Sprintf("Saved to %s", filepath.Base(path))).
|
||||
Show()
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Warning dialog
|
||||
|
||||
Show warnings:
|
||||
|
||||
```go
|
||||
app.Dialog.Warning().
|
||||
SetTitle("Warning").
|
||||
SetMessage("This action cannot be undone.").
|
||||
Show()
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Non-critical warnings
|
||||
- Deprecation notices
|
||||
- Caution messages
|
||||
- Potential issues
|
||||
|
||||
**Example - Disk space warning:**
|
||||
|
||||
```go
|
||||
func checkDiskSpace() {
|
||||
available := getDiskSpace()
|
||||
|
||||
if available < 100*1024*1024 { // Less than 100MB
|
||||
app.Dialog.Warning().
|
||||
SetTitle("Low Disk Space").
|
||||
SetMessage(fmt.Sprintf("Only %d MB available.", available/(1024*1024))).
|
||||
Show()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error dialog
|
||||
|
||||
Display errors:
|
||||
|
||||
```go
|
||||
app.Dialog.Error().
|
||||
SetTitle("Error").
|
||||
SetMessage("Failed to connect to server.").
|
||||
Show()
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Error messages
|
||||
- Failure notifications
|
||||
- Exception handling
|
||||
- Critical issues
|
||||
|
||||
**Example - Network error:**
|
||||
|
||||
```go
|
||||
func fetchData(url string) ([]byte, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Network Error").
|
||||
SetMessage(fmt.Sprintf("Failed to connect: %v", err)).
|
||||
Show()
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
```
|
||||
|
||||
## Question dialog
|
||||
|
||||
Ask users questions using button callbacks:
|
||||
|
||||
```go
|
||||
dialog := app.Dialog.Question().
|
||||
SetTitle("Confirm").
|
||||
SetMessage("Save changes before closing?")
|
||||
|
||||
saveBtn := dialog.AddButton("Save")
|
||||
dontSaveBtn := dialog.AddButton("Don't Save")
|
||||
cancelBtn := dialog.AddButton("Cancel")
|
||||
saveBtn.SetAsDefault()
|
||||
cancelBtn.SetAsCancel()
|
||||
|
||||
saveBtn.OnClick(func() {
|
||||
saveChanges()
|
||||
})
|
||||
|
||||
dontSaveBtn.OnClick(func() {
|
||||
// Continue without saving
|
||||
})
|
||||
|
||||
cancelBtn.OnClick(func() {
|
||||
// Don't close
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Confirm actions
|
||||
- Yes/No questions
|
||||
- Multiple choice
|
||||
- User decisions
|
||||
|
||||
**Example - Unsaved changes:**
|
||||
|
||||
```go
|
||||
func closeDocument() {
|
||||
if !hasUnsavedChanges() {
|
||||
closeWindow()
|
||||
return
|
||||
}
|
||||
|
||||
dialog := app.Dialog.Question().
|
||||
SetTitle("Unsaved Changes").
|
||||
SetMessage("Do you want to save your changes?")
|
||||
|
||||
saveBtn := dialog.AddButton("Save")
|
||||
dontSaveBtn := dialog.AddButton("Don't Save")
|
||||
cancelBtn := dialog.AddButton("Cancel")
|
||||
saveBtn.SetAsDefault()
|
||||
cancelBtn.SetAsCancel()
|
||||
|
||||
saveBtn.OnClick(func() {
|
||||
saveDocument()
|
||||
closeWindow()
|
||||
})
|
||||
|
||||
dontSaveBtn.OnClick(func() {
|
||||
closeWindow()
|
||||
})
|
||||
|
||||
// Cancel button does nothing - dialog just closes
|
||||
|
||||
dialog.Show()
|
||||
}
|
||||
```
|
||||
|
||||
## dialog Options
|
||||
|
||||
### Title and Message
|
||||
|
||||
```go
|
||||
dialog := app.Dialog.Info().
|
||||
SetTitle("Operation Complete").
|
||||
SetMessage("All files have been processed successfully.")
|
||||
```
|
||||
|
||||
**Best practices:**
|
||||
- **Title:** Short, descriptive (2-5 words)
|
||||
- **Message:** Clear, specific, actionable
|
||||
- **Avoid jargon:** Use plain language
|
||||
|
||||
### Buttons
|
||||
|
||||
**Single button (Info/Warning/Error):**
|
||||
|
||||
```go
|
||||
// Default "OK" button
|
||||
app.Dialog.Info().
|
||||
SetMessage("Done!").
|
||||
Show()
|
||||
```
|
||||
|
||||
**Multiple buttons (Question) with callbacks:**
|
||||
|
||||
```go
|
||||
dialog := app.Dialog.Question().
|
||||
SetMessage("Choose an action")
|
||||
|
||||
opt1Btn := dialog.AddButton("Option 1")
|
||||
opt2Btn := dialog.AddButton("Option 2")
|
||||
opt3Btn := dialog.AddButton("Option 3")
|
||||
|
||||
opt1Btn.OnClick(func() {
|
||||
// handle option 1
|
||||
})
|
||||
|
||||
opt2Btn.OnClick(func() {
|
||||
// handle option 2
|
||||
})
|
||||
|
||||
opt3Btn.OnClick(func() {
|
||||
// handle option 3
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
```
|
||||
|
||||
**Default button:**
|
||||
|
||||
```go
|
||||
dialog := app.Dialog.Question().
|
||||
SetMessage("Delete file?")
|
||||
|
||||
deleteBtn := dialog.AddButton("Delete")
|
||||
cancelBtn := dialog.AddButton("Cancel")
|
||||
cancelBtn.SetAsDefault() // Safe option
|
||||
cancelBtn.SetAsCancel()
|
||||
|
||||
deleteBtn.OnClick(func() {
|
||||
// handle delete
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
```
|
||||
|
||||
**Best practices:**
|
||||
- **1-3 buttons:** Don't overwhelm users
|
||||
- **Clear labels:** "Save" not "OK"
|
||||
- **Safe default:** Non-destructive action
|
||||
- **Order matters:** Most likely action first (except Cancel)
|
||||
|
||||
### Window Attachment
|
||||
|
||||
Attach to specific window:
|
||||
|
||||
```go
|
||||
dialog := app.Dialog.Question().
|
||||
SetMessage("Window-specific question").
|
||||
AttachToWindow(window)
|
||||
|
||||
dialog.Show()
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- dialog appears on correct window
|
||||
- Parent window disabled whilst shown
|
||||
- Better multi-window UX
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Confirm Destructive Action
|
||||
|
||||
```go
|
||||
func deleteFiles(paths []string) {
|
||||
// Confirm deletion
|
||||
message := fmt.Sprintf("Delete %d file(s)?", len(paths))
|
||||
if len(paths) == 1 {
|
||||
message = fmt.Sprintf("Delete %s?", filepath.Base(paths[0]))
|
||||
}
|
||||
|
||||
dialog := app.Dialog.Question().
|
||||
SetTitle("Confirm Delete").
|
||||
SetMessage(message)
|
||||
|
||||
deleteBtn := dialog.AddButton("Delete")
|
||||
cancelBtn := dialog.AddButton("Cancel")
|
||||
cancelBtn.SetAsDefault()
|
||||
cancelBtn.SetAsCancel()
|
||||
|
||||
deleteBtn.OnClick(func() {
|
||||
// Perform deletion
|
||||
var errs []error
|
||||
for _, path := range paths {
|
||||
if err := os.Remove(path); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Show result
|
||||
if len(errs) > 0 {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Delete Failed").
|
||||
SetMessage(fmt.Sprintf("Failed to delete %d file(s)", len(errs))).
|
||||
Show()
|
||||
} else {
|
||||
app.Dialog.Info().
|
||||
SetTitle("Delete Complete").
|
||||
SetMessage(fmt.Sprintf("Deleted %d file(s)", len(paths))).
|
||||
Show()
|
||||
}
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling with Retry
|
||||
|
||||
```go
|
||||
func connectToServer(url string, attempts int) {
|
||||
if err := tryConnect(url); err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if attempts >= 3 {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Connection Failed").
|
||||
SetMessage("Could not connect after 3 attempts.").
|
||||
Show()
|
||||
return
|
||||
}
|
||||
|
||||
dialog := app.Dialog.Question().
|
||||
SetTitle("Connection Failed").
|
||||
SetMessage("Failed to connect. Retry?")
|
||||
|
||||
retryBtn := dialog.AddButton("Retry")
|
||||
cancelBtn := dialog.AddButton("Cancel")
|
||||
cancelBtn.SetAsCancel()
|
||||
|
||||
retryBtn.OnClick(func() {
|
||||
connectToServer(url, attempts+1)
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
}
|
||||
```
|
||||
|
||||
### Multi-Step Workflow
|
||||
|
||||
```go
|
||||
func exportAndEmail() {
|
||||
// Step 1: Confirm export
|
||||
dialog := app.Dialog.Question().
|
||||
SetTitle("Export and Email").
|
||||
SetMessage("Export data and send via email?")
|
||||
|
||||
continueBtn := dialog.AddButton("Continue")
|
||||
cancelBtn := dialog.AddButton("Cancel")
|
||||
cancelBtn.SetAsCancel()
|
||||
|
||||
continueBtn.OnClick(func() {
|
||||
// Step 2: Export data
|
||||
data, err := exportData()
|
||||
if err != nil {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Export Failed").
|
||||
SetMessage(err.Error()).
|
||||
Show()
|
||||
return
|
||||
}
|
||||
|
||||
// Step 3: Send email
|
||||
if err := sendEmail(data); err != nil {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Email Failed").
|
||||
SetMessage(err.Error()).
|
||||
Show()
|
||||
return
|
||||
}
|
||||
|
||||
// Step 4: Success
|
||||
app.Dialog.Info().
|
||||
SetTitle("Success").
|
||||
SetMessage("Data exported and emailed successfully!").
|
||||
Show()
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
}
|
||||
```
|
||||
|
||||
### Validation with Feedback
|
||||
|
||||
```go
|
||||
func validateAndSave(data string) {
|
||||
// Validate
|
||||
if err := validate(data); err != nil {
|
||||
app.Dialog.Warning().
|
||||
SetTitle("Validation Warning").
|
||||
SetMessage(err.Error()).
|
||||
Show()
|
||||
|
||||
dialog := app.Dialog.Question().
|
||||
SetMessage("Save anyway?")
|
||||
|
||||
saveBtn := dialog.AddButton("Save")
|
||||
cancelBtn := dialog.AddButton("Cancel")
|
||||
cancelBtn.SetAsDefault()
|
||||
cancelBtn.SetAsCancel()
|
||||
|
||||
saveBtn.OnClick(func() {
|
||||
doSave(data)
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
return
|
||||
}
|
||||
|
||||
doSave(data)
|
||||
}
|
||||
|
||||
func doSave(data string) {
|
||||
if err := save(data); err != nil {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Save Failed").
|
||||
SetMessage(err.Error()).
|
||||
Show()
|
||||
return
|
||||
}
|
||||
|
||||
app.Dialog.Info().
|
||||
SetTitle("Saved").
|
||||
SetMessage("Data saved successfully!").
|
||||
Show()
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do
|
||||
|
||||
- **Be specific** - "File saved to Documents" not "Success"
|
||||
- **Use appropriate type** - Error for errors, Warning for warnings
|
||||
- **Provide context** - Include relevant details
|
||||
- **Use clear button labels** - "Delete" not "OK"
|
||||
- **Set safe defaults** - Non-destructive action
|
||||
- **Handle cancellation** - User might close dialog
|
||||
|
||||
### Don't
|
||||
|
||||
- **Don't overuse** - Interrupts workflow
|
||||
- **Don't use for frequent updates** - Use notifications instead
|
||||
- **Don't use generic messages** - "Error" tells nothing
|
||||
- **Don't block unnecessarily** - Consider async alternatives
|
||||
- **Don't use technical jargon** - Plain language
|
||||
|
||||
## Platform Differences
|
||||
|
||||
### macOS
|
||||
|
||||
- Sheet-style when attached to window
|
||||
- Standard keyboard shortcuts (⌘. for Cancel)
|
||||
- Follows system theme automatically
|
||||
- Accessibility built-in
|
||||
|
||||
### Windows
|
||||
|
||||
- Modal dialogs
|
||||
- TaskDialog appearance
|
||||
- Esc for Cancel
|
||||
- Follows system theme
|
||||
|
||||
### Linux
|
||||
|
||||
- GTK dialogs
|
||||
- Varies by desktop environment
|
||||
- Follows desktop theme
|
||||
- Standard keyboard navigation
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGrid>
|
||||
<Card title="File dialogs" icon="document">
|
||||
Open, save, and folder selection.
|
||||
|
||||
[Learn More →](/features/dialogs/file)
|
||||
</Card>
|
||||
|
||||
<Card title="Custom dialogs" icon="puzzle">
|
||||
Create custom dialog windows.
|
||||
|
||||
[Learn More →](/features/dialogs/custom)
|
||||
</Card>
|
||||
|
||||
<Card title="Notifications" icon="bell">
|
||||
Non-intrusive notifications.
|
||||
|
||||
[Learn More →](/features/notifications)
|
||||
</Card>
|
||||
|
||||
<Card title="Events" icon="star">
|
||||
Use events for non-blocking communication.
|
||||
|
||||
[Learn More →](/features/events/system)
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [dialog examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/dialogs).
|
||||
494
docs/src/content/docs/features/dialogs/overview.mdx
Normal file
494
docs/src/content/docs/features/dialogs/overview.mdx
Normal file
|
|
@ -0,0 +1,494 @@
|
|||
---
|
||||
title: dialogs Overview
|
||||
description: Display native system dialogs in your application
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Native dialogs
|
||||
|
||||
Wails provides **native system dialogs** that work across all platforms: message dialogs (info, warning, error, question), file dialogs (open, save, folder), and custom dialog windows with platform-native appearance and behaviour.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
// Information dialog
|
||||
app.Dialog.Info().
|
||||
SetTitle("Success").
|
||||
SetMessage("File saved successfully!").
|
||||
Show()
|
||||
|
||||
// Question dialog with button callbacks
|
||||
dialog := app.Dialog.Question().
|
||||
SetTitle("Confirm").
|
||||
SetMessage("Delete this file?")
|
||||
|
||||
deleteBtn := dialog.AddButton("Delete")
|
||||
cancelBtn := dialog.AddButton("Cancel")
|
||||
cancelBtn.SetAsDefault()
|
||||
cancelBtn.SetAsCancel()
|
||||
|
||||
deleteBtn.OnClick(func() {
|
||||
deleteFile()
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
|
||||
// File open dialog
|
||||
path, _ := app.Dialog.OpenFile().
|
||||
SetTitle("Select Image").
|
||||
AddFilter("Images", "*.png;*.jpg").
|
||||
PromptForSingleSelection()
|
||||
```
|
||||
|
||||
**That's it!** Native dialogs with minimal code.
|
||||
|
||||
## dialog Types
|
||||
|
||||
### Information dialog
|
||||
|
||||
Display simple messages:
|
||||
|
||||
```go
|
||||
app.Dialog.Info().
|
||||
SetTitle("Welcome").
|
||||
SetMessage("Welcome to our application!").
|
||||
Show()
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Success messages
|
||||
- Informational notices
|
||||
- Completion confirmations
|
||||
|
||||
### Warning dialog
|
||||
|
||||
Show warnings:
|
||||
|
||||
```go
|
||||
app.Dialog.Warning().
|
||||
SetTitle("Warning").
|
||||
SetMessage("This action cannot be undone.").
|
||||
Show()
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Non-critical warnings
|
||||
- Deprecation notices
|
||||
- Caution messages
|
||||
|
||||
### Error dialog
|
||||
|
||||
Display errors:
|
||||
|
||||
```go
|
||||
app.Dialog.Error().
|
||||
SetTitle("Error").
|
||||
SetMessage("Failed to save file: " + err.Error()).
|
||||
Show()
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Error messages
|
||||
- Failure notifications
|
||||
- Exception handling
|
||||
|
||||
### Question dialog
|
||||
|
||||
Ask users questions:
|
||||
|
||||
```go
|
||||
dialog := app.Dialog.Question().
|
||||
SetTitle("Confirm Delete").
|
||||
SetMessage("Are you sure you want to delete this file?")
|
||||
|
||||
deleteBtn := dialog.AddButton("Delete")
|
||||
cancelBtn := dialog.AddButton("Cancel")
|
||||
cancelBtn.SetAsDefault()
|
||||
cancelBtn.SetAsCancel()
|
||||
|
||||
deleteBtn.OnClick(func() {
|
||||
deleteFile()
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Confirm actions
|
||||
- Yes/No questions
|
||||
- Multiple choice
|
||||
|
||||
## File dialogs
|
||||
|
||||
### Open File dialog
|
||||
|
||||
Select files to open:
|
||||
|
||||
```go
|
||||
path, err := app.Dialog.OpenFile().
|
||||
SetTitle("Select Image").
|
||||
AddFilter("Images", "*.png;*.jpg;*.gif").
|
||||
AddFilter("All Files", "*.*").
|
||||
PromptForSingleSelection()
|
||||
|
||||
if err == nil {
|
||||
openFile(path)
|
||||
}
|
||||
```
|
||||
|
||||
**Multiple selection:**
|
||||
|
||||
```go
|
||||
paths, err := app.Dialog.OpenFile().
|
||||
SetTitle("Select Images").
|
||||
AddFilter("Images", "*.png;*.jpg").
|
||||
PromptForMultipleSelection()
|
||||
|
||||
for _, path := range paths {
|
||||
processFile(path)
|
||||
}
|
||||
```
|
||||
|
||||
### Save File dialog
|
||||
|
||||
Choose where to save:
|
||||
|
||||
```go
|
||||
path, err := app.Dialog.SaveFile().
|
||||
SetTitle("Save Document").
|
||||
SetFilename("document.txt").
|
||||
AddFilter("Text Files", "*.txt").
|
||||
AddFilter("All Files", "*.*").
|
||||
PromptForSingleSelection()
|
||||
|
||||
if err == nil {
|
||||
saveFile(path)
|
||||
}
|
||||
```
|
||||
|
||||
### Select Folder dialog
|
||||
|
||||
Choose a directory:
|
||||
|
||||
```go
|
||||
path, err := app.Dialog.OpenFile().
|
||||
CanChooseDirectories(true).
|
||||
CanChooseFiles(false).
|
||||
SetTitle("Select Output Folder").
|
||||
PromptForSingleSelection()
|
||||
|
||||
if err == nil {
|
||||
exportToFolder(path)
|
||||
}
|
||||
```
|
||||
|
||||
## dialog Options
|
||||
|
||||
### Title and Message
|
||||
|
||||
```go
|
||||
dialog := app.Dialog.Info().
|
||||
SetTitle("Success").
|
||||
SetMessage("Operation completed successfully!")
|
||||
```
|
||||
|
||||
### Buttons
|
||||
|
||||
**Single button (Info/Warning/Error):**
|
||||
|
||||
```go
|
||||
// Default "OK" button
|
||||
app.Dialog.Info().
|
||||
SetMessage("Done!").
|
||||
Show()
|
||||
```
|
||||
|
||||
**Multiple buttons (Question) with callbacks:**
|
||||
|
||||
```go
|
||||
dialog := app.Dialog.Question().
|
||||
SetMessage("Choose action")
|
||||
|
||||
opt1Btn := dialog.AddButton("Option 1")
|
||||
opt2Btn := dialog.AddButton("Option 2")
|
||||
opt3Btn := dialog.AddButton("Option 3")
|
||||
|
||||
opt1Btn.OnClick(func() {
|
||||
// handle option 1
|
||||
})
|
||||
|
||||
opt2Btn.OnClick(func() {
|
||||
// handle option 2
|
||||
})
|
||||
|
||||
opt3Btn.OnClick(func() {
|
||||
// handle option 3
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
```
|
||||
|
||||
**Default button:**
|
||||
|
||||
```go
|
||||
dialog := app.Dialog.Question().
|
||||
SetMessage("Delete file?")
|
||||
|
||||
deleteBtn := dialog.AddButton("Delete")
|
||||
cancelBtn := dialog.AddButton("Cancel")
|
||||
cancelBtn.SetAsDefault() // Safe option highlighted by default
|
||||
cancelBtn.SetAsCancel()
|
||||
|
||||
deleteBtn.OnClick(func() {
|
||||
// handle delete
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
```
|
||||
|
||||
### Window Attachment
|
||||
|
||||
Attach dialog to specific window:
|
||||
|
||||
```go
|
||||
dialog := app.Dialog.Info().
|
||||
SetMessage("Window-specific message").
|
||||
AttachToWindow(window)
|
||||
|
||||
dialog.Show()
|
||||
```
|
||||
|
||||
**Behaviour:**
|
||||
- dialog appears centred on parent window
|
||||
- Parent window disabled whilst dialog shown
|
||||
- dialog moves with parent window (macOS)
|
||||
|
||||
## Platform Behaviour
|
||||
|
||||
<Tabs syncKey="platform">
|
||||
<TabItem label="macOS" icon="apple">
|
||||
**macOS dialogs:**
|
||||
|
||||
- Native NSAlert appearance
|
||||
- Follow system theme (light/dark)
|
||||
- Support keyboard navigation
|
||||
- Standard shortcuts (⌘. for Cancel)
|
||||
- Accessibility features built-in
|
||||
- Sheet-style when attached to window
|
||||
|
||||
**Example:**
|
||||
```go
|
||||
// Appears as sheet on macOS
|
||||
app.Dialog.Question().
|
||||
SetMessage("Save changes?").
|
||||
AttachToWindow(window).
|
||||
Show()
|
||||
```
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="seti:windows">
|
||||
**Windows dialogs:**
|
||||
|
||||
- Native TaskDialog appearance
|
||||
- Follow system theme
|
||||
- Support keyboard navigation
|
||||
- Standard shortcuts (Esc for Cancel)
|
||||
- Accessibility features built-in
|
||||
- Modal to parent window
|
||||
|
||||
**Example:**
|
||||
```go
|
||||
// Modal dialog on Windows
|
||||
app.Dialog.Error().
|
||||
SetTitle("Error").
|
||||
SetMessage("Operation failed").
|
||||
Show()
|
||||
```
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="linux">
|
||||
**Linux dialogs:**
|
||||
|
||||
- GTK dialog appearance
|
||||
- Follow desktop theme
|
||||
- Support keyboard navigation
|
||||
- Desktop environment integration
|
||||
- Varies by DE (GNOME, KDE, etc.)
|
||||
|
||||
**Example:**
|
||||
```go
|
||||
// GTK dialog on Linux
|
||||
app.Dialog.Info().
|
||||
SetMessage("Update complete").
|
||||
Show()
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Confirm Before Destructive Action
|
||||
|
||||
```go
|
||||
func deleteFile(path string) {
|
||||
dialog := app.Dialog.Question().
|
||||
SetTitle("Confirm Delete").
|
||||
SetMessage(fmt.Sprintf("Delete %s?", filepath.Base(path)))
|
||||
|
||||
deleteBtn := dialog.AddButton("Delete")
|
||||
cancelBtn := dialog.AddButton("Cancel")
|
||||
cancelBtn.SetAsDefault()
|
||||
cancelBtn.SetAsCancel()
|
||||
|
||||
deleteBtn.OnClick(func() {
|
||||
os.Remove(path)
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling with dialog
|
||||
|
||||
```go
|
||||
func saveDocument(path string, data []byte) {
|
||||
if err := os.WriteFile(path, data, 0644); err != nil {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Save Failed").
|
||||
SetMessage(fmt.Sprintf("Could not save file: %v", err)).
|
||||
Show()
|
||||
return
|
||||
}
|
||||
|
||||
app.Dialog.Info().
|
||||
SetTitle("Success").
|
||||
SetMessage("File saved successfully!").
|
||||
Show()
|
||||
}
|
||||
```
|
||||
|
||||
### File Selection with Validation
|
||||
|
||||
```go
|
||||
func selectImageFile() (string, error) {
|
||||
path, err := app.Dialog.OpenFile().
|
||||
SetTitle("Select Image").
|
||||
AddFilter("Images", "*.png;*.jpg;*.jpeg;*.gif").
|
||||
PromptForSingleSelection()
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Validate file
|
||||
if !isValidImage(path) {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Invalid File").
|
||||
SetMessage("Selected file is not a valid image.").
|
||||
Show()
|
||||
return "", errors.New("invalid image")
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
```
|
||||
|
||||
### Multi-Step dialog Flow
|
||||
|
||||
```go
|
||||
func exportData() {
|
||||
// Step 1: Confirm export
|
||||
dialog := app.Dialog.Question().
|
||||
SetTitle("Export Data").
|
||||
SetMessage("Export all data to CSV?")
|
||||
|
||||
exportBtn := dialog.AddButton("Export")
|
||||
cancelBtn := dialog.AddButton("Cancel")
|
||||
cancelBtn.SetAsCancel()
|
||||
|
||||
exportBtn.OnClick(func() {
|
||||
// Step 2: Select destination
|
||||
path, err := app.Dialog.SaveFile().
|
||||
SetTitle("Save Export").
|
||||
SetFilename("export.csv").
|
||||
AddFilter("CSV Files", "*.csv").
|
||||
PromptForSingleSelection()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Step 3: Perform export
|
||||
if err := performExport(path); err != nil {
|
||||
app.Dialog.Error().
|
||||
SetTitle("Export Failed").
|
||||
SetMessage(err.Error()).
|
||||
Show()
|
||||
return
|
||||
}
|
||||
|
||||
// Step 4: Success
|
||||
app.Dialog.Info().
|
||||
SetTitle("Export Complete").
|
||||
SetMessage("Data exported successfully!").
|
||||
Show()
|
||||
})
|
||||
|
||||
dialog.Show()
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do
|
||||
|
||||
- **Use native dialogs** - Better UX than custom
|
||||
- **Provide clear messages** - Be specific
|
||||
- **Set appropriate titles** - Context matters
|
||||
- **Use default buttons wisely** - Safe option as default
|
||||
- **Handle cancellation** - User might cancel
|
||||
- **Validate file selections** - Check file types
|
||||
|
||||
### Don't
|
||||
|
||||
- **Don't overuse dialogs** - Interrupts workflow
|
||||
- **Don't use for frequent messages** - Use notifications
|
||||
- **Don't forget error handling** - User might cancel
|
||||
- **Don't block unnecessarily** - Consider alternatives
|
||||
- **Don't use generic messages** - Be specific
|
||||
- **Don't ignore platform differences** - Test on all platforms
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGrid>
|
||||
<Card title="Message dialogs" icon="information">
|
||||
Info, warning, and error dialogs.
|
||||
|
||||
[Learn More →](/features/dialogs/message)
|
||||
</Card>
|
||||
|
||||
<Card title="File dialogs" icon="document">
|
||||
Open, save, and folder selection.
|
||||
|
||||
[Learn More →](/features/dialogs/file)
|
||||
</Card>
|
||||
|
||||
<Card title="Custom dialogs" icon="puzzle">
|
||||
Create custom dialog windows.
|
||||
|
||||
[Learn More →](/features/dialogs/custom)
|
||||
</Card>
|
||||
|
||||
<Card title="Windows" icon="laptop">
|
||||
Learn about window management.
|
||||
|
||||
[Learn More →](/features/windows/basics)
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [dialog examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/dialogs).
|
||||
620
docs/src/content/docs/features/environment/info.mdx
Normal file
620
docs/src/content/docs/features/environment/info.mdx
Normal file
|
|
@ -0,0 +1,620 @@
|
|||
---
|
||||
title: Environment
|
||||
sidebar:
|
||||
order: 59
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
Wails provides comprehensive environment information through the EnvironmentManager API. This allows your application to detect system properties, theme preferences, and integrate with the operating system's file manager.
|
||||
|
||||
## Accessing the Environment Manager
|
||||
|
||||
The environment manager is accessed through the `Env` property on your application instance:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Name: "Environment Demo",
|
||||
})
|
||||
|
||||
// Access the environment manager
|
||||
env := app.Env
|
||||
```
|
||||
|
||||
## System Information
|
||||
|
||||
### Get Environment Information
|
||||
|
||||
Retrieve comprehensive information about the runtime environment:
|
||||
|
||||
```go
|
||||
envInfo := app.Env.Info()
|
||||
|
||||
app.Logger.Info("Environment information",
|
||||
"os", envInfo.OS, // "windows", "darwin", "linux"
|
||||
"arch", envInfo.Arch, // "amd64", "arm64", etc.
|
||||
"debug", envInfo.Debug, // Debug mode flag
|
||||
)
|
||||
|
||||
// Operating system details
|
||||
if envInfo.OSInfo != nil {
|
||||
app.Logger.Info("OS details",
|
||||
"name", envInfo.OSInfo.Name,
|
||||
"version", envInfo.OSInfo.Version,
|
||||
)
|
||||
}
|
||||
|
||||
// Platform-specific information
|
||||
for key, value := range envInfo.PlatformInfo {
|
||||
app.Logger.Info("Platform info", "key", key, "value", value)
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Structure
|
||||
|
||||
The environment information includes several important fields:
|
||||
|
||||
```go
|
||||
type EnvironmentInfo struct {
|
||||
OS string // Operating system: "windows", "darwin", "linux"
|
||||
Arch string // Architecture: "amd64", "arm64", "386", etc.
|
||||
Debug bool // Whether running in debug mode
|
||||
OSInfo *operatingsystem.OS // Detailed OS information
|
||||
PlatformInfo map[string]any // Platform-specific details
|
||||
}
|
||||
```
|
||||
|
||||
## Theme Detection
|
||||
|
||||
### Dark Mode Detection
|
||||
|
||||
Detect whether the system is using dark mode:
|
||||
|
||||
```go
|
||||
if app.Env.IsDarkMode() {
|
||||
app.Logger.Info("System is in dark mode")
|
||||
// Apply dark theme to your application
|
||||
applyDarkTheme()
|
||||
} else {
|
||||
app.Logger.Info("System is in light mode")
|
||||
// Apply light theme to your application
|
||||
applyLightTheme()
|
||||
}
|
||||
```
|
||||
|
||||
### Theme Change Monitoring
|
||||
|
||||
Listen for theme changes to update your application dynamically:
|
||||
|
||||
```go
|
||||
import "github.com/wailsapp/wails/v3/pkg/events"
|
||||
|
||||
// Listen for theme changes
|
||||
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) {
|
||||
if app.Env.IsDarkMode() {
|
||||
app.Logger.Info("Switched to dark mode")
|
||||
updateApplicationTheme("dark")
|
||||
} else {
|
||||
app.Logger.Info("Switched to light mode")
|
||||
updateApplicationTheme("light")
|
||||
}
|
||||
})
|
||||
|
||||
func updateApplicationTheme(theme string) {
|
||||
// Update your application's theme
|
||||
// This could emit an event to the frontend
|
||||
app.Event.Emit("theme:changed", theme)
|
||||
}
|
||||
```
|
||||
|
||||
## File Manager Integration
|
||||
|
||||
### Open File Manager
|
||||
|
||||
Open the system's file manager at a specific location:
|
||||
|
||||
```go
|
||||
// Open file manager at a directory
|
||||
err := app.Env.OpenFileManager("/Users/username/Documents", false)
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open file manager", "error", err)
|
||||
}
|
||||
|
||||
// Open file manager and select a specific file
|
||||
err = app.Env.OpenFileManager("/Users/username/Documents/report.pdf", true)
|
||||
if err != nil {
|
||||
app.Logger.Error("Failed to open file manager with selection", "error", err)
|
||||
}
|
||||
```
|
||||
|
||||
### Common Use Cases
|
||||
|
||||
Show files or folders in the file manager from your application:
|
||||
|
||||
```go
|
||||
func showInFileManager(app *application.App, path string) {
|
||||
err := app.Env.OpenFileManager(path, true)
|
||||
if err != nil {
|
||||
// Fallback: try opening just the directory
|
||||
dir := filepath.Dir(path)
|
||||
err = app.Env.OpenFileManager(dir, false)
|
||||
if err != nil {
|
||||
app.Logger.Error("Could not open file manager", "path", path, "error", err)
|
||||
|
||||
// Show error to user
|
||||
app.Dialog.Error().
|
||||
SetTitle("File Manager Error").
|
||||
SetMessage("Could not open file manager").
|
||||
Show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Usage examples
|
||||
func setupFileMenu(app *application.App) {
|
||||
menu := app.Menu.New()
|
||||
fileMenu := menu.AddSubmenu("File")
|
||||
|
||||
fileMenu.Add("Show Downloads Folder").OnClick(func(ctx *application.Context) {
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
downloadsDir := filepath.Join(homeDir, "Downloads")
|
||||
showInFileManager(app, downloadsDir)
|
||||
})
|
||||
|
||||
fileMenu.Add("Show Application Data").OnClick(func(ctx *application.Context) {
|
||||
configDir, _ := os.UserConfigDir()
|
||||
appDir := filepath.Join(configDir, "MyApp")
|
||||
showInFileManager(app, appDir)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Platform-Specific Behavior
|
||||
|
||||
### Adaptive Application Behavior
|
||||
|
||||
Use environment information to adapt your application's behavior:
|
||||
|
||||
```go
|
||||
func configureForPlatform(app *application.App) {
|
||||
envInfo := app.Env.Info()
|
||||
|
||||
switch envInfo.OS {
|
||||
case "darwin":
|
||||
configureMacOS(app)
|
||||
case "windows":
|
||||
configureWindows(app)
|
||||
case "linux":
|
||||
configureLinux(app)
|
||||
}
|
||||
|
||||
// Adapt to architecture
|
||||
if envInfo.Arch == "arm64" {
|
||||
app.Logger.Info("Running on ARM architecture")
|
||||
// Potentially optimize for ARM
|
||||
}
|
||||
}
|
||||
|
||||
func configureMacOS(app *application.App) {
|
||||
app.Logger.Info("Configuring for macOS")
|
||||
|
||||
// macOS-specific configuration
|
||||
menu := app.Menu.New()
|
||||
menu.AddRole(application.AppMenu) // Add standard macOS app menu
|
||||
|
||||
// Handle dark mode
|
||||
if app.Env.IsDarkMode() {
|
||||
setMacOSDarkTheme()
|
||||
}
|
||||
}
|
||||
|
||||
func configureWindows(app *application.App) {
|
||||
app.Logger.Info("Configuring for Windows")
|
||||
|
||||
// Windows-specific configuration
|
||||
// Set up Windows-style menus, key bindings, etc.
|
||||
}
|
||||
|
||||
func configureLinux(app *application.App) {
|
||||
app.Logger.Info("Configuring for Linux")
|
||||
|
||||
// Linux-specific configuration
|
||||
// Adapt to different desktop environments
|
||||
}
|
||||
```
|
||||
|
||||
## Debug Mode Handling
|
||||
|
||||
### Development vs Production
|
||||
|
||||
Use debug mode information to enable development features:
|
||||
|
||||
```go
|
||||
func setupApplicationMode(app *application.App) {
|
||||
envInfo := app.Env.Info()
|
||||
|
||||
if envInfo.Debug {
|
||||
app.Logger.Info("Running in debug mode")
|
||||
setupDevelopmentFeatures(app)
|
||||
} else {
|
||||
app.Logger.Info("Running in production mode")
|
||||
setupProductionFeatures(app)
|
||||
}
|
||||
}
|
||||
|
||||
func setupDevelopmentFeatures(app *application.App) {
|
||||
// Enable development-only features
|
||||
menu := app.Menu.New()
|
||||
|
||||
// Add development menu
|
||||
devMenu := menu.AddSubmenu("Development")
|
||||
devMenu.Add("Reload Application").OnClick(func(ctx *application.Context) {
|
||||
// Reload the application
|
||||
window := app.Window.Current()
|
||||
if window != nil {
|
||||
window.Reload()
|
||||
}
|
||||
})
|
||||
|
||||
devMenu.Add("Open DevTools").OnClick(func(ctx *application.Context) {
|
||||
window := app.Window.Current()
|
||||
if window != nil {
|
||||
window.OpenDevTools()
|
||||
}
|
||||
})
|
||||
|
||||
devMenu.Add("Show Environment").OnClick(func(ctx *application.Context) {
|
||||
showEnvironmentDialog(app)
|
||||
})
|
||||
}
|
||||
|
||||
func setupProductionFeatures(app *application.App) {
|
||||
// Production-only features
|
||||
// Disable debug logging, enable analytics, etc.
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Information Dialog
|
||||
|
||||
### Display System Information
|
||||
|
||||
Create a dialog showing environment information:
|
||||
|
||||
```go
|
||||
func showEnvironmentDialog(app *application.App) {
|
||||
envInfo := app.Env.Info()
|
||||
|
||||
details := fmt.Sprintf(`Environment Information:
|
||||
|
||||
Operating System: %s
|
||||
Architecture: %s
|
||||
Debug Mode: %t
|
||||
|
||||
Dark Mode: %t
|
||||
|
||||
Platform Information:`,
|
||||
envInfo.OS,
|
||||
envInfo.Arch,
|
||||
envInfo.Debug,
|
||||
app.Env.IsDarkMode())
|
||||
|
||||
// Add platform-specific details
|
||||
for key, value := range envInfo.PlatformInfo {
|
||||
details += fmt.Sprintf("\n%s: %v", key, value)
|
||||
}
|
||||
|
||||
if envInfo.OSInfo != nil {
|
||||
details += fmt.Sprintf("\n\nOS Details:\nName: %s\nVersion: %s",
|
||||
envInfo.OSInfo.Name,
|
||||
envInfo.OSInfo.Version)
|
||||
}
|
||||
|
||||
dialog := app.Dialog.Info()
|
||||
dialog.SetTitle("Environment Information")
|
||||
dialog.SetMessage(details)
|
||||
dialog.Show()
|
||||
}
|
||||
```
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
On macOS:
|
||||
|
||||
- Dark mode detection uses system appearance settings
|
||||
- File manager operations use Finder
|
||||
- Platform info includes macOS version details
|
||||
- Architecture may be "arm64" on Apple Silicon Macs
|
||||
|
||||
```go
|
||||
if envInfo.OS == "darwin" {
|
||||
// macOS-specific handling
|
||||
if envInfo.Arch == "arm64" {
|
||||
app.Logger.Info("Running on Apple Silicon")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
|
||||
On Windows:
|
||||
|
||||
- Dark mode detection uses Windows theme settings
|
||||
- File manager operations use Windows Explorer
|
||||
- Platform info includes Windows version details
|
||||
- May include additional Windows-specific information
|
||||
|
||||
```go
|
||||
if envInfo.OS == "windows" {
|
||||
// Windows-specific handling
|
||||
for key, value := range envInfo.PlatformInfo {
|
||||
if key == "windows_version" {
|
||||
app.Logger.Info("Windows version", "version", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
|
||||
On Linux:
|
||||
|
||||
- Dark mode detection varies by desktop environment
|
||||
- File manager operations use system default file manager
|
||||
- Platform info includes distribution details
|
||||
- Behavior may vary between different Linux distributions
|
||||
|
||||
```go
|
||||
if envInfo.OS == "linux" {
|
||||
// Linux-specific handling
|
||||
if distro, ok := envInfo.PlatformInfo["distribution"]; ok {
|
||||
app.Logger.Info("Linux distribution", "distro", distro)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Cache Environment Information**: Environment info rarely changes during runtime:
|
||||
```go
|
||||
type App struct {
|
||||
envInfo *application.EnvironmentInfo
|
||||
}
|
||||
|
||||
func (a *App) getEnvInfo() application.EnvironmentInfo {
|
||||
if a.envInfo == nil {
|
||||
info := a.app.Env.Info()
|
||||
a.envInfo = &info
|
||||
}
|
||||
return *a.envInfo
|
||||
}
|
||||
```
|
||||
|
||||
2. **Handle Theme Changes**: Listen for system theme changes:
|
||||
```go
|
||||
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) {
|
||||
updateTheme(app.Env.IsDarkMode())
|
||||
})
|
||||
```
|
||||
|
||||
3. **Graceful File Manager Failures**: Always handle file manager errors:
|
||||
```go
|
||||
func openFileManagerSafely(app *application.App, path string) {
|
||||
err := app.Env.OpenFileManager(path, false)
|
||||
if err != nil {
|
||||
// Provide fallback or user notification
|
||||
app.Logger.Warn("Could not open file manager", "path", path)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. **Platform-Specific Features**: Use environment info to enable platform features:
|
||||
```go
|
||||
envInfo := app.Env.Info()
|
||||
if envInfo.OS == "darwin" {
|
||||
// Enable macOS-specific features
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a complete example demonstrating environment management:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Environment Demo",
|
||||
})
|
||||
|
||||
// Setup application based on environment
|
||||
setupForEnvironment(app)
|
||||
|
||||
// Monitor theme changes
|
||||
monitorThemeChanges(app)
|
||||
|
||||
// Create menu with environment features
|
||||
setupEnvironmentMenu(app)
|
||||
|
||||
// Create main window
|
||||
window := app.Window.New()
|
||||
window.SetTitle("Environment Demo")
|
||||
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func setupForEnvironment(app *application.App) {
|
||||
envInfo := app.Env.Info()
|
||||
|
||||
app.Logger.Info("Application environment",
|
||||
"os", envInfo.OS,
|
||||
"arch", envInfo.Arch,
|
||||
"debug", envInfo.Debug,
|
||||
"darkMode", app.Env.IsDarkMode(),
|
||||
)
|
||||
|
||||
// Configure for platform
|
||||
switch envInfo.OS {
|
||||
case "darwin":
|
||||
app.Logger.Info("Configuring for macOS")
|
||||
// macOS-specific setup
|
||||
case "windows":
|
||||
app.Logger.Info("Configuring for Windows")
|
||||
// Windows-specific setup
|
||||
case "linux":
|
||||
app.Logger.Info("Configuring for Linux")
|
||||
// Linux-specific setup
|
||||
}
|
||||
|
||||
// Apply initial theme
|
||||
if app.Env.IsDarkMode() {
|
||||
applyDarkTheme(app)
|
||||
} else {
|
||||
applyLightTheme(app)
|
||||
}
|
||||
}
|
||||
|
||||
func monitorThemeChanges(app *application.App) {
|
||||
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) {
|
||||
if app.Env.IsDarkMode() {
|
||||
app.Logger.Info("System switched to dark mode")
|
||||
applyDarkTheme(app)
|
||||
} else {
|
||||
app.Logger.Info("System switched to light mode")
|
||||
applyLightTheme(app)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func setupEnvironmentMenu(app *application.App) {
|
||||
menu := app.Menu.New()
|
||||
|
||||
// Add platform-specific app menu
|
||||
if runtime.GOOS == "darwin" {
|
||||
menu.AddRole(application.AppMenu)
|
||||
}
|
||||
|
||||
// Tools menu
|
||||
toolsMenu := menu.AddSubmenu("Tools")
|
||||
|
||||
toolsMenu.Add("Show Environment Info").OnClick(func(ctx *application.Context) {
|
||||
showEnvironmentInfo(app)
|
||||
})
|
||||
|
||||
toolsMenu.Add("Open Downloads Folder").OnClick(func(ctx *application.Context) {
|
||||
openDownloadsFolder(app)
|
||||
})
|
||||
|
||||
toolsMenu.Add("Toggle Theme").OnClick(func(ctx *application.Context) {
|
||||
// This would typically be handled by the system
|
||||
// but shown here for demonstration
|
||||
toggleTheme(app)
|
||||
})
|
||||
|
||||
app.Menu.Set(menu)
|
||||
}
|
||||
|
||||
func showEnvironmentInfo(app *application.App) {
|
||||
envInfo := app.Env.Info()
|
||||
|
||||
message := fmt.Sprintf(`Environment Information:
|
||||
|
||||
Operating System: %s
|
||||
Architecture: %s
|
||||
Debug Mode: %t
|
||||
Dark Mode: %t
|
||||
|
||||
Platform Details:`,
|
||||
envInfo.OS,
|
||||
envInfo.Arch,
|
||||
envInfo.Debug,
|
||||
app.Env.IsDarkMode())
|
||||
|
||||
for key, value := range envInfo.PlatformInfo {
|
||||
message += fmt.Sprintf("\n%s: %v", key, value)
|
||||
}
|
||||
|
||||
if envInfo.OSInfo != nil {
|
||||
message += fmt.Sprintf("\n\nOS Information:\nName: %s\nVersion: %s",
|
||||
envInfo.OSInfo.Name,
|
||||
envInfo.OSInfo.Version)
|
||||
}
|
||||
|
||||
dialog := app.Dialog.Info()
|
||||
dialog.SetTitle("Environment Information")
|
||||
dialog.SetMessage(message)
|
||||
dialog.Show()
|
||||
}
|
||||
|
||||
func openDownloadsFolder(app *application.App) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
app.Logger.Error("Could not get home directory", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
downloadsDir := filepath.Join(homeDir, "Downloads")
|
||||
err = app.Env.OpenFileManager(downloadsDir, false)
|
||||
if err != nil {
|
||||
app.Logger.Error("Could not open Downloads folder", "error", err)
|
||||
|
||||
app.Dialog.Error().
|
||||
SetTitle("File Manager Error").
|
||||
SetMessage("Could not open Downloads folder").
|
||||
Show()
|
||||
}
|
||||
}
|
||||
|
||||
func applyDarkTheme(app *application.App) {
|
||||
app.Logger.Info("Applying dark theme")
|
||||
// Emit theme change to frontend
|
||||
app.Event.Emit("theme:apply", "dark")
|
||||
}
|
||||
|
||||
func applyLightTheme(app *application.App) {
|
||||
app.Logger.Info("Applying light theme")
|
||||
// Emit theme change to frontend
|
||||
app.Event.Emit("theme:apply", "light")
|
||||
}
|
||||
|
||||
func toggleTheme(app *application.App) {
|
||||
// This is just for demonstration
|
||||
// Real theme changes should come from the system
|
||||
currentlyDark := app.Env.IsDarkMode()
|
||||
if currentlyDark {
|
||||
applyLightTheme(app)
|
||||
} else {
|
||||
applyDarkTheme(app)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::tip[Pro Tip]
|
||||
Use environment information to provide platform-appropriate user experiences. For example, use Command key shortcuts on macOS and Control key shortcuts on Windows/Linux.
|
||||
:::
|
||||
|
||||
:::danger[Warning]
|
||||
Environment information is generally stable during application runtime, but theme preferences can change. Always listen for theme change events to keep your UI synchronized.
|
||||
:::
|
||||
579
docs/src/content/docs/features/events/system.mdx
Normal file
579
docs/src/content/docs/features/events/system.mdx
Normal file
|
|
@ -0,0 +1,579 @@
|
|||
---
|
||||
title: Event System
|
||||
description: Communicate between components with the event system
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Event System
|
||||
|
||||
Wails provides a **unified event system** for pub/sub communication. Emit events from anywhere, listen from anywhere—Go to JavaScript, JavaScript to Go, window to window—enabling decoupled architecture with typed events and lifecycle hooks.
|
||||
|
||||
## Quick Start
|
||||
|
||||
**Go (emit):**
|
||||
|
||||
```go
|
||||
app.Event.Emit("user-logged-in", map[string]interface{}{
|
||||
"userId": 123,
|
||||
"name": "Alice",
|
||||
})
|
||||
```
|
||||
|
||||
**JavaScript (listen):**
|
||||
|
||||
```javascript
|
||||
import { OnEvent } from '@wailsio/runtime'
|
||||
|
||||
OnEvent("user-logged-in", (data) => {
|
||||
console.log(`User ${data.name} logged in`)
|
||||
})
|
||||
```
|
||||
|
||||
**That's it!** Cross-language pub/sub.
|
||||
|
||||
## Event Types
|
||||
|
||||
### Custom Events
|
||||
|
||||
Your application-specific events:
|
||||
|
||||
```go
|
||||
// Emit from Go
|
||||
app.Event.Emit("order-created", order)
|
||||
app.Event.Emit("payment-processed", payment)
|
||||
app.Event.Emit("notification", message)
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Listen in JavaScript
|
||||
OnEvent("order-created", handleOrder)
|
||||
OnEvent("payment-processed", handlePayment)
|
||||
OnEvent("notification", showNotification)
|
||||
```
|
||||
|
||||
### System Events
|
||||
|
||||
Built-in OS and application events:
|
||||
|
||||
```go
|
||||
import "github.com/wailsapp/wails/v3/pkg/events"
|
||||
|
||||
// Theme changes
|
||||
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) {
|
||||
if e.Context().IsDarkMode() {
|
||||
app.Logger.Info("Dark mode enabled")
|
||||
}
|
||||
})
|
||||
|
||||
// Application lifecycle
|
||||
app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) {
|
||||
app.Logger.Info("Application started")
|
||||
})
|
||||
```
|
||||
|
||||
### Window Events
|
||||
|
||||
Window-specific events:
|
||||
|
||||
```go
|
||||
window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
|
||||
app.Logger.Info("Window focused")
|
||||
})
|
||||
|
||||
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
app.Logger.Info("Window closing")
|
||||
})
|
||||
```
|
||||
|
||||
## Emitting Events
|
||||
|
||||
### From Go
|
||||
|
||||
**Basic emit:**
|
||||
|
||||
```go
|
||||
app.Event.Emit("event-name", data)
|
||||
```
|
||||
|
||||
**With different data types:**
|
||||
|
||||
```go
|
||||
// String
|
||||
app.Event.Emit("message", "Hello")
|
||||
|
||||
// Number
|
||||
app.Event.Emit("count", 42)
|
||||
|
||||
// Struct
|
||||
app.Event.Emit("user", User{ID: 1, Name: "Alice"})
|
||||
|
||||
// Map
|
||||
app.Event.Emit("config", map[string]interface{}{
|
||||
"theme": "dark",
|
||||
"fontSize": 14,
|
||||
})
|
||||
|
||||
// Array
|
||||
app.Event.Emit("items", []string{"a", "b", "c"})
|
||||
```
|
||||
|
||||
**To specific window:**
|
||||
|
||||
```go
|
||||
window.EmitEvent("window-specific-event", data)
|
||||
```
|
||||
|
||||
### From JavaScript
|
||||
|
||||
```javascript
|
||||
import { Emit } from '@wailsio/runtime'
|
||||
|
||||
// Emit to Go
|
||||
Emit("button-clicked", { buttonId: "submit" })
|
||||
|
||||
// Emit to all windows
|
||||
Emit("broadcast-message", "Hello everyone")
|
||||
```
|
||||
|
||||
## Listening to Events
|
||||
|
||||
### In Go
|
||||
|
||||
**Application events:**
|
||||
|
||||
```go
|
||||
app.Event.On("custom-event", func(e *application.CustomEvent) {
|
||||
data := e.Data
|
||||
// Handle event
|
||||
})
|
||||
```
|
||||
|
||||
**With type assertion:**
|
||||
|
||||
```go
|
||||
app.Event.On("user-updated", func(e *application.CustomEvent) {
|
||||
user := e.Data.(User)
|
||||
app.Logger.Info("User updated", "name", user.Name)
|
||||
})
|
||||
```
|
||||
|
||||
**Multiple handlers:**
|
||||
|
||||
```go
|
||||
// All handlers will be called
|
||||
app.Event.On("order-created", logOrder)
|
||||
app.Event.On("order-created", sendEmail)
|
||||
app.Event.On("order-created", updateInventory)
|
||||
```
|
||||
|
||||
### In JavaScript
|
||||
|
||||
**Basic listener:**
|
||||
|
||||
```javascript
|
||||
import { OnEvent } from '@wailsio/runtime'
|
||||
|
||||
OnEvent("event-name", (data) => {
|
||||
console.log("Event received:", data)
|
||||
})
|
||||
```
|
||||
|
||||
**With cleanup:**
|
||||
|
||||
```javascript
|
||||
const unsubscribe = OnEvent("event-name", handleEvent)
|
||||
|
||||
// Later, stop listening
|
||||
unsubscribe()
|
||||
```
|
||||
|
||||
**Multiple handlers:**
|
||||
|
||||
```javascript
|
||||
OnEvent("data-updated", updateUI)
|
||||
OnEvent("data-updated", saveToCache)
|
||||
OnEvent("data-updated", logChange)
|
||||
```
|
||||
|
||||
## System Events
|
||||
|
||||
### Application Events
|
||||
|
||||
**Common events (cross-platform):**
|
||||
|
||||
```go
|
||||
import "github.com/wailsapp/wails/v3/pkg/events"
|
||||
|
||||
// Application started
|
||||
app.Event.OnApplicationEvent(events.Common.ApplicationStarted, func(e *application.ApplicationEvent) {
|
||||
app.Logger.Info("App started")
|
||||
})
|
||||
|
||||
// Theme changed
|
||||
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) {
|
||||
isDark := e.Context().IsDarkMode()
|
||||
app.Event.Emit("theme-changed", isDark)
|
||||
})
|
||||
|
||||
// File opened
|
||||
app.Event.OnApplicationEvent(events.Common.ApplicationOpenedWithFile, func(e *application.ApplicationEvent) {
|
||||
filePath := e.Context().OpenedFile()
|
||||
openFile(filePath)
|
||||
})
|
||||
```
|
||||
|
||||
**Platform-specific events:**
|
||||
|
||||
<Tabs syncKey="platform">
|
||||
<TabItem label="macOS" icon="apple">
|
||||
```go
|
||||
// Application became active
|
||||
app.Event.OnApplicationEvent(events.Mac.ApplicationDidBecomeActive, func(e *application.ApplicationEvent) {
|
||||
app.Logger.Info("App became active")
|
||||
})
|
||||
|
||||
// Application will terminate
|
||||
app.Event.OnApplicationEvent(events.Mac.ApplicationWillTerminate, func(e *application.ApplicationEvent) {
|
||||
cleanup()
|
||||
})
|
||||
```
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="seti:windows">
|
||||
```go
|
||||
// Power status changed
|
||||
app.Event.OnApplicationEvent(events.Windows.APMPowerStatusChange, func(e *application.ApplicationEvent) {
|
||||
app.Logger.Info("Power status changed")
|
||||
})
|
||||
|
||||
// System suspending
|
||||
app.Event.OnApplicationEvent(events.Windows.APMSuspend, func(e *application.ApplicationEvent) {
|
||||
saveState()
|
||||
})
|
||||
```
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="linux">
|
||||
```go
|
||||
// Application startup
|
||||
app.Event.OnApplicationEvent(events.Linux.ApplicationStartup, func(e *application.ApplicationEvent) {
|
||||
app.Logger.Info("App starting")
|
||||
})
|
||||
|
||||
// Theme changed
|
||||
app.Event.OnApplicationEvent(events.Linux.SystemThemeChanged, func(e *application.ApplicationEvent) {
|
||||
updateTheme()
|
||||
})
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Window Events
|
||||
|
||||
**Common window events:**
|
||||
|
||||
```go
|
||||
// Window focus
|
||||
window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
|
||||
app.Logger.Info("Window focused")
|
||||
})
|
||||
|
||||
// Window blur
|
||||
window.OnWindowEvent(events.Common.WindowBlur, func(e *application.WindowEvent) {
|
||||
app.Logger.Info("Window blurred")
|
||||
})
|
||||
|
||||
// Window closing
|
||||
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
if hasUnsavedChanges() {
|
||||
e.Cancel() // Prevent close
|
||||
}
|
||||
})
|
||||
|
||||
// Window closed
|
||||
window.OnWindowEvent(events.Common.WindowClosed, func(e *application.WindowEvent) {
|
||||
cleanup()
|
||||
})
|
||||
```
|
||||
|
||||
## Event Hooks
|
||||
|
||||
Hooks run **before** standard listeners and can **cancel** events:
|
||||
|
||||
```go
|
||||
// Hook - runs first, can cancel
|
||||
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
if hasUnsavedChanges() {
|
||||
result := showConfirmdialog("Unsaved changes. Close anyway?")
|
||||
if result != "yes" {
|
||||
e.Cancel() // Prevent window close
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Standard listener - runs after hooks
|
||||
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
app.Logger.Info("Window closing")
|
||||
})
|
||||
```
|
||||
|
||||
**Key differences:**
|
||||
|
||||
| Feature | Hooks | Standard Listeners |
|
||||
|---------|-------|-------------------|
|
||||
| Execution order | First, in registration order | After hooks, no guaranteed order |
|
||||
| Blocking | Synchronous, blocks next hook | Asynchronous, non-blocking |
|
||||
| Can cancel | Yes | No (already propagated) |
|
||||
| Use case | Control flow, validation | Logging, side effects |
|
||||
|
||||
## Event Patterns
|
||||
|
||||
### Pub/Sub Pattern
|
||||
|
||||
```go
|
||||
// Publisher (service)
|
||||
type OrderService struct {
|
||||
app *application.Application
|
||||
}
|
||||
|
||||
func (o *OrderService) CreateOrder(items []Item) (*Order, error) {
|
||||
order := &Order{Items: items}
|
||||
|
||||
if err := o.saveOrder(order); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Publish event
|
||||
o.app.Event.Emit("order-created", order)
|
||||
|
||||
return order, nil
|
||||
}
|
||||
|
||||
// Subscribers
|
||||
app.Event.On("order-created", func(e *application.CustomEvent) {
|
||||
order := e.Data.(*Order)
|
||||
sendConfirmationEmail(order)
|
||||
})
|
||||
|
||||
app.Event.On("order-created", func(e *application.CustomEvent) {
|
||||
order := e.Data.(*Order)
|
||||
updateInventory(order)
|
||||
})
|
||||
|
||||
app.Event.On("order-created", func(e *application.CustomEvent) {
|
||||
order := e.Data.(*Order)
|
||||
logOrder(order)
|
||||
})
|
||||
```
|
||||
|
||||
### Request/Response Pattern
|
||||
|
||||
```go
|
||||
// Frontend requests data
|
||||
Emit("get-user-data", { userId: 123 })
|
||||
|
||||
// Backend responds
|
||||
app.Event.On("get-user-data", func(e *application.CustomEvent) {
|
||||
data := e.Data.(map[string]interface{})
|
||||
userId := int(data["userId"].(float64))
|
||||
|
||||
user := getUserFromDB(userId)
|
||||
|
||||
// Send response
|
||||
app.Event.Emit("user-data-response", user)
|
||||
})
|
||||
|
||||
// Frontend receives response
|
||||
OnEvent("user-data-response", (user) => {
|
||||
displayUser(user)
|
||||
})
|
||||
```
|
||||
|
||||
**Note:** For request/response, **bindings are better**. Use events for notifications.
|
||||
|
||||
### Broadcast Pattern
|
||||
|
||||
```go
|
||||
// Broadcast to all windows
|
||||
app.Event.Emit("global-notification", "System update available")
|
||||
|
||||
// Each window handles it
|
||||
OnEvent("global-notification", (message) => {
|
||||
showNotification(message)
|
||||
})
|
||||
```
|
||||
|
||||
### Event Aggregation
|
||||
|
||||
```go
|
||||
type EventAggregator struct {
|
||||
events []Event
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (ea *EventAggregator) Add(event Event) {
|
||||
ea.mu.Lock()
|
||||
defer ea.mu.Unlock()
|
||||
|
||||
ea.events = append(ea.events, event)
|
||||
|
||||
// Emit batch every 100 events
|
||||
if len(ea.events) >= 100 {
|
||||
app.Event.Emit("event-batch", ea.events)
|
||||
ea.events = nil
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
**Go:**
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
type NotificationService struct {
|
||||
app *application.Application
|
||||
}
|
||||
|
||||
func (n *NotificationService) Notify(message string) {
|
||||
// Emit to all windows
|
||||
n.app.Event.Emit("notification", map[string]interface{}{
|
||||
"message": message,
|
||||
"timestamp": time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Event Demo",
|
||||
})
|
||||
|
||||
notifService := &NotificationService{app: app}
|
||||
|
||||
// System events
|
||||
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.ApplicationEvent) {
|
||||
isDark := e.Context().IsDarkMode()
|
||||
app.Event.Emit("theme-changed", isDark)
|
||||
})
|
||||
|
||||
// Custom events from frontend
|
||||
app.Event.On("user-action", func(e *application.CustomEvent) {
|
||||
data := e.Data.(map[string]interface{})
|
||||
action := data["action"].(string)
|
||||
|
||||
app.Logger.Info("User action", "action", action)
|
||||
|
||||
// Respond
|
||||
notifService.Notify("Action completed: " + action)
|
||||
})
|
||||
|
||||
// Window events
|
||||
window := app.Window.New()
|
||||
|
||||
window.OnWindowEvent(events.Common.WindowFocus, func(e *application.WindowEvent) {
|
||||
app.Event.Emit("window-focused", window.Name())
|
||||
})
|
||||
|
||||
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
// Confirm before close
|
||||
app.Event.Emit("confirm-close", nil)
|
||||
e.Cancel() // Wait for confirmation
|
||||
})
|
||||
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
**JavaScript:**
|
||||
|
||||
```javascript
|
||||
import { OnEvent, Emit } from '@wailsio/runtime'
|
||||
|
||||
// Listen for notifications
|
||||
OnEvent("notification", (data) => {
|
||||
showNotification(data.message)
|
||||
})
|
||||
|
||||
// Listen for theme changes
|
||||
OnEvent("theme-changed", (isDark) => {
|
||||
document.body.classList.toggle('dark', isDark)
|
||||
})
|
||||
|
||||
// Listen for window focus
|
||||
OnEvent("window-focused", (windowName) => {
|
||||
console.log(`Window ${windowName} focused`)
|
||||
})
|
||||
|
||||
// Handle close confirmation
|
||||
OnEvent("confirm-close", () => {
|
||||
if (confirm("Close window?")) {
|
||||
Emit("close-confirmed", true)
|
||||
}
|
||||
})
|
||||
|
||||
// Emit user actions
|
||||
document.getElementById('button').addEventListener('click', () => {
|
||||
Emit("user-action", { action: "button-clicked" })
|
||||
})
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Use events for notifications** - One-way communication
|
||||
- **Use bindings for requests** - Two-way communication
|
||||
- **Keep event names consistent** - Use kebab-case
|
||||
- **Document event data** - What fields are included?
|
||||
- **Unsubscribe when done** - Prevent memory leaks
|
||||
- **Use hooks for validation** - Control event flow
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Don't use events for RPC** - Use bindings instead
|
||||
- **Don't emit too frequently** - Batch if needed
|
||||
- **Don't block in handlers** - Keep them fast
|
||||
- **Don't forget to unsubscribe** - Memory leaks
|
||||
- **Don't use events for large data** - Use bindings
|
||||
- **Don't create event loops** - A emits B, B emits A
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGrid>
|
||||
<Card title="Custom Events" icon="star">
|
||||
Create your own event types.
|
||||
|
||||
[Learn More →](/features/events/custom)
|
||||
</Card>
|
||||
|
||||
<Card title="Event Patterns" icon="puzzle">
|
||||
Common event patterns and best practices.
|
||||
|
||||
[Learn More →](/features/events/patterns)
|
||||
</Card>
|
||||
|
||||
<Card title="Bindings" icon="rocket">
|
||||
Use bindings for request/response.
|
||||
|
||||
[Learn More →](/features/bindings/methods)
|
||||
</Card>
|
||||
|
||||
<Card title="Window Events" icon="laptop">
|
||||
Handle window lifecycle events.
|
||||
|
||||
[Learn More →](/features/windows/events)
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [event examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/events).
|
||||
434
docs/src/content/docs/features/keyboard/shortcuts.mdx
Normal file
434
docs/src/content/docs/features/keyboard/shortcuts.mdx
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
---
|
||||
title: Keyboard Shortcuts
|
||||
description: Register global keyboard shortcuts for quick access to functionality
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
Wails provides a powerful key binding system that allows you to register global keyboard shortcuts that work across all windows in your application. This enables users to quickly access functionality without navigating through menus.
|
||||
|
||||
## Accessing the Key Binding Manager
|
||||
|
||||
The key binding manager is accessed through the `KeyBindings` property on your application instance:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Name: "Keyboard Shortcuts Demo",
|
||||
})
|
||||
|
||||
// Access the key binding manager
|
||||
keyBindings := app.KeyBinding
|
||||
```
|
||||
|
||||
## Adding Key Bindings
|
||||
|
||||
### Basic Key Binding
|
||||
|
||||
Register a simple keyboard shortcut:
|
||||
|
||||
```go
|
||||
app.KeyBinding.Add("Ctrl+S", func(window application.Window) {
|
||||
// Handle save action
|
||||
app.Logger.Info("Save shortcut triggered")
|
||||
// Perform save operation...
|
||||
})
|
||||
```
|
||||
|
||||
### Multiple Key Bindings
|
||||
|
||||
Register multiple shortcuts for common operations:
|
||||
|
||||
```go
|
||||
// File operations
|
||||
app.KeyBinding.Add("Ctrl+N", func(window application.Window) {
|
||||
// New file
|
||||
window.EmitEvent("file:new", nil)
|
||||
})
|
||||
|
||||
app.KeyBinding.Add("Ctrl+O", func(window application.Window) {
|
||||
// Open file
|
||||
dialog := app.Dialog.OpenFile()
|
||||
if file, err := dialog.PromptForSingleSelection(); err == nil {
|
||||
window.EmitEvent("file:open", file)
|
||||
}
|
||||
})
|
||||
|
||||
app.KeyBinding.Add("Ctrl+S", func(window application.Window) {
|
||||
// Save file
|
||||
window.EmitEvent("file:save", nil)
|
||||
})
|
||||
|
||||
// Edit operations
|
||||
app.KeyBinding.Add("Ctrl+Z", func(window application.Window) {
|
||||
// Undo
|
||||
window.EmitEvent("edit:undo", nil)
|
||||
})
|
||||
|
||||
app.KeyBinding.Add("Ctrl+Y", func(window application.Window) {
|
||||
// Redo (Windows/Linux)
|
||||
window.EmitEvent("edit:redo", nil)
|
||||
})
|
||||
|
||||
app.KeyBinding.Add("Cmd+Shift+Z", func(window application.Window) {
|
||||
// Redo (macOS)
|
||||
window.EmitEvent("edit:redo", nil)
|
||||
})
|
||||
```
|
||||
|
||||
## Key Binding Accelerators
|
||||
|
||||
### Accelerator Format
|
||||
|
||||
Key bindings use a standard accelerator format with modifiers and keys:
|
||||
|
||||
```go
|
||||
// Modifier keys
|
||||
"Ctrl+S" // Control + S
|
||||
"Cmd+S" // Command + S (macOS)
|
||||
"Alt+F4" // Alt + F4
|
||||
"Shift+Ctrl+Z" // Shift + Control + Z
|
||||
|
||||
// Function keys
|
||||
"F1" // F1 key
|
||||
"Ctrl+F5" // Control + F5
|
||||
|
||||
// Special keys
|
||||
"Escape" // Escape key
|
||||
"Enter" // Enter key
|
||||
"Space" // Spacebar
|
||||
"Tab" // Tab key
|
||||
"Backspace" // Backspace key
|
||||
"Delete" // Delete key
|
||||
|
||||
// Arrow keys
|
||||
"Up" // Up arrow
|
||||
"Down" // Down arrow
|
||||
"Left" // Left arrow
|
||||
"Right" // Right arrow
|
||||
```
|
||||
|
||||
### Platform-Specific Accelerators
|
||||
|
||||
Handle platform differences for common shortcuts:
|
||||
|
||||
```go
|
||||
import "runtime"
|
||||
|
||||
// Cross-platform save shortcut
|
||||
if runtime.GOOS == "darwin" {
|
||||
app.KeyBinding.Add("Cmd+S", saveHandler)
|
||||
} else {
|
||||
app.KeyBinding.Add("Ctrl+S", saveHandler)
|
||||
}
|
||||
|
||||
// Or register both
|
||||
app.KeyBinding.Add("Ctrl+S", saveHandler)
|
||||
app.KeyBinding.Add("Cmd+S", saveHandler)
|
||||
```
|
||||
|
||||
## Managing Key Bindings
|
||||
|
||||
### Removing Key Bindings
|
||||
|
||||
Remove key bindings when they're no longer needed:
|
||||
|
||||
```go
|
||||
// Remove a specific key binding
|
||||
app.KeyBinding.Remove("Ctrl+S")
|
||||
|
||||
// Example: Temporary key binding for a modal
|
||||
app.KeyBinding.Add("Escape", func(window application.Window) {
|
||||
// Close modal
|
||||
window.EmitEvent("modal:close", nil)
|
||||
// Remove this temporary binding
|
||||
app.KeyBinding.Remove("Escape")
|
||||
})
|
||||
```
|
||||
|
||||
### Getting All Key Bindings
|
||||
|
||||
Retrieve all registered key bindings:
|
||||
|
||||
```go
|
||||
allBindings := app.KeyBinding.GetAll()
|
||||
for _, binding := range allBindings {
|
||||
app.Logger.Info("Key binding", "accelerator", binding.Accelerator)
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Context-Aware Key Bindings
|
||||
|
||||
Make key bindings context-aware by checking application state:
|
||||
|
||||
```go
|
||||
app.KeyBinding.Add("Ctrl+S", func(window application.Window) {
|
||||
// Check current application state
|
||||
if isEditMode() {
|
||||
// Save document
|
||||
saveDocument()
|
||||
} else if isInSettings() {
|
||||
// Save settings
|
||||
saveSettings()
|
||||
} else {
|
||||
app.Logger.Info("Save not available in current context")
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Window-Specific Actions
|
||||
|
||||
Key bindings receive the active window, allowing window-specific behavior:
|
||||
|
||||
```go
|
||||
app.KeyBinding.Add("F11", func(window application.Window) {
|
||||
// Toggle fullscreen for the active window
|
||||
window.ToggleFullscreen()
|
||||
})
|
||||
|
||||
app.KeyBinding.Add("Ctrl+W", func(window application.Window) {
|
||||
// Close the active window
|
||||
window.Close()
|
||||
})
|
||||
```
|
||||
|
||||
### Dynamic Key Binding Management
|
||||
|
||||
Dynamically add and remove key bindings based on application state:
|
||||
|
||||
```go
|
||||
func enableEditMode() {
|
||||
// Add edit-specific key bindings
|
||||
app.KeyBinding.Add("Ctrl+B", func(window application.Window) {
|
||||
window.EmitEvent("format:bold", nil)
|
||||
})
|
||||
|
||||
app.KeyBinding.Add("Ctrl+I", func(window application.Window) {
|
||||
window.EmitEvent("format:italic", nil)
|
||||
})
|
||||
|
||||
app.KeyBinding.Add("Ctrl+U", func(window application.Window) {
|
||||
window.EmitEvent("format:underline", nil)
|
||||
})
|
||||
}
|
||||
|
||||
func disableEditMode() {
|
||||
// Remove edit-specific key bindings
|
||||
app.KeyBinding.Remove("Ctrl+B")
|
||||
app.KeyBinding.Remove("Ctrl+I")
|
||||
app.KeyBinding.Remove("Ctrl+U")
|
||||
}
|
||||
```
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
On macOS:
|
||||
|
||||
- Use `Cmd` instead of `Ctrl` for standard shortcuts
|
||||
- `Cmd+Q` is typically reserved for quitting the application
|
||||
- `Cmd+H` hides the application
|
||||
- `Cmd+M` minimizes windows
|
||||
- Consider standard macOS keyboard shortcuts
|
||||
|
||||
Common macOS patterns:
|
||||
```go
|
||||
app.KeyBinding.Add("Cmd+N", newFileHandler) // New
|
||||
app.KeyBinding.Add("Cmd+O", openFileHandler) // Open
|
||||
app.KeyBinding.Add("Cmd+S", saveFileHandler) // Save
|
||||
app.KeyBinding.Add("Cmd+Z", undoHandler) // Undo
|
||||
app.KeyBinding.Add("Cmd+Shift+Z", redoHandler) // Redo
|
||||
app.KeyBinding.Add("Cmd+C", copyHandler) // Copy
|
||||
app.KeyBinding.Add("Cmd+V", pasteHandler) // Paste
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
|
||||
On Windows:
|
||||
|
||||
- Use `Ctrl` for standard shortcuts
|
||||
- `Alt+F4` closes applications
|
||||
- `F1` typically opens help
|
||||
- Consider Windows keyboard conventions
|
||||
|
||||
Common Windows patterns:
|
||||
```go
|
||||
app.KeyBinding.Add("Ctrl+N", newFileHandler) // New
|
||||
app.KeyBinding.Add("Ctrl+O", openFileHandler) // Open
|
||||
app.KeyBinding.Add("Ctrl+S", saveFileHandler) // Save
|
||||
app.KeyBinding.Add("Ctrl+Z", undoHandler) // Undo
|
||||
app.KeyBinding.Add("Ctrl+Y", redoHandler) // Redo
|
||||
app.KeyBinding.Add("Ctrl+C", copyHandler) // Copy
|
||||
app.KeyBinding.Add("Ctrl+V", pasteHandler) // Paste
|
||||
app.KeyBinding.Add("F1", helpHandler) // Help
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
|
||||
On Linux:
|
||||
|
||||
- Generally follows Windows conventions with `Ctrl`
|
||||
- May vary by desktop environment
|
||||
- Consider GNOME/KDE standard shortcuts
|
||||
- Some desktop environments reserve certain shortcuts
|
||||
|
||||
Common Linux patterns:
|
||||
```go
|
||||
app.KeyBinding.Add("Ctrl+N", newFileHandler) // New
|
||||
app.KeyBinding.Add("Ctrl+O", openFileHandler) // Open
|
||||
app.KeyBinding.Add("Ctrl+S", saveFileHandler) // Save
|
||||
app.KeyBinding.Add("Ctrl+Z", undoHandler) // Undo
|
||||
app.KeyBinding.Add("Ctrl+Shift+Z", redoHandler) // Redo
|
||||
app.KeyBinding.Add("Ctrl+C", copyHandler) // Copy
|
||||
app.KeyBinding.Add("Ctrl+V", pasteHandler) // Paste
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Standard Shortcuts**: Follow platform conventions for common operations:
|
||||
```go
|
||||
// Cross-platform save
|
||||
if runtime.GOOS == "darwin" {
|
||||
app.KeyBinding.Add("Cmd+S", saveHandler)
|
||||
} else {
|
||||
app.KeyBinding.Add("Ctrl+S", saveHandler)
|
||||
}
|
||||
```
|
||||
|
||||
2. **Provide Visual Feedback**: Let users know when shortcuts are triggered:
|
||||
```go
|
||||
app.KeyBinding.Add("Ctrl+S", func(window application.Window) {
|
||||
saveDocument()
|
||||
// Show brief notification
|
||||
window.EmitEvent("notification:show", "Document saved")
|
||||
})
|
||||
```
|
||||
|
||||
3. **Handle Conflicts**: Be careful not to override important system shortcuts:
|
||||
```go
|
||||
// Avoid overriding system shortcuts like:
|
||||
// Ctrl+Alt+Del (Windows)
|
||||
// Cmd+Space (macOS Spotlight)
|
||||
// Alt+Tab (Window switching)
|
||||
```
|
||||
|
||||
4. **Document Shortcuts**: Provide help or documentation for available shortcuts:
|
||||
```go
|
||||
app.KeyBinding.Add("F1", func(window application.Window) {
|
||||
// Show help dialog with available shortcuts
|
||||
showKeyboardShortcutsHelp()
|
||||
})
|
||||
```
|
||||
|
||||
5. **Clean Up**: Remove temporary key bindings when they're no longer needed:
|
||||
```go
|
||||
func enterEditMode() {
|
||||
app.KeyBinding.Add("Escape", exitEditModeHandler)
|
||||
}
|
||||
|
||||
func exitEditModeHandler(window application.Window) {
|
||||
exitEditMode()
|
||||
app.KeyBinding.Remove("Escape") // Clean up temporary binding
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a complete example of a text editor with keyboard shortcuts:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Text Editor with Shortcuts",
|
||||
})
|
||||
|
||||
// File operations
|
||||
if runtime.GOOS == "darwin" {
|
||||
app.KeyBinding.Add("Cmd+N", func(window application.Window) {
|
||||
window.EmitEvent("file:new", nil)
|
||||
})
|
||||
app.KeyBinding.Add("Cmd+O", func(window application.Window) {
|
||||
openFile(app, window)
|
||||
})
|
||||
app.KeyBinding.Add("Cmd+S", func(window application.Window) {
|
||||
window.EmitEvent("file:save", nil)
|
||||
})
|
||||
} else {
|
||||
app.KeyBinding.Add("Ctrl+N", func(window application.Window) {
|
||||
window.EmitEvent("file:new", nil)
|
||||
})
|
||||
app.KeyBinding.Add("Ctrl+O", func(window application.Window) {
|
||||
openFile(app, window)
|
||||
})
|
||||
app.KeyBinding.Add("Ctrl+S", func(window application.Window) {
|
||||
window.EmitEvent("file:save", nil)
|
||||
})
|
||||
}
|
||||
|
||||
// View operations
|
||||
app.KeyBinding.Add("F11", func(window application.Window) {
|
||||
window.ToggleFullscreen()
|
||||
})
|
||||
|
||||
app.KeyBinding.Add("F1", func(window application.Window) {
|
||||
showKeyboardShortcuts(window)
|
||||
})
|
||||
|
||||
// Create main window
|
||||
window := app.Window.New()
|
||||
window.SetTitle("Text Editor")
|
||||
|
||||
err := app.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func openFile(app *application.App, window application.Window) {
|
||||
dialog := app.Dialog.OpenFile()
|
||||
dialog.AddFilter("Text Files", "*.txt;*.md")
|
||||
|
||||
if file, err := dialog.PromptForSingleSelection(); err == nil {
|
||||
window.EmitEvent("file:open", file)
|
||||
}
|
||||
}
|
||||
|
||||
func showKeyboardShortcuts(window application.Window) {
|
||||
shortcuts := `
|
||||
Keyboard Shortcuts:
|
||||
- Ctrl/Cmd+N: New file
|
||||
- Ctrl/Cmd+O: Open file
|
||||
- Ctrl/Cmd+S: Save file
|
||||
- F11: Toggle fullscreen
|
||||
- F1: Show this help
|
||||
`
|
||||
window.EmitEvent("help:show", shortcuts)
|
||||
}
|
||||
```
|
||||
|
||||
:::tip[Pro Tip]
|
||||
Test your key bindings on all target platforms to ensure they work correctly and don't conflict with system shortcuts.
|
||||
:::
|
||||
|
||||
:::danger[Warning]
|
||||
Be careful not to override critical system shortcuts. Some key combinations are reserved by the operating system and cannot be captured by applications.
|
||||
:::
|
||||
640
docs/src/content/docs/features/menus/application.mdx
Normal file
640
docs/src/content/docs/features/menus/application.mdx
Normal file
|
|
@ -0,0 +1,640 @@
|
|||
---
|
||||
title: Application Menus
|
||||
description: Create native menu bars for your desktop application
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { Card, CardGrid, Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
## The Problem
|
||||
|
||||
Professional desktop applications need menu bars—File, Edit, View, Help. But menus work differently on each platform:
|
||||
- **macOS**: Global menu bar at top of screen
|
||||
- **Windows**: Menu bar in window title bar
|
||||
- **Linux**: Varies by desktop environment
|
||||
|
||||
Building platform-appropriate menus manually is tedious and error-prone.
|
||||
|
||||
## The Wails Solution
|
||||
|
||||
Wails provides a **unified API** that creates platform-native menus automatically. Write once, get native behaviour on all platforms.
|
||||
|
||||
{/* VISUAL PLACEHOLDER: Menu Bar Comparison
|
||||
Description: Three screenshots side-by-side showing the same Wails menu on:
|
||||
1. macOS - Global menu bar at top of screen with app name
|
||||
2. Windows - Menu bar in window title bar
|
||||
3. Linux (GNOME) - Menu bar in window
|
||||
All showing identical menu structure: File, Edit, View, Tools, Help
|
||||
Style: Clean screenshots with subtle borders, labels indicating platform
|
||||
*/}
|
||||
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "My App",
|
||||
})
|
||||
|
||||
// Create menu
|
||||
menu := application.NewMenu()
|
||||
|
||||
// Add standard menus (platform-appropriate)
|
||||
if runtime.GOOS == "darwin" {
|
||||
menu.AddRole(application.AppMenu) // macOS only
|
||||
}
|
||||
menu.AddRole(application.FileMenu)
|
||||
menu.AddRole(application.EditMenu)
|
||||
menu.AddRole(application.WindowMenu)
|
||||
menu.AddRole(application.HelpMenu)
|
||||
|
||||
// Set the menu
|
||||
app.Menu.Set(menu)
|
||||
|
||||
// Create window and run
|
||||
app.Window.New()
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
**That's it!** You now have platform-native menus with standard items.
|
||||
|
||||
## Creating Menus
|
||||
|
||||
### Basic Menu Creation
|
||||
|
||||
```go
|
||||
// Create a new menu
|
||||
menu := application.NewMenu()
|
||||
|
||||
// Add a top-level menu
|
||||
fileMenu := menu.AddSubmenu("File")
|
||||
|
||||
// Add menu items
|
||||
fileMenu.Add("New").OnClick(func(ctx *application.Context) {
|
||||
// Handle New
|
||||
})
|
||||
|
||||
fileMenu.Add("Open").OnClick(func(ctx *application.Context) {
|
||||
// Handle Open
|
||||
})
|
||||
|
||||
fileMenu.AddSeparator()
|
||||
|
||||
fileMenu.Add("Quit").OnClick(func(ctx *application.Context) {
|
||||
app.Quit()
|
||||
})
|
||||
```
|
||||
|
||||
### Setting the Menu
|
||||
|
||||
Platform-specific API:
|
||||
|
||||
<Tabs syncKey="platform">
|
||||
<TabItem label="macOS" icon="apple">
|
||||
**Global menu bar** (one per application):
|
||||
|
||||
```go
|
||||
app.Menu.Set(menu)
|
||||
```
|
||||
|
||||
The menu appears at the top of the screen and persists even when all windows are closed.
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="seti:windows">
|
||||
**Per-window menu bar**:
|
||||
|
||||
```go
|
||||
window.SetMenu(menu)
|
||||
```
|
||||
|
||||
Each window can have its own menu. The menu appears in the window's title bar.
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="linux">
|
||||
**Per-window menu bar** (usually):
|
||||
|
||||
```go
|
||||
window.SetMenu(menu)
|
||||
```
|
||||
|
||||
Behaviour varies by desktop environment. Some (like Unity) support global menus.
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
**Cross-platform helper:**
|
||||
|
||||
```go
|
||||
func setMenuForPlatform(app *application.App, window application.Window, menu *application.Menu) {
|
||||
if runtime.GOOS == "darwin" {
|
||||
app.Menu.Set(menu)
|
||||
} else {
|
||||
window.SetMenu(menu)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Menu Roles
|
||||
|
||||
Wails provides **predefined menu roles** that create platform-appropriate menu structures automatically.
|
||||
|
||||
### Available Roles
|
||||
|
||||
| Role | Description | Platform Notes |
|
||||
|------|-------------|----------------|
|
||||
| `AppMenu` | Application menu with About, Preferences, Quit | **macOS only** |
|
||||
| `FileMenu` | File operations (New, Open, Save, etc.) | All platforms |
|
||||
| `EditMenu` | Text editing (Undo, Redo, Cut, Copy, Paste) | All platforms |
|
||||
| `WindowMenu` | Window management (Minimise, Zoom, etc.) | All platforms |
|
||||
| `HelpMenu` | Help and information | All platforms |
|
||||
|
||||
### Using Roles
|
||||
|
||||
```go
|
||||
menu := application.NewMenu()
|
||||
|
||||
// macOS: Add application menu
|
||||
if runtime.GOOS == "darwin" {
|
||||
menu.AddRole(application.AppMenu)
|
||||
}
|
||||
|
||||
// All platforms: Add standard menus
|
||||
menu.AddRole(application.FileMenu)
|
||||
menu.AddRole(application.EditMenu)
|
||||
menu.AddRole(application.WindowMenu)
|
||||
menu.AddRole(application.HelpMenu)
|
||||
```
|
||||
|
||||
**What you get:**
|
||||
|
||||
<Tabs syncKey="platform">
|
||||
<TabItem label="macOS" icon="apple">
|
||||
**AppMenu** (with app name):
|
||||
- About [App Name]
|
||||
- Preferences... (⌘,)
|
||||
- ---
|
||||
- Services
|
||||
- ---
|
||||
- Hide [App Name] (⌘H)
|
||||
- Hide Others (⌥⌘H)
|
||||
- Show All
|
||||
- ---
|
||||
- Quit [App Name] (⌘Q)
|
||||
|
||||
**FileMenu**:
|
||||
- New (⌘N)
|
||||
- Open... (⌘O)
|
||||
- ---
|
||||
- Close Window (⌘W)
|
||||
|
||||
**EditMenu**:
|
||||
- Undo (⌘Z)
|
||||
- Redo (⇧⌘Z)
|
||||
- ---
|
||||
- Cut (⌘X)
|
||||
- Copy (⌘C)
|
||||
- Paste (⌘V)
|
||||
- Select All (⌘A)
|
||||
|
||||
**WindowMenu**:
|
||||
- Minimise (⌘M)
|
||||
- Zoom
|
||||
- ---
|
||||
- Bring All to Front
|
||||
|
||||
**HelpMenu**:
|
||||
- [App Name] Help
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="seti:windows">
|
||||
**FileMenu**:
|
||||
- New (Ctrl+N)
|
||||
- Open... (Ctrl+O)
|
||||
- ---
|
||||
- Exit (Alt+F4)
|
||||
|
||||
**EditMenu**:
|
||||
- Undo (Ctrl+Z)
|
||||
- Redo (Ctrl+Y)
|
||||
- ---
|
||||
- Cut (Ctrl+X)
|
||||
- Copy (Ctrl+C)
|
||||
- Paste (Ctrl+V)
|
||||
- Select All (Ctrl+A)
|
||||
|
||||
**WindowMenu**:
|
||||
- Minimise
|
||||
- Maximise
|
||||
|
||||
**HelpMenu**:
|
||||
- About [App Name]
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="linux">
|
||||
Similar to Windows, but keyboard shortcuts may vary by desktop environment.
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Customising Role Menus
|
||||
|
||||
Add items to role menus:
|
||||
|
||||
```go
|
||||
fileMenu := menu.AddRole(application.FileMenu)
|
||||
|
||||
// Add custom items
|
||||
fileMenu.Add("Import...").OnClick(handleImport)
|
||||
fileMenu.Add("Export...").OnClick(handleExport)
|
||||
```
|
||||
|
||||
## Custom Menus
|
||||
|
||||
Create your own menus for application-specific features:
|
||||
|
||||
```go
|
||||
// Add a custom top-level menu
|
||||
toolsMenu := menu.AddSubmenu("Tools")
|
||||
|
||||
// Add items
|
||||
toolsMenu.Add("Settings").OnClick(func(ctx *application.Context) {
|
||||
showSettingsWindow()
|
||||
})
|
||||
|
||||
toolsMenu.AddSeparator()
|
||||
|
||||
// Add checkbox
|
||||
toolsMenu.AddCheckbox("Dark Mode", false).OnClick(func(ctx *application.Context) {
|
||||
isDark := ctx.ClickedMenuItem().Checked()
|
||||
setTheme(isDark)
|
||||
})
|
||||
|
||||
// Add radio group
|
||||
toolsMenu.AddRadio("Small", true).OnClick(handleFontSize)
|
||||
toolsMenu.AddRadio("Medium", false).OnClick(handleFontSize)
|
||||
toolsMenu.AddRadio("Large", false).OnClick(handleFontSize)
|
||||
|
||||
// Add submenu
|
||||
advancedMenu := toolsMenu.AddSubmenu("Advanced")
|
||||
advancedMenu.Add("Configure...").OnClick(showAdvancedSettings)
|
||||
```
|
||||
|
||||
**For more menu item types**, see [Menu Reference](/features/menus/reference).
|
||||
|
||||
## Dynamic Menus
|
||||
|
||||
Update menus based on application state:
|
||||
|
||||
### Enable/Disable Items
|
||||
|
||||
```go
|
||||
var saveMenuItem *application.MenuItem
|
||||
|
||||
func createMenu() {
|
||||
menu := application.NewMenu()
|
||||
fileMenu := menu.AddSubmenu("File")
|
||||
|
||||
saveMenuItem = fileMenu.Add("Save")
|
||||
saveMenuItem.SetEnabled(false) // Initially disabled
|
||||
saveMenuItem.OnClick(handleSave)
|
||||
|
||||
app.Menu.Set(menu)
|
||||
}
|
||||
|
||||
func onDocumentChanged() {
|
||||
saveMenuItem.SetEnabled(hasUnsavedChanges())
|
||||
menu.Update() // Important!
|
||||
}
|
||||
```
|
||||
|
||||
:::caution[Always Call menu.Update()]
|
||||
After changing menu state (enable/disable, label, checked), **always call `menu.Update()`**. This is especially critical on Windows where menus are reconstructed.
|
||||
|
||||
See [Menu Reference](/features/menus/reference#enabled-state) for details.
|
||||
:::
|
||||
|
||||
### Change Labels
|
||||
|
||||
```go
|
||||
updateMenuItem := menu.Add("Check for Updates")
|
||||
|
||||
updateMenuItem.OnClick(func(ctx *application.Context) {
|
||||
updateMenuItem.SetLabel("Checking...")
|
||||
menu.Update()
|
||||
|
||||
checkForUpdates()
|
||||
|
||||
updateMenuItem.SetLabel("Check for Updates")
|
||||
menu.Update()
|
||||
})
|
||||
```
|
||||
|
||||
### Rebuild Menus
|
||||
|
||||
For major changes, rebuild the entire menu:
|
||||
|
||||
```go
|
||||
func rebuildFileMenu() {
|
||||
menu := application.NewMenu()
|
||||
fileMenu := menu.AddSubmenu("File")
|
||||
|
||||
fileMenu.Add("New").OnClick(handleNew)
|
||||
fileMenu.Add("Open").OnClick(handleOpen)
|
||||
|
||||
// Add recent files dynamically
|
||||
if hasRecentFiles() {
|
||||
recentMenu := fileMenu.AddSubmenu("Open Recent")
|
||||
for _, file := range getRecentFiles() {
|
||||
filePath := file // Capture for closure
|
||||
recentMenu.Add(filepath.Base(file)).OnClick(func(ctx *application.Context) {
|
||||
openFile(filePath)
|
||||
})
|
||||
}
|
||||
recentMenu.AddSeparator()
|
||||
recentMenu.Add("Clear Recent").OnClick(clearRecentFiles)
|
||||
}
|
||||
|
||||
fileMenu.AddSeparator()
|
||||
fileMenu.Add("Quit").OnClick(func(ctx *application.Context) {
|
||||
app.Quit()
|
||||
})
|
||||
|
||||
app.Menu.Set(menu)
|
||||
}
|
||||
```
|
||||
|
||||
## Window Control from Menus
|
||||
|
||||
Menu items can control windows:
|
||||
|
||||
```go
|
||||
viewMenu := menu.AddSubmenu("View")
|
||||
|
||||
// Toggle fullscreen
|
||||
viewMenu.Add("Toggle Fullscreen").OnClick(func(ctx *application.Context) {
|
||||
window, _ := app.Window.GetByName("main")
|
||||
window.ToggleFullscreen()
|
||||
})
|
||||
|
||||
// Zoom controls
|
||||
viewMenu.Add("Zoom In").SetAccelerator("CmdOrCtrl++").OnClick(func(ctx *application.Context) {
|
||||
// Increase zoom
|
||||
})
|
||||
|
||||
viewMenu.Add("Zoom Out").SetAccelerator("CmdOrCtrl+-").OnClick(func(ctx *application.Context) {
|
||||
// Decrease zoom
|
||||
})
|
||||
|
||||
viewMenu.Add("Reset Zoom").SetAccelerator("CmdOrCtrl+0").OnClick(func(ctx *application.Context) {
|
||||
// Reset zoom
|
||||
})
|
||||
```
|
||||
|
||||
**Get the active window:**
|
||||
|
||||
```go
|
||||
menuItem.OnClick(func(ctx *application.Context) {
|
||||
window := application.ContextWindow(ctx)
|
||||
// Use window
|
||||
})
|
||||
```
|
||||
|
||||
## Platform-Specific Considerations
|
||||
|
||||
### macOS
|
||||
|
||||
**Menu bar behaviour:**
|
||||
- Appears at **top of screen** (global)
|
||||
- Persists when all windows closed
|
||||
- First menu is **always the application menu**
|
||||
- Use `menu.AddRole(application.AppMenu)` for standard items
|
||||
|
||||
**Standard locations:**
|
||||
- **About**: Application menu
|
||||
- **Preferences**: Application menu (⌘,)
|
||||
- **Quit**: Application menu (⌘Q)
|
||||
- **Help**: Help menu
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
if runtime.GOOS == "darwin" {
|
||||
menu.AddRole(application.AppMenu) // Adds About, Preferences, Quit
|
||||
|
||||
// Don't add Quit to File menu on macOS
|
||||
// Don't add About to Help menu on macOS
|
||||
}
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
**Menu bar behaviour:**
|
||||
- Appears in **window title bar**
|
||||
- Each window has its own menu
|
||||
- No application menu
|
||||
|
||||
**Standard locations:**
|
||||
- **Exit**: File menu (Alt+F4)
|
||||
- **Settings**: Tools or Edit menu
|
||||
- **About**: Help menu
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
if runtime.GOOS == "windows" {
|
||||
fileMenu := menu.AddRole(application.FileMenu)
|
||||
// Exit is added automatically
|
||||
|
||||
helpMenu := menu.AddRole(application.HelpMenu)
|
||||
// About is added automatically
|
||||
}
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
**Menu bar behaviour:**
|
||||
- Usually per-window (like Windows)
|
||||
- Some DEs support global menus (Unity, GNOME with extension)
|
||||
- Appearance varies by desktop environment
|
||||
|
||||
**Best practice:** Follow Windows conventions, test on target DEs.
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a production-ready menu structure:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "My Application",
|
||||
})
|
||||
|
||||
// Create and set menu
|
||||
createMenu(app)
|
||||
|
||||
// Create main window
|
||||
app.Window.New()
|
||||
|
||||
app.Run()
|
||||
}
|
||||
|
||||
func createMenu(app *application.App) {
|
||||
menu := application.NewMenu()
|
||||
|
||||
// Platform-specific application menu (macOS only)
|
||||
if runtime.GOOS == "darwin" {
|
||||
menu.AddRole(application.AppMenu)
|
||||
}
|
||||
|
||||
// File menu
|
||||
fileMenu := menu.AddRole(application.FileMenu)
|
||||
fileMenu.Add("Import...").SetAccelerator("CmdOrCtrl+I").OnClick(handleImport)
|
||||
fileMenu.Add("Export...").SetAccelerator("CmdOrCtrl+E").OnClick(handleExport)
|
||||
|
||||
// Edit menu
|
||||
menu.AddRole(application.EditMenu)
|
||||
|
||||
// View menu
|
||||
viewMenu := menu.AddSubmenu("View")
|
||||
viewMenu.Add("Toggle Fullscreen").SetAccelerator("F11").OnClick(toggleFullscreen)
|
||||
viewMenu.AddSeparator()
|
||||
viewMenu.AddCheckbox("Show Sidebar", true).OnClick(toggleSidebar)
|
||||
viewMenu.AddCheckbox("Show Toolbar", true).OnClick(toggleToolbar)
|
||||
|
||||
// Tools menu
|
||||
toolsMenu := menu.AddSubmenu("Tools")
|
||||
|
||||
// Settings location varies by platform
|
||||
if runtime.GOOS == "darwin" {
|
||||
// On macOS, Preferences is in Application menu (added by AppMenu role)
|
||||
} else {
|
||||
toolsMenu.Add("Settings").SetAccelerator("CmdOrCtrl+,").OnClick(showSettings)
|
||||
}
|
||||
|
||||
toolsMenu.AddSeparator()
|
||||
toolsMenu.AddCheckbox("Dark Mode", false).OnClick(toggleDarkMode)
|
||||
|
||||
// Window menu
|
||||
menu.AddRole(application.WindowMenu)
|
||||
|
||||
// Help menu
|
||||
helpMenu := menu.AddRole(application.HelpMenu)
|
||||
helpMenu.Add("Documentation").OnClick(openDocumentation)
|
||||
|
||||
// About location varies by platform
|
||||
if runtime.GOOS == "darwin" {
|
||||
// On macOS, About is in Application menu (added by AppMenu role)
|
||||
} else {
|
||||
helpMenu.AddSeparator()
|
||||
helpMenu.Add("About").OnClick(showAbout)
|
||||
}
|
||||
|
||||
// Set the menu
|
||||
app.Menu.Set(menu)
|
||||
}
|
||||
|
||||
func handleImport(ctx *application.Context) {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
func handleExport(ctx *application.Context) {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
func toggleFullscreen(ctx *application.Context) {
|
||||
window := application.ContextWindow(ctx)
|
||||
window.ToggleFullscreen()
|
||||
}
|
||||
|
||||
func toggleSidebar(ctx *application.Context) {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
func toggleToolbar(ctx *application.Context) {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
func showSettings(ctx *application.Context) {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
func toggleDarkMode(ctx *application.Context) {
|
||||
isDark := ctx.ClickedMenuItem().Checked()
|
||||
// Apply theme
|
||||
}
|
||||
|
||||
func openDocumentation(ctx *application.Context) {
|
||||
// Open browser
|
||||
}
|
||||
|
||||
func showAbout(ctx *application.Context) {
|
||||
// Show about dialog
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Use menu roles** for standard menus (File, Edit, etc.)
|
||||
- **Follow platform conventions** for menu structure
|
||||
- **Add keyboard shortcuts** to common actions
|
||||
- **Call menu.Update()** after changing menu state
|
||||
- **Test on all platforms** - behaviour varies
|
||||
- **Keep menus shallow** - 2-3 levels maximum
|
||||
- **Use clear labels** - "Save Project" not "Save"
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Don't hardcode platform shortcuts** - Use `CmdOrCtrl`
|
||||
- **Don't put Quit in File menu on macOS** - It's in Application menu
|
||||
- **Don't put About in Help menu on macOS** - It's in Application menu
|
||||
- **Don't forget menu.Update()** - Menus won't work properly
|
||||
- **Don't nest too deeply** - Users get lost
|
||||
- **Don't use jargon** - Keep labels user-friendly
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGrid>
|
||||
<Card title="Menu Reference" icon="document">
|
||||
Complete reference for menu item types and properties.
|
||||
|
||||
[Learn More →](/features/menus/reference)
|
||||
</Card>
|
||||
|
||||
<Card title="Context Menus" icon="puzzle">
|
||||
Create right-click context menus.
|
||||
|
||||
[Learn More →](/features/menus/context)
|
||||
</Card>
|
||||
|
||||
<Card title="System Tray Menus" icon="star">
|
||||
Add system tray/menu bar integration.
|
||||
|
||||
[Learn More →](/features/menus/systray)
|
||||
</Card>
|
||||
|
||||
<Card title="Menu Patterns" icon="open-book">
|
||||
Common menu patterns and best practices.
|
||||
|
||||
[Learn More →](/guides/patterns/menus)
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [menu example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/menu).
|
||||
719
docs/src/content/docs/features/menus/context.mdx
Normal file
719
docs/src/content/docs/features/menus/context.mdx
Normal file
|
|
@ -0,0 +1,719 @@
|
|||
---
|
||||
title: Context Menus
|
||||
description: Create right-click context menus for your application
|
||||
sidebar:
|
||||
order: 2
|
||||
---
|
||||
|
||||
import { Card, CardGrid, Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
## The Problem
|
||||
|
||||
Users expect right-click menus with context-specific actions. Different elements need different menus:
|
||||
- **Text**: Cut, Copy, Paste
|
||||
- **Images**: Save, Copy, Open
|
||||
- **Custom elements**: Application-specific actions
|
||||
|
||||
Building context menus manually means handling mouse events, positioning, and platform differences.
|
||||
|
||||
## The Wails Solution
|
||||
|
||||
Wails provides **declarative context menus** using CSS properties. Associate menus with HTML elements, pass data, and handle clicks—all with native platform behaviour.
|
||||
|
||||
## Quick Start
|
||||
|
||||
**Go code:**
|
||||
|
||||
```go
|
||||
// Create and register context menu (auto-registered by name)
|
||||
contextMenu := application.NewContextMenu("editor-menu")
|
||||
contextMenu.Add("Cut").OnClick(handleCut)
|
||||
contextMenu.Add("Copy").OnClick(handleCopy)
|
||||
contextMenu.Add("Paste").OnClick(handlePaste)
|
||||
contextMenu.Update()
|
||||
```
|
||||
|
||||
**HTML:**
|
||||
|
||||
```html
|
||||
<textarea style="--custom-contextmenu: editor-menu">
|
||||
Right-click me!
|
||||
</textarea>
|
||||
```
|
||||
|
||||
**That's it!** Right-clicking the textarea shows your custom menu.
|
||||
|
||||
## Creating Context Menus
|
||||
|
||||
### Basic Context Menu
|
||||
|
||||
```go
|
||||
// Create menu (auto-registered by the name passed to NewContextMenu)
|
||||
contextMenu := application.NewContextMenu("text-menu")
|
||||
|
||||
// Add items
|
||||
contextMenu.Add("Cut").SetAccelerator("CmdOrCtrl+X").OnClick(func(ctx *application.Context) {
|
||||
// Handle cut
|
||||
})
|
||||
|
||||
contextMenu.Add("Copy").SetAccelerator("CmdOrCtrl+C").OnClick(func(ctx *application.Context) {
|
||||
// Handle copy
|
||||
})
|
||||
|
||||
contextMenu.Add("Paste").SetAccelerator("CmdOrCtrl+V").OnClick(func(ctx *application.Context) {
|
||||
// Handle paste
|
||||
})
|
||||
|
||||
contextMenu.Update()
|
||||
```
|
||||
|
||||
**Menu name:** Must be unique. Used to associate menu with HTML elements.
|
||||
|
||||
### With Submenus
|
||||
|
||||
```go
|
||||
contextMenu := application.NewContextMenu("image-menu")
|
||||
|
||||
// Add regular items
|
||||
contextMenu.Add("Open").OnClick(handleOpen)
|
||||
contextMenu.Add("Delete").OnClick(handleDelete)
|
||||
|
||||
contextMenu.AddSeparator()
|
||||
|
||||
// Add submenu
|
||||
exportMenu := contextMenu.AddSubmenu("Export As")
|
||||
exportMenu.Add("PNG").OnClick(exportPNG)
|
||||
exportMenu.Add("JPEG").OnClick(exportJPEG)
|
||||
exportMenu.Add("SVG").OnClick(exportSVG)
|
||||
|
||||
contextMenu.Update()
|
||||
```
|
||||
|
||||
### With Checkboxes and Radio Groups
|
||||
|
||||
```go
|
||||
contextMenu := application.NewContextMenu("view-menu")
|
||||
|
||||
// Checkbox
|
||||
contextMenu.AddCheckbox("Show Grid", true).OnClick(func(ctx *application.Context) {
|
||||
showGrid := ctx.ClickedMenuItem().Checked()
|
||||
// Toggle grid
|
||||
})
|
||||
|
||||
contextMenu.AddSeparator()
|
||||
|
||||
// Radio group
|
||||
contextMenu.AddRadio("Small", false).OnClick(handleSize)
|
||||
contextMenu.AddRadio("Medium", true).OnClick(handleSize)
|
||||
contextMenu.AddRadio("Large", false).OnClick(handleSize)
|
||||
|
||||
contextMenu.Update()
|
||||
```
|
||||
|
||||
**For all menu item types**, see [Menu Reference](/features/menus/reference).
|
||||
|
||||
## Associating with HTML Elements
|
||||
|
||||
Use CSS custom properties to attach context menus:
|
||||
|
||||
### Basic Association
|
||||
|
||||
```html
|
||||
<div style="--custom-contextmenu: menu-id">
|
||||
Right-click me!
|
||||
</div>
|
||||
```
|
||||
|
||||
**CSS property:** `--custom-contextmenu: <menu-id>`
|
||||
|
||||
### With Context Data
|
||||
|
||||
Pass data from HTML to Go:
|
||||
|
||||
```html
|
||||
<div style="--custom-contextmenu: file-menu; --custom-contextmenu-data: file-123">
|
||||
Right-click this file
|
||||
</div>
|
||||
```
|
||||
|
||||
**Go handler:**
|
||||
|
||||
```go
|
||||
contextMenu := application.NewContextMenu("file-menu")
|
||||
contextMenu.Add("Open").OnClick(func(ctx *application.Context) {
|
||||
fileID := ctx.ContextMenuData() // "file-123"
|
||||
openFile(fileID)
|
||||
})
|
||||
contextMenu.Update()
|
||||
```
|
||||
|
||||
**CSS properties:**
|
||||
- `--custom-contextmenu: <menu-id>` - Which menu to show
|
||||
- `--custom-contextmenu-data: <data>` - Data to pass to handlers
|
||||
|
||||
### Dynamic Data
|
||||
|
||||
Generate data dynamically in JavaScript:
|
||||
|
||||
```html
|
||||
<div id="file-item" style="--custom-contextmenu: file-menu">
|
||||
File.txt
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Set data dynamically
|
||||
const fileItem = document.getElementById('file-item')
|
||||
fileItem.style.setProperty('--custom-contextmenu-data', 'file-' + fileId)
|
||||
</script>
|
||||
```
|
||||
|
||||
### Multiple Elements, Same Menu
|
||||
|
||||
```html
|
||||
<div class="file-item" style="--custom-contextmenu: file-menu; --custom-contextmenu-data: file-1">
|
||||
Document.pdf
|
||||
</div>
|
||||
|
||||
<div class="file-item" style="--custom-contextmenu: file-menu; --custom-contextmenu-data: file-2">
|
||||
Image.png
|
||||
</div>
|
||||
|
||||
<div class="file-item" style="--custom-contextmenu: file-menu; --custom-contextmenu-data: file-3">
|
||||
Video.mp4
|
||||
</div>
|
||||
```
|
||||
|
||||
**One menu, different data for each element.**
|
||||
|
||||
## Context Data
|
||||
|
||||
### Accessing Context Data
|
||||
|
||||
```go
|
||||
contextMenu.Add("Process").OnClick(func(ctx *application.Context) {
|
||||
data := ctx.ContextMenuData() // Get data from HTML
|
||||
|
||||
// Use the data
|
||||
processItem(data)
|
||||
})
|
||||
```
|
||||
|
||||
**Data type:** Always `string`. Parse as needed.
|
||||
|
||||
### Passing Complex Data
|
||||
|
||||
Use JSON for complex data:
|
||||
|
||||
```html
|
||||
<div style="--custom-contextmenu: item-menu; --custom-contextmenu-data: {"id":123,"type":"image"}">
|
||||
Image.png
|
||||
</div>
|
||||
```
|
||||
|
||||
**Go handler:**
|
||||
|
||||
```go
|
||||
import "encoding/json"
|
||||
|
||||
type ItemData struct {
|
||||
ID int `json:"id"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
contextMenu.Add("Process").OnClick(func(ctx *application.Context) {
|
||||
dataStr := ctx.ContextMenuData()
|
||||
|
||||
var data ItemData
|
||||
if err := json.Unmarshal([]byte(dataStr), &data); err != nil {
|
||||
log.Printf("Invalid data: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
processItem(data.ID, data.Type)
|
||||
})
|
||||
```
|
||||
|
||||
:::caution[Security]
|
||||
**Always validate context data** from the frontend. Users can manipulate CSS properties, so treat data as untrusted input.
|
||||
:::
|
||||
|
||||
### Validation Example
|
||||
|
||||
```go
|
||||
contextMenu.Add("Delete").OnClick(func(ctx *application.Context) {
|
||||
fileID := ctx.ContextMenuData()
|
||||
|
||||
// Validate
|
||||
if !isValidFileID(fileID) {
|
||||
log.Printf("Invalid file ID: %s", fileID)
|
||||
return
|
||||
}
|
||||
|
||||
// Check permissions
|
||||
if !canDeleteFile(fileID) {
|
||||
showError("Permission denied")
|
||||
return
|
||||
}
|
||||
|
||||
// Safe to proceed
|
||||
deleteFile(fileID)
|
||||
})
|
||||
```
|
||||
|
||||
## Default Context Menu
|
||||
|
||||
The WebView provides a built-in context menu for standard operations (copy, paste, inspect). Control it with `--default-contextmenu`:
|
||||
|
||||
### Hide Default Menu
|
||||
|
||||
```html
|
||||
<div style="--default-contextmenu: hide">
|
||||
No default menu here
|
||||
</div>
|
||||
```
|
||||
|
||||
**Use case:** Custom UI elements where default menu doesn't make sense.
|
||||
|
||||
### Show Default Menu
|
||||
|
||||
```html
|
||||
<div style="--default-contextmenu: show">
|
||||
Default menu always shown
|
||||
</div>
|
||||
```
|
||||
|
||||
**Use case:** Text areas, input fields, editable content.
|
||||
|
||||
### Auto (Smart) Mode
|
||||
|
||||
```html
|
||||
<div style="--default-contextmenu: auto">
|
||||
Smart context menu
|
||||
</div>
|
||||
```
|
||||
|
||||
**Default behaviour.** Shows default menu when:
|
||||
- Text is selected
|
||||
- In text input fields
|
||||
- In editable content (`contenteditable`)
|
||||
|
||||
Hides default menu otherwise.
|
||||
|
||||
### Combining Custom and Default
|
||||
|
||||
```html
|
||||
<!-- Custom menu + default menu -->
|
||||
<textarea style="--custom-contextmenu: editor-menu; --default-contextmenu: show">
|
||||
Both menus available
|
||||
</textarea>
|
||||
```
|
||||
|
||||
**Behaviour:**
|
||||
1. Custom menu shows first
|
||||
2. If custom menu is empty or not found, default menu shows
|
||||
3. Both can coexist (platform-dependent)
|
||||
|
||||
## Dynamic Context Menus
|
||||
|
||||
Update menus based on application state:
|
||||
|
||||
### Enable/Disable Items
|
||||
|
||||
```go
|
||||
var cutMenuItem *application.MenuItem
|
||||
var copyMenuItem *application.MenuItem
|
||||
|
||||
func createContextMenu() {
|
||||
contextMenu := application.NewContextMenu("editor-menu")
|
||||
|
||||
cutMenuItem = contextMenu.Add("Cut")
|
||||
cutMenuItem.SetEnabled(false) // Initially disabled
|
||||
cutMenuItem.OnClick(handleCut)
|
||||
|
||||
copyMenuItem = contextMenu.Add("Copy")
|
||||
copyMenuItem.SetEnabled(false)
|
||||
copyMenuItem.OnClick(handleCopy)
|
||||
|
||||
contextMenu.Update()
|
||||
}
|
||||
|
||||
func onSelectionChanged(hasSelection bool) {
|
||||
cutMenuItem.SetEnabled(hasSelection)
|
||||
copyMenuItem.SetEnabled(hasSelection)
|
||||
contextMenu.Update() // Important!
|
||||
}
|
||||
```
|
||||
|
||||
:::caution[Always Call Update()]
|
||||
After changing menu state, **call `contextMenu.Update()`**. This is critical on Windows.
|
||||
|
||||
See [Menu Reference](/features/menus/reference#enabled-state) for details.
|
||||
:::
|
||||
|
||||
### Change Labels
|
||||
|
||||
```go
|
||||
playMenuItem := contextMenu.Add("Play")
|
||||
|
||||
playMenuItem.OnClick(func(ctx *application.Context) {
|
||||
if isPlaying {
|
||||
playMenuItem.SetLabel("Pause")
|
||||
} else {
|
||||
playMenuItem.SetLabel("Play")
|
||||
}
|
||||
contextMenu.Update()
|
||||
})
|
||||
```
|
||||
|
||||
### Rebuild Menus
|
||||
|
||||
For major changes, rebuild the entire menu:
|
||||
|
||||
```go
|
||||
func rebuildContextMenu(fileType string) {
|
||||
contextMenu := application.NewContextMenu("file-menu")
|
||||
|
||||
// Common items
|
||||
contextMenu.Add("Open").OnClick(handleOpen)
|
||||
contextMenu.Add("Delete").OnClick(handleDelete)
|
||||
|
||||
contextMenu.AddSeparator()
|
||||
|
||||
// Type-specific items
|
||||
switch fileType {
|
||||
case "image":
|
||||
contextMenu.Add("Edit Image").OnClick(editImage)
|
||||
contextMenu.Add("Set as Wallpaper").OnClick(setWallpaper)
|
||||
case "video":
|
||||
contextMenu.Add("Play").OnClick(playVideo)
|
||||
contextMenu.Add("Extract Audio").OnClick(extractAudio)
|
||||
case "document":
|
||||
contextMenu.Add("Print").OnClick(printDocument)
|
||||
contextMenu.Add("Export PDF").OnClick(exportPDF)
|
||||
}
|
||||
|
||||
contextMenu.Update()
|
||||
}
|
||||
```
|
||||
|
||||
## Platform Behaviour
|
||||
|
||||
Context menus are **platform-native**:
|
||||
|
||||
<Tabs syncKey="platform">
|
||||
<TabItem label="macOS" icon="apple">
|
||||
**Native macOS context menus:**
|
||||
- System animations and transitions
|
||||
- Right-click = Control+Click (automatic)
|
||||
- Adapts to system appearance (light/dark)
|
||||
- Standard text operations in default menu
|
||||
- Native scrolling for long menus
|
||||
|
||||
**macOS conventions:**
|
||||
- Use sentence case for menu items
|
||||
- Use ellipsis (...) for items that open dialogs
|
||||
- Common shortcuts: ⌘C (Copy), ⌘V (Paste)
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="seti:windows">
|
||||
**Native Windows context menus:**
|
||||
- Windows native style
|
||||
- Follows Windows theme
|
||||
- Standard Windows operations in default menu
|
||||
- Touch and pen input support
|
||||
|
||||
**Windows conventions:**
|
||||
- Use title case for menu items
|
||||
- Use ellipsis (...) for items that open dialogs
|
||||
- Common shortcuts: Ctrl+C (Copy), Ctrl+V (Paste)
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="linux">
|
||||
**Desktop environment integration:**
|
||||
- Adapts to desktop theme (GTK, Qt, etc.)
|
||||
- Right-click behaviour follows system settings
|
||||
- Default menu content varies by environment
|
||||
- Positioning follows DE conventions
|
||||
|
||||
**Linux considerations:**
|
||||
- Test on target desktop environments
|
||||
- GTK and Qt have different behaviours
|
||||
- Some DEs customise context menus
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Complete Example
|
||||
|
||||
**Go code:**
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
type FileData struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Context Menu Demo",
|
||||
})
|
||||
|
||||
// Create context menus (auto-registered by name)
|
||||
createFileMenu()
|
||||
createImageMenu()
|
||||
createTextMenu()
|
||||
|
||||
app.Window.New()
|
||||
app.Run()
|
||||
}
|
||||
|
||||
func createFileMenu() {
|
||||
menu := application.NewContextMenu("file-menu")
|
||||
|
||||
menu.Add("Open").OnClick(func(ctx *application.Context) {
|
||||
data := parseFileData(ctx.ContextMenuData())
|
||||
openFile(data.ID)
|
||||
})
|
||||
|
||||
menu.Add("Rename").OnClick(func(ctx *application.Context) {
|
||||
data := parseFileData(ctx.ContextMenuData())
|
||||
renameFile(data.ID)
|
||||
})
|
||||
|
||||
menu.AddSeparator()
|
||||
|
||||
menu.Add("Delete").OnClick(func(ctx *application.Context) {
|
||||
data := parseFileData(ctx.ContextMenuData())
|
||||
deleteFile(data.ID)
|
||||
})
|
||||
|
||||
menu.Update()
|
||||
}
|
||||
|
||||
func createImageMenu() {
|
||||
menu := application.NewContextMenu("image-menu")
|
||||
|
||||
menu.Add("View").OnClick(func(ctx *application.Context) {
|
||||
data := parseFileData(ctx.ContextMenuData())
|
||||
viewImage(data.ID)
|
||||
})
|
||||
|
||||
menu.Add("Edit").OnClick(func(ctx *application.Context) {
|
||||
data := parseFileData(ctx.ContextMenuData())
|
||||
editImage(data.ID)
|
||||
})
|
||||
|
||||
menu.AddSeparator()
|
||||
|
||||
exportMenu := menu.AddSubmenu("Export As")
|
||||
exportMenu.Add("PNG").OnClick(exportPNG)
|
||||
exportMenu.Add("JPEG").OnClick(exportJPEG)
|
||||
exportMenu.Add("WebP").OnClick(exportWebP)
|
||||
|
||||
menu.Update()
|
||||
}
|
||||
|
||||
func createTextMenu() {
|
||||
menu := application.NewContextMenu("text-menu")
|
||||
|
||||
menu.Add("Cut").SetAccelerator("CmdOrCtrl+X").OnClick(handleCut)
|
||||
menu.Add("Copy").SetAccelerator("CmdOrCtrl+C").OnClick(handleCopy)
|
||||
menu.Add("Paste").SetAccelerator("CmdOrCtrl+V").OnClick(handlePaste)
|
||||
|
||||
menu.Update()
|
||||
}
|
||||
|
||||
func parseFileData(dataStr string) FileData {
|
||||
var data FileData
|
||||
if err := json.Unmarshal([]byte(dataStr), &data); err != nil {
|
||||
log.Printf("Invalid file data: %v", err)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// Handler implementations...
|
||||
func openFile(id string) { /* ... */ }
|
||||
func renameFile(id string) { /* ... */ }
|
||||
func deleteFile(id string) { /* ... */ }
|
||||
func viewImage(id string) { /* ... */ }
|
||||
func editImage(id string) { /* ... */ }
|
||||
func exportPNG(ctx *application.Context) { /* ... */ }
|
||||
func exportJPEG(ctx *application.Context) { /* ... */ }
|
||||
func exportWebP(ctx *application.Context) { /* ... */ }
|
||||
func handleCut(ctx *application.Context) { /* ... */ }
|
||||
func handleCopy(ctx *application.Context) { /* ... */ }
|
||||
func handlePaste(ctx *application.Context) { /* ... */ }
|
||||
```
|
||||
|
||||
**HTML:**
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
.file-item {
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
border: 1px solid #ccc;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.file-item:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Files</h2>
|
||||
|
||||
<!-- Regular file -->
|
||||
<div class="file-item"
|
||||
style="--custom-contextmenu: file-menu;
|
||||
--custom-contextmenu-data: {"id":"file-1","type":"document","name":"Report.pdf"}">
|
||||
📄 Report.pdf
|
||||
</div>
|
||||
|
||||
<!-- Image file -->
|
||||
<div class="file-item"
|
||||
style="--custom-contextmenu: image-menu;
|
||||
--custom-contextmenu-data: {"id":"file-2","type":"image","name":"Photo.jpg"}">
|
||||
🖼️ Photo.jpg
|
||||
</div>
|
||||
|
||||
<h2>Text Editor</h2>
|
||||
|
||||
<!-- Text area with custom menu + default menu -->
|
||||
<textarea
|
||||
style="--custom-contextmenu: text-menu; --default-contextmenu: show"
|
||||
placeholder="Type here, then right-click...">
|
||||
</textarea>
|
||||
|
||||
<h2>No Context Menu</h2>
|
||||
|
||||
<!-- Disable default menu -->
|
||||
<div style="--default-contextmenu: hide; padding: 20px; border: 1px solid #ccc;">
|
||||
Right-click here - no menu appears
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Keep menus focused** - Only relevant actions for the element
|
||||
- **Validate context data** - Treat as untrusted input
|
||||
- **Use clear labels** - "Delete File" not "Delete"
|
||||
- **Call menu.Update()** - After changing menu state
|
||||
- **Test on all platforms** - Behaviour varies
|
||||
- **Provide keyboard shortcuts** - For common actions
|
||||
- **Group related items** - Use separators
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Don't trust context data** - Always validate
|
||||
- **Don't make menus too long** - 7-10 items maximum
|
||||
- **Don't forget menu.Update()** - Menus won't work properly
|
||||
- **Don't nest too deeply** - 2 levels maximum
|
||||
- **Don't use jargon** - Keep labels user-friendly
|
||||
- **Don't block handlers** - Keep them fast
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Context Menu Not Appearing
|
||||
|
||||
**Possible causes:**
|
||||
1. Menu ID mismatch
|
||||
2. CSS property typo
|
||||
3. Runtime not initialised
|
||||
|
||||
**Solution:**
|
||||
|
||||
```go
|
||||
// Check menu is created and registered with the correct name
|
||||
contextMenu := application.NewContextMenu("my-menu")
|
||||
// ... add items ...
|
||||
contextMenu.Update()
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- Check ID matches -->
|
||||
<div style="--custom-contextmenu: my-menu">
|
||||
```
|
||||
|
||||
### Context Data Not Received
|
||||
|
||||
**Possible causes:**
|
||||
1. CSS property not set
|
||||
2. Data contains special characters
|
||||
|
||||
**Solution:**
|
||||
|
||||
```html
|
||||
<!-- Escape quotes in JSON -->
|
||||
<div style="--custom-contextmenu-data: {"id":123}">
|
||||
```
|
||||
|
||||
Or use JavaScript:
|
||||
|
||||
```javascript
|
||||
element.style.setProperty('--custom-contextmenu-data', JSON.stringify(data))
|
||||
```
|
||||
|
||||
### Menu Items Not Responding
|
||||
|
||||
**Cause:** Forgot to call `menu.Update()` after enabling
|
||||
|
||||
**Solution:**
|
||||
|
||||
```go
|
||||
menuItem.SetEnabled(true)
|
||||
contextMenu.Update() // Add this!
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGrid>
|
||||
<Card title="Menu Reference" icon="document">
|
||||
Complete reference for menu item types and properties.
|
||||
|
||||
[Learn More →](/features/menus/reference)
|
||||
</Card>
|
||||
|
||||
<Card title="Application Menus" icon="list-format">
|
||||
Create application menu bars.
|
||||
|
||||
[Learn More →](/features/menus/application)
|
||||
</Card>
|
||||
|
||||
<Card title="System Tray Menus" icon="star">
|
||||
Add system tray/menu bar integration.
|
||||
|
||||
[Learn More →](/features/menus/systray)
|
||||
</Card>
|
||||
|
||||
<Card title="Menu Patterns" icon="open-book">
|
||||
Common menu patterns and best practices.
|
||||
|
||||
[Learn More →](/guides/patterns/menus)
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [context menu example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/contextmenus).
|
||||
562
docs/src/content/docs/features/menus/reference.mdx
Normal file
562
docs/src/content/docs/features/menus/reference.mdx
Normal file
|
|
@ -0,0 +1,562 @@
|
|||
---
|
||||
title: Menu Reference
|
||||
description: Complete reference for menu item types, properties, and methods
|
||||
sidebar:
|
||||
order: 4
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
## Menu Reference
|
||||
|
||||
Complete reference for menu item types, properties, and dynamic behaviour. Build professional, responsive menus with checkboxes, radio groups, separators, and dynamic updates.
|
||||
|
||||
## Menu Item Types
|
||||
|
||||
### Regular Menu Items
|
||||
|
||||
The most common type—displays text and triggers an action:
|
||||
|
||||
```go
|
||||
menuItem := menu.Add("Click Me")
|
||||
menuItem.OnClick(func(ctx *application.Context) {
|
||||
fmt.Println("Menu item clicked!")
|
||||
})
|
||||
```
|
||||
|
||||
**Use for:** Commands, actions, opening windows
|
||||
|
||||
### Checkboxes
|
||||
|
||||
Toggle-able menu items with checked/unchecked state:
|
||||
|
||||
```go
|
||||
checkbox := menu.AddCheckbox("Enable Feature", true) // true = initially checked
|
||||
checkbox.OnClick(func(ctx *application.Context) {
|
||||
isChecked := ctx.ClickedMenuItem().Checked()
|
||||
fmt.Printf("Feature is now: %v\n", isChecked)
|
||||
})
|
||||
```
|
||||
|
||||
**Use for:** Boolean settings, feature toggles, view options
|
||||
|
||||
**Important:** The checked state toggles automatically when clicked.
|
||||
|
||||
### Radio Groups
|
||||
|
||||
Mutually exclusive options—only one can be selected:
|
||||
|
||||
```go
|
||||
menu.AddRadio("Small", true) // true = initially selected
|
||||
menu.AddRadio("Medium", false)
|
||||
menu.AddRadio("Large", false)
|
||||
```
|
||||
|
||||
**Use for:** Mutually exclusive choices (size, theme, mode)
|
||||
|
||||
**How grouping works:**
|
||||
- Adjacent radio items form a group automatically
|
||||
- Selecting one deselects others in the group
|
||||
- Separate groups with a separator or regular item
|
||||
|
||||
**Example with multiple groups:**
|
||||
|
||||
```go
|
||||
// Group 1: Size
|
||||
menu.AddRadio("Small", true)
|
||||
menu.AddRadio("Medium", false)
|
||||
menu.AddRadio("Large", false)
|
||||
|
||||
menu.AddSeparator()
|
||||
|
||||
// Group 2: Theme
|
||||
menu.AddRadio("Light", true)
|
||||
menu.AddRadio("Dark", false)
|
||||
```
|
||||
|
||||
### Submenus
|
||||
|
||||
Nested menu structures for organisation:
|
||||
|
||||
```go
|
||||
submenu := menu.AddSubmenu("More Options")
|
||||
submenu.Add("Submenu Item 1").OnClick(func(ctx *application.Context) {
|
||||
// Handle click
|
||||
})
|
||||
submenu.Add("Submenu Item 2")
|
||||
```
|
||||
|
||||
**Use for:** Grouping related items, reducing clutter
|
||||
|
||||
**Nesting limit:** Most platforms support 2-3 levels. Avoid deeper nesting.
|
||||
|
||||
### Separators
|
||||
|
||||
Visual dividers between menu items:
|
||||
|
||||
```go
|
||||
menu.Add("Item 1")
|
||||
menu.AddSeparator()
|
||||
menu.Add("Item 2")
|
||||
```
|
||||
|
||||
**Use for:** Grouping related items visually
|
||||
|
||||
**Best practice:** Don't start or end menus with separators.
|
||||
|
||||
## Menu Item Properties
|
||||
|
||||
### Label
|
||||
|
||||
The text displayed for the menu item:
|
||||
|
||||
```go
|
||||
menuItem := menu.Add("Initial Label")
|
||||
menuItem.SetLabel("New Label")
|
||||
|
||||
// Get current label
|
||||
label := menuItem.Label()
|
||||
```
|
||||
|
||||
**Dynamic labels:**
|
||||
|
||||
```go
|
||||
updateMenuItem := menu.Add("Check for Updates")
|
||||
updateMenuItem.OnClick(func(ctx *application.Context) {
|
||||
updateMenuItem.SetLabel("Checking...")
|
||||
menu.Update() // Important on Windows!
|
||||
|
||||
// Perform update check
|
||||
checkForUpdates()
|
||||
|
||||
updateMenuItem.SetLabel("Check for Updates")
|
||||
menu.Update()
|
||||
})
|
||||
```
|
||||
|
||||
### Enabled State
|
||||
|
||||
Control whether the menu item can be interacted with:
|
||||
|
||||
```go
|
||||
menuItem := menu.Add("Save")
|
||||
menuItem.SetEnabled(false) // Greyed out, can't click
|
||||
|
||||
// Enable it later
|
||||
menuItem.SetEnabled(true)
|
||||
menu.Update() // Important: Call this after changing enabled state!
|
||||
|
||||
// Check current state
|
||||
isEnabled := menuItem.Enabled()
|
||||
```
|
||||
|
||||
:::caution[Windows Menu Behaviour]
|
||||
On Windows, menus need to be reconstructed when their state changes. **Always call `menu.Update()` after enabling/disabling menu items**, especially if the item was created whilst disabled.
|
||||
|
||||
**Why:** Windows menus are rebuilt from scratch when updated. If you don't call `Update()`, click handlers won't fire properly.
|
||||
:::
|
||||
|
||||
**Example: Dynamic enable/disable**
|
||||
|
||||
```go
|
||||
var hasSelection bool
|
||||
|
||||
cutMenuItem := menu.Add("Cut")
|
||||
cutMenuItem.SetEnabled(false) // Initially disabled
|
||||
|
||||
copyMenuItem := menu.Add("Copy")
|
||||
copyMenuItem.SetEnabled(false)
|
||||
|
||||
// When selection changes
|
||||
func onSelectionChanged(selected bool) {
|
||||
hasSelection = selected
|
||||
cutMenuItem.SetEnabled(hasSelection)
|
||||
copyMenuItem.SetEnabled(hasSelection)
|
||||
menu.Update() // Critical on Windows!
|
||||
}
|
||||
```
|
||||
|
||||
**Common pattern: Enable on condition**
|
||||
|
||||
```go
|
||||
saveMenuItem := menu.Add("Save")
|
||||
|
||||
func updateSaveMenuItem() {
|
||||
canSave := hasUnsavedChanges() && !isSaving()
|
||||
saveMenuItem.SetEnabled(canSave)
|
||||
menu.Update()
|
||||
}
|
||||
|
||||
// Call whenever state changes
|
||||
onDocumentChanged(func() {
|
||||
updateSaveMenuItem()
|
||||
})
|
||||
```
|
||||
|
||||
### Checked State
|
||||
|
||||
For checkbox and radio items, control or query their checked state:
|
||||
|
||||
```go
|
||||
checkbox := menu.AddCheckbox("Feature", false)
|
||||
checkbox.SetChecked(true)
|
||||
menu.Update()
|
||||
|
||||
// Query state
|
||||
isChecked := checkbox.Checked()
|
||||
```
|
||||
|
||||
**Auto-toggle:** Checkboxes toggle automatically when clicked. You don't need to call `SetChecked()` in the click handler.
|
||||
|
||||
**Manual control:**
|
||||
|
||||
```go
|
||||
checkbox := menu.AddCheckbox("Auto-save", false)
|
||||
|
||||
// Sync with external state
|
||||
func syncAutoSave(enabled bool) {
|
||||
checkbox.SetChecked(enabled)
|
||||
menu.Update()
|
||||
}
|
||||
```
|
||||
|
||||
### Accelerators (Keyboard Shortcuts)
|
||||
|
||||
Add keyboard shortcuts to menu items:
|
||||
|
||||
```go
|
||||
saveMenuItem := menu.Add("Save")
|
||||
saveMenuItem.SetAccelerator("CmdOrCtrl+S")
|
||||
|
||||
quitMenuItem := menu.Add("Quit")
|
||||
quitMenuItem.SetAccelerator("CmdOrCtrl+Q")
|
||||
```
|
||||
|
||||
**Accelerator format:**
|
||||
- `CmdOrCtrl` - Cmd on macOS, Ctrl on Windows/Linux
|
||||
- `Shift`, `Alt`, `Option` - Modifier keys
|
||||
- `A-Z`, `0-9` - Letter/number keys
|
||||
- `F1-F12` - Function keys
|
||||
- `Enter`, `Space`, `Backspace`, etc. - Special keys
|
||||
|
||||
**Examples:**
|
||||
|
||||
```go
|
||||
"CmdOrCtrl+S" // Save
|
||||
"CmdOrCtrl+Shift+S" // Save As
|
||||
"CmdOrCtrl+W" // Close Window
|
||||
"CmdOrCtrl+Q" // Quit
|
||||
"F5" // Refresh
|
||||
"CmdOrCtrl+," // Preferences (macOS convention)
|
||||
"Alt+F4" // Close (Windows convention)
|
||||
```
|
||||
|
||||
**Platform-specific accelerators:**
|
||||
|
||||
```go
|
||||
if runtime.GOOS == "darwin" {
|
||||
prefsMenuItem.SetAccelerator("Cmd+,")
|
||||
} else {
|
||||
prefsMenuItem.SetAccelerator("Ctrl+P")
|
||||
}
|
||||
```
|
||||
|
||||
### Tooltip
|
||||
|
||||
Add hover text to menu items (platform support varies):
|
||||
|
||||
```go
|
||||
menuItem := menu.Add("Advanced Options")
|
||||
menuItem.SetTooltip("Configure advanced settings")
|
||||
```
|
||||
|
||||
**Platform support:**
|
||||
- **Windows:** ✅ Supported
|
||||
- **macOS:** ❌ Not supported (tooltips not standard for menus)
|
||||
- **Linux:** ⚠️ Varies by desktop environment
|
||||
|
||||
### Hidden State
|
||||
|
||||
Hide menu items without removing them:
|
||||
|
||||
```go
|
||||
debugMenuItem := menu.Add("Debug Mode")
|
||||
debugMenuItem.SetHidden(true) // Hidden
|
||||
|
||||
// Show in debug builds
|
||||
if isDebugBuild {
|
||||
debugMenuItem.SetHidden(false)
|
||||
menu.Update()
|
||||
}
|
||||
```
|
||||
|
||||
**Use for:** Debug options, feature flags, conditional features
|
||||
|
||||
## Event Handling
|
||||
|
||||
### OnClick Handler
|
||||
|
||||
Execute code when menu item is clicked:
|
||||
|
||||
```go
|
||||
menuItem := menu.Add("Click Me")
|
||||
menuItem.OnClick(func(ctx *application.Context) {
|
||||
// Handle click
|
||||
fmt.Println("Clicked!")
|
||||
})
|
||||
```
|
||||
|
||||
**Context provides:**
|
||||
- `ctx.ClickedMenuItem()` - The menu item that was clicked
|
||||
- Window context (if from window menu)
|
||||
- Application context
|
||||
|
||||
**Example: Access menu item in handler**
|
||||
|
||||
```go
|
||||
checkbox := menu.AddCheckbox("Feature", false)
|
||||
checkbox.OnClick(func(ctx *application.Context) {
|
||||
item := ctx.ClickedMenuItem()
|
||||
isChecked := item.Checked()
|
||||
fmt.Printf("Feature is now: %v\n", isChecked)
|
||||
})
|
||||
```
|
||||
|
||||
### Multiple Handlers
|
||||
|
||||
You can set multiple handlers (last one wins):
|
||||
|
||||
```go
|
||||
menuItem := menu.Add("Action")
|
||||
menuItem.OnClick(func(ctx *application.Context) {
|
||||
fmt.Println("First handler")
|
||||
})
|
||||
|
||||
// This replaces the first handler
|
||||
menuItem.OnClick(func(ctx *application.Context) {
|
||||
fmt.Println("Second handler - this one runs")
|
||||
})
|
||||
```
|
||||
|
||||
**Best practice:** Set handler once, use conditional logic inside if needed.
|
||||
|
||||
## Dynamic Menus
|
||||
|
||||
### Updating Menu Items
|
||||
|
||||
**The golden rule:** Always call `menu.Update()` after changing menu state.
|
||||
|
||||
```go
|
||||
// ✅ Correct
|
||||
menuItem.SetEnabled(true)
|
||||
menu.Update()
|
||||
|
||||
// ❌ Wrong (especially on Windows)
|
||||
menuItem.SetEnabled(true)
|
||||
// Forgot to call Update() - click handlers may not work!
|
||||
```
|
||||
|
||||
**Why this matters:**
|
||||
- **Windows:** Menus are reconstructed when updated
|
||||
- **macOS/Linux:** Less critical but still recommended
|
||||
- **Click handlers:** Won't fire properly without Update()
|
||||
|
||||
### Rebuilding Menus
|
||||
|
||||
For major changes, rebuild the entire menu:
|
||||
|
||||
```go
|
||||
func rebuildFileMenu() {
|
||||
menu := app.Menu.New()
|
||||
|
||||
menu.Add("New").OnClick(handleNew)
|
||||
menu.Add("Open").OnClick(handleOpen)
|
||||
|
||||
if hasRecentFiles() {
|
||||
recentMenu := menu.AddSubmenu("Open Recent")
|
||||
for _, file := range getRecentFiles() {
|
||||
recentMenu.Add(file).OnClick(func(ctx *application.Context) {
|
||||
openFile(file)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
menu.AddSeparator()
|
||||
menu.Add("Quit").OnClick(handleQuit)
|
||||
|
||||
// Set the new menu
|
||||
window.SetMenu(menu)
|
||||
}
|
||||
```
|
||||
|
||||
**When to rebuild:**
|
||||
- Recent files list changes
|
||||
- Plugin menus change
|
||||
- Major state transitions
|
||||
|
||||
**When to update:**
|
||||
- Enable/disable items
|
||||
- Change labels
|
||||
- Toggle checkboxes
|
||||
|
||||
### Context-Sensitive Menus
|
||||
|
||||
Adjust menus based on application state:
|
||||
|
||||
```go
|
||||
func updateEditMenu() {
|
||||
cutMenuItem.SetEnabled(hasSelection())
|
||||
copyMenuItem.SetEnabled(hasSelection())
|
||||
pasteMenuItem.SetEnabled(hasClipboardContent())
|
||||
undoMenuItem.SetEnabled(canUndo())
|
||||
redoMenuItem.SetEnabled(canRedo())
|
||||
menu.Update()
|
||||
}
|
||||
|
||||
// Call whenever state changes
|
||||
onSelectionChanged(updateEditMenu)
|
||||
onClipboardChanged(updateEditMenu)
|
||||
onUndoStackChanged(updateEditMenu)
|
||||
```
|
||||
|
||||
## Platform Differences
|
||||
|
||||
### Menu Bar Location
|
||||
|
||||
| Platform | Location | Notes |
|
||||
|----------|----------|-------|
|
||||
| **macOS** | Top of screen | Global menu bar |
|
||||
| **Windows** | Top of window | Per-window menu |
|
||||
| **Linux** | Top of window | Per-window (usually) |
|
||||
|
||||
### Standard Menus
|
||||
|
||||
**macOS:**
|
||||
- Has "Application" menu (with app name)
|
||||
- "Preferences" in Application menu
|
||||
- "Quit" in Application menu
|
||||
|
||||
**Windows/Linux:**
|
||||
- No Application menu
|
||||
- "Preferences" in Edit or Tools menu
|
||||
- "Exit" in File menu
|
||||
|
||||
**Example: Platform-appropriate structure**
|
||||
|
||||
```go
|
||||
menu := app.Menu.New()
|
||||
|
||||
// macOS gets Application menu
|
||||
if runtime.GOOS == "darwin" {
|
||||
menu.AddRole(application.AppMenu)
|
||||
}
|
||||
|
||||
// File menu
|
||||
fileMenu := menu.AddSubmenu("File")
|
||||
fileMenu.Add("New")
|
||||
fileMenu.Add("Open")
|
||||
|
||||
// Preferences location varies
|
||||
if runtime.GOOS == "darwin" {
|
||||
// On macOS, preferences are in Application menu (added by AppMenu role)
|
||||
} else {
|
||||
// On Windows/Linux, add to Edit or Tools menu
|
||||
editMenu := menu.AddSubmenu("Edit")
|
||||
editMenu.Add("Preferences")
|
||||
}
|
||||
```
|
||||
|
||||
### Accelerator Conventions
|
||||
|
||||
**macOS:**
|
||||
- `Cmd+` for most shortcuts
|
||||
- `Cmd+,` for Preferences
|
||||
- `Cmd+Q` for Quit
|
||||
|
||||
**Windows:**
|
||||
- `Ctrl+` for most shortcuts
|
||||
- `Ctrl+P` or `Ctrl+,` for Preferences
|
||||
- `Alt+F4` for Exit (or `Ctrl+Q`)
|
||||
|
||||
**Linux:**
|
||||
- Generally follows Windows conventions
|
||||
- Desktop environment may override
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Call menu.Update()** after changing menu state (especially on Windows)
|
||||
- **Use radio groups** for mutually exclusive options
|
||||
- **Use checkboxes** for toggleable features
|
||||
- **Add accelerators** to common actions
|
||||
- **Group related items** with separators
|
||||
- **Test on all platforms** - behaviour varies
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Don't forget menu.Update()** - Click handlers won't work properly
|
||||
- **Don't nest too deeply** - 2-3 levels maximum
|
||||
- **Don't start/end with separators** - Looks unprofessional
|
||||
- **Don't use tooltips on macOS** - Not supported
|
||||
- **Don't hardcode platform shortcuts** - Use `CmdOrCtrl`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Menu Items Not Responding
|
||||
|
||||
**Symptom:** Click handlers don't fire
|
||||
|
||||
**Cause:** Forgot to call `menu.Update()` after enabling item
|
||||
|
||||
**Solution:**
|
||||
|
||||
```go
|
||||
menuItem.SetEnabled(true)
|
||||
menu.Update() // Add this!
|
||||
```
|
||||
|
||||
### Menu Items Greyed Out
|
||||
|
||||
**Symptom:** Can't click menu items
|
||||
|
||||
**Cause:** Items are disabled
|
||||
|
||||
**Solution:**
|
||||
|
||||
```go
|
||||
menuItem.SetEnabled(true)
|
||||
menu.Update()
|
||||
```
|
||||
|
||||
### Accelerators Not Working
|
||||
|
||||
**Symptom:** Keyboard shortcuts don't trigger menu items
|
||||
|
||||
**Causes:**
|
||||
1. Accelerator format incorrect
|
||||
2. Conflict with system shortcuts
|
||||
3. Window doesn't have focus
|
||||
|
||||
**Solution:**
|
||||
|
||||
```go
|
||||
// Check format
|
||||
menuItem.SetAccelerator("CmdOrCtrl+S") // ✅ Correct
|
||||
menuItem.SetAccelerator("Ctrl+S") // ❌ Wrong (macOS uses Cmd)
|
||||
|
||||
// Avoid conflicts
|
||||
// ❌ Cmd+H (Hide Window on macOS - system shortcut)
|
||||
// ✅ Cmd+Shift+H (Custom shortcut)
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Application Menus](/features/menus/application) - Create application menu bars
|
||||
- [Context Menus](/features/menus/context) - Right-click context menus
|
||||
- [System Tray Menus](/features/menus/systray) - System tray/menu bar menus
|
||||
- [Menu Patterns](/guides/patterns/menus) - Common menu patterns and best practices
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [menu examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/menu).
|
||||
703
docs/src/content/docs/features/menus/systray.mdx
Normal file
703
docs/src/content/docs/features/menus/systray.mdx
Normal file
|
|
@ -0,0 +1,703 @@
|
|||
---
|
||||
title: System Tray Menus
|
||||
description: Add system tray (notification area) integration to your application
|
||||
sidebar:
|
||||
order: 3
|
||||
---
|
||||
|
||||
import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## System Tray Menus
|
||||
|
||||
Wails provides **unified system tray APIs** that work across all platforms. Create tray icons with menus, attach windows, and handle clicks with native platform behaviour for background applications, services, and quick-access utilities.
|
||||
{/* VISUAL PLACEHOLDER: System Tray Comparison
|
||||
Description: Three screenshots showing the same Wails system tray icon on:
|
||||
1. Windows - Notification area (bottom-right)
|
||||
2. macOS - Menu bar (top-right) with label
|
||||
3. Linux (GNOME) - Top bar
|
||||
All showing the same icon and menu structure
|
||||
Style: Clean screenshots with arrows pointing to tray icon, menu expanded
|
||||
*/}
|
||||
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
//go:embed assets/icon.png
|
||||
var icon []byte
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Tray App",
|
||||
})
|
||||
|
||||
// Create system tray
|
||||
systray := app.SystemTray.New()
|
||||
systray.SetIcon(icon)
|
||||
systray.SetLabel("My App")
|
||||
|
||||
// Add menu
|
||||
menu := application.NewMenu()
|
||||
menu.Add("Show").OnClick(func(ctx *application.Context) {
|
||||
// Show main window
|
||||
})
|
||||
menu.Add("Quit").OnClick(func(ctx *application.Context) {
|
||||
app.Quit()
|
||||
})
|
||||
systray.SetMenu(menu)
|
||||
|
||||
// Create hidden window
|
||||
window := app.Window.New()
|
||||
window.Hide()
|
||||
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
**Result:** System tray icon with menu on all platforms.
|
||||
|
||||
## Creating a System Tray
|
||||
|
||||
### Basic System Tray
|
||||
|
||||
```go
|
||||
// Create system tray
|
||||
systray := app.SystemTray.New()
|
||||
|
||||
// Set icon
|
||||
systray.SetIcon(iconBytes)
|
||||
|
||||
// Set label (macOS) / tooltip (Windows)
|
||||
systray.SetLabel("My Application")
|
||||
```
|
||||
|
||||
### With Icon
|
||||
|
||||
Icons should be embedded:
|
||||
|
||||
```go
|
||||
import _ "embed"
|
||||
|
||||
//go:embed assets/icon.png
|
||||
var icon []byte
|
||||
|
||||
//go:embed assets/icon-dark.png
|
||||
var iconDark []byte
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "My App",
|
||||
})
|
||||
|
||||
systray := app.SystemTray.New()
|
||||
systray.SetIcon(icon)
|
||||
systray.SetDarkModeIcon(iconDark) // macOS dark mode
|
||||
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
**Icon requirements:**
|
||||
|
||||
| Platform | Size | Format | Notes |
|
||||
|----------|------|--------|-------|
|
||||
| **Windows** | 16x16 or 32x32 | PNG, ICO | Notification area |
|
||||
| **macOS** | 18x18 to 22x22 | PNG | Menu bar, template recommended |
|
||||
| **Linux** | 22x22 to 48x48 | PNG, SVG | Varies by DE |
|
||||
|
||||
### Template Icons (macOS)
|
||||
|
||||
Template icons adapt to light/dark mode automatically:
|
||||
|
||||
```go
|
||||
systray.SetTemplateIcon(iconBytes)
|
||||
```
|
||||
|
||||
**Template icon guidelines:**
|
||||
- Use black and clear (transparent) colours only
|
||||
- Black becomes white in dark mode
|
||||
- Name file with `Template` suffix: `iconTemplate.png`
|
||||
- [Design guide](https://bjango.com/articles/designingmenubarextras/)
|
||||
|
||||
## Adding Menus
|
||||
|
||||
System tray menus work like application menus:
|
||||
|
||||
```go
|
||||
menu := application.NewMenu()
|
||||
|
||||
// Add items
|
||||
menu.Add("Open").OnClick(func(ctx *application.Context) {
|
||||
showMainWindow()
|
||||
})
|
||||
|
||||
menu.AddSeparator()
|
||||
|
||||
menu.AddCheckbox("Start at Login", false).OnClick(func(ctx *application.Context) {
|
||||
enabled := ctx.ClickedMenuItem().Checked()
|
||||
setStartAtLogin(enabled)
|
||||
})
|
||||
|
||||
menu.AddSeparator()
|
||||
|
||||
menu.Add("Quit").OnClick(func(ctx *application.Context) {
|
||||
app.Quit()
|
||||
})
|
||||
|
||||
// Set menu
|
||||
systray.SetMenu(menu)
|
||||
```
|
||||
|
||||
**For all menu item types**, see [Menu Reference](/features/menus/reference).
|
||||
|
||||
## Attaching Windows
|
||||
|
||||
Attach a window to the tray icon for automatic show/hide:
|
||||
|
||||
```go
|
||||
// Create window
|
||||
window := app.Window.New()
|
||||
|
||||
// Attach to tray
|
||||
systray.AttachWindow(window)
|
||||
|
||||
// Configure behaviour
|
||||
systray.WindowOffset(10) // Pixels from tray icon
|
||||
systray.WindowDebounce(200 * time.Millisecond) // Click debounce
|
||||
```
|
||||
|
||||
**Behaviour:**
|
||||
- Window starts hidden
|
||||
- **Left-click tray icon** → Toggle window visibility
|
||||
- **Right-click tray icon** → Show menu (if set)
|
||||
- Window positioned near tray icon
|
||||
|
||||
**Example: Popup window**
|
||||
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Quick Access",
|
||||
Width: 300,
|
||||
Height: 400,
|
||||
Frameless: true, // No title bar
|
||||
AlwaysOnTop: true, // Stay on top
|
||||
})
|
||||
|
||||
systray.AttachWindow(window)
|
||||
systray.WindowOffset(5)
|
||||
```
|
||||
|
||||
## Click Handlers
|
||||
|
||||
Handle tray icon clicks:
|
||||
|
||||
```go
|
||||
systray := app.SystemTray.New()
|
||||
|
||||
// Left click
|
||||
systray.OnClick(func() {
|
||||
fmt.Println("Tray icon clicked")
|
||||
})
|
||||
|
||||
// Right click
|
||||
systray.OnRightClick(func() {
|
||||
fmt.Println("Tray icon right-clicked")
|
||||
})
|
||||
|
||||
// Double click
|
||||
systray.OnDoubleClick(func() {
|
||||
fmt.Println("Tray icon double-clicked")
|
||||
})
|
||||
|
||||
// Mouse enter/leave
|
||||
systray.OnMouseEnter(func() {
|
||||
fmt.Println("Mouse entered tray icon")
|
||||
})
|
||||
|
||||
systray.OnMouseLeave(func() {
|
||||
fmt.Println("Mouse left tray icon")
|
||||
})
|
||||
```
|
||||
|
||||
**Platform support:**
|
||||
|
||||
| Event | Windows | macOS | Linux |
|
||||
|-------|---------|-------|-------|
|
||||
| OnClick | ✅ | ✅ | ✅ |
|
||||
| OnRightClick | ✅ | ✅ | ✅ |
|
||||
| OnDoubleClick | ✅ | ✅ | ⚠️ Varies |
|
||||
| OnMouseEnter | ✅ | ✅ | ⚠️ Varies |
|
||||
| OnMouseLeave | ✅ | ✅ | ⚠️ Varies |
|
||||
|
||||
## Dynamic Updates
|
||||
|
||||
Update tray icon and menu dynamically:
|
||||
|
||||
### Change Icon
|
||||
|
||||
```go
|
||||
var isActive bool
|
||||
|
||||
func updateTrayIcon() {
|
||||
if isActive {
|
||||
systray.SetIcon(activeIcon)
|
||||
systray.SetLabel("Active")
|
||||
} else {
|
||||
systray.SetIcon(inactiveIcon)
|
||||
systray.SetLabel("Inactive")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Update Menu
|
||||
|
||||
```go
|
||||
var isPaused bool
|
||||
|
||||
pauseMenuItem := menu.Add("Pause")
|
||||
|
||||
pauseMenuItem.OnClick(func(ctx *application.Context) {
|
||||
isPaused = !isPaused
|
||||
|
||||
if isPaused {
|
||||
pauseMenuItem.SetLabel("Resume")
|
||||
} else {
|
||||
pauseMenuItem.SetLabel("Pause")
|
||||
}
|
||||
|
||||
menu.Update() // Important!
|
||||
})
|
||||
```
|
||||
|
||||
:::caution[Always Call Update()]
|
||||
After changing menu state, **call `menu.Update()`**. See [Menu Reference](/features/menus/reference#enabled-state).
|
||||
:::
|
||||
|
||||
### Rebuild Menu
|
||||
|
||||
For major changes, rebuild the entire menu:
|
||||
|
||||
```go
|
||||
func rebuildTrayMenu(status string) {
|
||||
menu := application.NewMenu()
|
||||
|
||||
// Status-specific items
|
||||
switch status {
|
||||
case "syncing":
|
||||
menu.Add("Syncing...").SetEnabled(false)
|
||||
menu.Add("Pause Sync").OnClick(pauseSync)
|
||||
case "synced":
|
||||
menu.Add("Up to date ✓").SetEnabled(false)
|
||||
menu.Add("Sync Now").OnClick(startSync)
|
||||
case "error":
|
||||
menu.Add("Sync Error").SetEnabled(false)
|
||||
menu.Add("Retry").OnClick(retrySync)
|
||||
}
|
||||
|
||||
menu.AddSeparator()
|
||||
menu.Add("Quit").OnClick(func(ctx *application.Context) {
|
||||
app.Quit()
|
||||
})
|
||||
|
||||
systray.SetMenu(menu)
|
||||
}
|
||||
```
|
||||
|
||||
## Platform-Specific Features
|
||||
|
||||
<Tabs syncKey="platform">
|
||||
<TabItem label="macOS" icon="apple">
|
||||
**Menu bar integration:**
|
||||
|
||||
```go
|
||||
// Set label (appears next to icon)
|
||||
systray.SetLabel("My App")
|
||||
|
||||
// Use template icon (adapts to dark mode)
|
||||
systray.SetTemplateIcon(iconBytes)
|
||||
|
||||
// Set icon position
|
||||
systray.SetIconPosition(application.IconPositionRight)
|
||||
```
|
||||
|
||||
**Icon positions:**
|
||||
- `IconPositionLeft` - Icon left of label
|
||||
- `IconPositionRight` - Icon right of label
|
||||
- `IconPositionOnly` - Icon only, no label
|
||||
- `IconPositionNone` - Label only, no icon
|
||||
|
||||
**Best practices:**
|
||||
- Use template icons (black + transparent)
|
||||
- Keep labels short (3-5 characters)
|
||||
- 18x18 to 22x22 pixels for Retina displays
|
||||
- Test in both light and dark modes
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="seti:windows">
|
||||
**Notification area integration:**
|
||||
|
||||
```go
|
||||
// Set tooltip (appears on hover)
|
||||
systray.SetTooltip("My Application")
|
||||
|
||||
// Or use SetLabel (same as tooltip on Windows)
|
||||
systray.SetLabel("My Application")
|
||||
|
||||
// Show/Hide functionality (fully functional)
|
||||
systray.Show() // Show tray icon
|
||||
systray.Hide() // Hide tray icon
|
||||
```
|
||||
|
||||
**Icon requirements:**
|
||||
- 16x16 or 32x32 pixels
|
||||
- PNG or ICO format
|
||||
- Transparent background
|
||||
|
||||
**Tooltip limits:**
|
||||
- Maximum 127 UTF-16 characters
|
||||
- Longer tooltips will be truncated
|
||||
- Keep concise for best experience
|
||||
|
||||
**Platform features:**
|
||||
- Tray icon survives Windows Explorer restarts
|
||||
- Show() and Hide() methods fully functional
|
||||
- Proper lifecycle management
|
||||
|
||||
**Best practices:**
|
||||
- Use 32x32 for high-DPI displays
|
||||
- Keep tooltips under 127 characters
|
||||
- Test on different Windows versions
|
||||
- Consider notification area overflow
|
||||
- Use Show/Hide for conditional tray visibility
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="linux">
|
||||
**System tray integration:**
|
||||
|
||||
Uses StatusNotifierItem specification (most modern DEs).
|
||||
|
||||
```go
|
||||
systray.SetIcon(iconBytes)
|
||||
systray.SetLabel("My App")
|
||||
```
|
||||
|
||||
**Desktop environment support:**
|
||||
- **GNOME**: Top bar (with extension)
|
||||
- **KDE Plasma**: System tray
|
||||
- **XFCE**: Notification area
|
||||
- **Others**: Varies
|
||||
|
||||
**Best practices:**
|
||||
- Use 22x22 or 24x24 pixels
|
||||
- SVG icons scale better
|
||||
- Test on target desktop environments
|
||||
- Provide fallback for unsupported DEs
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a production-ready system tray application:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"time"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
//go:embed assets/icon.png
|
||||
var icon []byte
|
||||
|
||||
//go:embed assets/icon-active.png
|
||||
var iconActive []byte
|
||||
|
||||
type TrayApp struct {
|
||||
app *application.App
|
||||
systray *application.SystemTray
|
||||
window *application.WebviewWindow
|
||||
menu *application.Menu
|
||||
isActive bool
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Tray Application",
|
||||
Mac: application.MacOptions{
|
||||
ApplicationShouldTerminateAfterLastWindowClosed: false,
|
||||
},
|
||||
})
|
||||
|
||||
trayApp := &TrayApp{app: app}
|
||||
trayApp.setup()
|
||||
|
||||
app.Run()
|
||||
}
|
||||
|
||||
func (t *TrayApp) setup() {
|
||||
// Create system tray
|
||||
t.systray = t.app.SystemTray.New()
|
||||
t.systray.SetIcon(icon)
|
||||
t.systray.SetLabel("Inactive")
|
||||
|
||||
// Create menu
|
||||
t.createMenu()
|
||||
|
||||
// Create window (hidden by default)
|
||||
t.window = t.app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Tray Application",
|
||||
Width: 400,
|
||||
Height: 600,
|
||||
Hidden: true,
|
||||
})
|
||||
|
||||
// Attach window to tray
|
||||
t.systray.AttachWindow(t.window)
|
||||
t.systray.WindowOffset(10)
|
||||
|
||||
// Handle tray clicks
|
||||
t.systray.OnRightClick(func() {
|
||||
t.systray.OpenMenu()
|
||||
})
|
||||
|
||||
// Start background task
|
||||
go t.backgroundTask()
|
||||
}
|
||||
|
||||
func (t *TrayApp) createMenu() {
|
||||
t.menu = application.NewMenu()
|
||||
|
||||
// Status item (disabled)
|
||||
statusItem := t.menu.Add("Status: Inactive")
|
||||
statusItem.SetEnabled(false)
|
||||
|
||||
t.menu.AddSeparator()
|
||||
|
||||
// Toggle active
|
||||
t.menu.Add("Start").OnClick(func(ctx *application.Context) {
|
||||
t.toggleActive()
|
||||
})
|
||||
|
||||
// Show window
|
||||
t.menu.Add("Show Window").OnClick(func(ctx *application.Context) {
|
||||
t.window.Show()
|
||||
t.window.Focus()
|
||||
})
|
||||
|
||||
t.menu.AddSeparator()
|
||||
|
||||
// Settings
|
||||
t.menu.AddCheckbox("Start at Login", false).OnClick(func(ctx *application.Context) {
|
||||
enabled := ctx.ClickedMenuItem().Checked()
|
||||
t.setStartAtLogin(enabled)
|
||||
})
|
||||
|
||||
t.menu.AddSeparator()
|
||||
|
||||
// Quit
|
||||
t.menu.Add("Quit").OnClick(func(ctx *application.Context) {
|
||||
t.app.Quit()
|
||||
})
|
||||
|
||||
t.systray.SetMenu(t.menu)
|
||||
}
|
||||
|
||||
func (t *TrayApp) toggleActive() {
|
||||
t.isActive = !t.isActive
|
||||
t.updateTray()
|
||||
}
|
||||
|
||||
func (t *TrayApp) updateTray() {
|
||||
if t.isActive {
|
||||
t.systray.SetIcon(iconActive)
|
||||
t.systray.SetLabel("Active")
|
||||
} else {
|
||||
t.systray.SetIcon(icon)
|
||||
t.systray.SetLabel("Inactive")
|
||||
}
|
||||
|
||||
// Rebuild menu with new status
|
||||
t.createMenu()
|
||||
}
|
||||
|
||||
func (t *TrayApp) backgroundTask() {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
if t.isActive {
|
||||
fmt.Println("Background task running...")
|
||||
// Do work
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TrayApp) setStartAtLogin(enabled bool) {
|
||||
// Implementation varies by platform
|
||||
fmt.Printf("Start at login: %v\n", enabled)
|
||||
}
|
||||
```
|
||||
|
||||
## Visibility Control
|
||||
|
||||
Show/hide the tray icon dynamically:
|
||||
|
||||
```go
|
||||
// Hide tray icon
|
||||
systray.Hide()
|
||||
|
||||
// Show tray icon
|
||||
systray.Show()
|
||||
```
|
||||
|
||||
**Platform Support:**
|
||||
|
||||
| Platform | Hide() | Show() | Notes |
|
||||
|----------|--------|--------|-------|
|
||||
| **Windows** | ✅ | ✅ | Fully functional - icon appears/disappears from notification area |
|
||||
| **macOS** | ✅ | ✅ | Menu bar item shows/hides |
|
||||
| **Linux** | ✅ | ✅ | Varies by desktop environment |
|
||||
|
||||
**Use cases:**
|
||||
- Temporarily hide tray icon based on user preference
|
||||
- Headless mode with tray icon appearing only when needed
|
||||
- Toggle visibility based on application state
|
||||
|
||||
**Example - Conditional Tray Visibility:**
|
||||
|
||||
```go
|
||||
func (t *TrayApp) setTrayVisibility(visible bool) {
|
||||
if visible {
|
||||
t.systray.Show()
|
||||
} else {
|
||||
t.systray.Hide()
|
||||
}
|
||||
}
|
||||
|
||||
// Show tray only when updates are available
|
||||
func (t *TrayApp) checkForUpdates() {
|
||||
if hasUpdates {
|
||||
t.systray.Show()
|
||||
t.systray.SetLabel("Update Available")
|
||||
} else {
|
||||
t.systray.Hide()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Cleanup
|
||||
|
||||
Destroy the tray icon when done:
|
||||
|
||||
```go
|
||||
// In OnShutdown
|
||||
app := application.New(application.Options{
|
||||
OnShutdown: func() {
|
||||
if systray != nil {
|
||||
systray.Destroy()
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Important:** Always destroy system tray on shutdown to release resources.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Use template icons on macOS** - Adapts to dark mode
|
||||
- **Keep labels short** - 3-5 characters maximum
|
||||
- **Provide tooltips on Windows** - Helps users identify your app
|
||||
- **Test on all platforms** - Behaviour varies
|
||||
- **Handle clicks appropriately** - Left-click for main action, right-click for menu
|
||||
- **Update icon for status** - Visual feedback is important
|
||||
- **Destroy on shutdown** - Release resources
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Don't use large icons** - Follow platform guidelines
|
||||
- **Don't use long labels** - Gets truncated
|
||||
- **Don't forget dark mode** - Test on macOS dark mode
|
||||
- **Don't block click handlers** - Keep them fast
|
||||
- **Don't forget menu.Update()** - After changing menu state
|
||||
- **Don't assume tray support** - Some Linux DEs don't support it
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Tray Icon Not Appearing
|
||||
|
||||
**Possible causes:**
|
||||
1. Icon format not supported
|
||||
2. Icon size too large/small
|
||||
3. System tray not supported (Linux)
|
||||
|
||||
**Solution:**
|
||||
- Verify icon format and size match platform requirements (see icon table above)
|
||||
- On Linux, ensure your desktop environment supports StatusNotifierItem or has an AppIndicator extension installed
|
||||
- Test with a known-good PNG icon to rule out format issues
|
||||
|
||||
### Icon Looks Wrong on macOS
|
||||
|
||||
**Cause:** Not using template icon
|
||||
|
||||
**Solution:**
|
||||
|
||||
```go
|
||||
// Use template icon
|
||||
systray.SetTemplateIcon(iconBytes)
|
||||
|
||||
// Or design icon as template (black + transparent)
|
||||
```
|
||||
|
||||
### Menu Not Updating
|
||||
|
||||
**Cause:** Forgot to call `menu.Update()`
|
||||
|
||||
**Solution:**
|
||||
|
||||
```go
|
||||
menuItem.SetLabel("New Label")
|
||||
menu.Update() // Add this!
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGrid>
|
||||
<Card title="Menu Reference" icon="document">
|
||||
Complete reference for menu item types and properties.
|
||||
|
||||
[Learn More →](/features/menus/reference)
|
||||
</Card>
|
||||
|
||||
<Card title="Application Menus" icon="list-format">
|
||||
Create application menu bars.
|
||||
|
||||
[Learn More →](/features/menus/application)
|
||||
</Card>
|
||||
|
||||
<Card title="Context Menus" icon="puzzle">
|
||||
Create right-click context menus.
|
||||
|
||||
[Learn More →](/features/menus/context)
|
||||
</Card>
|
||||
|
||||
<Card title="System Tray Tutorial" icon="open-book">
|
||||
Build a complete system tray application.
|
||||
|
||||
[Learn More →](/tutorials/system-tray)
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [system tray examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/systray-basic).
|
||||
307
docs/src/content/docs/features/notifications/overview.mdx
Normal file
307
docs/src/content/docs/features/notifications/overview.mdx
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
---
|
||||
title: Notifications
|
||||
description: Display native system notifications with action buttons and text input
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
## Introduction
|
||||
|
||||
Wails provides a comprehensive cross-platform notification system for desktop applications. This service allows you to display native system notifications, with support for interactive elements like action buttons and text input fields.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Creating the Service
|
||||
|
||||
First, initialize the notifications service:
|
||||
|
||||
```go
|
||||
import "github.com/wailsapp/wails/v3/pkg/application"
|
||||
import "github.com/wailsapp/wails/v3/pkg/services/notifications"
|
||||
|
||||
// Create a new notification service
|
||||
notifier := notifications.New()
|
||||
|
||||
//Register the service with the application
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(notifier),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Notification Authorization
|
||||
|
||||
Notifications on macOS require user authorization. Request and check authorization:
|
||||
|
||||
```go
|
||||
authorized, err := notifier.CheckNotificationAuthorization()
|
||||
if err != nil {
|
||||
// Handle authorization error
|
||||
}
|
||||
if authorized {
|
||||
// Send notifications
|
||||
} else {
|
||||
// Request authorization
|
||||
authorized, err = notifier.RequestNotificationAuthorization()
|
||||
}
|
||||
```
|
||||
On Windows and Linux this always returns `true`.
|
||||
|
||||
## Notification Types
|
||||
|
||||
### Basic Notifications
|
||||
|
||||
Send a basic notification with a unique id, title, optional subtitle (macOS and Linux), and body text to users:
|
||||
|
||||
```go
|
||||
notifier.SendNotification(notifications.NotificationOptions{
|
||||
ID: "unique-id",
|
||||
Title: "New Calendar Invite",
|
||||
Subtitle: "From: Jane Doe", // Optional
|
||||
Body: "Tap to view the event",
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
### Interactive Notifications
|
||||
Send a notification with action buttons and text inputs. These notifications require a notification category to be resgistered first:
|
||||
|
||||
```go
|
||||
// Define a unique category id
|
||||
categoryID := "unique-category-id"
|
||||
|
||||
// Define a category with actions
|
||||
category := notifications.NotificationCategory{
|
||||
ID: categoryID,
|
||||
Actions: []notifications.NotificationAction{
|
||||
{
|
||||
ID: "OPEN",
|
||||
Title: "Open",
|
||||
},
|
||||
{
|
||||
ID: "ARCHIVE",
|
||||
Title: "Archive",
|
||||
Destructive: true, /* macOS-specific */
|
||||
},
|
||||
},
|
||||
HasReplyField: true,
|
||||
ReplyPlaceholder: "message...",
|
||||
ReplyButtonTitle: "Reply",
|
||||
}
|
||||
|
||||
// Register the category
|
||||
notifier.RegisterNotificationCategory(category)
|
||||
|
||||
// Send an interactive notification with the actions registered in the provided category
|
||||
notifier.SendNotificationWithActions(notifications.NotificationOptions{
|
||||
ID: "unique-id",
|
||||
Title: "New Message",
|
||||
Subtitle: "From: Jane Doe",
|
||||
Body: "Are you able to make it?",
|
||||
CategoryID: categoryID,
|
||||
})
|
||||
```
|
||||
|
||||
## Notification Responses
|
||||
|
||||
Process user interactions with notifications:
|
||||
|
||||
```go
|
||||
notifier.OnNotificationResponse(func(result notifications.NotificationResult) {
|
||||
response := result.Response
|
||||
fmt.Printf("Notification %s was actioned with: %s\n", response.ID, response.ActionIdentifier)
|
||||
|
||||
if response.ActionIdentifier == "TEXT_REPLY" {
|
||||
fmt.Printf("User replied: %s\n", response.UserText)
|
||||
}
|
||||
|
||||
if data, ok := response.UserInfo["sender"].(string); ok {
|
||||
fmt.Printf("Original sender: %s\n", data)
|
||||
}
|
||||
|
||||
// Emit an event to the frontend
|
||||
app.Event.Emit("notification", result.Response)
|
||||
})
|
||||
```
|
||||
|
||||
## Notification Customisation
|
||||
|
||||
### Custom Metadata
|
||||
|
||||
Basic and interactive notifications can include custom data:
|
||||
|
||||
```go
|
||||
notifier.SendNotification(notifications.NotificationOptions{
|
||||
ID: "unique-id",
|
||||
Title: "New Calendar Invite",
|
||||
Subtitle: "From: Jane Doe", // Optional
|
||||
Body: "Tap to view the event",
|
||||
Data: map[string]interface{}{
|
||||
"sender": "jane.doe@example.com",
|
||||
"timestamp": "2025-03-10T15:30:00Z",
|
||||
}
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
On macOS, notifications:
|
||||
|
||||
- Require user authorization
|
||||
- Require the app to be notorized for distribution
|
||||
- Use system-standard notification appearances
|
||||
- Support `subtitle`
|
||||
- Support user text input
|
||||
- Support the `Destructive` action option
|
||||
- Automatically handle dark/light mode
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
|
||||
On Windows, notifications:
|
||||
|
||||
- Use Windows system toast styles
|
||||
- Adapt to Windows theme settings
|
||||
- Support user text input
|
||||
- Support high DPI displays
|
||||
- Do not support `subtitle`
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
|
||||
On Linux, dialog behaviour depends on the desktop environment:
|
||||
|
||||
- Use native notifications when available
|
||||
- Follow desktop environment theme
|
||||
- Position according to desktop environment rules
|
||||
- Support `subtitle`
|
||||
- Do not support user text input
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Check and request for authorization:
|
||||
- macOS requires user authorization
|
||||
|
||||
2. Provide clear and concise notifications:
|
||||
- Use descriptive titles, subtitles, text, and action titles
|
||||
|
||||
3. Handle dialog responses appropriately:
|
||||
- Check for errors in notification responses
|
||||
- Provide feedback for user actions
|
||||
|
||||
4. Consider platform conventions:
|
||||
- Follow platform-specific notification patterns
|
||||
- Respect system settings
|
||||
|
||||
## Examples
|
||||
|
||||
Explore this example:
|
||||
|
||||
- [Notifications](/examples/notifications)
|
||||
|
||||
## API Reference
|
||||
|
||||
### Service Management
|
||||
| Method | Description |
|
||||
|--------------------------------------------|-------------------------------------------------------|
|
||||
| `New()` | Creates a new notifications service |
|
||||
|
||||
### Notification Authorization
|
||||
| Method | Description |
|
||||
|----------------------------------------------|------------------------------------------------------------|
|
||||
| `RequestNotificationAuthorization()` | Requests permission to display notifications (macOS) |
|
||||
| `CheckNotificationAuthorization()` | Checks current notification authorization status (macOS) |
|
||||
|
||||
### Sending Notifications
|
||||
| Method | Description |
|
||||
|------------------------------------------------------------|---------------------------------------------------|
|
||||
| `SendNotification(options NotificationOptions)` | Sends a basic notification |
|
||||
| `SendNotificationWithActions(options NotificationOptions)` | Sends an interactive notification with actions |
|
||||
|
||||
### Notification Categories
|
||||
| Method | Description |
|
||||
|---------------------------------------------------------------|---------------------------------------------------|
|
||||
| `RegisterNotificationCategory(category NotificationCategory)` | Registers a reusable notification category |
|
||||
| `RemoveNotificationCategory(categoryID string)` | Removes a previously registered category |
|
||||
|
||||
### Managing Notifications
|
||||
| Method | Description |
|
||||
|-------------------------------------------------|---------------------------------------------------------------------|
|
||||
| `RemoveAllPendingNotifications()` | Removes all pending notifications (macOS and Linux only) |
|
||||
| `RemovePendingNotification(identifier string)` | Removes a specific pending notification (macOS and Linux only) |
|
||||
| `RemoveAllDeliveredNotifications()` | Removes all delivered notifications (macOS and Linux only) |
|
||||
| `RemoveDeliveredNotification(identifier string)`| Removes a specific delivered notification (macOS and Linux only) |
|
||||
| `RemoveNotification(identifier string)` | Removes a notification (Linux-specific) |
|
||||
|
||||
### Event Handling
|
||||
| Method | Description |
|
||||
|--------------------------------------------------------------------|-------------------------------------------------|
|
||||
| `OnNotificationResponse(callback func(result NotificationResult))` | Registers a callback for notification responses |
|
||||
|
||||
### Structs and Types
|
||||
|
||||
#### NotificationOptions
|
||||
```go
|
||||
type NotificationOptions struct {
|
||||
ID string // Unique identifier for the notification
|
||||
Title string // Main notification title
|
||||
Subtitle string // Subtitle text (macOS and Linux only)
|
||||
Body string // Main notification content
|
||||
CategoryID string // Category identifier for interactive notifications
|
||||
Data map[string]interface{} // Custom data to associate with the notification
|
||||
}
|
||||
```
|
||||
|
||||
#### NotificationCategory
|
||||
```go
|
||||
type NotificationCategory struct {
|
||||
ID string // Unique identifier for the category
|
||||
Actions []NotificationAction // Button actions for the notification
|
||||
HasReplyField bool // Whether to include a text input field
|
||||
ReplyPlaceholder string // Placeholder text for the input field
|
||||
ReplyButtonTitle string // Text for the reply button
|
||||
}
|
||||
```
|
||||
|
||||
#### NotificationAction
|
||||
```go
|
||||
type NotificationAction struct {
|
||||
ID string // Unique identifier for the action
|
||||
Title string // Button text
|
||||
Destructive bool // Whether the action is destructive (macOS-specific)
|
||||
}
|
||||
```
|
||||
|
||||
#### NotificationResponse
|
||||
```go
|
||||
type NotificationResponse struct {
|
||||
ID string // Notification identifier
|
||||
ActionIdentifier string // Action that was triggered
|
||||
CategoryID string // Category of the notification
|
||||
Title string // Title of the notification
|
||||
Subtitle string // Subtitle of the notification
|
||||
Body string // Body text of the notification
|
||||
UserText string // Text entered by the user
|
||||
UserInfo map[string]interface{} // Custom data from the notification
|
||||
}
|
||||
```
|
||||
|
||||
#### NotificationResult
|
||||
```go
|
||||
type NotificationResult struct {
|
||||
Response NotificationResponse // Response data
|
||||
Error error // Any error that occurred
|
||||
}
|
||||
```
|
||||
235
docs/src/content/docs/features/platform/dock.mdx
Normal file
235
docs/src/content/docs/features/platform/dock.mdx
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
---
|
||||
title: Dock & Taskbar
|
||||
description: Manage dock icon visibility and display badges on macOS and Windows
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
## Introduction
|
||||
|
||||
Wails provides a cross-platform Dock service for desktop applications. This service allows you to:
|
||||
|
||||
- Hide and show the application icon in the macOS Dock
|
||||
- Display badges on your application tile or dock/taskbar icon (macOS and Windows)
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Creating the Service
|
||||
|
||||
First, initialize the dock service:
|
||||
|
||||
```go
|
||||
import "github.com/wailsapp/wails/v3/pkg/application"
|
||||
import "github.com/wailsapp/wails/v3/pkg/services/dock"
|
||||
|
||||
// Create a new Dock service
|
||||
dockService := dock.New()
|
||||
|
||||
// Register the service with the application
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(dockService),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Creating the Service with Custom Badge Options (Windows Only)
|
||||
|
||||
On Windows, you can customize the badge appearance with various options:
|
||||
|
||||
```go
|
||||
import "github.com/wailsapp/wails/v3/pkg/application"
|
||||
import "github.com/wailsapp/wails/v3/pkg/services/dock"
|
||||
import "image/color"
|
||||
|
||||
// Create a dock service with custom badge options
|
||||
options := dock.BadgeOptions{
|
||||
TextColour: color.RGBA{255, 255, 255, 255}, // White text
|
||||
BackgroundColour: color.RGBA{0, 0, 255, 255}, // Blue background
|
||||
FontName: "consolab.ttf", // Bold Consolas font
|
||||
FontSize: 20, // Font size for single character
|
||||
SmallFontSize: 14, // Font size for multiple characters
|
||||
}
|
||||
|
||||
dockService := dock.NewWithOptions(options)
|
||||
|
||||
// Register the service with the application
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(dockService),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Dock Operations
|
||||
|
||||
### Hiding the dock app icon
|
||||
|
||||
Hide the app icon from the macOS Dock:
|
||||
|
||||
```go
|
||||
// Hide the app icon
|
||||
dockService.HideAppIcon()
|
||||
```
|
||||
|
||||
### Showing the dock app icon
|
||||
|
||||
Show the app icon in the macOS Dock:
|
||||
|
||||
```go
|
||||
// Show the app icon
|
||||
dockService.ShowAppIcon()
|
||||
```
|
||||
|
||||
## Badge Operations
|
||||
|
||||
### Setting a Badge
|
||||
|
||||
Set a badge on the application tile/dock icon:
|
||||
|
||||
```go
|
||||
// Set a default badge
|
||||
dockService.SetBadge("")
|
||||
|
||||
// Set a numeric badge
|
||||
dockService.SetBadge("3")
|
||||
|
||||
// Set a text badge
|
||||
dockService.SetBadge("New")
|
||||
```
|
||||
|
||||
### Setting a Custom Badge (Windows Only)
|
||||
|
||||
Set a badge with one-off options applied:
|
||||
|
||||
```go
|
||||
options := dock.BadgeOptions{
|
||||
BackgroundColour: color.RGBA{0, 255, 255, 255},
|
||||
FontName: "arialb.ttf", // System font
|
||||
FontSize: 16,
|
||||
SmallFontSize: 10,
|
||||
TextColour: color.RGBA{0, 0, 0, 255},
|
||||
}
|
||||
|
||||
// Set a default badge
|
||||
dockService.SetCustomBadge("", options)
|
||||
|
||||
// Set a numeric badge
|
||||
dockService.SetCustomBadge("3", options)
|
||||
|
||||
// Set a text badge
|
||||
dockService.SetCustomBadge("New", options)
|
||||
```
|
||||
|
||||
### Removing a Badge
|
||||
|
||||
Remove the badge from the application icon:
|
||||
|
||||
```go
|
||||
dockService.RemoveBadge()
|
||||
```
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="macOS" icon="fa-brands:apple">
|
||||
|
||||
On macOS:
|
||||
|
||||
- The dock icon can be **hidden** and **shown**
|
||||
- Badges are displayed directly on the dock icon
|
||||
- Badge options are **not customizable** (any options passed to `NewWithOptions`/`SetCustomBadge` are ignored)
|
||||
- The standard macOS dock badge styling is used and automatically adapts to appearance
|
||||
- Label overflow is handled by the system
|
||||
- Providing an empty label displays a default badge of "●"
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="fa-brands:windows">
|
||||
|
||||
On Windows:
|
||||
|
||||
- Hiding/showing the taskbar icon is not currently supported by this service
|
||||
- Badges are displayed as an overlay icon in the taskbar
|
||||
- Badges support text values
|
||||
- Badge appearance can be customized via `BadgeOptions`
|
||||
- The application must have a window for badges to display
|
||||
- A smaller font size is automatically used for multi-character labels
|
||||
- Label overflow is not handled
|
||||
- Customization options:
|
||||
- **TextColour**: Text color (default: white)
|
||||
- **BackgroundColour**: Badge background color (default: red)
|
||||
- **FontName**: Font file name (default: "segoeuib.ttf")
|
||||
- **FontSize**: Font size for single character (default: 18)
|
||||
- **SmallFontSize**: Font size for multiple characters (default: 14)
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="fa-brands:linux">
|
||||
|
||||
On Linux:
|
||||
|
||||
- Dock icon visibility and badge functionality are not available
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **When hiding the dock icon (macOS):**
|
||||
- Ensure users can still access your app (e.g., via [system tray](https://v3alpha.wails.io/learn/systray/))
|
||||
- Include a "Quit" option in your alternative UI
|
||||
- The app won't appear in Command+Tab switcher
|
||||
- Open windows remain visible and functional
|
||||
- Closing all windows may not quit the app (macOS behavior varies)
|
||||
- Users lose the standard way to quit via Dock right-click
|
||||
|
||||
2. **Use badges sparingly:**
|
||||
- Too many badge updates can distract users
|
||||
- Reserve badges for important notifications
|
||||
|
||||
3. **Keep badge text short:**
|
||||
- Numeric badges are most effective
|
||||
- On macOS, text badges should be brief
|
||||
|
||||
4. **For Windows badge customization:**
|
||||
- Ensure high contrast between text and background colors
|
||||
- Test with different text lengths as font size decreases with length
|
||||
- Use common system fonts to ensure availability
|
||||
|
||||
|
||||
## API Reference
|
||||
|
||||
### Service Management
|
||||
| Method | Description |
|
||||
|--------------------------------------------|-------------------------------------------------------|
|
||||
| `New()` | Creates a new dock service |
|
||||
| `NewWithOptions(options BadgeOptions)` | Creates a new dock service with custom badge options (Windows only; options are ignored on macOS and Linux) |
|
||||
|
||||
### Dock Operations
|
||||
| Method | Description |
|
||||
|--------------------------------|-------------------------------------------------------------|
|
||||
| `HideAppIcon()` | Hides the app icon from the macOS Dock (macOS only) |
|
||||
| `ShowAppIcon()` | Shows the app icon in the macOS Dock (macOS only) |
|
||||
|
||||
### Badge Operations
|
||||
| Method | Description |
|
||||
|---------------------------------------------------|------------------------------------------------------------|
|
||||
| `SetBadge(label string) error` | Sets a badge with the specified label |
|
||||
| `SetCustomBadge(label string, options BadgeOptions) error` | Sets a badge with the specified label and custom styling options (Windows only) |
|
||||
| `RemoveBadge() error` | Removes the badge from the application icon |
|
||||
|
||||
### Structs and Types
|
||||
|
||||
```go
|
||||
// Options for customizing badge appearance (Windows only)
|
||||
type BadgeOptions struct {
|
||||
TextColour color.RGBA // Color of the badge text
|
||||
BackgroundColour color.RGBA // Color of the badge background
|
||||
FontName string // Font file name (e.g., "segoeuib.ttf")
|
||||
FontSize int // Font size for single character
|
||||
SmallFontSize int // Font size for multiple characters
|
||||
}
|
||||
```
|
||||
466
docs/src/content/docs/features/screens/info.mdx
Normal file
466
docs/src/content/docs/features/screens/info.mdx
Normal file
|
|
@ -0,0 +1,466 @@
|
|||
---
|
||||
title: Screen Information
|
||||
description: Get information about displays and monitors
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Screen Information
|
||||
|
||||
Wails provides a **unified screen API** that works across all platforms. Get screen information, detect multiple monitors, query screen properties (size, position, DPI), identify the primary display, and handle DPI scaling with consistent code.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
// Get all screens
|
||||
screens := app.Screens.GetAll()
|
||||
|
||||
for _, screen := range screens {
|
||||
fmt.Printf("Screen: %s (%dx%d)\n",
|
||||
screen.Name, screen.Width, screen.Height)
|
||||
}
|
||||
|
||||
// Get primary screen
|
||||
primary := app.Screens.GetPrimary()
|
||||
fmt.Printf("Primary: %s\n", primary.Name)
|
||||
```
|
||||
|
||||
**That's it!** Cross-platform screen information.
|
||||
|
||||
## Getting Screen Information
|
||||
|
||||
### All Screens
|
||||
|
||||
```go
|
||||
screens := app.Screens.GetAll()
|
||||
|
||||
for _, screen := range screens {
|
||||
fmt.Printf("ID: %s\n", screen.ID)
|
||||
fmt.Printf("Name: %s\n", screen.Name)
|
||||
fmt.Printf("Size: %dx%d\n", screen.Width, screen.Height)
|
||||
fmt.Printf("Position: %d,%d\n", screen.X, screen.Y)
|
||||
fmt.Printf("Scale: %.2f\n", screen.ScaleFactor)
|
||||
fmt.Printf("Primary: %v\n", screen.IsPrimary)
|
||||
fmt.Println("---")
|
||||
}
|
||||
```
|
||||
|
||||
### Primary Screen
|
||||
|
||||
```go
|
||||
primary := app.Screens.GetPrimary()
|
||||
|
||||
fmt.Printf("Primary screen: %s\n", primary.Name)
|
||||
fmt.Printf("Resolution: %dx%d\n", primary.Width, primary.Height)
|
||||
fmt.Printf("Scale factor: %.2f\n", primary.ScaleFactor)
|
||||
```
|
||||
|
||||
### Current Screen
|
||||
|
||||
Get the screen containing a window:
|
||||
|
||||
```go
|
||||
screen := app.Screens.GetCurrent(window)
|
||||
|
||||
fmt.Printf("Window is on: %s\n", screen.Name)
|
||||
```
|
||||
|
||||
### Screen by ID
|
||||
|
||||
```go
|
||||
screen := app.Screens.GetByID("screen-id")
|
||||
if screen != nil {
|
||||
fmt.Printf("Found screen: %s\n", screen.Name)
|
||||
}
|
||||
```
|
||||
|
||||
## Screen Properties
|
||||
|
||||
### Screen Structure
|
||||
|
||||
```go
|
||||
type Screen struct {
|
||||
ID string // Unique identifier
|
||||
Name string // Display name
|
||||
X int // X position
|
||||
Y int // Y position
|
||||
Width int // Width in pixels
|
||||
Height int // Height in pixels
|
||||
ScaleFactor float32 // DPI scale (1.0, 1.5, 2.0, etc.)
|
||||
IsPrimary bool // Is this the primary screen?
|
||||
}
|
||||
```
|
||||
|
||||
### Physical vs Logical Pixels
|
||||
|
||||
```go
|
||||
screen := app.Screens.GetPrimary()
|
||||
|
||||
// Logical pixels (what you use)
|
||||
logicalWidth := screen.Width
|
||||
logicalHeight := screen.Height
|
||||
|
||||
// Physical pixels (actual display)
|
||||
physicalWidth := int(float32(screen.Width) * screen.ScaleFactor)
|
||||
physicalHeight := int(float32(screen.Height) * screen.ScaleFactor)
|
||||
|
||||
fmt.Printf("Logical: %dx%d\n", logicalWidth, logicalHeight)
|
||||
fmt.Printf("Physical: %dx%d\n", physicalWidth, physicalHeight)
|
||||
fmt.Printf("Scale: %.2f\n", screen.ScaleFactor)
|
||||
```
|
||||
|
||||
**Common scale factors:**
|
||||
- `1.0` - Standard DPI (96 DPI)
|
||||
- `1.25` - 125% scaling (120 DPI)
|
||||
- `1.5` - 150% scaling (144 DPI)
|
||||
- `2.0` - 200% scaling (192 DPI) - Retina
|
||||
- `3.0` - 300% scaling (288 DPI) - 4K/5K
|
||||
|
||||
## Window Positioning
|
||||
|
||||
### Centre on Screen
|
||||
|
||||
```go
|
||||
func centreOnScreen(window *application.WebviewWindow, screen *Screen) {
|
||||
windowWidth, windowHeight := window.Size()
|
||||
|
||||
x := screen.X + (screen.Width-windowWidth)/2
|
||||
y := screen.Y + (screen.Height-windowHeight)/2
|
||||
|
||||
window.SetPosition(x, y)
|
||||
}
|
||||
```
|
||||
|
||||
### Position on Specific Screen
|
||||
|
||||
```go
|
||||
func moveToScreen(window *application.WebviewWindow, screenIndex int) {
|
||||
screens := app.Screens.GetAll()
|
||||
|
||||
if screenIndex < 0 || screenIndex >= len(screens) {
|
||||
return
|
||||
}
|
||||
|
||||
screen := screens[screenIndex]
|
||||
|
||||
// Centre on target screen
|
||||
centreOnScreen(window, screen)
|
||||
}
|
||||
```
|
||||
|
||||
### Position Relative to Screen
|
||||
|
||||
```go
|
||||
// Top-left corner
|
||||
func positionTopLeft(window *application.WebviewWindow, screen *Screen) {
|
||||
window.SetPosition(screen.X+10, screen.Y+10)
|
||||
}
|
||||
|
||||
// Top-right corner
|
||||
func positionTopRight(window *application.WebviewWindow, screen *Screen) {
|
||||
windowWidth, _ := window.Size()
|
||||
window.SetPosition(screen.X+screen.Width-windowWidth-10, screen.Y+10)
|
||||
}
|
||||
|
||||
// Bottom-right corner
|
||||
func positionBottomRight(window *application.WebviewWindow, screen *Screen) {
|
||||
windowWidth, windowHeight := window.Size()
|
||||
window.SetPosition(
|
||||
screen.X+screen.Width-windowWidth-10,
|
||||
screen.Y+screen.Height-windowHeight-10,
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Multi-Monitor Support
|
||||
|
||||
### Detect Multiple Monitors
|
||||
|
||||
```go
|
||||
func hasMultipleMonitors() bool {
|
||||
return len(app.Screens.GetAll()) > 1
|
||||
}
|
||||
|
||||
func getMonitorCount() int {
|
||||
return len(app.Screens.GetAll())
|
||||
}
|
||||
```
|
||||
|
||||
### List All Monitors
|
||||
|
||||
```go
|
||||
func listMonitors() {
|
||||
screens := app.Screens.GetAll()
|
||||
|
||||
fmt.Printf("Found %d monitor(s):\n", len(screens))
|
||||
|
||||
for i, screen := range screens {
|
||||
primary := ""
|
||||
if screen.IsPrimary {
|
||||
primary = " (Primary)"
|
||||
}
|
||||
|
||||
fmt.Printf("%d. %s%s\n", i+1, screen.Name, primary)
|
||||
fmt.Printf(" Resolution: %dx%d\n", screen.Width, screen.Height)
|
||||
fmt.Printf(" Position: %d,%d\n", screen.X, screen.Y)
|
||||
fmt.Printf(" Scale: %.2fx\n", screen.ScaleFactor)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Choose Monitor
|
||||
|
||||
```go
|
||||
func chooseMonitor() (*Screen, error) {
|
||||
screens := app.Screens.GetAll()
|
||||
|
||||
if len(screens) == 1 {
|
||||
return screens[0], nil
|
||||
}
|
||||
|
||||
// Show dialog to choose
|
||||
var options []string
|
||||
for i, screen := range screens {
|
||||
primary := ""
|
||||
if screen.IsPrimary {
|
||||
primary = " (Primary)"
|
||||
}
|
||||
options = append(options,
|
||||
fmt.Sprintf("%d. %s%s - %dx%d",
|
||||
i+1, screen.Name, primary, screen.Width, screen.Height))
|
||||
}
|
||||
|
||||
// Use dialog to select
|
||||
// (Implementation depends on your dialog system)
|
||||
|
||||
return screens[0], nil
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Multi-Monitor Window Manager
|
||||
|
||||
```go
|
||||
type MultiMonitorManager struct {
|
||||
app *application.App
|
||||
windows map[int]*application.WebviewWindow
|
||||
}
|
||||
|
||||
func NewMultiMonitorManager(app *application.App) *MultiMonitorManager {
|
||||
return &MultiMonitorManager{
|
||||
app: app,
|
||||
windows: make(map[int]*application.WebviewWindow),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MultiMonitorManager) CreateWindowOnScreen(screenIndex int) error {
|
||||
screens := m.app.Screens.GetAll()
|
||||
|
||||
if screenIndex < 0 || screenIndex >= len(screens) {
|
||||
return errors.New("invalid screen index")
|
||||
}
|
||||
|
||||
screen := screens[screenIndex]
|
||||
|
||||
// Create window
|
||||
window := m.app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: fmt.Sprintf("Window on %s", screen.Name),
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
})
|
||||
|
||||
// Centre on screen
|
||||
x := screen.X + (screen.Width-800)/2
|
||||
y := screen.Y + (screen.Height-600)/2
|
||||
window.SetPosition(x, y)
|
||||
|
||||
window.Show()
|
||||
|
||||
m.windows[screenIndex] = window
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MultiMonitorManager) CreateWindowOnEachScreen() {
|
||||
screens := m.app.Screens.GetAll()
|
||||
|
||||
for i := range screens {
|
||||
m.CreateWindowOnScreen(i)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Screen Change Detection
|
||||
|
||||
```go
|
||||
type ScreenMonitor struct {
|
||||
app *application.App
|
||||
lastScreens []*Screen
|
||||
changeHandler func([]*Screen)
|
||||
}
|
||||
|
||||
func NewScreenMonitor(app *application.App) *ScreenMonitor {
|
||||
return &ScreenMonitor{
|
||||
app: app,
|
||||
lastScreens: app.Screens.GetAll(),
|
||||
}
|
||||
}
|
||||
|
||||
func (sm *ScreenMonitor) OnScreenChange(handler func([]*Screen)) {
|
||||
sm.changeHandler = handler
|
||||
}
|
||||
|
||||
func (sm *ScreenMonitor) Start() {
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
sm.checkScreens()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (sm *ScreenMonitor) checkScreens() {
|
||||
current := sm.app.Screens.GetAll()
|
||||
|
||||
if len(current) != len(sm.lastScreens) {
|
||||
sm.lastScreens = current
|
||||
if sm.changeHandler != nil {
|
||||
sm.changeHandler(current)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### DPI-Aware Window Sizing
|
||||
|
||||
```go
|
||||
func createDPIAwareWindow(screen *Screen) *application.WebviewWindow {
|
||||
// Base size at 1.0 scale
|
||||
baseWidth := 800
|
||||
baseHeight := 600
|
||||
|
||||
// Adjust for DPI
|
||||
width := int(float32(baseWidth) * screen.ScaleFactor)
|
||||
height := int(float32(baseHeight) * screen.ScaleFactor)
|
||||
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "DPI-Aware Window",
|
||||
Width: width,
|
||||
Height: height,
|
||||
})
|
||||
|
||||
// Centre on screen
|
||||
x := screen.X + (screen.Width-width)/2
|
||||
y := screen.Y + (screen.Height-height)/2
|
||||
window.SetPosition(x, y)
|
||||
|
||||
return window
|
||||
}
|
||||
```
|
||||
|
||||
### Screen Layout Visualiser
|
||||
|
||||
```go
|
||||
func visualiseScreenLayout() string {
|
||||
screens := app.Screens.GetAll()
|
||||
|
||||
var layout strings.Builder
|
||||
layout.WriteString("Screen Layout:\n\n")
|
||||
|
||||
for i, screen := range screens {
|
||||
primary := ""
|
||||
if screen.IsPrimary {
|
||||
primary = " [PRIMARY]"
|
||||
}
|
||||
|
||||
layout.WriteString(fmt.Sprintf("Screen %d: %s%s\n", i+1, screen.Name, primary))
|
||||
layout.WriteString(fmt.Sprintf(" Position: (%d, %d)\n", screen.X, screen.Y))
|
||||
layout.WriteString(fmt.Sprintf(" Size: %dx%d\n", screen.Width, screen.Height))
|
||||
layout.WriteString(fmt.Sprintf(" Scale: %.2fx\n", screen.ScaleFactor))
|
||||
layout.WriteString(fmt.Sprintf(" Physical: %dx%d\n",
|
||||
int(float32(screen.Width)*screen.ScaleFactor),
|
||||
int(float32(screen.Height)*screen.ScaleFactor)))
|
||||
layout.WriteString("\n")
|
||||
}
|
||||
|
||||
return layout.String()
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Check screen count** - Handle single and multiple monitors
|
||||
- **Use logical pixels** - Wails handles DPI automatically
|
||||
- **Centre windows** - Better UX than fixed positions
|
||||
- **Validate positions** - Ensure windows are visible
|
||||
- **Handle screen changes** - Monitors can be added/removed
|
||||
- **Test on different DPI** - 100%, 125%, 150%, 200%
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Don't hardcode positions** - Use screen dimensions
|
||||
- **Don't assume primary screen** - User might have multiple
|
||||
- **Don't ignore scale factor** - Important for DPI awareness
|
||||
- **Don't position off-screen** - Validate coordinates
|
||||
- **Don't forget screen changes** - Laptops dock/undock
|
||||
- **Don't use physical pixels** - Use logical pixels
|
||||
|
||||
## Platform Differences
|
||||
|
||||
### macOS
|
||||
|
||||
- Retina displays (2x scale factor)
|
||||
- Multiple displays common
|
||||
- Coordinate system: (0,0) at bottom-left
|
||||
- Spaces (virtual desktops) affect positioning
|
||||
|
||||
### Windows
|
||||
|
||||
- Various DPI scaling (100%, 125%, 150%, 200%)
|
||||
- Multiple displays common
|
||||
- Coordinate system: (0,0) at top-left
|
||||
- Per-monitor DPI awareness
|
||||
|
||||
### Linux
|
||||
|
||||
- Varies by desktop environment
|
||||
- X11 vs Wayland differences
|
||||
- DPI scaling support varies
|
||||
- Multiple displays supported
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGrid>
|
||||
<Card title="Windows" icon="laptop">
|
||||
Learn about window management.
|
||||
|
||||
[Learn More →](/features/windows/basics)
|
||||
</Card>
|
||||
|
||||
<Card title="Window Options" icon="seti:config">
|
||||
Configure window appearance.
|
||||
|
||||
[Learn More →](/features/windows/options)
|
||||
</Card>
|
||||
|
||||
<Card title="Multiple Windows" icon="puzzle">
|
||||
Multi-window patterns.
|
||||
|
||||
[Learn More →](/features/windows/multiple)
|
||||
</Card>
|
||||
|
||||
<Card title="Bindings" icon="rocket">
|
||||
Call Go functions from JavaScript.
|
||||
|
||||
[Learn More →](/features/bindings/methods)
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [screen examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).
|
||||
595
docs/src/content/docs/features/windows/basics.mdx
Normal file
595
docs/src/content/docs/features/windows/basics.mdx
Normal file
|
|
@ -0,0 +1,595 @@
|
|||
---
|
||||
title: Window Basics
|
||||
description: Creating and managing application windows in Wails
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Window Management
|
||||
|
||||
Wails provides a **unified window management API** that works across all platforms. Create windows, control their behaviour, and manage multiple windows with full control over creation, appearance, behaviour, and lifecycle.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "github.com/wailsapp/wails/v3/pkg/application"
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "My App",
|
||||
})
|
||||
|
||||
// Create a window
|
||||
window := app.Window.New()
|
||||
|
||||
// Configure it
|
||||
window.SetTitle("Hello Wails")
|
||||
window.SetSize(800, 600)
|
||||
window.Center()
|
||||
|
||||
// Show it
|
||||
window.Show()
|
||||
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
**That's it!** You have a cross-platform window.
|
||||
|
||||
## Creating Windows
|
||||
|
||||
### Basic Window
|
||||
|
||||
The simplest way to create a window:
|
||||
|
||||
```go
|
||||
window := app.Window.New()
|
||||
```
|
||||
|
||||
**What you get:**
|
||||
- Default size (800x600)
|
||||
- Default title (application name)
|
||||
- WebView ready for your frontend
|
||||
- Platform-native appearance
|
||||
|
||||
### Window with Options
|
||||
|
||||
Create a window with custom configuration:
|
||||
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "My Application",
|
||||
Width: 1200,
|
||||
Height: 800,
|
||||
X: 100, // Position from left
|
||||
Y: 100, // Position from top
|
||||
AlwaysOnTop: false,
|
||||
Frameless: false,
|
||||
Hidden: false,
|
||||
MinWidth: 400,
|
||||
MinHeight: 300,
|
||||
MaxWidth: 1920,
|
||||
MaxHeight: 1080,
|
||||
})
|
||||
```
|
||||
|
||||
**Common options:**
|
||||
|
||||
| Option | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `Title` | `string` | Window title |
|
||||
| `Width` | `int` | Window width in pixels |
|
||||
| `Height` | `int` | Window height in pixels |
|
||||
| `X` | `int` | X position (from left) |
|
||||
| `Y` | `int` | Y position (from top) |
|
||||
| `AlwaysOnTop` | `bool` | Keep window above others |
|
||||
| `Frameless` | `bool` | Remove title bar and borders |
|
||||
| `Hidden` | `bool` | Start hidden |
|
||||
| `MinWidth` | `int` | Minimum width |
|
||||
| `MinHeight` | `int` | Minimum height |
|
||||
| `MaxWidth` | `int` | Maximum width |
|
||||
| `MaxHeight` | `int` | Maximum height |
|
||||
|
||||
**See [Window Options](/features/windows/options) for complete list.**
|
||||
|
||||
### Named Windows
|
||||
|
||||
Give windows names for easy retrieval:
|
||||
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "main-window",
|
||||
Title: "Main Application",
|
||||
})
|
||||
|
||||
// Later, find it by name
|
||||
mainWindow, ok := app.Window.GetByName("main-window")
|
||||
if ok {
|
||||
mainWindow.Show()
|
||||
}
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Multiple windows (main, settings, about)
|
||||
- Finding windows from different parts of your code
|
||||
- Window communication
|
||||
|
||||
## Controlling Windows
|
||||
|
||||
### Show and Hide
|
||||
|
||||
```go
|
||||
// Show window
|
||||
window.Show()
|
||||
|
||||
// Hide window
|
||||
window.Hide()
|
||||
|
||||
// Check if visible
|
||||
if window.IsVisible() {
|
||||
fmt.Println("Window is visible")
|
||||
}
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Splash screens (show, then hide)
|
||||
- Settings windows (hide when not needed)
|
||||
- Popup windows (show on demand)
|
||||
|
||||
### Position and Size
|
||||
|
||||
```go
|
||||
// Set size
|
||||
window.SetSize(1024, 768)
|
||||
|
||||
// Set position
|
||||
window.SetPosition(100, 100)
|
||||
|
||||
// Centre on screen
|
||||
window.Center()
|
||||
|
||||
// Get current size
|
||||
width, height := window.Size()
|
||||
|
||||
// Get current position
|
||||
x, y := window.Position()
|
||||
```
|
||||
|
||||
**Coordinate system:**
|
||||
- (0, 0) is top-left of primary screen
|
||||
- Positive X goes right
|
||||
- Positive Y goes down
|
||||
|
||||
### Window State
|
||||
|
||||
```go
|
||||
// Minimise
|
||||
window.Minimise()
|
||||
|
||||
// Maximise
|
||||
window.Maximise()
|
||||
|
||||
// Fullscreen
|
||||
window.Fullscreen()
|
||||
|
||||
// Restore to normal
|
||||
window.Restore()
|
||||
|
||||
// Check state
|
||||
if window.IsMinimised() {
|
||||
fmt.Println("Window is minimised")
|
||||
}
|
||||
|
||||
if window.IsMaximised() {
|
||||
fmt.Println("Window is maximised")
|
||||
}
|
||||
|
||||
if window.IsFullscreen() {
|
||||
fmt.Println("Window is fullscreen")
|
||||
}
|
||||
```
|
||||
|
||||
**State transitions:**
|
||||
|
||||
```
|
||||
Normal ←→ Minimised
|
||||
Normal ←→ Maximised
|
||||
Normal ←→ Fullscreen
|
||||
```
|
||||
|
||||
### Title and Appearance
|
||||
|
||||
```go
|
||||
// Set title
|
||||
window.SetTitle("My Application - Document.txt")
|
||||
|
||||
// Set background colour
|
||||
window.SetBackgroundColour(0, 0, 0, 255) // RGBA
|
||||
|
||||
// Set always on top
|
||||
window.SetAlwaysOnTop(true)
|
||||
|
||||
// Set resizable
|
||||
window.SetResizable(false)
|
||||
```
|
||||
|
||||
### Closing Windows
|
||||
|
||||
```go
|
||||
// Close window
|
||||
window.Close()
|
||||
|
||||
// Destroy window (force close)
|
||||
window.Destroy()
|
||||
```
|
||||
|
||||
**Difference:**
|
||||
- `Close()` - Triggers close event, can be cancelled
|
||||
- `Destroy()` - Immediate destruction, cannot be cancelled
|
||||
|
||||
## Finding Windows
|
||||
|
||||
### By Name
|
||||
|
||||
```go
|
||||
window, ok := app.Window.GetByName("settings")
|
||||
if ok {
|
||||
window.Show()
|
||||
}
|
||||
```
|
||||
|
||||
### Current Window
|
||||
|
||||
Get the currently focused window:
|
||||
|
||||
```go
|
||||
current := app.Window.Current()
|
||||
if current != nil {
|
||||
current.SetTitle("Active Window")
|
||||
}
|
||||
```
|
||||
|
||||
### All Windows
|
||||
|
||||
Get all windows:
|
||||
|
||||
```go
|
||||
windows := app.Window.GetAll()
|
||||
fmt.Printf("Total windows: %d\n", len(windows))
|
||||
|
||||
for _, w := range windows {
|
||||
fmt.Printf("Window: %s\n", w.Name())
|
||||
}
|
||||
```
|
||||
|
||||
## Window Lifecycle
|
||||
|
||||
### Creation
|
||||
|
||||
```go
|
||||
app.Window.OnCreate(func(window application.Window) {
|
||||
fmt.Printf("Window created: %s\n", window.Name())
|
||||
|
||||
// Configure new windows
|
||||
window.SetMinSize(400, 300)
|
||||
})
|
||||
```
|
||||
|
||||
### Closing
|
||||
|
||||
```go
|
||||
window.OnClose(func() bool {
|
||||
// Return false to cancel close
|
||||
// Return true to allow close
|
||||
|
||||
if hasUnsavedChanges() {
|
||||
result := showConfirmdialog("Unsaved changes. Close anyway?")
|
||||
return result == "yes"
|
||||
}
|
||||
return true
|
||||
})
|
||||
```
|
||||
|
||||
**Important:** `OnClose` only works for user-initiated closes (clicking X button). It doesn't prevent `window.Destroy()`.
|
||||
|
||||
### Destruction
|
||||
|
||||
```go
|
||||
window.OnDestroy(func() {
|
||||
fmt.Println("Window destroyed")
|
||||
// Cleanup resources
|
||||
})
|
||||
```
|
||||
|
||||
## Multiple Windows
|
||||
|
||||
### Creating Multiple Windows
|
||||
|
||||
```go
|
||||
// Main window
|
||||
mainWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "main",
|
||||
Title: "Main Application",
|
||||
Width: 1200,
|
||||
Height: 800,
|
||||
})
|
||||
|
||||
// Settings window
|
||||
settingsWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "settings",
|
||||
Title: "Settings",
|
||||
Width: 600,
|
||||
Height: 400,
|
||||
Hidden: true, // Start hidden
|
||||
})
|
||||
|
||||
// Show settings when needed
|
||||
settingsWindow.Show()
|
||||
```
|
||||
|
||||
### Window Communication
|
||||
|
||||
Windows can communicate via events:
|
||||
|
||||
```go
|
||||
// In main window
|
||||
app.Event.Emit("data-updated", map[string]interface{}{
|
||||
"value": 42,
|
||||
})
|
||||
|
||||
// In settings window
|
||||
app.Event.On("data-updated", func(event *application.CustomEvent) {
|
||||
data := event.Data.(map[string]interface{})
|
||||
value := data["value"].(int)
|
||||
fmt.Printf("Received: %d\n", value)
|
||||
})
|
||||
```
|
||||
|
||||
**See [Events](/features/events/system) for more.**
|
||||
|
||||
### Parent-Child Windows
|
||||
|
||||
```go
|
||||
// Create child window
|
||||
childWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Child Window",
|
||||
Parent: mainWindow, // Set parent
|
||||
})
|
||||
```
|
||||
|
||||
**Behaviour:**
|
||||
- Child closes when parent closes
|
||||
- Child stays above parent (on some platforms)
|
||||
- Child minimises with parent (on some platforms)
|
||||
|
||||
**Platform support:**
|
||||
- **macOS:** Full support
|
||||
- **Windows:** Partial support
|
||||
- **Linux:** Varies by desktop environment
|
||||
|
||||
## Platform-Specific Features
|
||||
|
||||
<Tabs syncKey="platform">
|
||||
<TabItem label="Windows" icon="seti:windows">
|
||||
**Windows-specific features:**
|
||||
|
||||
```go
|
||||
// Flash taskbar button
|
||||
window.Flash(true) // Start flashing
|
||||
window.Flash(false) // Stop flashing
|
||||
|
||||
// Trigger Windows 11 Snap Assist (Win+Z)
|
||||
window.SnapAssist()
|
||||
|
||||
// Set window icon
|
||||
window.SetIcon(iconBytes)
|
||||
```
|
||||
|
||||
**Snap Assist:**
|
||||
Shows Windows 11 snap layout options for the window.
|
||||
|
||||
**Taskbar flashing:**
|
||||
Useful for notifications when window is minimised.
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="macOS" icon="apple">
|
||||
**macOS-specific features:**
|
||||
|
||||
```go
|
||||
// Transparent title bar
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Mac: application.MacOptions{
|
||||
TitleBarAppearsTransparent: true,
|
||||
Backdrop: application.MacBackdropTranslucent,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Backdrop types:**
|
||||
- `MacBackdropNormal` - Standard window
|
||||
- `MacBackdropTranslucent` - Translucent background
|
||||
- `MacBackdropTransparent` - Fully transparent
|
||||
|
||||
**Native fullscreen:**
|
||||
macOS fullscreen creates a new Space (virtual desktop).
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="linux">
|
||||
**Linux-specific features:**
|
||||
|
||||
```go
|
||||
// Set window icon
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Linux: application.LinuxOptions{
|
||||
Icon: iconBytes,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Desktop environment notes:**
|
||||
- GNOME: Full support
|
||||
- KDE Plasma: Full support
|
||||
- XFCE: Partial support
|
||||
- Others: Varies
|
||||
|
||||
**Window managers:**
|
||||
- Tiling WMs may ignore size/position
|
||||
- Some WMs don't support always-on-top
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Splash Screen
|
||||
|
||||
```go
|
||||
// Create splash screen
|
||||
splash := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Loading...",
|
||||
Width: 400,
|
||||
Height: 300,
|
||||
Frameless: true,
|
||||
AlwaysOnTop: true,
|
||||
})
|
||||
|
||||
// Show splash
|
||||
splash.Show()
|
||||
|
||||
// Initialise application
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Hide splash, show main window
|
||||
splash.Close()
|
||||
mainWindow.Show()
|
||||
```
|
||||
|
||||
### Settings Window
|
||||
|
||||
```go
|
||||
var settingsWindow *application.WebviewWindow
|
||||
|
||||
func showSettings() {
|
||||
if settingsWindow == nil {
|
||||
settingsWindow = app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "settings",
|
||||
Title: "Settings",
|
||||
Width: 600,
|
||||
Height: 400,
|
||||
})
|
||||
}
|
||||
|
||||
settingsWindow.Show()
|
||||
settingsWindow.Focus()
|
||||
}
|
||||
```
|
||||
|
||||
### Confirm Before Close
|
||||
|
||||
```go
|
||||
window.OnClose(func() bool {
|
||||
if hasUnsavedChanges() {
|
||||
// Show dialog
|
||||
result := showConfirmdialog("Unsaved changes. Close anyway?")
|
||||
return result == "yes"
|
||||
}
|
||||
return true
|
||||
})
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Name important windows** - Easier to find later
|
||||
- **Set minimum size** - Prevent unusable layouts
|
||||
- **Centre windows** - Better UX than random position
|
||||
- **Handle close events** - Prevent data loss
|
||||
- **Test on all platforms** - Behaviour varies
|
||||
- **Use appropriate sizes** - Consider different screen sizes
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Don't create too many windows** - Confusing for users
|
||||
- **Don't forget to close windows** - Memory leaks
|
||||
- **Don't hardcode positions** - Different screen sizes
|
||||
- **Don't ignore platform differences** - Test thoroughly
|
||||
- **Don't block the UI thread** - Use goroutines for long operations
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Window Not Showing
|
||||
|
||||
**Possible causes:**
|
||||
1. Window created as hidden
|
||||
2. Window off-screen
|
||||
3. Window behind other windows
|
||||
|
||||
**Solution:**
|
||||
|
||||
```go
|
||||
window.Show()
|
||||
window.Center()
|
||||
window.Focus()
|
||||
```
|
||||
|
||||
### Window Wrong Size
|
||||
|
||||
**Cause:** DPI scaling on Windows/Linux
|
||||
|
||||
**Solution:**
|
||||
|
||||
```go
|
||||
// Wails handles DPI automatically
|
||||
// Just use logical pixels
|
||||
window.SetSize(800, 600)
|
||||
```
|
||||
|
||||
### Window Closes Immediately
|
||||
|
||||
**Cause:** Application exits when last window closes
|
||||
|
||||
**Solution:**
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Mac: application.MacOptions{
|
||||
ApplicationShouldTerminateAfterLastWindowClosed: false,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGrid>
|
||||
<Card title="Window Options" icon="seti:config">
|
||||
Complete reference for all window options.
|
||||
|
||||
[Learn More →](/features/windows/options)
|
||||
</Card>
|
||||
|
||||
<Card title="Multiple Windows" icon="laptop">
|
||||
Patterns for multi-window applications.
|
||||
|
||||
[Learn More →](/features/windows/multiple)
|
||||
</Card>
|
||||
|
||||
<Card title="Frameless Windows" icon="star">
|
||||
Create custom window chrome.
|
||||
|
||||
[Learn More →](/features/windows/frameless)
|
||||
</Card>
|
||||
|
||||
<Card title="Window Events" icon="rocket">
|
||||
Handle window lifecycle events.
|
||||
|
||||
[Learn More →](/features/windows/events)
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [window examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).
|
||||
693
docs/src/content/docs/features/windows/events.mdx
Normal file
693
docs/src/content/docs/features/windows/events.mdx
Normal file
|
|
@ -0,0 +1,693 @@
|
|||
---
|
||||
title: Window Events
|
||||
description: Handle window lifecycle and state change events
|
||||
sidebar:
|
||||
order: 5
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
## Window Events
|
||||
|
||||
Wails provides **comprehensive event hooks** for window lifecycle and state changes: creation, focus/blur, resize/move, minimise/maximise, and close events. Register callbacks, handle events, and coordinate between windows with simple, consistent APIs.
|
||||
|
||||
## Lifecycle Events
|
||||
|
||||
### OnCreate
|
||||
|
||||
Called when a window is created:
|
||||
|
||||
```go
|
||||
app.Window.OnCreate(func(window application.Window) {
|
||||
fmt.Printf("Window created: %s\n", window.Name())
|
||||
|
||||
// Configure all new windows
|
||||
window.SetMinSize(400, 300)
|
||||
|
||||
// Register window-specific handlers
|
||||
window.OnClose(func() bool {
|
||||
return confirmClose()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Configure all windows consistently
|
||||
- Register event handlers
|
||||
- Track window creation
|
||||
- Initialise window-specific resources
|
||||
|
||||
### OnClose
|
||||
|
||||
Called when user attempts to close window:
|
||||
|
||||
```go
|
||||
window.OnClose(func() bool {
|
||||
// Return false to cancel close
|
||||
// Return true to allow close
|
||||
|
||||
if hasUnsavedChanges() {
|
||||
result := showConfirmdialog("Unsaved changes. Close anyway?")
|
||||
return result == "yes"
|
||||
}
|
||||
return true
|
||||
})
|
||||
```
|
||||
|
||||
**Important:**
|
||||
- Only triggered by user actions (clicking X button)
|
||||
- NOT triggered by `window.Destroy()`
|
||||
- Can cancel the close by returning `false`
|
||||
|
||||
**Use cases:**
|
||||
- Confirm before closing
|
||||
- Save state
|
||||
- Prevent accidental closure
|
||||
- Cleanup before close
|
||||
|
||||
**Example with dialog:**
|
||||
|
||||
```go
|
||||
window.OnClose(func() bool {
|
||||
if !hasUnsavedChanges() {
|
||||
return true
|
||||
}
|
||||
|
||||
// Show confirmation dialog
|
||||
dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Confirm Close",
|
||||
Width: 400,
|
||||
Height: 150,
|
||||
Parent: window,
|
||||
AlwaysOnTop: true,
|
||||
})
|
||||
|
||||
// Wait for user response
|
||||
result := waitFordialogResult(dialog)
|
||||
|
||||
return result == "yes"
|
||||
})
|
||||
```
|
||||
|
||||
### OnDestroy
|
||||
|
||||
Called when window is destroyed:
|
||||
|
||||
```go
|
||||
window.OnDestroy(func() {
|
||||
fmt.Printf("Window destroyed: %s\n", window.Name())
|
||||
|
||||
// Cleanup resources
|
||||
closeDatabase()
|
||||
|
||||
// Remove from tracking
|
||||
removeWindowFromRegistry(window.Name())
|
||||
|
||||
// Update application state
|
||||
updateWindowCount()
|
||||
})
|
||||
```
|
||||
|
||||
**Important:**
|
||||
- Always called when window is destroyed
|
||||
- Cannot be cancelled
|
||||
- Last chance to cleanup
|
||||
|
||||
**Use cases:**
|
||||
- Release resources
|
||||
- Close connections
|
||||
- Update application state
|
||||
- Remove from tracking
|
||||
|
||||
**Example with resource cleanup:**
|
||||
|
||||
```go
|
||||
type ManagedWindow struct {
|
||||
window *application.WebviewWindow
|
||||
db *sql.DB
|
||||
listeners []func()
|
||||
}
|
||||
|
||||
func (mw *ManagedWindow) Setup() {
|
||||
mw.window.OnDestroy(func() {
|
||||
// Close database
|
||||
if mw.db != nil {
|
||||
mw.db.Close()
|
||||
}
|
||||
|
||||
// Remove event listeners
|
||||
for _, listener := range mw.listeners {
|
||||
listener()
|
||||
}
|
||||
|
||||
// Clear references
|
||||
mw.db = nil
|
||||
mw.listeners = nil
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Focus Events
|
||||
|
||||
### OnFocus
|
||||
|
||||
Called when window gains focus:
|
||||
|
||||
```go
|
||||
window.OnFocus(func() {
|
||||
fmt.Println("Window gained focus")
|
||||
|
||||
// Update UI
|
||||
updateTitleBar(true)
|
||||
|
||||
// Refresh data
|
||||
refreshContent()
|
||||
|
||||
// Notify other windows
|
||||
app.Event.Emit("window-focused", window.Name())
|
||||
})
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Update UI appearance
|
||||
- Refresh data
|
||||
- Resume operations
|
||||
- Coordinate with other windows
|
||||
|
||||
### OnBlur
|
||||
|
||||
Called when window loses focus:
|
||||
|
||||
```go
|
||||
window.OnBlur(func() {
|
||||
fmt.Println("Window lost focus")
|
||||
|
||||
// Update UI
|
||||
updateTitleBar(false)
|
||||
|
||||
// Pause operations
|
||||
pauseAnimations()
|
||||
|
||||
// Save state
|
||||
saveCurrentState()
|
||||
})
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Update UI appearance
|
||||
- Pause operations
|
||||
- Save state
|
||||
- Reduce resource usage
|
||||
|
||||
**Example: Focus-aware UI:**
|
||||
|
||||
```go
|
||||
type FocusAwareWindow struct {
|
||||
window *application.WebviewWindow
|
||||
focused bool
|
||||
}
|
||||
|
||||
func (fw *FocusAwareWindow) Setup() {
|
||||
fw.window.OnFocus(func() {
|
||||
fw.focused = true
|
||||
fw.updateAppearance()
|
||||
})
|
||||
|
||||
fw.window.OnBlur(func() {
|
||||
fw.focused = false
|
||||
fw.updateAppearance()
|
||||
})
|
||||
}
|
||||
|
||||
func (fw *FocusAwareWindow) updateAppearance() {
|
||||
if fw.focused {
|
||||
fw.window.EmitEvent("update-theme", "active")
|
||||
} else {
|
||||
fw.window.EmitEvent("update-theme", "inactive")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## State Change Events
|
||||
|
||||
### OnMinimise / OnUnMinimise
|
||||
|
||||
Called when window is minimised or restored:
|
||||
|
||||
```go
|
||||
window.OnMinimise(func() {
|
||||
fmt.Println("Window minimised")
|
||||
|
||||
// Pause expensive operations
|
||||
pauseRendering()
|
||||
|
||||
// Save state
|
||||
saveWindowState()
|
||||
})
|
||||
|
||||
window.OnUnMinimise(func() {
|
||||
fmt.Println("Window restored from minimised")
|
||||
|
||||
// Resume operations
|
||||
resumeRendering()
|
||||
|
||||
// Refresh data
|
||||
refreshContent()
|
||||
})
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Pause/resume operations
|
||||
- Save/restore state
|
||||
- Reduce resource usage
|
||||
- Update UI
|
||||
|
||||
### OnMaximise / OnUnMaximise
|
||||
|
||||
Called when window is maximised or restored:
|
||||
|
||||
```go
|
||||
window.OnMaximise(func() {
|
||||
fmt.Println("Window maximised")
|
||||
|
||||
// Adjust layout
|
||||
window.EmitEvent("layout-mode", "maximised")
|
||||
|
||||
// Update button icon
|
||||
updateMaximiseButton("restore")
|
||||
})
|
||||
|
||||
window.OnUnMaximise(func() {
|
||||
fmt.Println("Window restored from maximised")
|
||||
|
||||
// Adjust layout
|
||||
window.EmitEvent("layout-mode", "normal")
|
||||
|
||||
// Update button icon
|
||||
updateMaximiseButton("maximise")
|
||||
})
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Adjust layout
|
||||
- Update UI
|
||||
- Save window state
|
||||
- Coordinate with other windows
|
||||
|
||||
### OnFullscreen / OnUnFullscreen
|
||||
|
||||
Called when window enters or exits fullscreen:
|
||||
|
||||
```go
|
||||
window.OnFullscreen(func() {
|
||||
fmt.Println("Window entered fullscreen")
|
||||
|
||||
// Hide UI chrome
|
||||
window.EmitEvent("chrome-visibility", false)
|
||||
|
||||
// Adjust layout
|
||||
window.EmitEvent("layout-mode", "fullscreen")
|
||||
})
|
||||
|
||||
window.OnUnFullscreen(func() {
|
||||
fmt.Println("Window exited fullscreen")
|
||||
|
||||
// Show UI chrome
|
||||
window.EmitEvent("chrome-visibility", true)
|
||||
|
||||
// Restore layout
|
||||
window.EmitEvent("layout-mode", "normal")
|
||||
})
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Show/hide UI elements
|
||||
- Adjust layout
|
||||
- Update controls
|
||||
- Save preferences
|
||||
|
||||
## Position and Size Events
|
||||
|
||||
### OnMove
|
||||
|
||||
Called when window is moved:
|
||||
|
||||
```go
|
||||
window.OnMove(func(x, y int) {
|
||||
fmt.Printf("Window moved to: %d, %d\n", x, y)
|
||||
|
||||
// Save position
|
||||
saveWindowPosition(x, y)
|
||||
|
||||
// Update related windows
|
||||
updateRelatedWindowPositions(x, y)
|
||||
})
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Save window position
|
||||
- Update related windows
|
||||
- Snap to edges
|
||||
- Multi-monitor handling
|
||||
|
||||
### OnResize
|
||||
|
||||
Called when window is resized:
|
||||
|
||||
```go
|
||||
window.OnResize(func(width, height int) {
|
||||
fmt.Printf("Window resized to: %dx%d\n", width, height)
|
||||
|
||||
// Save size
|
||||
saveWindowSize(width, height)
|
||||
|
||||
// Adjust layout
|
||||
window.EmitEvent("window-size", map[string]int{
|
||||
"width": width,
|
||||
"height": height,
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Save window size
|
||||
- Adjust layout
|
||||
- Update UI
|
||||
- Responsive design
|
||||
|
||||
**Example: Responsive layout:**
|
||||
|
||||
```go
|
||||
window.OnResize(func(width, height int) {
|
||||
var layout string
|
||||
|
||||
if width < 600 {
|
||||
layout = "compact"
|
||||
} else if width < 1200 {
|
||||
layout = "normal"
|
||||
} else {
|
||||
layout = "wide"
|
||||
}
|
||||
|
||||
window.EmitEvent("layout-changed", layout)
|
||||
})
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a production-ready window with full event handling:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
type WindowState struct {
|
||||
X int `json:"x"`
|
||||
Y int `json:"y"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Maximised bool `json:"maximised"`
|
||||
Fullscreen bool `json:"fullscreen"`
|
||||
}
|
||||
|
||||
type ManagedWindow struct {
|
||||
app *application.App
|
||||
window *application.WebviewWindow
|
||||
state WindowState
|
||||
dirty bool
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Event Demo",
|
||||
})
|
||||
|
||||
mw := &ManagedWindow{app: app}
|
||||
mw.CreateWindow()
|
||||
mw.LoadState()
|
||||
mw.SetupEventHandlers()
|
||||
|
||||
app.Run()
|
||||
}
|
||||
|
||||
func (mw *ManagedWindow) CreateWindow() {
|
||||
mw.window = mw.app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "main",
|
||||
Title: "Event Demo",
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
})
|
||||
}
|
||||
|
||||
func (mw *ManagedWindow) SetupEventHandlers() {
|
||||
// Focus events
|
||||
mw.window.OnFocus(func() {
|
||||
fmt.Println("Window focused")
|
||||
mw.window.EmitEvent("focus-state", true)
|
||||
})
|
||||
|
||||
mw.window.OnBlur(func() {
|
||||
fmt.Println("Window blurred")
|
||||
mw.window.EmitEvent("focus-state", false)
|
||||
})
|
||||
|
||||
// State change events
|
||||
mw.window.OnMinimise(func() {
|
||||
fmt.Println("Window minimised")
|
||||
mw.SaveState()
|
||||
})
|
||||
|
||||
mw.window.OnUnMinimise(func() {
|
||||
fmt.Println("Window restored")
|
||||
})
|
||||
|
||||
mw.window.OnMaximise(func() {
|
||||
fmt.Println("Window maximised")
|
||||
mw.state.Maximised = true
|
||||
mw.dirty = true
|
||||
})
|
||||
|
||||
mw.window.OnUnMaximise(func() {
|
||||
fmt.Println("Window restored from maximised")
|
||||
mw.state.Maximised = false
|
||||
mw.dirty = true
|
||||
})
|
||||
|
||||
mw.window.OnFullscreen(func() {
|
||||
fmt.Println("Window fullscreen")
|
||||
mw.state.Fullscreen = true
|
||||
mw.dirty = true
|
||||
})
|
||||
|
||||
mw.window.OnUnFullscreen(func() {
|
||||
fmt.Println("Window exited fullscreen")
|
||||
mw.state.Fullscreen = false
|
||||
mw.dirty = true
|
||||
})
|
||||
|
||||
// Position and size events
|
||||
mw.window.OnMove(func(x, y int) {
|
||||
mw.state.X = x
|
||||
mw.state.Y = y
|
||||
mw.dirty = true
|
||||
})
|
||||
|
||||
mw.window.OnResize(func(width, height int) {
|
||||
mw.state.Width = width
|
||||
mw.state.Height = height
|
||||
mw.dirty = true
|
||||
})
|
||||
|
||||
// Lifecycle events
|
||||
mw.window.OnClose(func() bool {
|
||||
if mw.dirty {
|
||||
mw.SaveState()
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
mw.window.OnDestroy(func() {
|
||||
fmt.Println("Window destroyed")
|
||||
if mw.dirty {
|
||||
mw.SaveState()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (mw *ManagedWindow) LoadState() {
|
||||
data, err := os.ReadFile("window-state.json")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &mw.state); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Restore window state
|
||||
mw.window.SetPosition(mw.state.X, mw.state.Y)
|
||||
mw.window.SetSize(mw.state.Width, mw.state.Height)
|
||||
|
||||
if mw.state.Maximised {
|
||||
mw.window.Maximise()
|
||||
}
|
||||
|
||||
if mw.state.Fullscreen {
|
||||
mw.window.Fullscreen()
|
||||
}
|
||||
}
|
||||
|
||||
func (mw *ManagedWindow) SaveState() {
|
||||
data, err := json.Marshal(mw.state)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
os.WriteFile("window-state.json", data, 0644)
|
||||
mw.dirty = false
|
||||
|
||||
fmt.Println("Window state saved")
|
||||
}
|
||||
```
|
||||
|
||||
## Event Coordination
|
||||
|
||||
### Cross-Window Events
|
||||
|
||||
Coordinate between multiple windows:
|
||||
|
||||
```go
|
||||
// In main window
|
||||
mainWindow.OnFocus(func() {
|
||||
// Notify all windows
|
||||
app.Event.Emit("main-window-focused", nil)
|
||||
})
|
||||
|
||||
// In other windows
|
||||
app.Event.On("main-window-focused", func(event *application.CustomEvent) {
|
||||
// Update UI
|
||||
updateRelativeToMain()
|
||||
})
|
||||
```
|
||||
|
||||
### Event Chains
|
||||
|
||||
Chain events together:
|
||||
|
||||
```go
|
||||
window.OnMaximise(func() {
|
||||
// Save state
|
||||
saveWindowState()
|
||||
|
||||
// Update layout
|
||||
window.EmitEvent("layout-changed", "maximised")
|
||||
|
||||
// Notify other windows
|
||||
app.Event.Emit("window-maximised", window.Name())
|
||||
})
|
||||
```
|
||||
|
||||
### Debounced Events
|
||||
|
||||
Debounce frequent events:
|
||||
|
||||
```go
|
||||
var resizeTimer *time.Timer
|
||||
|
||||
window.OnResize(func(width, height int) {
|
||||
if resizeTimer != nil {
|
||||
resizeTimer.Stop()
|
||||
}
|
||||
|
||||
resizeTimer = time.AfterFunc(500*time.Millisecond, func() {
|
||||
// Save after resize stops
|
||||
saveWindowSize(width, height)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Save state on close** - Restore window position/size
|
||||
- **Cleanup on destroy** - Release resources
|
||||
- **Debounce frequent events** - Resize, move
|
||||
- **Handle focus changes** - Update UI appropriately
|
||||
- **Coordinate windows** - Use events for communication
|
||||
- **Test all events** - Ensure handlers work correctly
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Don't block event handlers** - Keep them fast
|
||||
- **Don't forget cleanup** - Memory leaks
|
||||
- **Don't ignore errors** - Log or handle them
|
||||
- **Don't save on every event** - Debounce first
|
||||
- **Don't create circular events** - Infinite loops
|
||||
- **Don't forget platform differences** - Test thoroughly
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### OnClose Not Firing
|
||||
|
||||
**Cause:** Using `window.Destroy()` instead of `window.Close()`
|
||||
|
||||
**Solution:**
|
||||
|
||||
```go
|
||||
// ✅ Triggers OnClose
|
||||
window.Close()
|
||||
|
||||
// ❌ Doesn't trigger OnClose
|
||||
window.Destroy()
|
||||
```
|
||||
|
||||
### Events Not Firing
|
||||
|
||||
**Cause:** Handler registered after event occurred
|
||||
|
||||
**Solution:**
|
||||
|
||||
```go
|
||||
// Register handlers immediately after creation
|
||||
window := app.Window.New()
|
||||
window.OnClose(func() bool { return true })
|
||||
```
|
||||
|
||||
### Memory Leaks
|
||||
|
||||
**Cause:** Not cleaning up in OnDestroy
|
||||
|
||||
**Solution:**
|
||||
|
||||
```go
|
||||
window.OnDestroy(func() {
|
||||
// Always cleanup
|
||||
closeResources()
|
||||
removeReferences()
|
||||
})
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
**Window Basics** - Learn the fundamentals of window management
|
||||
[Learn More →](/features/windows/basics)
|
||||
|
||||
**Multiple Windows** - Patterns for multi-window applications
|
||||
[Learn More →](/features/windows/multiple)
|
||||
|
||||
**Events System** - Deep dive into the event system
|
||||
[Learn More →](/features/events/system)
|
||||
|
||||
**Application Lifecycle** - Understand the application lifecycle
|
||||
[Learn More →](/concepts/lifecycle)
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).
|
||||
870
docs/src/content/docs/features/windows/frameless.mdx
Normal file
870
docs/src/content/docs/features/windows/frameless.mdx
Normal file
|
|
@ -0,0 +1,870 @@
|
|||
---
|
||||
title: Frameless Windows
|
||||
description: Create custom window chrome with frameless windows
|
||||
sidebar:
|
||||
order: 4
|
||||
---
|
||||
|
||||
import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Frameless Windows
|
||||
|
||||
Wails provides **frameless window support** with CSS-based drag regions and platform-native behaviour. Remove the platform-native title bar for complete control over window chrome, custom designs, and unique user experiences whilst maintaining essential functionality like dragging, resizing, and system controls.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Frameless App",
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
Frameless: true,
|
||||
})
|
||||
```
|
||||
|
||||
**CSS for draggable title bar:**
|
||||
|
||||
```css
|
||||
.titlebar {
|
||||
--wails-draggable: drag;
|
||||
height: 40px;
|
||||
background: #333;
|
||||
}
|
||||
|
||||
.titlebar button {
|
||||
--wails-draggable: no-drag;
|
||||
}
|
||||
```
|
||||
|
||||
**HTML:**
|
||||
|
||||
```html
|
||||
<div class="titlebar">
|
||||
<span>My Application</span>
|
||||
<button onclick="window.close()">×</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
**That's it!** You have a custom title bar.
|
||||
|
||||
## Creating Frameless Windows
|
||||
|
||||
### Basic Frameless Window
|
||||
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Frameless: true,
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
})
|
||||
```
|
||||
|
||||
**What you get:**
|
||||
- No title bar
|
||||
- No window borders
|
||||
- No system buttons
|
||||
- Transparent background (optional)
|
||||
|
||||
**What you need to implement:**
|
||||
- Draggable area
|
||||
- Close/minimise/maximise buttons
|
||||
- Resize handles (if resizable)
|
||||
|
||||
### With Transparent Background
|
||||
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Frameless: true,
|
||||
BackgroundType: application.BackgroundTypeTransparent,
|
||||
})
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- Rounded corners
|
||||
- Custom shapes
|
||||
- Overlay windows
|
||||
- Splash screens
|
||||
|
||||
## Drag Regions
|
||||
|
||||
### CSS-Based Dragging
|
||||
|
||||
Use the `--wails-draggable` CSS property:
|
||||
|
||||
```css
|
||||
/* Draggable area */
|
||||
.titlebar {
|
||||
--wails-draggable: drag;
|
||||
}
|
||||
|
||||
/* Non-draggable elements within draggable area */
|
||||
.titlebar button {
|
||||
--wails-draggable: no-drag;
|
||||
}
|
||||
```
|
||||
|
||||
**Values:**
|
||||
- `drag` - Area is draggable
|
||||
- `no-drag` - Area is not draggable (even if parent is)
|
||||
|
||||
### Complete Title Bar Example
|
||||
|
||||
```html
|
||||
<div class="titlebar">
|
||||
<div class="title">My Application</div>
|
||||
<div class="controls">
|
||||
<button class="minimize">−</button>
|
||||
<button class="maximize">□</button>
|
||||
<button class="close">×</button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
```css
|
||||
.titlebar {
|
||||
--wails-draggable: drag;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
background: #2c2c2c;
|
||||
color: white;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.controls button {
|
||||
--wails-draggable: no-drag;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.controls button:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.controls .close:hover {
|
||||
background: #e81123;
|
||||
}
|
||||
```
|
||||
|
||||
**JavaScript for buttons:**
|
||||
|
||||
```javascript
|
||||
import { WindowMinimise, WindowMaximise, WindowClose } from '@wailsio/runtime'
|
||||
|
||||
document.querySelector('.minimize').addEventListener('click', WindowMinimise)
|
||||
document.querySelector('.maximize').addEventListener('click', WindowMaximise)
|
||||
document.querySelector('.close').addEventListener('click', WindowClose)
|
||||
```
|
||||
|
||||
## System Buttons
|
||||
|
||||
### Implementing Close/Minimise/Maximise
|
||||
|
||||
**Go side:**
|
||||
|
||||
```go
|
||||
type WindowControls struct {
|
||||
window *application.WebviewWindow
|
||||
}
|
||||
|
||||
func (wc *WindowControls) Minimise() {
|
||||
wc.window.Minimise()
|
||||
}
|
||||
|
||||
func (wc *WindowControls) Maximise() {
|
||||
if wc.window.IsMaximised() {
|
||||
wc.window.UnMaximise()
|
||||
} else {
|
||||
wc.window.Maximise()
|
||||
}
|
||||
}
|
||||
|
||||
func (wc *WindowControls) Close() {
|
||||
wc.window.Close()
|
||||
}
|
||||
```
|
||||
|
||||
**JavaScript side:**
|
||||
|
||||
```javascript
|
||||
import { Minimise, Maximise, Close } from './bindings/WindowControls'
|
||||
|
||||
document.querySelector('.minimize').addEventListener('click', Minimise)
|
||||
document.querySelector('.maximize').addEventListener('click', Maximise)
|
||||
document.querySelector('.close').addEventListener('click', Close)
|
||||
```
|
||||
|
||||
**Or use runtime methods:**
|
||||
|
||||
```javascript
|
||||
import {
|
||||
WindowMinimise,
|
||||
WindowMaximise,
|
||||
WindowClose
|
||||
} from '@wailsio/runtime'
|
||||
|
||||
document.querySelector('.minimize').addEventListener('click', WindowMinimise)
|
||||
document.querySelector('.maximize').addEventListener('click', WindowMaximise)
|
||||
document.querySelector('.close').addEventListener('click', WindowClose)
|
||||
```
|
||||
|
||||
### Toggle Maximise State
|
||||
|
||||
Track maximise state for button icon:
|
||||
|
||||
```javascript
|
||||
import { WindowIsMaximised, WindowMaximise, WindowUnMaximise } from '@wailsio/runtime'
|
||||
|
||||
async function toggleMaximise() {
|
||||
const isMaximised = await WindowIsMaximised()
|
||||
|
||||
if (isMaximised) {
|
||||
await WindowUnMaximise()
|
||||
} else {
|
||||
await WindowMaximise()
|
||||
}
|
||||
|
||||
updateMaximiseButton()
|
||||
}
|
||||
|
||||
async function updateMaximiseButton() {
|
||||
const isMaximised = await WindowIsMaximised()
|
||||
const button = document.querySelector('.maximize')
|
||||
button.textContent = isMaximised ? '❐' : '□'
|
||||
}
|
||||
```
|
||||
|
||||
## Resize Handles
|
||||
|
||||
### CSS-Based Resize
|
||||
|
||||
Wails provides automatic resize handles for frameless windows:
|
||||
|
||||
```css
|
||||
/* Enable resize on all edges */
|
||||
body {
|
||||
--wails-resize: all;
|
||||
}
|
||||
|
||||
/* Or specific edges */
|
||||
.resize-top {
|
||||
--wails-resize: top;
|
||||
}
|
||||
|
||||
.resize-bottom {
|
||||
--wails-resize: bottom;
|
||||
}
|
||||
|
||||
.resize-left {
|
||||
--wails-resize: left;
|
||||
}
|
||||
|
||||
.resize-right {
|
||||
--wails-resize: right;
|
||||
}
|
||||
|
||||
/* Corners */
|
||||
.resize-top-left {
|
||||
--wails-resize: top-left;
|
||||
}
|
||||
|
||||
.resize-top-right {
|
||||
--wails-resize: top-right;
|
||||
}
|
||||
|
||||
.resize-bottom-left {
|
||||
--wails-resize: bottom-left;
|
||||
}
|
||||
|
||||
.resize-bottom-right {
|
||||
--wails-resize: bottom-right;
|
||||
}
|
||||
```
|
||||
|
||||
**Values:**
|
||||
- `all` - Resize from all edges
|
||||
- `top`, `bottom`, `left`, `right` - Specific edges
|
||||
- `top-left`, `top-right`, `bottom-left`, `bottom-right` - Corners
|
||||
- `none` - No resize
|
||||
|
||||
### Resize Handle Example
|
||||
|
||||
```html
|
||||
<div class="window">
|
||||
<div class="titlebar">...</div>
|
||||
<div class="content">...</div>
|
||||
<div class="resize-handle resize-bottom-right"></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
```css
|
||||
.resize-handle {
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.resize-bottom-right {
|
||||
--wails-resize: bottom-right;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
cursor: nwse-resize;
|
||||
}
|
||||
```
|
||||
|
||||
## Platform-Specific Behaviour
|
||||
|
||||
<Tabs syncKey="platform">
|
||||
<TabItem label="Windows" icon="seti:windows">
|
||||
**Windows frameless windows:**
|
||||
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Frameless: true,
|
||||
Windows: application.WindowsOptions{
|
||||
DisableFramelessWindowDecorations: false,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Automatic drop shadow
|
||||
- Snap layouts support (Windows 11)
|
||||
- Aero Snap support
|
||||
- DPI scaling
|
||||
|
||||
**Disable decorations:**
|
||||
```go
|
||||
Windows: application.WindowsOptions{
|
||||
DisableFramelessWindowDecorations: true,
|
||||
},
|
||||
```
|
||||
|
||||
**Snap Assist:**
|
||||
```go
|
||||
// Trigger Windows 11 Snap Assist
|
||||
window.SnapAssist()
|
||||
```
|
||||
|
||||
**Custom title bar height:**
|
||||
Windows automatically detects drag regions from CSS.
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="macOS" icon="apple">
|
||||
**macOS frameless windows:**
|
||||
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Frameless: true,
|
||||
Mac: application.MacOptions{
|
||||
TitleBarAppearsTransparent: true,
|
||||
InvisibleTitleBarHeight: 40,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Native fullscreen support
|
||||
- Traffic light buttons (optional)
|
||||
- Vibrancy effects
|
||||
- Transparent title bar
|
||||
|
||||
**Hide traffic lights:**
|
||||
```go
|
||||
Mac: application.MacOptions{
|
||||
TitleBarStyle: application.MacTitleBarStyleHidden,
|
||||
},
|
||||
```
|
||||
|
||||
**Invisible title bar:**
|
||||
Allows dragging whilst hiding the title bar:
|
||||
```go
|
||||
Mac: application.MacOptions{
|
||||
InvisibleTitleBarHeight: 40,
|
||||
},
|
||||
```
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="linux">
|
||||
**Linux frameless windows:**
|
||||
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Frameless: true,
|
||||
})
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Basic frameless support
|
||||
- CSS drag regions
|
||||
- Varies by desktop environment
|
||||
|
||||
**Desktop environment notes:**
|
||||
- **GNOME:** Good support
|
||||
- **KDE Plasma:** Good support
|
||||
- **XFCE:** Basic support
|
||||
- **Tiling WMs:** Limited support
|
||||
|
||||
**Compositor required:**
|
||||
Transparency requires a compositor (most modern DEs have one).
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Pattern 1: Modern Title Bar
|
||||
|
||||
```html
|
||||
<div class="modern-titlebar">
|
||||
<div class="app-icon">
|
||||
<img src="/icon.png" alt="App Icon">
|
||||
</div>
|
||||
<div class="title">My Application</div>
|
||||
<div class="controls">
|
||||
<button class="minimize">−</button>
|
||||
<button class="maximize">□</button>
|
||||
<button class="close">×</button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
```css
|
||||
.modern-titlebar {
|
||||
--wails-draggable: drag;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
background: linear-gradient(to bottom, #3a3a3a, #2c2c2c);
|
||||
border-bottom: 1px solid #1a1a1a;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
--wails-draggable: no-drag;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.title {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
color: #e0e0e0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.controls button {
|
||||
--wails-draggable: no-drag;
|
||||
width: 46px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #e0e0e0;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.controls button:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.controls .close:hover {
|
||||
background: #e81123;
|
||||
color: white;
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Splash Screen
|
||||
|
||||
```go
|
||||
splash := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Loading...",
|
||||
Width: 400,
|
||||
Height: 300,
|
||||
Frameless: true,
|
||||
AlwaysOnTop: true,
|
||||
BackgroundType: application.BackgroundTypeTransparent,
|
||||
})
|
||||
```
|
||||
|
||||
```css
|
||||
body {
|
||||
background: transparent;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.splash {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 3: Rounded Window
|
||||
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Frameless: true,
|
||||
BackgroundType: application.BackgroundTypeTransparent,
|
||||
})
|
||||
```
|
||||
|
||||
```css
|
||||
body {
|
||||
background: transparent;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.window {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
|
||||
overflow: hidden;
|
||||
height: calc(100vh - 16px);
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
--wails-draggable: drag;
|
||||
background: #f5f5f5;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 4: Overlay Window
|
||||
|
||||
```go
|
||||
overlay := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Frameless: true,
|
||||
AlwaysOnTop: true,
|
||||
BackgroundType: application.BackgroundTypeTransparent,
|
||||
})
|
||||
```
|
||||
|
||||
```css
|
||||
body {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a production-ready frameless window:
|
||||
|
||||
**Go:**
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
//go:embed frontend/dist
|
||||
var assets embed.FS
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "Frameless App",
|
||||
})
|
||||
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Frameless Application",
|
||||
Width: 1000,
|
||||
Height: 700,
|
||||
MinWidth: 800,
|
||||
MinHeight: 600,
|
||||
Frameless: true,
|
||||
|
||||
Assets: application.AssetOptions{
|
||||
Handler: application.AssetFileServerFS(assets),
|
||||
},
|
||||
|
||||
Mac: application.MacOptions{
|
||||
TitleBarAppearsTransparent: true,
|
||||
InvisibleTitleBarHeight: 40,
|
||||
},
|
||||
|
||||
Windows: application.WindowsOptions{
|
||||
DisableFramelessWindowDecorations: false,
|
||||
},
|
||||
})
|
||||
|
||||
window.Center()
|
||||
window.Show()
|
||||
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
**HTML:**
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="window">
|
||||
<div class="titlebar">
|
||||
<div class="title">Frameless Application</div>
|
||||
<div class="controls">
|
||||
<button class="minimize" title="Minimise">−</button>
|
||||
<button class="maximize" title="Maximise">□</button>
|
||||
<button class="close" title="Close">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h1>Hello from Frameless Window!</h1>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/main.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
**CSS:**
|
||||
|
||||
```css
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.window {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.titlebar {
|
||||
--wails-draggable: drag;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
background: #ffffff;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.controls button {
|
||||
--wails-draggable: no-drag;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.controls button:hover {
|
||||
background: #f0f0f0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.controls .close:hover {
|
||||
background: #e81123;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 40px;
|
||||
overflow: auto;
|
||||
}
|
||||
```
|
||||
|
||||
**JavaScript:**
|
||||
|
||||
```javascript
|
||||
import {
|
||||
WindowMinimise,
|
||||
WindowMaximise,
|
||||
WindowUnMaximise,
|
||||
WindowIsMaximised,
|
||||
WindowClose
|
||||
} from '@wailsio/runtime'
|
||||
|
||||
// Minimise button
|
||||
document.querySelector('.minimize').addEventListener('click', () => {
|
||||
WindowMinimise()
|
||||
})
|
||||
|
||||
// Maximise/restore button
|
||||
const maximiseBtn = document.querySelector('.maximize')
|
||||
maximiseBtn.addEventListener('click', async () => {
|
||||
const isMaximised = await WindowIsMaximised()
|
||||
|
||||
if (isMaximised) {
|
||||
await WindowUnMaximise()
|
||||
} else {
|
||||
await WindowMaximise()
|
||||
}
|
||||
|
||||
updateMaximiseButton()
|
||||
})
|
||||
|
||||
// Close button
|
||||
document.querySelector('.close').addEventListener('click', () => {
|
||||
WindowClose()
|
||||
})
|
||||
|
||||
// Update maximise button icon
|
||||
async function updateMaximiseButton() {
|
||||
const isMaximised = await WindowIsMaximised()
|
||||
maximiseBtn.textContent = isMaximised ? '❐' : '□'
|
||||
maximiseBtn.title = isMaximised ? 'Restore' : 'Maximise'
|
||||
}
|
||||
|
||||
// Initial state
|
||||
updateMaximiseButton()
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Provide draggable area** - Users need to move the window
|
||||
- **Implement system buttons** - Close, minimise, maximise
|
||||
- **Set minimum size** - Prevent unusable layouts
|
||||
- **Test on all platforms** - Behaviour varies
|
||||
- **Use CSS for drag regions** - Flexible and maintainable
|
||||
- **Provide visual feedback** - Hover states on buttons
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Don't forget resize handles** - If window is resizable
|
||||
- **Don't make entire window draggable** - Prevents interaction
|
||||
- **Don't forget no-drag on buttons** - They won't work
|
||||
- **Don't use tiny drag areas** - Hard to grab
|
||||
- **Don't forget platform differences** - Test thoroughly
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Window Won't Drag
|
||||
|
||||
**Cause:** Missing `--wails-draggable: drag`
|
||||
|
||||
**Solution:**
|
||||
|
||||
```css
|
||||
.titlebar {
|
||||
--wails-draggable: drag;
|
||||
}
|
||||
```
|
||||
|
||||
### Buttons Don't Work
|
||||
|
||||
**Cause:** Buttons are in draggable area
|
||||
|
||||
**Solution:**
|
||||
|
||||
```css
|
||||
.titlebar button {
|
||||
--wails-draggable: no-drag;
|
||||
}
|
||||
```
|
||||
|
||||
### Can't Resize Window
|
||||
|
||||
**Cause:** Missing resize handles
|
||||
|
||||
**Solution:**
|
||||
|
||||
```css
|
||||
body {
|
||||
--wails-resize: all;
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGrid>
|
||||
<Card title="Window Basics" icon="laptop">
|
||||
Learn the fundamentals of window management.
|
||||
|
||||
[Learn More →](/features/windows/basics)
|
||||
</Card>
|
||||
|
||||
<Card title="Window Options" icon="seti:config">
|
||||
Complete reference for window options.
|
||||
|
||||
[Learn More →](/features/windows/options)
|
||||
</Card>
|
||||
|
||||
<Card title="Window Events" icon="rocket">
|
||||
Handle window lifecycle events.
|
||||
|
||||
[Learn More →](/features/windows/events)
|
||||
</Card>
|
||||
|
||||
<Card title="Multiple Windows" icon="puzzle">
|
||||
Patterns for multi-window applications.
|
||||
|
||||
[Learn More →](/features/windows/multiple)
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [frameless example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/frameless).
|
||||
812
docs/src/content/docs/features/windows/multiple.mdx
Normal file
812
docs/src/content/docs/features/windows/multiple.mdx
Normal file
|
|
@ -0,0 +1,812 @@
|
|||
---
|
||||
title: Multiple Windows
|
||||
description: Patterns and best practices for multi-window applications
|
||||
sidebar:
|
||||
order: 3
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
## Multi-Window Applications
|
||||
|
||||
Wails v3 provides **native multi-window support** for creating settings windows, document windows, tool palettes, and inspector windows. Track windows, enable communication between them, and manage their lifecycle with simple, consistent APIs.
|
||||
|
||||
### Main + Settings Window
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "github.com/wailsapp/wails/v3/pkg/application"
|
||||
|
||||
type App struct {
|
||||
app *application.App
|
||||
mainWindow *application.WebviewWindow
|
||||
settingsWindow *application.WebviewWindow
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := &App{}
|
||||
|
||||
app.app = application.New(application.Options{
|
||||
Name: "Multi-Window App",
|
||||
})
|
||||
|
||||
// Create main window
|
||||
app.mainWindow = app.app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "main",
|
||||
Title: "Main Application",
|
||||
Width: 1200,
|
||||
Height: 800,
|
||||
})
|
||||
|
||||
// Create settings window (hidden initially)
|
||||
app.settingsWindow = app.app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "settings",
|
||||
Title: "Settings",
|
||||
Width: 600,
|
||||
Height: 400,
|
||||
Hidden: true,
|
||||
})
|
||||
|
||||
app.app.Run()
|
||||
}
|
||||
|
||||
// Show settings from main window
|
||||
func (a *App) ShowSettings() {
|
||||
if a.settingsWindow != nil {
|
||||
a.settingsWindow.Show()
|
||||
a.settingsWindow.Focus()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key points:**
|
||||
- Main window always visible
|
||||
- Settings window created but hidden
|
||||
- Show settings on demand
|
||||
- Reuse same window (don't create multiple)
|
||||
|
||||
## Window Tracking
|
||||
|
||||
### Get All Windows
|
||||
|
||||
```go
|
||||
windows := app.Window.GetAll()
|
||||
fmt.Printf("Total windows: %d\n", len(windows))
|
||||
|
||||
for _, window := range windows {
|
||||
fmt.Printf("- %s\n", window.Name())
|
||||
}
|
||||
```
|
||||
|
||||
### Find Specific Window
|
||||
|
||||
```go
|
||||
// By name
|
||||
settings, ok := app.Window.GetByName("settings")
|
||||
if ok {
|
||||
settings.Show()
|
||||
}
|
||||
|
||||
// Current (focused) window
|
||||
current := app.Window.Current()
|
||||
```
|
||||
|
||||
### Window Registry Pattern
|
||||
|
||||
Track windows in your application:
|
||||
|
||||
```go
|
||||
type WindowManager struct {
|
||||
windows map[string]*application.WebviewWindow
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (wm *WindowManager) Register(name string, window *application.WebviewWindow) {
|
||||
wm.mu.Lock()
|
||||
defer wm.mu.Unlock()
|
||||
wm.windows[name] = window
|
||||
}
|
||||
|
||||
func (wm *WindowManager) Get(name string) *application.WebviewWindow {
|
||||
wm.mu.RLock()
|
||||
defer wm.mu.RUnlock()
|
||||
return wm.windows[name]
|
||||
}
|
||||
|
||||
func (wm *WindowManager) Remove(name string) {
|
||||
wm.mu.Lock()
|
||||
defer wm.mu.Unlock()
|
||||
delete(wm.windows, name)
|
||||
}
|
||||
```
|
||||
|
||||
## Window Communication
|
||||
|
||||
### Using Events
|
||||
|
||||
Windows communicate via the event system:
|
||||
|
||||
```go
|
||||
// In main window - emit event
|
||||
app.Event.Emit("settings-changed", map[string]interface{}{
|
||||
"theme": "dark",
|
||||
"fontSize": 14,
|
||||
})
|
||||
|
||||
// In settings window - listen for event
|
||||
app.Event.On("settings-changed", func(event *application.CustomEvent) {
|
||||
data := event.Data.(map[string]interface{})
|
||||
theme := data["theme"].(string)
|
||||
fontSize := data["fontSize"].(int)
|
||||
|
||||
// Update UI
|
||||
updateSettings(theme, fontSize)
|
||||
})
|
||||
```
|
||||
|
||||
### Shared State Pattern
|
||||
|
||||
Use a shared state manager:
|
||||
|
||||
```go
|
||||
type AppState struct {
|
||||
theme string
|
||||
fontSize int
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
var state = &AppState{
|
||||
theme: "light",
|
||||
fontSize: 12,
|
||||
}
|
||||
|
||||
func (s *AppState) SetTheme(theme string) {
|
||||
s.mu.Lock()
|
||||
s.theme = theme
|
||||
s.mu.Unlock()
|
||||
|
||||
// Notify all windows
|
||||
app.Event.Emit("theme-changed", theme)
|
||||
}
|
||||
|
||||
func (s *AppState) GetTheme() string {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.theme
|
||||
}
|
||||
```
|
||||
|
||||
### Window-to-Window Messages
|
||||
|
||||
Send messages between specific windows:
|
||||
|
||||
```go
|
||||
// Get target window
|
||||
targetWindow, ok := app.Window.GetByName("preview")
|
||||
if ok {
|
||||
// Emit event to specific window
|
||||
targetWindow.EmitEvent("update-preview", previewData)
|
||||
}
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Pattern 1: Singleton Windows
|
||||
|
||||
Ensure only one instance of a window exists:
|
||||
|
||||
```go
|
||||
var settingsWindow *application.WebviewWindow
|
||||
|
||||
func ShowSettings(app *application.App) {
|
||||
// Create if doesn't exist
|
||||
if settingsWindow == nil {
|
||||
settingsWindow = app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "settings",
|
||||
Title: "Settings",
|
||||
Width: 600,
|
||||
Height: 400,
|
||||
})
|
||||
|
||||
// Cleanup on close
|
||||
settingsWindow.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
settingsWindow = nil
|
||||
})
|
||||
}
|
||||
|
||||
// Show and focus
|
||||
settingsWindow.Show()
|
||||
settingsWindow.Focus()
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Document Windows
|
||||
|
||||
Multiple instances of the same window type:
|
||||
|
||||
```go
|
||||
type DocumentWindow struct {
|
||||
window *application.WebviewWindow
|
||||
filePath string
|
||||
modified bool
|
||||
}
|
||||
|
||||
var documents = make(map[string]*DocumentWindow)
|
||||
|
||||
func OpenDocument(app *application.App, filePath string) {
|
||||
// Check if already open
|
||||
if doc, exists := documents[filePath]; exists {
|
||||
doc.window.Show()
|
||||
doc.window.Focus()
|
||||
return
|
||||
}
|
||||
|
||||
// Create new document window
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: filepath.Base(filePath),
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
})
|
||||
|
||||
doc := &DocumentWindow{
|
||||
window: window,
|
||||
filePath: filePath,
|
||||
modified: false,
|
||||
}
|
||||
|
||||
documents[filePath] = doc
|
||||
|
||||
// Cleanup on close
|
||||
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
delete(documents, filePath)
|
||||
})
|
||||
|
||||
// Load document
|
||||
loadDocument(window, filePath)
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 3: Tool Palettes
|
||||
|
||||
Floating windows that stay on top:
|
||||
|
||||
```go
|
||||
func CreateToolPalette(app *application.App) *application.WebviewWindow {
|
||||
palette := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "tools",
|
||||
Title: "Tools",
|
||||
Width: 200,
|
||||
Height: 400,
|
||||
AlwaysOnTop: true,
|
||||
Resizable: false,
|
||||
})
|
||||
|
||||
return palette
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 4: Modal dialogs
|
||||
|
||||
Child windows that block parent:
|
||||
|
||||
```go
|
||||
func ShowModaldialog(parent *application.WebviewWindow, title string) {
|
||||
dialog := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: title,
|
||||
Width: 400,
|
||||
Height: 200,
|
||||
Parent: parent,
|
||||
AlwaysOnTop: true,
|
||||
Resizable: false,
|
||||
})
|
||||
|
||||
// Disable parent (platform-specific)
|
||||
parent.SetEnabled(false)
|
||||
|
||||
// Re-enable parent on close
|
||||
dialog.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
parent.SetEnabled(true)
|
||||
parent.Focus()
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 5: Inspector/Preview Windows
|
||||
|
||||
Linked windows that update together:
|
||||
|
||||
```go
|
||||
type EditorApp struct {
|
||||
editor *application.WebviewWindow
|
||||
preview *application.WebviewWindow
|
||||
}
|
||||
|
||||
func (e *EditorApp) UpdatePreview(content string) {
|
||||
if e.preview != nil && e.preview.IsVisible() {
|
||||
e.preview.EmitEvent("content-changed", content)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *EditorApp) TogglePreview() {
|
||||
if e.preview == nil {
|
||||
e.preview = app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "preview",
|
||||
Title: "Preview",
|
||||
Width: 600,
|
||||
Height: 800,
|
||||
})
|
||||
|
||||
e.preview.OnWindowEvent(events.Common.WindowClosing, func(e2 *application.WindowEvent) {
|
||||
e.preview = nil
|
||||
})
|
||||
}
|
||||
|
||||
if e.preview.IsVisible() {
|
||||
e.preview.Hide()
|
||||
} else {
|
||||
e.preview.Show()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Parent-Child Relationships
|
||||
|
||||
### Creating Child Windows
|
||||
|
||||
```go
|
||||
childWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Child Window",
|
||||
Parent: parentWindow,
|
||||
})
|
||||
```
|
||||
|
||||
**Behaviour:**
|
||||
- Child closes when parent closes
|
||||
- Child stays above parent (on some platforms)
|
||||
- Child minimises with parent (on some platforms)
|
||||
|
||||
**Platform support:**
|
||||
|
||||
| Feature | macOS | Windows | Linux |
|
||||
|---------|-------|---------|-------|
|
||||
| Auto-close | ✅ | ✅ | ⚠️ Varies |
|
||||
| Stay above | ✅ | ⚠️ Partial | ⚠️ Varies |
|
||||
| Minimise together | ✅ | ❌ | ⚠️ Varies |
|
||||
|
||||
### Modal Behaviour
|
||||
|
||||
Create modal-like behaviour:
|
||||
|
||||
```go
|
||||
func ShowModal(parent *application.WebviewWindow) {
|
||||
modal := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Modal dialog",
|
||||
Width: 400,
|
||||
Height: 200,
|
||||
Parent: parent,
|
||||
AlwaysOnTop: true,
|
||||
})
|
||||
|
||||
// Disable parent interaction
|
||||
parent.SetEnabled(false)
|
||||
|
||||
// Re-enable on close
|
||||
modal.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
parent.SetEnabled(true)
|
||||
parent.Focus()
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** True modal behaviour (blocking) varies by platform.
|
||||
|
||||
## Window Lifecycle Management
|
||||
|
||||
### Creation Callbacks
|
||||
|
||||
Be notified when windows are created:
|
||||
|
||||
```go
|
||||
app.Window.OnCreate(func(window application.Window) {
|
||||
fmt.Printf("Window created: %s\n", window.Name())
|
||||
|
||||
// Configure all new windows
|
||||
window.SetMinSize(400, 300)
|
||||
})
|
||||
```
|
||||
|
||||
### Window Close Callbacks
|
||||
|
||||
Cleanup when windows are closed:
|
||||
|
||||
```go
|
||||
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
fmt.Printf("Window %s closing\n", window.Name())
|
||||
|
||||
// Cleanup resources
|
||||
cleanup(window.Name())
|
||||
|
||||
// Remove from tracking
|
||||
removeFromRegistry(window.Name())
|
||||
})
|
||||
```
|
||||
|
||||
### Application Quit Behaviour
|
||||
|
||||
Control when application quits:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Mac: application.MacOptions{
|
||||
// Don't quit when last window closes
|
||||
ApplicationShouldTerminateAfterLastWindowClosed: false,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Use cases:**
|
||||
- System tray applications
|
||||
- Background services
|
||||
- Menu bar applications (macOS)
|
||||
|
||||
## Memory Management
|
||||
|
||||
### Preventing Leaks
|
||||
|
||||
Always clean up window references:
|
||||
|
||||
```go
|
||||
var windows = make(map[string]*application.WebviewWindow)
|
||||
|
||||
func CreateWindow(name string) {
|
||||
window := app.Window.New()
|
||||
windows[name] = window
|
||||
|
||||
// IMPORTANT: Clean up on close
|
||||
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
delete(windows, name)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Closing vs Destroying
|
||||
|
||||
```go
|
||||
// Close - triggers WindowClosing event, can be cancelled via RegisterHook
|
||||
window.Close()
|
||||
|
||||
// Destroy - immediate, cannot be cancelled
|
||||
window.Destroy()
|
||||
```
|
||||
|
||||
**Best practice:** Use `Close()` for user-initiated closes, `Destroy()` for cleanup.
|
||||
|
||||
### Resource Cleanup
|
||||
|
||||
```go
|
||||
type ManagedWindow struct {
|
||||
window *application.WebviewWindow
|
||||
resources []io.Closer
|
||||
}
|
||||
|
||||
func (mw *ManagedWindow) Destroy() {
|
||||
// Close all resources
|
||||
for _, resource := range mw.resources {
|
||||
resource.Close()
|
||||
}
|
||||
|
||||
// Destroy window
|
||||
mw.window.Destroy()
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Patterns
|
||||
|
||||
### Window Pool
|
||||
|
||||
Reuse windows instead of creating new ones:
|
||||
|
||||
```go
|
||||
type WindowPool struct {
|
||||
available []*application.WebviewWindow
|
||||
inUse map[string]*application.WebviewWindow
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (wp *WindowPool) Acquire() *application.WebviewWindow {
|
||||
wp.mu.Lock()
|
||||
defer wp.mu.Unlock()
|
||||
|
||||
// Reuse available window
|
||||
if len(wp.available) > 0 {
|
||||
window := wp.available[0]
|
||||
wp.available = wp.available[1:]
|
||||
wp.inUse[window.Name()] = window
|
||||
return window
|
||||
}
|
||||
|
||||
// Create new window
|
||||
window := app.Window.New()
|
||||
wp.inUse[window.Name()] = window
|
||||
return window
|
||||
}
|
||||
|
||||
func (wp *WindowPool) Release(window *application.WebviewWindow) {
|
||||
wp.mu.Lock()
|
||||
defer wp.mu.Unlock()
|
||||
|
||||
delete(wp.inUse, window.Name())
|
||||
window.Hide()
|
||||
wp.available = append(wp.available, window)
|
||||
}
|
||||
```
|
||||
|
||||
### Window Groups
|
||||
|
||||
Manage related windows together:
|
||||
|
||||
```go
|
||||
type WindowGroup struct {
|
||||
name string
|
||||
windows []*application.WebviewWindow
|
||||
}
|
||||
|
||||
func (wg *WindowGroup) Add(window *application.WebviewWindow) {
|
||||
wg.windows = append(wg.windows, window)
|
||||
}
|
||||
|
||||
func (wg *WindowGroup) ShowAll() {
|
||||
for _, window := range wg.windows {
|
||||
window.Show()
|
||||
}
|
||||
}
|
||||
|
||||
func (wg *WindowGroup) HideAll() {
|
||||
for _, window := range wg.windows {
|
||||
window.Hide()
|
||||
}
|
||||
}
|
||||
|
||||
func (wg *WindowGroup) CloseAll() {
|
||||
for _, window := range wg.windows {
|
||||
window.Close()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Workspace Management
|
||||
|
||||
Save and restore window layouts:
|
||||
|
||||
```go
|
||||
type WindowLayout struct {
|
||||
Windows []WindowState `json:"windows"`
|
||||
}
|
||||
|
||||
type WindowState struct {
|
||||
Name string `json:"name"`
|
||||
X int `json:"x"`
|
||||
Y int `json:"y"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
}
|
||||
|
||||
func SaveLayout() *WindowLayout {
|
||||
layout := &WindowLayout{}
|
||||
|
||||
for _, window := range app.Window.GetAll() {
|
||||
x, y := window.Position()
|
||||
width, height := window.Size()
|
||||
|
||||
layout.Windows = append(layout.Windows, WindowState{
|
||||
Name: window.Name(),
|
||||
X: x,
|
||||
Y: y,
|
||||
Width: width,
|
||||
Height: height,
|
||||
})
|
||||
}
|
||||
|
||||
return layout
|
||||
}
|
||||
|
||||
func RestoreLayout(layout *WindowLayout) {
|
||||
for _, state := range layout.Windows {
|
||||
window, ok := app.Window.GetByName(state.Name)
|
||||
if ok {
|
||||
window.SetPosition(state.X, state.Y)
|
||||
window.SetSize(state.Width, state.Height)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a production-ready multi-window application:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"sync"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
)
|
||||
|
||||
type MultiWindowApp struct {
|
||||
app *application.App
|
||||
windows map[string]*application.WebviewWindow
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func main() {
|
||||
mwa := &MultiWindowApp{
|
||||
windows: make(map[string]*application.WebviewWindow),
|
||||
}
|
||||
|
||||
mwa.app = application.New(application.Options{
|
||||
Name: "Multi-Window Application",
|
||||
Mac: application.MacOptions{
|
||||
ApplicationShouldTerminateAfterLastWindowClosed: false,
|
||||
},
|
||||
})
|
||||
|
||||
// Create main window
|
||||
mwa.CreateMainWindow()
|
||||
|
||||
// Load saved layout
|
||||
mwa.LoadLayout()
|
||||
|
||||
mwa.app.Run()
|
||||
}
|
||||
|
||||
func (mwa *MultiWindowApp) CreateMainWindow() {
|
||||
window := mwa.app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "main",
|
||||
Title: "Main Application",
|
||||
Width: 1200,
|
||||
Height: 800,
|
||||
})
|
||||
|
||||
mwa.RegisterWindow("main", window)
|
||||
}
|
||||
|
||||
func (mwa *MultiWindowApp) ShowSettings() {
|
||||
if window := mwa.GetWindow("settings"); window != nil {
|
||||
window.Show()
|
||||
window.Focus()
|
||||
return
|
||||
}
|
||||
|
||||
window := mwa.app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "settings",
|
||||
Title: "Settings",
|
||||
Width: 600,
|
||||
Height: 400,
|
||||
})
|
||||
|
||||
mwa.RegisterWindow("settings", window)
|
||||
}
|
||||
|
||||
func (mwa *MultiWindowApp) OpenDocument(path string) {
|
||||
name := "doc-" + path
|
||||
|
||||
if window := mwa.GetWindow(name); window != nil {
|
||||
window.Show()
|
||||
window.Focus()
|
||||
return
|
||||
}
|
||||
|
||||
window := mwa.app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: name,
|
||||
Title: path,
|
||||
Width: 800,
|
||||
Height: 600,
|
||||
})
|
||||
|
||||
mwa.RegisterWindow(name, window)
|
||||
}
|
||||
|
||||
func (mwa *MultiWindowApp) RegisterWindow(name string, window *application.WebviewWindow) {
|
||||
mwa.mu.Lock()
|
||||
mwa.windows[name] = window
|
||||
mwa.mu.Unlock()
|
||||
|
||||
window.OnWindowEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
|
||||
mwa.UnregisterWindow(name)
|
||||
})
|
||||
}
|
||||
|
||||
func (mwa *MultiWindowApp) UnregisterWindow(name string) {
|
||||
mwa.mu.Lock()
|
||||
delete(mwa.windows, name)
|
||||
mwa.mu.Unlock()
|
||||
}
|
||||
|
||||
func (mwa *MultiWindowApp) GetWindow(name string) *application.WebviewWindow {
|
||||
mwa.mu.RLock()
|
||||
defer mwa.mu.RUnlock()
|
||||
return mwa.windows[name]
|
||||
}
|
||||
|
||||
func (mwa *MultiWindowApp) SaveLayout() {
|
||||
layout := make(map[string]WindowState)
|
||||
|
||||
mwa.mu.RLock()
|
||||
for name, window := range mwa.windows {
|
||||
x, y := window.Position()
|
||||
width, height := window.Size()
|
||||
|
||||
layout[name] = WindowState{
|
||||
X: x,
|
||||
Y: y,
|
||||
Width: width,
|
||||
Height: height,
|
||||
}
|
||||
}
|
||||
mwa.mu.RUnlock()
|
||||
|
||||
data, _ := json.Marshal(layout)
|
||||
os.WriteFile("layout.json", data, 0644)
|
||||
}
|
||||
|
||||
func (mwa *MultiWindowApp) LoadLayout() {
|
||||
data, err := os.ReadFile("layout.json")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var layout map[string]WindowState
|
||||
if err := json.Unmarshal(data, &layout); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for name, state := range layout {
|
||||
if window := mwa.GetWindow(name); window != nil {
|
||||
window.SetPosition(state.X, state.Y)
|
||||
window.SetSize(state.Width, state.Height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type WindowState struct {
|
||||
X int `json:"x"`
|
||||
Y int `json:"y"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Track windows** - Keep references for easy access
|
||||
- **Clean up on close** - Use `OnWindowEvent` with `WindowClosing` to prevent memory leaks
|
||||
- **Use events for communication** - Decoupled architecture
|
||||
- **Reuse windows** - Don't create duplicates
|
||||
- **Save/restore layouts** - Better UX
|
||||
- **Handle window close** - Confirm before closing with unsaved data
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Don't create unlimited windows** - Memory and performance issues
|
||||
- **Don't forget to clean up** - Memory leaks
|
||||
- **Don't use global variables carelessly** - Thread-safety issues
|
||||
- **Don't block window creation** - Create asynchronously if needed
|
||||
- **Don't ignore platform differences** - Test on all platforms
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Window Basics](/features/windows/basics) - Learn the fundamentals of window management
|
||||
- [Window Events](/features/windows/events) - Handle window lifecycle events
|
||||
- [Events System](/features/events/system) - Deep dive into the event system
|
||||
- [Frameless Windows](/features/windows/frameless) - Create custom window chrome
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [multi-window example](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/multi-window).
|
||||
852
docs/src/content/docs/features/windows/options.mdx
Normal file
852
docs/src/content/docs/features/windows/options.mdx
Normal file
|
|
@ -0,0 +1,852 @@
|
|||
---
|
||||
title: Window Options
|
||||
description: Complete reference for WebviewWindowOptions
|
||||
sidebar:
|
||||
order: 2
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
## Window Configuration Options
|
||||
|
||||
Wails provides comprehensive window configuration with dozens of options for size, position, appearance, and behaviour. This reference covers all available options across Windows, macOS, and Linux as the **complete reference** for `WebviewWindowOptions`. Every option, every platform, with examples and constraints.
|
||||
|
||||
## WebviewWindowOptions Structure
|
||||
|
||||
```go
|
||||
type WebviewWindowOptions struct {
|
||||
// Identity
|
||||
Name string
|
||||
Title string
|
||||
|
||||
// Size and Position
|
||||
Width int
|
||||
Height int
|
||||
X int
|
||||
Y int
|
||||
MinWidth int
|
||||
MinHeight int
|
||||
MaxWidth int
|
||||
MaxHeight int
|
||||
|
||||
// Initial State
|
||||
Hidden bool
|
||||
Frameless bool
|
||||
Resizable bool
|
||||
AlwaysOnTop bool
|
||||
Fullscreen bool
|
||||
Minimised bool
|
||||
Maximised bool
|
||||
WindowState WindowState
|
||||
|
||||
// Appearance
|
||||
BackgroundColour RGBA
|
||||
BackgroundType BackgroundType
|
||||
|
||||
// Content
|
||||
URL string
|
||||
HTML string
|
||||
|
||||
// Assets
|
||||
Assets application.AssetOptions
|
||||
|
||||
// Security
|
||||
ContentProtectionEnabled bool
|
||||
|
||||
// Lifecycle
|
||||
OnClose func() bool
|
||||
OnDestroy func()
|
||||
|
||||
// Platform-Specific
|
||||
Mac MacOptions
|
||||
Windows WindowsOptions
|
||||
Linux LinuxOptions
|
||||
}
|
||||
```
|
||||
|
||||
## Core Options
|
||||
|
||||
### Name
|
||||
|
||||
**Type:** `string`
|
||||
**Default:** Auto-generated UUID
|
||||
**Platform:** All
|
||||
|
||||
```go
|
||||
Name: "main-window"
|
||||
```
|
||||
|
||||
**Purpose:** Unique identifier for finding windows later.
|
||||
|
||||
**Best practices:**
|
||||
- Use descriptive names: `"main"`, `"settings"`, `"about"`
|
||||
- Use kebab-case: `"file-browser"`, `"color-picker"`
|
||||
- Keep it short and memorable
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "settings-window",
|
||||
})
|
||||
|
||||
// Later...
|
||||
settings, ok := app.Window.GetByName("settings-window")
|
||||
```
|
||||
|
||||
### Title
|
||||
|
||||
**Type:** `string`
|
||||
**Default:** Application name
|
||||
**Platform:** All
|
||||
|
||||
```go
|
||||
Title: "My Application"
|
||||
```
|
||||
|
||||
**Purpose:** Text shown in title bar and taskbar.
|
||||
|
||||
**Dynamic updates:**
|
||||
|
||||
```go
|
||||
window.SetTitle("My Application - Document.txt")
|
||||
```
|
||||
|
||||
### Width / Height
|
||||
|
||||
**Type:** `int` (pixels)
|
||||
**Default:** 800 x 600
|
||||
**Platform:** All
|
||||
**Constraints:** Must be positive
|
||||
|
||||
```go
|
||||
Width: 1200,
|
||||
Height: 800,
|
||||
```
|
||||
|
||||
**Purpose:** Initial window size in logical pixels.
|
||||
|
||||
**Notes:**
|
||||
- Wails handles DPI scaling automatically
|
||||
- Use logical pixels, not physical pixels
|
||||
- Consider minimum screen resolution (1024x768)
|
||||
|
||||
**Example sizes:**
|
||||
|
||||
| Use Case | Width | Height |
|
||||
|----------|-------|--------|
|
||||
| Small utility | 400 | 300 |
|
||||
| Standard app | 1024 | 768 |
|
||||
| Large app | 1440 | 900 |
|
||||
| Full HD | 1920 | 1080 |
|
||||
|
||||
### X / Y
|
||||
|
||||
**Type:** `int` (pixels)
|
||||
**Default:** Centred on screen
|
||||
**Platform:** All
|
||||
|
||||
```go
|
||||
X: 100, // 100px from left edge
|
||||
Y: 100, // 100px from top edge
|
||||
```
|
||||
|
||||
**Purpose:** Initial window position.
|
||||
|
||||
**Coordinate system:**
|
||||
- (0, 0) is top-left of primary screen
|
||||
- Positive X goes right
|
||||
- Positive Y goes down
|
||||
|
||||
**Best practice:** Use `Center()` instead:
|
||||
|
||||
```go
|
||||
window := app.Window.New()
|
||||
window.Center()
|
||||
```
|
||||
|
||||
### MinWidth / MinHeight
|
||||
|
||||
**Type:** `int` (pixels)
|
||||
**Default:** 0 (no minimum)
|
||||
**Platform:** All
|
||||
|
||||
```go
|
||||
MinWidth: 400,
|
||||
MinHeight: 300,
|
||||
```
|
||||
|
||||
**Purpose:** Prevent window from being too small.
|
||||
|
||||
**Use cases:**
|
||||
- Prevent broken layouts
|
||||
- Ensure usability
|
||||
- Maintain aspect ratio
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
// Prevent window smaller than 400x300
|
||||
MinWidth: 400,
|
||||
MinHeight: 300,
|
||||
```
|
||||
|
||||
### MaxWidth / MaxHeight
|
||||
|
||||
**Type:** `int` (pixels)
|
||||
**Default:** 0 (no maximum)
|
||||
**Platform:** All
|
||||
|
||||
```go
|
||||
MaxWidth: 1920,
|
||||
MaxHeight: 1080,
|
||||
```
|
||||
|
||||
**Purpose:** Prevent window from being too large.
|
||||
|
||||
**Use cases:**
|
||||
- Fixed-size applications
|
||||
- Prevent excessive resource usage
|
||||
- Maintain design constraints
|
||||
|
||||
## State Options
|
||||
|
||||
### Hidden
|
||||
|
||||
**Type:** `bool`
|
||||
**Default:** `false`
|
||||
**Platform:** All
|
||||
|
||||
```go
|
||||
Hidden: true,
|
||||
```
|
||||
|
||||
**Purpose:** Create window without showing it.
|
||||
|
||||
**Use cases:**
|
||||
- Background windows
|
||||
- Windows shown on demand
|
||||
- Splash screens (create, load, then show)
|
||||
- Prevent white flash while loading content
|
||||
|
||||
**Platform improvements:**
|
||||
- **Windows:** Fixed white window flash - window stays invisible until `Show()` is called
|
||||
- **macOS:** Full support
|
||||
- **Linux:** Full support
|
||||
|
||||
**Recommended pattern for smooth loading:**
|
||||
|
||||
```go
|
||||
// Create hidden window
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "main-window",
|
||||
Hidden: true,
|
||||
BackgroundColour: application.NewRGBA(30, 30, 30, 255), // Match your theme
|
||||
})
|
||||
|
||||
// Load content while hidden
|
||||
// ... content loads ...
|
||||
|
||||
// Show when ready (no flash!)
|
||||
window.Show()
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
settings := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Name: "settings",
|
||||
Hidden: true,
|
||||
})
|
||||
|
||||
// Show when needed
|
||||
settings.Show()
|
||||
```
|
||||
|
||||
### Frameless
|
||||
|
||||
**Type:** `bool`
|
||||
**Default:** `false`
|
||||
**Platform:** All
|
||||
|
||||
```go
|
||||
Frameless: true,
|
||||
```
|
||||
|
||||
**Purpose:** Remove title bar and window borders.
|
||||
|
||||
**Use cases:**
|
||||
- Custom window chrome
|
||||
- Splash screens
|
||||
- Kiosk applications
|
||||
- Custom-designed windows
|
||||
|
||||
**Important:** You'll need to implement:
|
||||
- Window dragging
|
||||
- Close/minimise/maximise buttons
|
||||
- Resize handles (if resizable)
|
||||
|
||||
**See [Frameless Windows](/features/windows/frameless) for details.**
|
||||
|
||||
### Resizable
|
||||
|
||||
**Type:** `bool`
|
||||
**Default:** `true`
|
||||
**Platform:** All
|
||||
|
||||
```go
|
||||
Resizable: false,
|
||||
```
|
||||
|
||||
**Purpose:** Allow/prevent window resizing.
|
||||
|
||||
**Use cases:**
|
||||
- Fixed-size applications
|
||||
- Splash screens
|
||||
- dialogs
|
||||
|
||||
**Note:** Users can still maximise/fullscreen unless you prevent those too.
|
||||
|
||||
### AlwaysOnTop
|
||||
|
||||
**Type:** `bool`
|
||||
**Default:** `false`
|
||||
**Platform:** All
|
||||
|
||||
```go
|
||||
AlwaysOnTop: true,
|
||||
```
|
||||
|
||||
**Purpose:** Keep window above all other windows.
|
||||
|
||||
**Use cases:**
|
||||
- Floating toolbars
|
||||
- Notifications
|
||||
- Picture-in-picture
|
||||
- Timers
|
||||
|
||||
**Platform notes:**
|
||||
- **macOS:** Full support
|
||||
- **Windows:** Full support
|
||||
- **Linux:** Depends on window manager
|
||||
|
||||
### Fullscreen
|
||||
|
||||
**Type:** `bool`
|
||||
**Default:** `false`
|
||||
**Platform:** All
|
||||
|
||||
```go
|
||||
Fullscreen: true,
|
||||
```
|
||||
|
||||
**Purpose:** Start in fullscreen mode.
|
||||
|
||||
**Platform behaviour:**
|
||||
- **macOS:** Creates new Space (virtual desktop)
|
||||
- **Windows:** Covers taskbar
|
||||
- **Linux:** Varies by desktop environment
|
||||
|
||||
**Toggle at runtime:**
|
||||
|
||||
```go
|
||||
window.Fullscreen()
|
||||
window.UnFullscreen()
|
||||
```
|
||||
|
||||
### Minimised / Maximised
|
||||
|
||||
**Type:** `bool`
|
||||
**Default:** `false`
|
||||
**Platform:** All
|
||||
|
||||
```go
|
||||
Minimised: true, // Start minimised
|
||||
Maximised: true, // Start maximised
|
||||
```
|
||||
|
||||
**Purpose:** Set initial window state.
|
||||
|
||||
**Note:** Don't set both to `true` - behaviour is undefined.
|
||||
|
||||
### WindowState
|
||||
|
||||
**Type:** `WindowState` enum
|
||||
**Default:** `WindowStateNormal`
|
||||
**Platform:** All
|
||||
|
||||
```go
|
||||
WindowState: application.WindowStateMaximised,
|
||||
```
|
||||
|
||||
**Values:**
|
||||
- `WindowStateNormal` - Normal window
|
||||
- `WindowStateMinimised` - Minimised
|
||||
- `WindowStateMaximised` - Maximised
|
||||
- `WindowStateFullscreen` - Fullscreen
|
||||
- `WindowStateHidden` - Hidden
|
||||
|
||||
**Preferred over individual flags:**
|
||||
|
||||
```go
|
||||
// ✅ Preferred
|
||||
WindowState: application.WindowStateMaximised,
|
||||
|
||||
// ❌ Avoid
|
||||
Maximised: true,
|
||||
```
|
||||
|
||||
## Appearance Options
|
||||
|
||||
### BackgroundColour
|
||||
|
||||
**Type:** `RGBA` struct
|
||||
**Default:** White (255, 255, 255, 255)
|
||||
**Platform:** All
|
||||
|
||||
```go
|
||||
BackgroundColour: application.NewRGBA(0, 0, 0, 255),
|
||||
```
|
||||
|
||||
**Purpose:** Window background colour before content loads.
|
||||
|
||||
**Use cases:**
|
||||
- Match your app's theme
|
||||
- Prevent white flash on dark themes
|
||||
- Smooth loading experience
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
// Dark theme
|
||||
BackgroundColour: application.NewRGBA(30, 30, 30, 255),
|
||||
|
||||
// Light theme
|
||||
BackgroundColour: application.NewRGB(255, 255, 255),
|
||||
```
|
||||
|
||||
**Helper method:**
|
||||
|
||||
```go
|
||||
window.SetBackgroundColour(application.NewRGBA(30, 30, 30, 255))
|
||||
```
|
||||
|
||||
### BackgroundType
|
||||
|
||||
**Type:** `BackgroundType` enum
|
||||
**Default:** `BackgroundTypeSolid`
|
||||
**Platform:** macOS, Windows (partial)
|
||||
|
||||
```go
|
||||
BackgroundType: application.BackgroundTypeTranslucent,
|
||||
```
|
||||
|
||||
**Values:**
|
||||
- `BackgroundTypeSolid` - Solid colour
|
||||
- `BackgroundTypeTransparent` - Fully transparent
|
||||
- `BackgroundTypeTranslucent` - Semi-transparent blur
|
||||
|
||||
**Platform support:**
|
||||
- **macOS:** All types supported
|
||||
- **Windows:** Transparent and Translucent (Windows 11+)
|
||||
- **Linux:** Solid only
|
||||
|
||||
**Example (macOS):**
|
||||
|
||||
```go
|
||||
BackgroundType: application.BackgroundTypeTranslucent,
|
||||
Mac: application.MacOptions{
|
||||
Backdrop: application.MacBackdropTranslucent,
|
||||
},
|
||||
```
|
||||
|
||||
## Content Options
|
||||
|
||||
### URL
|
||||
|
||||
**Type:** `string`
|
||||
**Default:** Empty (loads from Assets)
|
||||
**Platform:** All
|
||||
|
||||
```go
|
||||
URL: "https://example.com",
|
||||
```
|
||||
|
||||
**Purpose:** Load external URL instead of embedded assets.
|
||||
|
||||
**Use cases:**
|
||||
- Development (load from dev server)
|
||||
- Web-based applications
|
||||
- Hybrid applications
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
// Development
|
||||
URL: "http://localhost:5173",
|
||||
|
||||
// Production
|
||||
Assets: application.AssetOptions{
|
||||
Handler: application.AssetFileServerFS(assets),
|
||||
},
|
||||
```
|
||||
|
||||
### HTML
|
||||
|
||||
**Type:** `string`
|
||||
**Default:** Empty
|
||||
**Platform:** All
|
||||
|
||||
```go
|
||||
HTML: "<h1>Hello World</h1>",
|
||||
```
|
||||
|
||||
**Purpose:** Load HTML string directly.
|
||||
|
||||
**Use cases:**
|
||||
- Simple windows
|
||||
- Generated content
|
||||
- Testing
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
HTML: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>Simple Window</title></head>
|
||||
<body><h1>Hello from Wails!</h1></body>
|
||||
</html>
|
||||
`,
|
||||
```
|
||||
|
||||
### Assets
|
||||
|
||||
**Type:** `AssetOptions`
|
||||
**Default:** Inherited from application
|
||||
**Platform:** All
|
||||
|
||||
```go
|
||||
Assets: application.AssetOptions{
|
||||
Handler: application.AssetFileServerFS(assets),
|
||||
},
|
||||
```
|
||||
|
||||
**Purpose:** Serve embedded frontend assets.
|
||||
|
||||
**See [Build System](/concepts/build-system) for details.**
|
||||
|
||||
## Security Options
|
||||
|
||||
### ContentProtectionEnabled
|
||||
|
||||
**Type:** `bool`
|
||||
**Default:** `false`
|
||||
**Platform:** Windows (10+), macOS
|
||||
|
||||
```go
|
||||
ContentProtectionEnabled: true,
|
||||
```
|
||||
|
||||
**Purpose:** Prevent screen capture of window contents.
|
||||
|
||||
**Platform support:**
|
||||
- **Windows:** Windows 10 build 19041+ (full), older versions (partial)
|
||||
- **macOS:** Full support
|
||||
- **Linux:** Not supported
|
||||
|
||||
**Use cases:**
|
||||
- Banking applications
|
||||
- Password managers
|
||||
- Medical records
|
||||
- Confidential documents
|
||||
|
||||
**Important notes:**
|
||||
1. Doesn't prevent physical photography
|
||||
2. Some tools may bypass protection
|
||||
3. Part of comprehensive security, not sole protection
|
||||
4. DevTools windows not protected automatically
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
Title: "Secure Window",
|
||||
ContentProtectionEnabled: true,
|
||||
})
|
||||
|
||||
// Toggle at runtime
|
||||
window.SetContentProtection(true)
|
||||
```
|
||||
|
||||
## Lifecycle Options
|
||||
|
||||
### OnClose
|
||||
|
||||
**Type:** `func() bool`
|
||||
**Default:** `nil` (always allow close)
|
||||
**Platform:** All
|
||||
|
||||
```go
|
||||
OnClose: func() bool {
|
||||
// Return false to cancel close
|
||||
// Return true to allow close
|
||||
return true
|
||||
},
|
||||
```
|
||||
|
||||
**Purpose:** Handle window close events, optionally prevent closing.
|
||||
|
||||
**Use cases:**
|
||||
- Confirm before closing with unsaved changes
|
||||
- Save state before closing
|
||||
- Prevent accidental closure
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
OnClose: func() bool {
|
||||
if hasUnsavedChanges() {
|
||||
result := showConfirmdialog("Unsaved changes. Close anyway?")
|
||||
return result == "yes"
|
||||
}
|
||||
return true
|
||||
},
|
||||
```
|
||||
|
||||
**Important:** Only triggered by user actions (clicking X), not `window.Destroy()`.
|
||||
|
||||
### OnDestroy
|
||||
|
||||
**Type:** `func()`
|
||||
**Default:** `nil`
|
||||
**Platform:** All
|
||||
|
||||
```go
|
||||
OnDestroy: func() {
|
||||
// Cleanup code
|
||||
fmt.Println("Window destroyed")
|
||||
},
|
||||
```
|
||||
|
||||
**Purpose:** Cleanup when window is destroyed.
|
||||
|
||||
**Use cases:**
|
||||
- Release resources
|
||||
- Close connections
|
||||
- Update application state
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
OnDestroy: func() {
|
||||
// Close database connection
|
||||
if db != nil {
|
||||
db.Close()
|
||||
}
|
||||
|
||||
// Remove from window list
|
||||
removeWindow(window.Name())
|
||||
},
|
||||
```
|
||||
|
||||
## Platform-Specific Options
|
||||
|
||||
### Mac Options
|
||||
|
||||
```go
|
||||
Mac: application.MacOptions{
|
||||
TitleBarAppearsTransparent: true,
|
||||
Backdrop: application.MacBackdropTranslucent,
|
||||
InvisibleTitleBarHeight: 50,
|
||||
TitleBarStyle: application.MacTitleBarStyleHidden,
|
||||
},
|
||||
```
|
||||
|
||||
**TitleBarAppearsTransparent** (`bool`)
|
||||
- Makes title bar transparent
|
||||
- Content extends into title bar area
|
||||
|
||||
**Backdrop** (`MacBackdrop`)
|
||||
- `MacBackdropNormal` - Standard
|
||||
- `MacBackdropTranslucent` - Blurred translucent
|
||||
- `MacBackdropTransparent` - Fully transparent
|
||||
|
||||
**InvisibleTitleBarHeight** (`int`)
|
||||
- Height of invisible title bar (for dragging)
|
||||
- Only when `TitleBarStyle` is `MacTitleBarStyleHidden`
|
||||
|
||||
**TitleBarStyle** (`MacTitleBarStyle`)
|
||||
- `MacTitleBarStyleDefault` - Standard title bar
|
||||
- `MacTitleBarStyleHidden` - Hidden title bar
|
||||
- `MacTitleBarStyleHiddenInset` - Hidden with inset
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
Mac: application.MacOptions{
|
||||
TitleBarAppearsTransparent: true,
|
||||
Backdrop: application.MacBackdropTranslucent,
|
||||
InvisibleTitleBarHeight: 50,
|
||||
},
|
||||
```
|
||||
|
||||
### Windows Options
|
||||
|
||||
```go
|
||||
Windows: application.WindowsOptions{
|
||||
DisableWindowIcon: false,
|
||||
WindowBackdropType: application.WindowsBackdropTypeAuto,
|
||||
CustomTheme: nil,
|
||||
DisableFramelessWindowDecorations: false,
|
||||
},
|
||||
```
|
||||
|
||||
**DisableWindowIcon** (`bool`)
|
||||
- Remove icon from title bar
|
||||
- Cleaner appearance
|
||||
|
||||
**WindowBackdropType** (`WindowsBackdropType`)
|
||||
- `WindowsBackdropTypeAuto` - System default
|
||||
- `WindowsBackdropTypeNone` - No backdrop
|
||||
- `WindowsBackdropTypeMica` - Mica material (Windows 11)
|
||||
- `WindowsBackdropTypeAcrylic` - Acrylic material (Windows 11)
|
||||
- `WindowsBackdropTypeTabbed` - Tabbed material (Windows 11)
|
||||
|
||||
**CustomTheme** (`*WindowsTheme`)
|
||||
- Custom colour theme
|
||||
- Override system colours
|
||||
|
||||
**DisableFramelessWindowDecorations** (`bool`)
|
||||
- Disable default frameless decorations
|
||||
- For custom window chrome
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
Windows: application.WindowsOptions{
|
||||
WindowBackdropType: application.WindowsBackdropTypeMica,
|
||||
DisableWindowIcon: true,
|
||||
},
|
||||
```
|
||||
|
||||
### Linux Options
|
||||
|
||||
```go
|
||||
Linux: application.LinuxOptions{
|
||||
Icon: []byte{/* PNG data */},
|
||||
WindowIsTranslucent: false,
|
||||
},
|
||||
```
|
||||
|
||||
**Icon** (`[]byte`)
|
||||
- Window icon (PNG format)
|
||||
- Shown in title bar and taskbar
|
||||
|
||||
**WindowIsTranslucent** (`bool`)
|
||||
- Enable window translucency
|
||||
- Requires compositor support
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
//go:embed icon.png
|
||||
var icon []byte
|
||||
|
||||
Linux: application.LinuxOptions{
|
||||
Icon: icon,
|
||||
},
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a production-ready window configuration:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
//go:embed frontend/dist
|
||||
var assets embed.FS
|
||||
|
||||
//go:embed icon.png
|
||||
var icon []byte
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Name: "My Application",
|
||||
})
|
||||
|
||||
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
|
||||
// Identity
|
||||
Name: "main-window",
|
||||
Title: "My Application",
|
||||
|
||||
// Size and Position
|
||||
Width: 1200,
|
||||
Height: 800,
|
||||
MinWidth: 800,
|
||||
MinHeight: 600,
|
||||
|
||||
// Initial State
|
||||
WindowState: application.WindowStateNormal,
|
||||
Resizable: true,
|
||||
|
||||
// Appearance
|
||||
BackgroundColour: application.NewRGB(255, 255, 255),
|
||||
|
||||
// Content
|
||||
Assets: application.AssetOptions{
|
||||
Handler: application.AssetFileServerFS(assets),
|
||||
},
|
||||
|
||||
// Lifecycle
|
||||
OnClose: func() bool {
|
||||
if hasUnsavedChanges() {
|
||||
result := showConfirmdialog("Unsaved changes. Close anyway?")
|
||||
return result == "yes"
|
||||
}
|
||||
return true
|
||||
},
|
||||
|
||||
OnDestroy: func() {
|
||||
cleanup()
|
||||
},
|
||||
|
||||
// Platform-Specific
|
||||
Mac: application.MacOptions{
|
||||
TitleBarAppearsTransparent: true,
|
||||
Backdrop: application.MacBackdropTranslucent,
|
||||
},
|
||||
|
||||
Windows: application.WindowsOptions{
|
||||
WindowBackdropType: application.WindowsBackdropTypeMica,
|
||||
DisableWindowIcon: false,
|
||||
},
|
||||
|
||||
Linux: application.LinuxOptions{
|
||||
Icon: icon,
|
||||
},
|
||||
})
|
||||
|
||||
window.Center()
|
||||
window.Show()
|
||||
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Window Basics](/features/windows/basics) - Creating and controlling windows
|
||||
- [Multiple Windows](/features/windows/multiple) - Multi-window patterns
|
||||
- [Frameless Windows](/features/windows/frameless) - Custom window chrome
|
||||
- [Window Events](/features/windows/events) - Lifecycle events
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).
|
||||
86
docs/src/content/docs/feedback.mdx
Normal file
86
docs/src/content/docs/feedback.mdx
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
---
|
||||
title: v3 Alpha Feedback
|
||||
sidebar:
|
||||
order: 40
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
We welcome (and encourage) your feedback! Please search for existing tickets or
|
||||
posts before creating new ones. Here are the different ways to provide feedback:
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Bugs" icon="error">
|
||||
|
||||
If you find a bug, please let us know by posting into the [v3 Alpha Feedback](https://discord.gg/bdj28QNHmT) channel on Discord.
|
||||
|
||||
- The post should clearly state what the bug is and have a simple reproducible example. If the docs are unclear what *should* happen, please include that in the post.
|
||||
- The post should be given the `Bug` tag.
|
||||
- Please include the output of `wails doctor` in your post.
|
||||
- If the bug is behavior that does not align with current documentation, e.g. a window does not resize properly, please do the following:
|
||||
- Update an existing example in the `v3/example` directory or create a new example in the `v3/examples` folder that clearly shows the issue.
|
||||
- Open a [PR](https://github.com/wailsapp/wails/pulls) with the title `[v3 alpha test] <description of bug>`.
|
||||
- Please include a link to the PR in your post.
|
||||
|
||||
:::caution
|
||||
|
||||
_Remember_, unexpected behavior isn't necessarily a bug - it might just not do
|
||||
what you expect it to do. Use `Suggestions` for this.
|
||||
|
||||
:::
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Fixes" icon="approve-check">
|
||||
|
||||
If you have a fix for a bug or an update for documentation, please do the following:
|
||||
|
||||
- Open a pull request on the [Wails repository](https://github.com/wailsapp/wails). The title of the PR should start with `[v3 alpha]`.
|
||||
- Create a post in the [v3 Alpha Feedback](https://discord.gg/bdj28QNHmT) channel.
|
||||
- The post should be given the `PR` tag.
|
||||
- Please include a link to the PR in your post.
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Suggestions" icon="puzzle" id="abc">
|
||||
|
||||
If you have a suggestion, please let us know by posting into the [v3 Alpha Feedback](https://discord.gg/bdj28QNHmT) channel on Discord:
|
||||
|
||||
- The post should be given the `Suggestion` tag.
|
||||
|
||||
Please feel free to reach out to us on [Discord](https://discord.gg/bdj28QNHmT) if you have any questions.
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Upvoting" icon="star">
|
||||
|
||||
- Posts can be "upvoted" by using the :thumbsup: emoji. Please apply to any posts that are a priority for you.
|
||||
- Please *don't* just add comments like "+1" or "me too".
|
||||
- Please feel free to comment if there is more to add to the post, such as "this bug also affect ARM builds" or "Another option would be to ....."
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
There is a list of known issues & work in progress can be found
|
||||
[here](https://github.com/orgs/wailsapp/projects/6).
|
||||
|
||||
## Things we are looking for feedback on
|
||||
|
||||
- The API
|
||||
- Is it easy to use?
|
||||
- Does it do what you expect?
|
||||
- Is it missing anything?
|
||||
- Is there anything that should be removed?
|
||||
- Is it consistent between Go and JS?
|
||||
- The build system
|
||||
- Is it easy to use?
|
||||
- Can we improve it?
|
||||
- The examples
|
||||
- Are they clear?
|
||||
- Do they cover the basics?
|
||||
- Features
|
||||
- What features are missing?
|
||||
- What features are not needed?
|
||||
- Documentation
|
||||
- What could be clearer?
|
||||
114
docs/src/content/docs/getting-started/installation.mdx
Normal file
114
docs/src/content/docs/getting-started/installation.mdx
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
---
|
||||
title: Installation
|
||||
sidebar:
|
||||
order: 10
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
## Supported Platforms
|
||||
|
||||
- Windows 10/11 AMD64/ARM64
|
||||
- macOS 10.15+ AMD64 (Can deploy to macOS 10.13+)
|
||||
- macOS 11.0+ ARM64
|
||||
- Ubuntu 24.04 AMD64/ARM64 (other Linux may work too!)
|
||||
|
||||
## Dependencies
|
||||
|
||||
Wails has a number of common dependencies that are required before installation:
|
||||
|
||||
<Tabs>
|
||||
<TabItem label="Go (At least 1.24)" icon="seti:go">
|
||||
|
||||
Download Go from the [Go Downloads Page](https://go.dev/dl/).
|
||||
|
||||
Ensure that you follow the official [Go installation instructions](https://go.dev/doc/install). You will also need to ensure that your `PATH` environment variable also includes the path to your `~/go/bin` directory. Restart your terminal and do the following checks:
|
||||
|
||||
- Check Go is installed correctly: `go version`
|
||||
- Check `~/go/bin` is in your PATH variable
|
||||
- Mac / Linux: `echo $PATH | grep go/bin`
|
||||
- Windows: `$env:PATH -split ';' | Where-Object { $_ -like '*\go\bin' }`
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="npm (Optional)" icon="seti:npm">
|
||||
|
||||
Although Wails doesn't require npm to be installed, it is needed by most of the bundled templates.
|
||||
|
||||
Download the latest node installer from the [Node Downloads Page](https://nodejs.org/en/download/). It is best to use the latest release as that is what we generally test against.
|
||||
|
||||
Run `npm --version` to verify.
|
||||
|
||||
:::note
|
||||
If you prefer a different package manager to npm, feel free to use it. You will need to update the project Taskfiles to use it.
|
||||
:::
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Platform Specific Dependencies
|
||||
|
||||
You will also need to install platform specific dependencies:
|
||||
|
||||
<Tabs syncKey="platform">
|
||||
<TabItem label="Mac" icon="apple">
|
||||
|
||||
Wails requires that the xcode command line tools are installed. This can be
|
||||
done by running:
|
||||
|
||||
```sh
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Windows" icon="seti:windows">
|
||||
|
||||
Wails requires that the [WebView2 Runtime](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) is installed. Almost all Windows installations will already have this installed. You can check using the `wails doctor` command.
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Linux" icon="linux">
|
||||
|
||||
Linux requires the standard `gcc` build tools plus `gtk3` and `webkit2gtk`. Run <code>wails doctor</code> after installation to be shown how to install the dependencies. If your distro/package manager is not supported, please let us know on discord.
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Installation
|
||||
|
||||
To install the Wails CLI using Go Modules, run the following commands:
|
||||
|
||||
```shell
|
||||
go install -v github.com/wailsapp/wails/v3/cmd/wails3@latest
|
||||
```
|
||||
|
||||
If you would like to install the latest development version, run the following
|
||||
commands:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/wailsapp/wails.git
|
||||
cd wails
|
||||
git checkout v3-alpha
|
||||
cd v3/cmd/wails3
|
||||
go install
|
||||
```
|
||||
|
||||
When using the development version, all generated projects will use Go's
|
||||
[replace](https://go.dev/ref/mod#go-mod-file-replace) directive to ensure
|
||||
projects use the development version of Wails.
|
||||
|
||||
## System Check
|
||||
|
||||
Running `wails3 doctor` will check if you have the correct dependencies
|
||||
installed. If not, it will advise on what is missing and help on how to rectify
|
||||
any problems.
|
||||
|
||||
## The `wails3` command appears to be missing?
|
||||
|
||||
If your system is reporting that the `wails3` command is missing, check the
|
||||
following:
|
||||
|
||||
- Make sure you have followed the above `Go installation guide` correctly and
|
||||
that the `go/bin` directory is in the `PATH` environment variable.
|
||||
- Close/Reopen current terminals to pick up the new `PATH` variable.
|
||||
272
docs/src/content/docs/getting-started/your-first-app.mdx
Normal file
272
docs/src/content/docs/getting-started/your-first-app.mdx
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
---
|
||||
title: Your First Application
|
||||
sidebar:
|
||||
order: 20
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
import { Badge } from '@astrojs/starlight/components';
|
||||
import { Steps } from "@astrojs/starlight/components";
|
||||
import { FileTree } from '@astrojs/starlight/components';
|
||||
|
||||
import wails_init from '../../../assets/wails_init.mp4';
|
||||
import wails_build from '../../../assets/wails_build.mp4';
|
||||
import wails_dev from '../../../assets/wails_dev.mp4';
|
||||
|
||||
|
||||
This guide shows you how to create your first Wails v3 application, covering project setup, building, and development workflow.
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<Steps>
|
||||
|
||||
1. ## Creating a New Project
|
||||
|
||||
Open your terminal and run the following command to create a new Wails
|
||||
project:
|
||||
|
||||
```bash
|
||||
wails3 init -n myfirstapp
|
||||
```
|
||||
|
||||
This command creates a new directory called `myfirstapp` with all the
|
||||
necessary files.
|
||||
|
||||
<video src={wails_init} controls></video>
|
||||
|
||||
2. ## Exploring the Project Structure
|
||||
|
||||
Navigate to the `myfirstapp` directory. You'll find several files and
|
||||
folders:
|
||||
|
||||
<FileTree>
|
||||
- build/ Contains files used by the build process
|
||||
- appicon.png The application icon
|
||||
- config.yml Build configuration
|
||||
- Taskfile.yml Build tasks
|
||||
- darwin/ macOS specific build files
|
||||
- Info.dev.plist Development configuration
|
||||
- Info.plist Production configuration
|
||||
- Taskfile.yml macOS build tasks
|
||||
- icons.icns macOS application icon
|
||||
- linux/ Linux specific build files
|
||||
- Taskfile.yml Linux build tasks
|
||||
- appimage/ AppImage packaging
|
||||
- build.sh AppImage build script
|
||||
- nfpm/ NFPM packaging
|
||||
- nfpm.yaml Package configuration
|
||||
- scripts/ Build scripts
|
||||
- windows/ Windows specific build files
|
||||
- Taskfile.yml Windows build tasks
|
||||
- icon.ico Windows application icon
|
||||
- info.json Application metadata
|
||||
- wails.exe.manifest Windows manifest file
|
||||
- nsis/ NSIS installer files
|
||||
- project.nsi NSIS project file
|
||||
- wails_tools.nsh NSIS helper scripts
|
||||
- frontend/ Frontend application files
|
||||
- index.html Main HTML file
|
||||
- main.js Main JavaScript file
|
||||
- package.json NPM package configuration
|
||||
- public/ Static assets
|
||||
- Inter Font License.txt Font license
|
||||
- .gitignore Git ignore file
|
||||
- README.md Project documentation
|
||||
- Taskfile.yml Project tasks
|
||||
- go.mod Go module file
|
||||
- go.sum Go module checksums
|
||||
- greetservice.go Greeting service
|
||||
- main.go Main application code
|
||||
</FileTree>
|
||||
|
||||
Take a moment to explore these files and familiarize yourself with the
|
||||
structure.
|
||||
|
||||
:::note
|
||||
Although Wails v3 uses [Task](https://taskfile.dev/) as its
|
||||
default build system, there is nothing stopping you from using `make` or any other
|
||||
alternative build system.
|
||||
:::
|
||||
|
||||
3. ## Building Your Application
|
||||
|
||||
To build your application, execute:
|
||||
|
||||
```bash
|
||||
wails3 build
|
||||
```
|
||||
|
||||
This command compiles a debug version of your application and saves it in a
|
||||
new `bin` directory.
|
||||
|
||||
:::note
|
||||
`wails3 build` is shorthand for `wails3 task build` and will run the `build` task in `Taskfile.yml`.
|
||||
:::
|
||||
|
||||
<video src={wails_build} controls></video>
|
||||
|
||||
|
||||
Once built, you can run this like you would any normal application:
|
||||
|
||||
|
||||
<Tabs syncKey="platform">
|
||||
|
||||
<TabItem label="Mac" icon="apple">
|
||||
|
||||
```sh
|
||||
./bin/myfirstapp
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Windows" icon="seti:windows">
|
||||
|
||||
```sh
|
||||
bin\myfirstapp.exe
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="linux">
|
||||
|
||||
```sh
|
||||
./bin/myfirstapp
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
You'll see a simple UI, the starting point for your application. As it is
|
||||
the debug version, you'll also see logs in the console window. This is
|
||||
useful for debugging purposes.
|
||||
|
||||
4. ## Dev Mode
|
||||
|
||||
We can also run the application in development mode. This mode allows you to
|
||||
make changes to your frontend code and see the changes reflected in the
|
||||
running application without having to rebuild the entire application.
|
||||
|
||||
1. Open a new terminal window.
|
||||
2. Run `wails3 dev`. The application will compile and run in debug mode.
|
||||
3. Open `frontend/index.html` in your editor of choice.
|
||||
4. Edit the code and change `Please enter your name below` to
|
||||
`Please enter your name below!!!`.
|
||||
5. Save the file.
|
||||
|
||||
This change will reflect in your application immediately.
|
||||
|
||||
Any changes to backend code will trigger a rebuild:
|
||||
|
||||
1. Open `greetservice.go`.
|
||||
2. Change the line that has `return "Hello " + name + "!"` to
|
||||
`return "Hello there " + name + "!"`.
|
||||
3. Save the file.
|
||||
|
||||
The application will update within a matter of seconds.
|
||||
|
||||
<video src={wails_dev} controls></video>
|
||||
|
||||
5. ## Packaging Your Application
|
||||
|
||||
Once your application is ready for distribution, you can create
|
||||
platform-specific packages:
|
||||
|
||||
<Tabs syncKey="platform">
|
||||
|
||||
<TabItem label="Mac" icon="apple">
|
||||
|
||||
To create a `.app` bundle:
|
||||
|
||||
```bash
|
||||
wails3 package
|
||||
```
|
||||
|
||||
This will create a production build and package it into a `.app` bundle in the `bin` directory.
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Windows" icon="seti:windows">
|
||||
|
||||
To create an NSIS installer:
|
||||
|
||||
```bash
|
||||
wails3 package
|
||||
```
|
||||
|
||||
This will create a production build and package it into an NSIS installer in the `bin` directory.
|
||||
|
||||
</TabItem>
|
||||
<TabItem label="Linux" icon="linux">
|
||||
|
||||
Wails supports multiple package formats for Linux distribution:
|
||||
|
||||
```bash
|
||||
# Create all package types (AppImage, deb, rpm, and Arch Linux)
|
||||
wails3 package
|
||||
|
||||
# Or create specific package types
|
||||
wails3 task linux:create:appimage # AppImage format
|
||||
wails3 task linux:create:deb # Debian package
|
||||
wails3 task linux:create:rpm # Red Hat package
|
||||
wails3 task linux:create:aur # Arch Linux package
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
For more detailed information about packaging options and configuration,
|
||||
check out our [Packaging Guide](/guides/packaging).
|
||||
|
||||
6. ## Setting up Version Control and Module Name
|
||||
|
||||
Your project is created with the placeholder module name `changeme`. It's recommended to update this to match your repository URL:
|
||||
|
||||
1. Create a new repository on GitHub (or your preferred Git host)
|
||||
2. Initialize git in your project directory:
|
||||
```bash
|
||||
git init
|
||||
git add .
|
||||
git commit -m "Initial commit"
|
||||
```
|
||||
3. Set your remote repository (replace with your repository URL):
|
||||
```bash
|
||||
git remote add origin https://github.com/username/myfirstapp.git
|
||||
```
|
||||
4. Update your module name in `go.mod` to match your repository URL:
|
||||
```bash
|
||||
go mod edit -module github.com/username/myfirstapp
|
||||
```
|
||||
5. Push your code:
|
||||
```bash
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
This ensures your Go module name aligns with Go's module naming conventions and makes it easier to share your code.
|
||||
|
||||
:::tip[Pro Tip]
|
||||
You can automate all of the initialisation steps by using the `-git` flag when creating your project:
|
||||
```bash
|
||||
wails3 init -n myfirstapp -git github.com/username/myfirstapp
|
||||
```
|
||||
This supports various Git URL formats:
|
||||
- HTTPS: `https://github.com/username/project`
|
||||
- SSH: `git@github.com:username/project` or `ssh://git@github.com/username/project`
|
||||
- Git protocol: `git://github.com/username/project`
|
||||
- Filesystem: `file:///path/to/project.git`
|
||||
:::
|
||||
|
||||
</Steps>
|
||||
|
||||
## Congratulations!
|
||||
|
||||
You've just created, developed and packaged your first Wails application.
|
||||
This is just the beginning of what you can achieve with Wails v3.
|
||||
|
||||
## Next Steps
|
||||
|
||||
If you are new to Wails, we recommend reading through our Tutorials next which will be a practical guide through
|
||||
the various features of Wails. The first tutorial is [Creating a Service](/tutorials/01-creating-a-service).
|
||||
|
||||
If you are a more advanced user, check out our [Guides](/guides) for more detailed information on how to use Wails.
|
||||
8
docs/src/content/docs/guides/_category_.json
Normal file
8
docs/src/content/docs/guides/_category_.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"label": "Guides",
|
||||
"position": 3,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Comprehensive guides for developing Wails applications"
|
||||
}
|
||||
}
|
||||
199
docs/src/content/docs/guides/architecture.mdx
Normal file
199
docs/src/content/docs/guides/architecture.mdx
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
---
|
||||
title: Architecture Patterns
|
||||
description: Design patterns for Wails applications
|
||||
sidebar:
|
||||
order: 7
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Proven patterns for organising your Wails application.
|
||||
|
||||
## Service Layer Pattern
|
||||
|
||||
### Structure
|
||||
|
||||
```
|
||||
app/
|
||||
├── main.go
|
||||
├── services/
|
||||
│ ├── user_service.go
|
||||
│ ├── data_service.go
|
||||
│ └── file_service.go
|
||||
└── models/
|
||||
└── user.go
|
||||
```
|
||||
|
||||
### Implementation
|
||||
|
||||
```go
|
||||
// Service interface
|
||||
type UserService interface {
|
||||
Create(email, password string) (*User, error)
|
||||
GetByID(id int) (*User, error)
|
||||
Update(user *User) error
|
||||
Delete(id int) error
|
||||
}
|
||||
|
||||
// Implementation
|
||||
type userService struct {
|
||||
app *application.App
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewUserService(app *application.App, db *sql.DB) UserService {
|
||||
return &userService{app: app, db: db}
|
||||
}
|
||||
```
|
||||
|
||||
## Repository Pattern
|
||||
|
||||
### Structure
|
||||
|
||||
```go
|
||||
// Repository interface
|
||||
type UserRepository interface {
|
||||
Create(user *User) error
|
||||
FindByID(id int) (*User, error)
|
||||
Update(user *User) error
|
||||
Delete(id int) error
|
||||
}
|
||||
|
||||
// Service uses repository
|
||||
type UserService struct {
|
||||
repo UserRepository
|
||||
}
|
||||
|
||||
func (s *UserService) Create(email, password string) (*User, error) {
|
||||
user := &User{Email: email}
|
||||
return user, s.repo.Create(user)
|
||||
}
|
||||
```
|
||||
|
||||
## Event-Driven Architecture
|
||||
|
||||
### Event Bus
|
||||
|
||||
```go
|
||||
type EventBus struct {
|
||||
app *application.Application
|
||||
listeners map[string][]func(interface{})
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (eb *EventBus) Subscribe(event string, handler func(interface{})) {
|
||||
eb.mu.Lock()
|
||||
defer eb.mu.Unlock()
|
||||
eb.listeners[event] = append(eb.listeners[event], handler)
|
||||
}
|
||||
|
||||
func (eb *EventBus) Publish(event string, data interface{}) {
|
||||
eb.mu.RLock()
|
||||
handlers := eb.listeners[event]
|
||||
eb.mu.RUnlock()
|
||||
|
||||
for _, handler := range handlers {
|
||||
go handler(data)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```go
|
||||
// Subscribe
|
||||
eventBus.Subscribe("user.created", func(data interface{}) {
|
||||
user := data.(*User)
|
||||
sendWelcomeEmail(user)
|
||||
})
|
||||
|
||||
// Publish
|
||||
eventBus.Publish("user.created", user)
|
||||
```
|
||||
|
||||
## Dependency Injection
|
||||
|
||||
### Manual DI
|
||||
|
||||
```go
|
||||
type App struct {
|
||||
userService *UserService
|
||||
fileService *FileService
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewApp() *App {
|
||||
db := openDatabase()
|
||||
|
||||
return &App{
|
||||
db: db,
|
||||
userService: NewUserService(db),
|
||||
fileService: NewFileService(db),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using Wire
|
||||
|
||||
```go
|
||||
// wire.go
|
||||
//go:build wireinject
|
||||
|
||||
func InitializeApp() (*App, error) {
|
||||
wire.Build(
|
||||
openDatabase,
|
||||
NewUserService,
|
||||
NewFileService,
|
||||
NewApp,
|
||||
)
|
||||
return nil, nil
|
||||
}
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
### Centralised State
|
||||
|
||||
```go
|
||||
type AppState struct {
|
||||
currentUser *User
|
||||
settings *Settings
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (s *AppState) SetUser(user *User) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.currentUser = user
|
||||
}
|
||||
|
||||
func (s *AppState) GetUser() *User {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.currentUser
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- Separate concerns
|
||||
- Use interfaces
|
||||
- Inject dependencies
|
||||
- Handle errors properly
|
||||
- Keep services focused
|
||||
- Document architecture
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- Don't create god objects
|
||||
- Don't tightly couple components
|
||||
- Don't skip error handling
|
||||
- Don't ignore concurrency
|
||||
- Don't over-engineer
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Security](/guides/security) - Security best practices
|
||||
- [Best Practices](/features/bindings/best-practices) - Bindings best practices
|
||||
171
docs/src/content/docs/guides/auto-updates.mdx
Normal file
171
docs/src/content/docs/guides/auto-updates.mdx
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
---
|
||||
title: Auto-Updates
|
||||
description: Implement automatic application updates
|
||||
sidebar:
|
||||
order: 4
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Keep your application up-to-date with automatic updates.
|
||||
|
||||
## Update Strategies
|
||||
|
||||
### 1. Check on Startup
|
||||
|
||||
```go
|
||||
func (a *App) checkForUpdates() {
|
||||
latest, err := getLatestVersion()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if isNewer(latest, currentVersion) {
|
||||
a.promptUpdate(latest)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Periodic Checks
|
||||
|
||||
```go
|
||||
func (a *App) startUpdateChecker() {
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
a.checkForUpdates()
|
||||
}
|
||||
}()
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Manual Check
|
||||
|
||||
```go
|
||||
func (a *App) CheckForUpdates() {
|
||||
result, _ := a.app.QuestionDialog().
|
||||
SetMessage("Check for updates?").
|
||||
SetButtons("Check", "Cancel").
|
||||
Show()
|
||||
|
||||
if result == "Check" {
|
||||
a.checkForUpdates()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
### Version Checking
|
||||
|
||||
```go
|
||||
type UpdateInfo struct {
|
||||
Version string `json:"version"`
|
||||
DownloadURL string `json:"download_url"`
|
||||
ReleaseNotes string `json:"release_notes"`
|
||||
}
|
||||
|
||||
func getLatestVersion() (*UpdateInfo, error) {
|
||||
resp, err := http.Get("https://api.example.com/latest")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var info UpdateInfo
|
||||
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &info, nil
|
||||
}
|
||||
```
|
||||
|
||||
### Download and Install
|
||||
|
||||
```go
|
||||
func (a *App) downloadUpdate(url string) error {
|
||||
// Download update
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Save to temp file
|
||||
tmpFile, err := os.CreateTemp("", "update-*.exe")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tmpFile.Close()
|
||||
|
||||
// Copy data
|
||||
if _, err := io.Copy(tmpFile, resp.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Launch installer and quit
|
||||
return a.installUpdate(tmpFile.Name())
|
||||
}
|
||||
```
|
||||
|
||||
## Third-Party Solutions
|
||||
|
||||
### Squirrel
|
||||
|
||||
```go
|
||||
import "github.com/Squirrel/go-squirrel"
|
||||
|
||||
func setupUpdater() {
|
||||
updater := squirrel.NewUpdater(squirrel.Options{
|
||||
URL: "https://updates.example.com",
|
||||
})
|
||||
|
||||
updater.CheckForUpdates()
|
||||
}
|
||||
```
|
||||
|
||||
### Self-Hosted
|
||||
|
||||
Host update manifests on your own server:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0.1",
|
||||
"platforms": {
|
||||
"windows": {
|
||||
"url": "https://example.com/myapp-1.0.1-windows.exe",
|
||||
"sha256": "..."
|
||||
},
|
||||
"darwin": {
|
||||
"url": "https://example.com/myapp-1.0.1-macos.dmg",
|
||||
"sha256": "..."
|
||||
}
|
||||
},
|
||||
"release_notes": "Bug fixes and improvements"
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- Verify update signatures
|
||||
- Show release notes
|
||||
- Allow users to skip versions
|
||||
- Test update process thoroughly
|
||||
- Provide rollback mechanism
|
||||
- Use HTTPS for downloads
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- Don't force immediate updates
|
||||
- Don't skip signature verification
|
||||
- Don't interrupt user work
|
||||
- Don't forget error handling
|
||||
- Don't lose user data
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Creating Installers](/guides/installers) - Package your application
|
||||
- [Testing](/guides/testing) - Test your application
|
||||
282
docs/src/content/docs/guides/build/customization.mdx
Normal file
282
docs/src/content/docs/guides/build/customization.mdx
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
---
|
||||
title: Build Customization
|
||||
description: Customize your build process using Task and Taskfile.yml
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { FileTree } from "@astrojs/starlight/components";
|
||||
|
||||
## Overview
|
||||
|
||||
The Wails build system is a flexible and powerful tool designed to streamline
|
||||
the build process for your Wails applications. It leverages
|
||||
[Task](https://taskfile.dev), a task runner that allows you to define and run
|
||||
tasks easily. While the v3 build system is the default, Wails encourages a
|
||||
"bring your own tooling" approach, allowing developers to customize their build
|
||||
process as needed.
|
||||
|
||||
Learn more about how to use Task in the
|
||||
[official documentation](https://taskfile.dev/usage/).
|
||||
|
||||
## Task: The Heart of the Build System
|
||||
|
||||
[Task](https://taskfile.dev) is a modern alternative to Make, written in Go. It
|
||||
uses a YAML file to define tasks and their dependencies. In the Wails build
|
||||
system, [Task](https://taskfile.dev) plays a central role in orchestrating the
|
||||
build process.
|
||||
|
||||
The main `Taskfile.yml` is located in the project root, while platform-specific
|
||||
tasks are defined in `build/<platform>/Taskfile.yml` files. A common `Taskfile.yml`
|
||||
file in the `build` directory contains common tasks that are shared across
|
||||
platforms.
|
||||
|
||||
<FileTree>
|
||||
|
||||
- Project Root
|
||||
- Taskfile.yml
|
||||
- build
|
||||
- windows/Taskfile.yml
|
||||
- darwin/Taskfile.yml
|
||||
- linux/Taskfile.yml
|
||||
- Taskfile.yml
|
||||
|
||||
</FileTree>
|
||||
|
||||
## Taskfile.yml
|
||||
|
||||
The `Taskfile.yml` file in the project root is the main entry point for the build system. It defines
|
||||
the tasks and their dependencies. Here's the default `Taskfile.yml` file:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
common: ./build/Taskfile.yml
|
||||
windows: ./build/windows/Taskfile.yml
|
||||
darwin: ./build/darwin/Taskfile.yml
|
||||
linux: ./build/linux/Taskfile.yml
|
||||
|
||||
vars:
|
||||
APP_NAME: "myproject"
|
||||
BIN_DIR: "bin"
|
||||
VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
summary: Builds the application
|
||||
cmds:
|
||||
- task: "{{OS}}:build"
|
||||
|
||||
package:
|
||||
summary: Packages a production build of the application
|
||||
cmds:
|
||||
- task: "{{OS}}:package"
|
||||
|
||||
run:
|
||||
summary: Runs the application
|
||||
cmds:
|
||||
- task: "{{OS}}:run"
|
||||
|
||||
dev:
|
||||
summary: Runs the application in development mode
|
||||
cmds:
|
||||
- wails3 dev -config ./build/config.yml -port {{.VITE_PORT}}
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Platform-Specific Taskfiles
|
||||
|
||||
Each platform has its own Taskfile, located in the platform directories beneath the `build` directory. These
|
||||
files define the core tasks for that platform. Each taskfile includes common tasks from the `build/Taskfile.yml` file.
|
||||
|
||||
### Windows
|
||||
|
||||
Location: `build/windows/Taskfile.yml`
|
||||
|
||||
The Windows-specific Taskfile includes tasks for building, packaging, and
|
||||
running the application on Windows. Key features include:
|
||||
|
||||
- Building with optional production flags
|
||||
- Generating `.ico` icon file
|
||||
- Generating Windows `.syso` file
|
||||
- Creating an NSIS installer for packaging
|
||||
|
||||
### Linux
|
||||
|
||||
Location: `build/linux/Taskfile.yml`
|
||||
|
||||
The Linux-specific Taskfile includes tasks for building, packaging, and running
|
||||
the application on Linux. Key features include:
|
||||
|
||||
- Building with optional production flags
|
||||
- Creating an AppImage, deb, rpm, and Arch Linux packages
|
||||
- Generating `.desktop` file for Linux applications
|
||||
|
||||
### macOS
|
||||
|
||||
Location: `build/darwin/Taskfile.yml`
|
||||
|
||||
The macOS-specific Taskfile includes tasks for building, packaging, and running
|
||||
the application on macOS. Key features include:
|
||||
|
||||
- Building binaries for amd64, arm64 and universal (both) architectures
|
||||
- Generating `.icns` icon file
|
||||
- Creating an `.app` bundle for distributing
|
||||
- Ad-hoc signing `.app` bundles
|
||||
- Setting macOS-specific build flags and environment variables
|
||||
|
||||
## Task Execution and Command Aliases
|
||||
|
||||
The `wails3 task` command is an embedded version of [Taskfile](https://taskfile.dev), which executes
|
||||
the tasks defined in your `Taskfile.yml`.
|
||||
|
||||
The `wails3 build` and `wails3 package` commands are aliases for
|
||||
`wails3 task build` and `wails3 task package` respectively. When you run these
|
||||
commands, Wails internally translates them to the appropriate task execution:
|
||||
|
||||
- `wails3 build` → `wails3 task build`
|
||||
- `wails3 package` → `wails3 task package`
|
||||
|
||||
### Passing Parameters to Tasks
|
||||
|
||||
You can pass CLI variables to tasks using the `KEY=VALUE` format. These variables are forwarded through the alias commands:
|
||||
|
||||
```bash
|
||||
# These are equivalent:
|
||||
wails3 build PLATFORM=linux CONFIG=production
|
||||
wails3 task build PLATFORM=linux CONFIG=production
|
||||
|
||||
# Package with custom version:
|
||||
wails3 package VERSION=2.0.0 OUTPUT=myapp.pkg
|
||||
```
|
||||
|
||||
In your `Taskfile.yml`, you can access these variables using Go template syntax:
|
||||
|
||||
```yaml
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- echo "Building for {{.PLATFORM | default "darwin"}}"
|
||||
- go build -tags {{.CONFIG | default "debug"}} -o myapp
|
||||
```
|
||||
|
||||
## Common Build Process
|
||||
|
||||
Across all platforms, the build process typically includes the following steps:
|
||||
|
||||
1. Tidying Go modules
|
||||
2. Building the frontend
|
||||
3. Generating icons
|
||||
4. Compiling the Go code with platform-specific flags
|
||||
5. Packaging the application (platform-specific)
|
||||
|
||||
## Customising the Build Process
|
||||
|
||||
While the v3 build system provides a solid default configuration, you can easily
|
||||
customise it to fit your project's needs. By modifying the `Taskfile.yml` and
|
||||
platform-specific Taskfiles, you can:
|
||||
|
||||
- Add new tasks
|
||||
- Modify existing tasks
|
||||
- Change the order of task execution
|
||||
- Integrate with other tools and scripts
|
||||
|
||||
This flexibility allows you to tailor the build process to your specific
|
||||
requirements while still benefiting from the structure provided by the Wails
|
||||
build system.
|
||||
|
||||
:::tip[Learning Taskfile]
|
||||
We highly recommend reading the [Taskfile](https://taskfile.dev) documentation to
|
||||
understand how to use Taskfile effectively.
|
||||
You can find out which version of Taskfile is embedded in the Wails CLI by running `wails3 task --version`.
|
||||
:::
|
||||
|
||||
## Development Mode
|
||||
|
||||
The Wails build system includes a powerful development mode that enhances the
|
||||
developer experience by providing live reloading and hot module replacement.
|
||||
This mode is activated using the `wails3 dev` command.
|
||||
|
||||
### How It Works
|
||||
|
||||
When you run `wails3 dev`, the following process occurs:
|
||||
|
||||
1. The command checks for an available port, defaulting to 9245 if not
|
||||
specified.
|
||||
2. It sets up the environment variables for the frontend dev server (Vite).
|
||||
3. It starts the file watcher using the [refresh](https://github.com/atterpac/refresh) library.
|
||||
|
||||
The [refresh](https://github.com/atterpac/refresh) library is responsible for
|
||||
monitoring file changes and triggering rebuilds. It uses the configuration defined under the `dev_mode` key in the `./build/config.yaml` file.
|
||||
It may be configured to ignore certain directories and files, to determine which files to watch and what actions to take when changes are detected.
|
||||
The default configuration works pretty well, but feel free to customise it to your needs.
|
||||
|
||||
### Configuration
|
||||
|
||||
Here's an example of its structure:
|
||||
|
||||
```yaml
|
||||
dev_mode:
|
||||
root_path: .
|
||||
log_level: warn
|
||||
debounce: 1000
|
||||
ignore:
|
||||
dir:
|
||||
- .git
|
||||
- node_modules
|
||||
- frontend
|
||||
- bin
|
||||
file:
|
||||
- .DS_Store
|
||||
- .gitignore
|
||||
- .gitkeep
|
||||
watched_extension:
|
||||
- "*.go"
|
||||
git_ignore: true
|
||||
executes:
|
||||
- cmd: wails3 task common:install:frontend:deps
|
||||
type: once
|
||||
- cmd: wails3 task common:dev:frontend
|
||||
type: background
|
||||
- cmd: go mod tidy
|
||||
type: blocking
|
||||
- cmd: wails3 task build
|
||||
type: blocking
|
||||
- cmd: wails3 task run
|
||||
type: primary
|
||||
```
|
||||
|
||||
This configuration file allows you to:
|
||||
|
||||
- Set the root path for file watching
|
||||
- Configure logging level
|
||||
- Set a debounce time for file change events
|
||||
- Ignore specific directories, files, or file extensions
|
||||
- Define commands to execute on file changes
|
||||
|
||||
### Customising Development Mode
|
||||
|
||||
You can customise the development mode experience by modifying these values in the `config.yaml` file.
|
||||
|
||||
Some ways to customise include:
|
||||
|
||||
1. Changing the watched directories or files
|
||||
2. Adjusting the debounce time to control how quickly the system responds to
|
||||
changes
|
||||
3. Adding or modifying the execute commands to fit your project's needs
|
||||
|
||||
### Using a browser for development
|
||||
|
||||
Whilst Wails v2 fully supported the use of a browser for development, it caused a lot
|
||||
of confusion. Applications that would work in the browser would not necessarily
|
||||
work in the desktop application, as not all browser APIs are available in webviews.
|
||||
|
||||
For UI-focused development work, you still have the flexibility to use a browser
|
||||
in v3, by accessing the Vite URL at `http://localhost:9245` in dev mode. This
|
||||
gives you access to powerful browser dev tools while working on styling and
|
||||
layout. Be aware that Go bindings *will not work* in this mode.
|
||||
When you're ready to test functionality like bindings and events, simply
|
||||
switch to the desktop view to ensure everything works perfectly in the
|
||||
production environment.
|
||||
415
docs/src/content/docs/guides/build/windows.mdx
Normal file
415
docs/src/content/docs/guides/build/windows.mdx
Normal file
|
|
@ -0,0 +1,415 @@
|
|||
---
|
||||
title: Windows Packaging
|
||||
description: Package your Wails application for Windows distribution
|
||||
sidebar:
|
||||
order: 4
|
||||
---
|
||||
|
||||
import { Tabs, TabItem } from '@astrojs/starlight/components';
|
||||
|
||||
Learn how to package your Wails application for Windows distribution using various installer formats.
|
||||
|
||||
## Overview
|
||||
|
||||
Wails provides several packaging options for Windows:
|
||||
- **NSIS** - Nullsoft Scriptable Install System (recommended)
|
||||
- **MSI** - Windows Installer Package
|
||||
- **Portable** - Standalone executable (no installation)
|
||||
- **MSIX** - Modern Windows app package
|
||||
|
||||
## NSIS Installer
|
||||
|
||||
NSIS creates professional installers with customization options and automatic system integration.
|
||||
|
||||
### Quick Start
|
||||
|
||||
Build an NSIS installer:
|
||||
|
||||
```bash
|
||||
wails3 build
|
||||
wails3 task nsis
|
||||
```
|
||||
|
||||
This generates `build/bin/<AppName>-<version>-<arch>-installer.exe`.
|
||||
|
||||
### Features
|
||||
|
||||
**Automatic Integration:**
|
||||
- ✅ Start Menu shortcuts
|
||||
- ✅ Desktop shortcuts (optional)
|
||||
- ✅ File associations
|
||||
- ✅ **Custom URL protocol registration** (NEW)
|
||||
- ✅ Uninstaller creation
|
||||
- ✅ Registry entries
|
||||
|
||||
**Custom Protocols:**
|
||||
|
||||
Wails automatically registers custom URL protocols defined in your application:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Name: "My Application",
|
||||
Protocols: []application.Protocol{
|
||||
{
|
||||
Scheme: "myapp",
|
||||
Description: "My Application Protocol",
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
The NSIS installer:
|
||||
1. Registers all protocols during installation
|
||||
2. Associates them with your application executable
|
||||
3. Removes them during uninstallation
|
||||
|
||||
**No additional configuration needed!**
|
||||
|
||||
### Customization
|
||||
|
||||
Customize the installer via `wails.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "MyApp",
|
||||
"outputfilename": "myapp.exe",
|
||||
"nsis": {
|
||||
"companyName": "My Company",
|
||||
"productName": "My Application",
|
||||
"productDescription": "An amazing desktop application",
|
||||
"productVersion": "1.0.0",
|
||||
"installerIcon": "build/appicon.ico",
|
||||
"license": "LICENSE.txt",
|
||||
"allowInstallDirCustomization": true,
|
||||
"installDirectory": "$PROGRAMFILES64\\MyApp",
|
||||
"createDesktopShortcut": true,
|
||||
"runAfterInstall": true,
|
||||
"adminPrivileges": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### NSIS Options
|
||||
|
||||
| Option | Type | Description | Default |
|
||||
|--------|------|-------------|---------|
|
||||
| `companyName` | string | Company name shown in installer | Project name |
|
||||
| `productName` | string | Product name | App name |
|
||||
| `productDescription` | string | Description shown in installer | App description |
|
||||
| `productVersion` | string | Version number (e.g., "1.0.0") | "1.0.0" |
|
||||
| `installerIcon` | string | Path to .ico file for installer | App icon |
|
||||
| `license` | string | Path to license file (txt) | None |
|
||||
| `allowInstallDirCustomization` | boolean | Let users choose install location | true |
|
||||
| `installDirectory` | string | Default installation directory | `$PROGRAMFILES64\<ProductName>` |
|
||||
| `createDesktopShortcut` | boolean | Create desktop shortcut | true |
|
||||
| `runAfterInstall` | boolean | Run app after installation | false |
|
||||
| `adminPrivileges` | boolean | Require admin rights to install | false |
|
||||
|
||||
### Advanced NSIS
|
||||
|
||||
#### Custom NSIS Script
|
||||
|
||||
For advanced customization, provide your own NSIS script:
|
||||
|
||||
```bash
|
||||
# Create custom template
|
||||
cp build/nsis/installer.nsi build/nsis/installer.custom.nsi
|
||||
|
||||
# Edit build/nsis/installer.custom.nsi
|
||||
# Add custom sections, pages, or logic
|
||||
|
||||
# Build with custom script
|
||||
wails3 task nsis --script build/nsis/installer.custom.nsi
|
||||
```
|
||||
|
||||
#### Available Macros
|
||||
|
||||
Wails provides NSIS macros for common tasks:
|
||||
|
||||
**wails.associateCustomProtocols**
|
||||
- Registers all custom URL protocols defined in `application.Options.Protocols`
|
||||
- Called automatically during installation
|
||||
- Creates registry entries under `HKEY_CURRENT_USER\SOFTWARE\Classes\`
|
||||
|
||||
**wails.unassociateCustomProtocols**
|
||||
- Removes custom URL protocol registrations
|
||||
- Called automatically during uninstallation
|
||||
- Cleans up all protocol-related registry entries
|
||||
|
||||
**Example usage in custom NSIS script:**
|
||||
|
||||
```nsis
|
||||
Section "Install"
|
||||
# ... your installation code ...
|
||||
|
||||
# Register custom protocols
|
||||
!insertmacro wails.associateCustomProtocols
|
||||
|
||||
# ... more installation code ...
|
||||
SectionEnd
|
||||
|
||||
Section "Uninstall"
|
||||
# Remove custom protocols
|
||||
!insertmacro wails.unassociateCustomProtocols
|
||||
|
||||
# ... your uninstallation code ...
|
||||
SectionEnd
|
||||
```
|
||||
|
||||
## MSI Installer
|
||||
|
||||
Windows Installer Package format with Windows logo certification support.
|
||||
|
||||
### Build MSI
|
||||
|
||||
```bash
|
||||
wails3 build
|
||||
wails3 task msi
|
||||
```
|
||||
|
||||
Generates `build/bin/<AppName>-<version>-<arch>.msi`.
|
||||
|
||||
### Customization
|
||||
|
||||
Configure via `wails.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"msi": {
|
||||
"productCode": "{GUID}",
|
||||
"upgradeCode": "{GUID}",
|
||||
"manufacturer": "My Company",
|
||||
"installScope": "perMachine",
|
||||
"shortcuts": {
|
||||
"desktop": true,
|
||||
"startMenu": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### MSI vs NSIS
|
||||
|
||||
| Feature | NSIS | MSI |
|
||||
|---------|------|-----|
|
||||
| Customization | ✅ High | ⚠️ Limited |
|
||||
| File Size | ✅ Smaller | ⚠️ Larger |
|
||||
| Corporate Deployment | ⚠️ Less common | ✅ Preferred |
|
||||
| Custom UI | ✅ Full control | ⚠️ Restricted |
|
||||
| Windows Logo | ❌ No | ✅ Yes |
|
||||
| Protocol Registration | ✅ Automatic | ⚠️ Manual |
|
||||
|
||||
**Use NSIS when:**
|
||||
- You want maximum customization
|
||||
- You need custom branding and UI
|
||||
- You want automatic protocol registration
|
||||
- File size matters
|
||||
|
||||
**Use MSI when:**
|
||||
- You need Windows logo certification
|
||||
- You're deploying in enterprise environments
|
||||
- You need Group Policy support
|
||||
- You want Windows Update integration
|
||||
|
||||
## Portable Executable
|
||||
|
||||
Single executable with no installation required.
|
||||
|
||||
### Build Portable
|
||||
|
||||
```bash
|
||||
wails3 build
|
||||
```
|
||||
|
||||
Output: `build/bin/<appname>.exe`
|
||||
|
||||
### Characteristics
|
||||
|
||||
- No installation required
|
||||
- No registry changes
|
||||
- No administrator privileges needed
|
||||
- Can run from USB drives
|
||||
- No automatic updates
|
||||
- No Start Menu integration
|
||||
|
||||
### Use Cases
|
||||
|
||||
- Trial versions
|
||||
- USB stick applications
|
||||
- Corporate environments with restricted installations
|
||||
- Quick testing and demos
|
||||
|
||||
## MSIX Packages
|
||||
|
||||
Modern Windows app package format for Microsoft Store and sideloading.
|
||||
|
||||
See [MSIX Packaging Guide](/guides/build/msix) for detailed information.
|
||||
|
||||
## Code Signing
|
||||
|
||||
Sign your executables and installers to:
|
||||
- Avoid Windows SmartScreen warnings
|
||||
- Establish publisher identity
|
||||
- Enable automatic updates
|
||||
- Meet corporate security requirements
|
||||
|
||||
See [Code Signing Guide](/guides/build/signing) for details.
|
||||
|
||||
## Icon Requirements
|
||||
|
||||
### Application Icon
|
||||
|
||||
**Format:** `.ico` file with multiple resolutions
|
||||
|
||||
**Recommended sizes:**
|
||||
- 16x16, 32x32, 48x48, 64x64, 128x128, 256x256
|
||||
|
||||
**Create from PNG:**
|
||||
|
||||
```bash
|
||||
# Using ImageMagick
|
||||
magick convert icon.png -define icon:auto-resize=256,128,64,48,32,16 appicon.ico
|
||||
|
||||
# Using online tools
|
||||
# https://icoconvert.com/
|
||||
```
|
||||
|
||||
### Installer Icon
|
||||
|
||||
Same `.ico` file can be used for both application and installer.
|
||||
|
||||
Configure in `wails.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"nsis": {
|
||||
"installerIcon": "build/appicon.ico"
|
||||
},
|
||||
"windows": {
|
||||
"applicationIcon": "build/appicon.ico"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Building for Different Architectures
|
||||
|
||||
### AMD64 (64-bit)
|
||||
|
||||
```bash
|
||||
wails3 build -platform windows/amd64
|
||||
```
|
||||
|
||||
### ARM64
|
||||
|
||||
```bash
|
||||
wails3 build -platform windows/arm64
|
||||
```
|
||||
|
||||
### Universal Build
|
||||
|
||||
Build for all architectures:
|
||||
|
||||
```bash
|
||||
wails3 build -platform windows/amd64,windows/arm64
|
||||
```
|
||||
|
||||
## Distribution Checklist
|
||||
|
||||
Before distributing your Windows application:
|
||||
|
||||
- [ ] Test installer on clean Windows installation
|
||||
- [ ] Verify application icon displays correctly
|
||||
- [ ] Test uninstaller completely removes application
|
||||
- [ ] Verify Start Menu shortcuts work
|
||||
- [ ] Test custom protocol handlers (if used)
|
||||
- [ ] Check SmartScreen behavior (sign your app if possible)
|
||||
- [ ] Test on Windows 10 and Windows 11
|
||||
- [ ] Verify file associations work (if used)
|
||||
- [ ] Test with antivirus software
|
||||
- [ ] Include license and documentation
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### NSIS Build Fails
|
||||
|
||||
**Error:** `makensis: command not found`
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Install NSIS
|
||||
winget install NSIS.NSIS
|
||||
|
||||
# Or download from https://nsis.sourceforge.io/
|
||||
```
|
||||
|
||||
### Custom Protocols Not Working
|
||||
|
||||
**Check registration:**
|
||||
```powershell
|
||||
# Check registry
|
||||
Get-ItemProperty -Path "HKCU:\SOFTWARE\Classes\myapp"
|
||||
|
||||
# Test protocol
|
||||
Start-Process "myapp://test"
|
||||
```
|
||||
|
||||
**Fix:**
|
||||
1. Reinstall with NSIS installer
|
||||
2. Run installer as administrator if needed
|
||||
3. Verify `Protocols` configuration in Go code
|
||||
|
||||
### SmartScreen Warning
|
||||
|
||||
**Cause:** Unsigned executable
|
||||
|
||||
**Solutions:**
|
||||
1. **Code sign your application** (recommended)
|
||||
2. Build reputation (downloads over time)
|
||||
3. Submit to Microsoft for analysis
|
||||
4. Use Extended Validation (EV) certificate for immediate trust
|
||||
|
||||
### Installer Won't Run
|
||||
|
||||
**Possible causes:**
|
||||
- Antivirus blocking
|
||||
- Missing dependencies
|
||||
- Corrupted download
|
||||
- User permissions
|
||||
|
||||
**Solutions:**
|
||||
1. Temporarily disable antivirus for testing
|
||||
2. Run as administrator
|
||||
3. Re-download installer
|
||||
4. Check Windows Event Viewer for errors
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- **Code sign your releases** - Avoids SmartScreen warnings
|
||||
- **Test on clean Windows installations** - Don't rely on dev environment
|
||||
- **Provide both installer and portable versions** - Give users choice
|
||||
- **Include comprehensive uninstaller** - Remove all traces
|
||||
- **Use semantic versioning** - Clear version numbering
|
||||
- **Test protocol handlers thoroughly** - Validate all URL inputs
|
||||
- **Provide clear installation instructions** - Help users succeed
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- **Don't skip code signing** - Users will see scary warnings
|
||||
- **Don't require admin for normal apps** - Only if truly necessary
|
||||
- **Don't install to non-standard locations** - Use `$PROGRAMFILES64`
|
||||
- **Don't leave orphaned registry entries** - Clean up properly
|
||||
- **Don't forget to test uninstaller** - Broken uninstallers frustrate users
|
||||
- **Don't hardcode paths** - Use Windows environment variables
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Code Signing](/guides/build/signing) - Sign your executables and installers
|
||||
- [MSIX Packaging](/guides/build/msix) - Modern Windows app packages
|
||||
- [Custom Protocols](/guides/distribution/custom-protocols) - Deep linking and URL schemes
|
||||
- [Auto-Updates](/guides/distribution/auto-updates) - Keep your app current
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [Windows packaging examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples).
|
||||
176
docs/src/content/docs/guides/building.mdx
Normal file
176
docs/src/content/docs/guides/building.mdx
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
---
|
||||
title: Building Applications
|
||||
description: Build and package your Wails application
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
import { Card, CardGrid, Tabs, TabItem } from "@astrojs/starlight/components";
|
||||
|
||||
## Overview
|
||||
|
||||
Wails provides simple commands to build your application for development and production.
|
||||
|
||||
## Development Build
|
||||
|
||||
### Quick Start
|
||||
|
||||
```bash
|
||||
wails3 dev
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Hot reload for frontend changes
|
||||
- Automatic Go rebuild on changes
|
||||
- Debug mode enabled
|
||||
- Fast iteration
|
||||
|
||||
### Dev Options
|
||||
|
||||
```bash
|
||||
# Specify frontend dev server
|
||||
wails3 dev -devserver http://localhost:5173
|
||||
|
||||
# Skip frontend dev server
|
||||
wails3 dev -nofrontend
|
||||
|
||||
# Custom build flags
|
||||
wails3 dev -tags dev
|
||||
```
|
||||
|
||||
## Production Build
|
||||
|
||||
### Basic Build
|
||||
|
||||
```bash
|
||||
wails3 build
|
||||
```
|
||||
|
||||
**Output:** Optimized binary in `build/bin/`
|
||||
|
||||
### Build Options
|
||||
|
||||
```bash
|
||||
# Build for specific platform
|
||||
wails3 build -platform windows/amd64
|
||||
|
||||
# Custom output directory
|
||||
wails3 build -o ./dist/myapp
|
||||
|
||||
# Skip frontend build
|
||||
wails3 build -nofrontend
|
||||
|
||||
# Production optimizations
|
||||
wails3 build -ldflags "-s -w"
|
||||
```
|
||||
|
||||
## Build Configuration
|
||||
|
||||
### wails.json
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "myapp",
|
||||
"frontend": {
|
||||
"dir": "./frontend",
|
||||
"install": "npm install",
|
||||
"build": "npm run build",
|
||||
"dev": "npm run dev",
|
||||
"devServerUrl": "http://localhost:5173"
|
||||
},
|
||||
"build": {
|
||||
"output": "myapp",
|
||||
"ldflags": "-s -w"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Platform-Specific Builds
|
||||
|
||||
<Tabs syncKey="platform">
|
||||
<TabItem label="Windows" icon="seti:windows">
|
||||
```bash
|
||||
# Windows executable
|
||||
wails3 build -platform windows/amd64
|
||||
|
||||
# With icon
|
||||
wails3 build -platform windows/amd64 -icon icon.ico
|
||||
|
||||
# Console app (shows terminal)
|
||||
wails3 build -platform windows/amd64 -windowsconsole
|
||||
```
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="macOS" icon="apple">
|
||||
```bash
|
||||
# macOS app bundle
|
||||
wails3 build -platform darwin/amd64
|
||||
|
||||
# Universal binary (Intel + Apple Silicon)
|
||||
wails3 build -platform darwin/universal
|
||||
|
||||
# With icon
|
||||
wails3 build -platform darwin/amd64 -icon icon.icns
|
||||
```
|
||||
</TabItem>
|
||||
|
||||
<TabItem label="Linux" icon="linux">
|
||||
```bash
|
||||
# Linux executable
|
||||
wails3 build -platform linux/amd64
|
||||
|
||||
# With icon
|
||||
wails3 build -platform linux/amd64 -icon icon.png
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Optimization
|
||||
|
||||
### Binary Size
|
||||
|
||||
```bash
|
||||
# Strip debug symbols
|
||||
wails3 build -ldflags "-s -w"
|
||||
|
||||
# UPX compression (external tool)
|
||||
upx --best --lzma build/bin/myapp
|
||||
```
|
||||
|
||||
### Performance
|
||||
|
||||
```bash
|
||||
# Enable optimizations
|
||||
wails3 build -tags production
|
||||
|
||||
# Disable debug features
|
||||
wails3 build -ldflags "-X main.debug=false"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build Fails
|
||||
|
||||
**Problem:** Build errors
|
||||
|
||||
**Solutions:**
|
||||
- Check `go.mod` is up to date
|
||||
- Run `go mod tidy`
|
||||
- Verify frontend builds: `cd frontend && npm run build`
|
||||
- Check wails.json configuration
|
||||
|
||||
### Large Binary Size
|
||||
|
||||
**Problem:** Binary is too large
|
||||
|
||||
**Solutions:**
|
||||
- Use `-ldflags "-s -w"` to strip symbols
|
||||
- Remove unused dependencies
|
||||
- Use UPX compression
|
||||
- Check embedded assets size
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Cross-Platform Building](/guides/cross-platform) - Build for multiple platforms
|
||||
- [Distribution](/guides/installers) - Create installers and packages
|
||||
- [Build System](/concepts/build-system) - Understand the build system
|
||||
539
docs/src/content/docs/guides/cli.mdx
Normal file
539
docs/src/content/docs/guides/cli.mdx
Normal file
|
|
@ -0,0 +1,539 @@
|
|||
---
|
||||
title: CLI Reference
|
||||
description: Complete reference for the Wails CLI commands
|
||||
sidebar:
|
||||
order: 1
|
||||
---
|
||||
|
||||
The Wails CLI provides a comprehensive set of commands to help you develop, build, and maintain your Wails applications.
|
||||
|
||||
## Core Commands
|
||||
|
||||
Core commands are the primary commands used for project creation, development, and building.
|
||||
|
||||
All CLI commands are of the following format: `wails3 <command>`.
|
||||
|
||||
### `init`
|
||||
Initializes a new Wails project. During this initialization, the `go mod tidy` command is run to bring the project packages up to date. This can be bypassed by using the `-skipgomodtidy` flag with the `init` command.
|
||||
|
||||
```bash
|
||||
wails3 init [flags]
|
||||
```
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|-----------------------|----------------------|--------------------------|
|
||||
| `-p` | Package name | `main` |
|
||||
| `-t` | Template name or URL | `vanilla` |
|
||||
| `-n` | Project name | |
|
||||
| `-d` | Project directory | `.` |
|
||||
| `-q` | Suppress output | `false` |
|
||||
| `-l` | List templates | `false` |
|
||||
| `-git` | Git repository URL | |
|
||||
| `-productname` | Product name | `My Product` |
|
||||
| `-productdescription` | Product description | `My Product Description` |
|
||||
| `-productversion` | Product version | `0.1.0` |
|
||||
| `-productcompany` | Company name | `My Company` |
|
||||
| `-productcopyright` | Copyright notice | ` now, My Company` |
|
||||
| `-productcomments` | File comments | `This is a comment` |
|
||||
| `-productidentifier` | Product identifier | |
|
||||
| `-skipgomodtidy` | Skip go mod tidy | `false` |
|
||||
|
||||
The `-git` flag accepts various Git URL formats:
|
||||
- HTTPS: `https://github.com/username/project`
|
||||
- SSH: `git@github.com:username/project` or `ssh://git@github.com/username/project`
|
||||
- Git protocol: `git://github.com/username/project`
|
||||
- Filesystem: `file:///path/to/project.git`
|
||||
|
||||
When provided, this flag will:
|
||||
1. Initialize a git repository in the project directory
|
||||
2. Set the specified URL as the remote origin
|
||||
3. Update the module name in `go.mod` to match the repository URL
|
||||
4. Add all files
|
||||
|
||||
### `dev`
|
||||
Runs the application in development mode. This will give you a live view of your frontend code, and you can make changes and see them reflected
|
||||
in the running application without having to rebuild the entire application. Changes to your Go code will also be detected and the application
|
||||
will automatically rebuild and relaunch.
|
||||
|
||||
```bash
|
||||
wails3 dev [flags]
|
||||
```
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|-----------|----------------------|----------------------|
|
||||
| `-config` | Config file path | `./build/config.yml` |
|
||||
| `-port` | Vite dev server port | `9245` |
|
||||
| `-s` | Enable HTTPS | `false` |
|
||||
|
||||
|
||||
:::note
|
||||
This is equivalent to running `wails3 task dev` and runs the `dev` task in the project's main Taskfile. You can customise this by editing the `Taskfile.yml` file.
|
||||
:::
|
||||
|
||||
|
||||
### `build`
|
||||
Builds a debug version of your application. It defaults to building for the current platform and architecture.
|
||||
|
||||
```bash
|
||||
wails3 build [CLI variables...]
|
||||
```
|
||||
|
||||
You can pass CLI variables to customize the build:
|
||||
```bash
|
||||
wails3 build PLATFORM=linux CONFIG=production
|
||||
```
|
||||
|
||||
:::note
|
||||
This is equivalent to running `wails3 task build` which runs the `build` task in the project's main Taskfile. Any CLI variables passed to `build` are forwarded to the underlying task. You can customise the build process by editing the `Taskfile.yml` file.
|
||||
:::
|
||||
|
||||
### `package`
|
||||
|
||||
Creates platform-specific packages for distribution.
|
||||
|
||||
```bash
|
||||
wails3 package [CLI variables...]
|
||||
```
|
||||
|
||||
You can pass CLI variables to customize the packaging:
|
||||
```bash
|
||||
wails3 package VERSION=2.0.0 OUTPUT=myapp.pkg
|
||||
```
|
||||
|
||||
#### Package Types
|
||||
|
||||
The following package types are available for each platform:
|
||||
|
||||
| Platform | Package Type |
|
||||
|----------|-------------------------------------------|
|
||||
| Windows | `.exe` |
|
||||
| macOS | `.app`, |
|
||||
| Linux | `.AppImage`, `.deb`, `.rpm`, `.archlinux` |
|
||||
|
||||
:::note
|
||||
This is equivalent to `wails3 task package` which runs the `package` task in the project's main Taskfile. Any CLI variables passed to `package` are forwarded to the underlying task. You can customise the packaging process by editing the `Taskfile.yml` file.
|
||||
:::
|
||||
|
||||
|
||||
### `task`
|
||||
Runs tasks defined in your project's Taskfile.yml. This is an embedded version of [Taskfile](https://taskfile.dev) that allows you to define and run custom build, test, and deployment tasks.
|
||||
|
||||
```bash
|
||||
wails3 task [taskname] [CLI variables...] [flags]
|
||||
```
|
||||
|
||||
#### CLI Variables
|
||||
You can pass variables to tasks in the format `KEY=VALUE`:
|
||||
```bash
|
||||
wails3 task build PLATFORM=linux CONFIG=production
|
||||
wails3 task deploy ENV=staging VERSION=1.2.3
|
||||
```
|
||||
|
||||
These variables can be accessed in your Taskfile.yml using Go template syntax:
|
||||
```yaml
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- echo "Building for {{.PLATFORM | default "darwin"}}"
|
||||
- echo "Config: {{.CONFIG | default "debug"}}"
|
||||
```
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|--------------|-------------------------------------------|----------|
|
||||
| `-h` | Shows Task usage | `false` |
|
||||
| `-i` | Creates a new Taskfile.yml | `false` |
|
||||
| `-list` | Lists tasks with descriptions | `false` |
|
||||
| `-list-all` | Lists all tasks (with or without descriptions) | `false` |
|
||||
| `-json` | Formats task list as JSON | `false` |
|
||||
| `-status` | Exits with non-zero if task is not up-to-date | `false` |
|
||||
| `-f` | Forces execution even when task is up-to-date | `false` |
|
||||
| `-w` | Enables watch mode for the given task | `false` |
|
||||
| `-v` | Enables verbose mode | `false` |
|
||||
| `-version` | Prints Task version | `false` |
|
||||
| `-s` | Disables echoing | `false` |
|
||||
| `-p` | Executes tasks in parallel | `false` |
|
||||
| `-dry` | Compiles and prints tasks without executing | `false` |
|
||||
| `-summary` | Shows summary about a task | `false` |
|
||||
| `-x` | Pass-through the exit code of the task | `false` |
|
||||
| `-dir` | Sets directory of execution | |
|
||||
| `-taskfile` | Choose which Taskfile to run | |
|
||||
| `-output` | Sets output style: [interleaved|group|prefixed] | |
|
||||
| `-c` | Colored output (enabled by default) | `true` |
|
||||
| `-C` | Limit number of tasks to run concurrently | |
|
||||
| `-interval` | Interval to watch for changes (in seconds) | |
|
||||
|
||||
#### Examples
|
||||
```bash
|
||||
# Run the default task
|
||||
wails3 task
|
||||
|
||||
# Run a specific task
|
||||
wails3 task test
|
||||
|
||||
# Run a task with variables
|
||||
wails3 task build PLATFORM=windows ARCH=amd64
|
||||
|
||||
# List all available tasks
|
||||
wails3 task --list
|
||||
|
||||
# Run multiple tasks in parallel
|
||||
wails3 task -p task1 task2 task3
|
||||
|
||||
# Watch for changes and re-run task
|
||||
wails3 task -w dev
|
||||
```
|
||||
|
||||
### `doctor`
|
||||
Performs a system check and displays a status report.
|
||||
|
||||
```bash
|
||||
wails3 doctor
|
||||
```
|
||||
|
||||
## Generate Commands
|
||||
|
||||
Generate commands help create various project assets like bindings, icons, and build files. All generate commands use the base command: `wails3 generate <command>`.
|
||||
|
||||
### `generate bindings`
|
||||
Generates bindings and models for your Go code.
|
||||
|
||||
```bash
|
||||
wails3 generate bindings [flags] [patterns...]
|
||||
```
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|-------------|---------------------------|---------------------|
|
||||
| `-f` | Additional Go build flags | |
|
||||
| `-d` | Output directory | `frontend/bindings` |
|
||||
| `-models` | Models filename | `models` |
|
||||
| `-index` | Index filename | `index` |
|
||||
| `-ts` | Generate TypeScript | `false` |
|
||||
| `-i` | Use TS interfaces | `false` |
|
||||
| `-b` | Use bundled runtime | `false` |
|
||||
| `-names` | Use names instead of IDs | `false` |
|
||||
| `-noindex` | Skip index files | `false` |
|
||||
| `-dry` | Dry run | `false` |
|
||||
| `-silent` | Silent mode | `false` |
|
||||
| `-v` | Debug output | `false` |
|
||||
| `-clean` | Clean output directory | `false` |
|
||||
|
||||
### `generate build-assets`
|
||||
Generates build assets for your application.
|
||||
|
||||
```bash
|
||||
wails3 generate build-assets [flags]
|
||||
```
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|----------------|---------------------|--------------------|
|
||||
| `-name` | Project name | |
|
||||
| `-dir` | Output directory | `build` |
|
||||
| `-silent` | Suppress output | `false` |
|
||||
| `-company` | Company name | |
|
||||
| `-productname` | Product name | |
|
||||
| `-description` | Product description | |
|
||||
| `-version` | Product version | |
|
||||
| `-identifier` | Product identifier | `com.wails.[name]` |
|
||||
| `-copyright` | Copyright notice | |
|
||||
| `-comments` | File comments | |
|
||||
|
||||
### `generate icons`
|
||||
Generates application icons.
|
||||
|
||||
```bash
|
||||
wails3 generate icons [flags]
|
||||
```
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|--------------------|------------------------------|-----------------------|
|
||||
| `-input` | Input PNG file | Required |
|
||||
| `-windowsfilename` | Windows output filename | |
|
||||
| `-macfilename` | macOS output filename | |
|
||||
| `-sizes` | Icon sizes (comma-separated) | `256,128,64,48,32,16` |
|
||||
| `-example` | Generate example icon | `false` |
|
||||
|
||||
### `generate syso`
|
||||
Generates Windows .syso file.
|
||||
|
||||
```bash
|
||||
wails3 generate syso [flags]
|
||||
```
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|-------------|---------------------------|----------------------------|
|
||||
| `-manifest` | Path to manifest file | Required |
|
||||
| `-icon` | Path to icon file | Required |
|
||||
| `-info` | Path to version info file | |
|
||||
| `-arch` | Target architecture | Current GOARCH |
|
||||
| `-out` | Output filename | `rsrc_windows_[arch].syso` |
|
||||
|
||||
### `generate .desktop`
|
||||
Generates a Linux .desktop file.
|
||||
|
||||
```bash
|
||||
wails3 generate .desktop [flags]
|
||||
```
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|------------------|---------------------------|------------------|
|
||||
| `-name` | Application name | Required |
|
||||
| `-exec` | Executable path | Required |
|
||||
| `-icon` | Icon path | |
|
||||
| `-categories` | Application categories | `Utility` |
|
||||
| `-comment` | Application comment | |
|
||||
| `-terminal` | Run in terminal | `false` |
|
||||
| `-keywords` | Search keywords | |
|
||||
| `-version` | Application version | |
|
||||
| `-genericname` | Generic name | |
|
||||
| `-startupnotify` | Show startup notification | `false` |
|
||||
| `-mimetype` | Supported MIME types | |
|
||||
| `-output` | Output filename | `[name].desktop` |
|
||||
|
||||
### `generate runtime`
|
||||
Generates the pre-built version of the runtime.
|
||||
|
||||
```bash
|
||||
wails3 generate runtime
|
||||
```
|
||||
|
||||
### `generate constants`
|
||||
Generates JavaScript constants from Go code.
|
||||
|
||||
```bash
|
||||
wails3 generate constants
|
||||
```
|
||||
|
||||
### `generate appimage`
|
||||
Generates a Linux AppImage.
|
||||
|
||||
```bash
|
||||
wails3 generate appimage [flags]
|
||||
```
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|-------------|-----------------------|----------------|
|
||||
| `-binary` | Path to binary | Required |
|
||||
| `-icon` | Path to icon file | Required |
|
||||
| `-desktop` | Path to .desktop file | Required |
|
||||
| `-builddir` | Build directory | Temp directory |
|
||||
| `-output` | Output directory | `.` |
|
||||
|
||||
Base command: `wails3 service`
|
||||
|
||||
## Service Commands
|
||||
|
||||
Service commands help manage Wails services. All service commands use the base command: `wails3 service <command>`.
|
||||
|
||||
### `service init`
|
||||
Initializes a new service.
|
||||
|
||||
```bash
|
||||
wails3 service init [flags]
|
||||
```
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|------|---------------------|-------------------|
|
||||
| `-n` | Service name | `example_service` |
|
||||
| `-d` | Service description | `Example service` |
|
||||
| `-p` | Package name | |
|
||||
| `-o` | Output directory | `.` |
|
||||
| `-q` | Suppress output | `false` |
|
||||
| `-a` | Author name | |
|
||||
| `-v` | Version | |
|
||||
| `-w` | Website URL | |
|
||||
| `-r` | Repository URL | |
|
||||
| `-l` | License | |
|
||||
|
||||
Base command: `wails3 tool`
|
||||
|
||||
## Tool Commands
|
||||
|
||||
Tool commands provide utilities for development and debugging. All tool commands use the base command: `wails3 tool <command>`.
|
||||
|
||||
### `tool checkport`
|
||||
Checks if a port is open. Useful for testing if vite is running.
|
||||
|
||||
```bash
|
||||
wails3 tool checkport [flags]
|
||||
```
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|---------|---------------|-------------|
|
||||
| `-port` | Port to check | `9245` |
|
||||
| `-host` | Host to check | `localhost` |
|
||||
|
||||
### `tool watcher`
|
||||
Watches files and runs a command when they change.
|
||||
|
||||
```bash
|
||||
wails3 tool watcher [flags]
|
||||
```
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|------------|---------------------|----------------------|
|
||||
| `-config` | Config file path | `./build/config.yml` |
|
||||
| `-ignore` | Patterns to ignore | |
|
||||
| `-include` | Patterns to include | |
|
||||
|
||||
### `tool cp`
|
||||
Copies files.
|
||||
|
||||
```bash
|
||||
wails3 tool cp
|
||||
```
|
||||
|
||||
### `tool buildinfo`
|
||||
Shows build information about the application.
|
||||
|
||||
```bash
|
||||
wails3 tool buildinfo
|
||||
```
|
||||
|
||||
### `tool version`
|
||||
Bumps a semantic version based on the provided flags.
|
||||
|
||||
```bash
|
||||
wails3 tool version [flags]
|
||||
```
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|---------------|--------------------------------------------------|---------|
|
||||
| `-v` | Current version to bump | |
|
||||
| `-major` | Bump major version | `false` |
|
||||
| `-minor` | Bump minor version | `false` |
|
||||
| `-patch` | Bump patch version | `false` |
|
||||
| `-prerelease` | Bump prerelease version (e.g., alpha.5 to alpha.6) | `false` |
|
||||
|
||||
The command follows the precedence order: major > minor > patch > prerelease. It preserves the "v" prefix if present in the input version, as well as any prerelease and metadata components.
|
||||
|
||||
Example usage:
|
||||
```bash
|
||||
wails3 tool version -v 1.2.3 -major # Output: 2.0.0
|
||||
wails3 tool version -v v1.2.3 -minor # Output: v1.3.0
|
||||
wails3 tool version -v 1.2.3-alpha -patch # Output: 1.2.4-alpha
|
||||
wails3 tool version -v v3.0.0-alpha.5 -prerelease # Output: v3.0.0-alpha.6
|
||||
```
|
||||
|
||||
### `tool package`
|
||||
Generates Linux packages (deb, rpm, archlinux).
|
||||
|
||||
```bash
|
||||
wails3 tool package [flags]
|
||||
```
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|-----------|--------------------------------------|----------|
|
||||
| `-format` | Package format (deb, rpm, archlinux) | `deb` |
|
||||
| `-name` | Executable name | Required |
|
||||
| `-config` | Config file path | Required |
|
||||
| `-out` | Output directory | `.` |
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|-----------|--------------------------------------|---------|
|
||||
| `-format` | Package format (deb, rpm, archlinux) | `deb` |
|
||||
| `-name` | Executable name | `myapp` |
|
||||
| `-config` | Config file path | |
|
||||
| `-out` | Output directory | `.` |
|
||||
|
||||
Base command: `wails3 update`
|
||||
|
||||
## Update Commands
|
||||
|
||||
Update commands help manage and update project assets. All update commands use the base command: `wails3 update <command>`.
|
||||
|
||||
### `update cli`
|
||||
Updates the Wails CLI to a new version.
|
||||
|
||||
```bash
|
||||
wails3 update cli [flags]
|
||||
```
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|-------------|---------------------------------|---------|
|
||||
| `-pre` | Update to latest pre-release | `false` |
|
||||
| `-version` | Update to specific version | |
|
||||
| `-nocolour` | Disable colored output | `false` |
|
||||
|
||||
The update cli command allows you to update your Wails CLI installation. By default, it updates to the latest stable release.
|
||||
You can use the `-pre` flag to update to the latest pre-release version, or specify a particular version using the `-version` flag.
|
||||
|
||||
After updating, remember to update your project's go.mod file to use the same version:
|
||||
```bash
|
||||
require github.com/wailsapp/wails/v3 v3.x.x
|
||||
```
|
||||
|
||||
### `update build-assets`
|
||||
Updates the build assets using the given config file.
|
||||
|
||||
```bash
|
||||
wails3 update build-assets [flags]
|
||||
```
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|----------------|---------------------|---------|
|
||||
| `-config` | Config file path | |
|
||||
| `-dir` | Output directory | `build` |
|
||||
| `-silent` | Suppress output | `false` |
|
||||
| `-company` | Company name | |
|
||||
| `-productname` | Product name | |
|
||||
| `-description` | Product description | |
|
||||
| `-version` | Product version | |
|
||||
| `-identifier` | Product identifier | |
|
||||
| `-copyright` | Copyright notice | |
|
||||
| `-comments` | File comments | |
|
||||
|
||||
Base command: `wails3`
|
||||
|
||||
## Utility Commands
|
||||
|
||||
Utility commands provide helpful shortcuts for common tasks. Use these commands directly with the base command: `wails3 <command>`.
|
||||
|
||||
### `docs`
|
||||
Opens the Wails documentation in your default browser.
|
||||
|
||||
```bash
|
||||
wails3 docs
|
||||
```
|
||||
|
||||
### `releasenotes`
|
||||
Shows the release notes for the current or specified version.
|
||||
|
||||
```bash
|
||||
wails3 releasenotes [flags]
|
||||
```
|
||||
|
||||
#### Flags
|
||||
| Flag | Description | Default |
|
||||
|------|-----------------------------------|---------|
|
||||
| `-v` | Version to show release notes for | |
|
||||
| `-n` | Disable colour output | `false` |
|
||||
|
||||
### `version`
|
||||
Prints the current version of Wails.
|
||||
|
||||
```bash
|
||||
wails3 version
|
||||
```
|
||||
|
||||
### `sponsor`
|
||||
Opens the Wails sponsorship page in your default browser.
|
||||
|
||||
```bash
|
||||
wails3 sponsor
|
||||
202
docs/src/content/docs/guides/cross-platform.mdx
Normal file
202
docs/src/content/docs/guides/cross-platform.mdx
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
---
|
||||
title: Cross-Platform Building
|
||||
description: Build for multiple platforms from a single machine
|
||||
sidebar:
|
||||
order: 2
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Wails supports cross-platform compilation, allowing you to build for Windows, macOS, and Linux from any platform.
|
||||
|
||||
## Supported Platforms
|
||||
|
||||
```bash
|
||||
# List available platforms
|
||||
wails3 build -platform list
|
||||
|
||||
# Common platforms:
|
||||
# - windows/amd64
|
||||
# - darwin/amd64
|
||||
# - darwin/arm64
|
||||
# - darwin/universal
|
||||
# - linux/amd64
|
||||
# - linux/arm64
|
||||
```
|
||||
|
||||
## Building for Windows
|
||||
|
||||
### From macOS/Linux
|
||||
|
||||
```bash
|
||||
# Install dependencies (one-time)
|
||||
# macOS: brew install mingw-w64
|
||||
# Linux: apt-get install mingw-w64
|
||||
|
||||
# Build for Windows
|
||||
wails3 build -platform windows/amd64
|
||||
```
|
||||
|
||||
### Windows-Specific Options
|
||||
|
||||
```bash
|
||||
# With custom icon
|
||||
wails3 build -platform windows/amd64 -icon app.ico
|
||||
|
||||
# Console application
|
||||
wails3 build -platform windows/amd64 -windowsconsole
|
||||
|
||||
# With manifest
|
||||
wails3 build -platform windows/amd64 -manifest app.manifest
|
||||
```
|
||||
|
||||
## Building for macOS
|
||||
|
||||
### From Windows/Linux
|
||||
|
||||
```bash
|
||||
# Note: macOS builds from other platforms have limitations
|
||||
# Recommended: Use macOS for macOS builds
|
||||
|
||||
# Build for Intel Macs
|
||||
wails3 build -platform darwin/amd64
|
||||
|
||||
# Build for Apple Silicon
|
||||
wails3 build -platform darwin/arm64
|
||||
|
||||
# Universal binary (both architectures)
|
||||
wails3 build -platform darwin/universal
|
||||
```
|
||||
|
||||
### macOS-Specific Options
|
||||
|
||||
```bash
|
||||
# With custom icon
|
||||
wails3 build -platform darwin/universal -icon app.icns
|
||||
|
||||
# Code signing (macOS only)
|
||||
wails3 build -platform darwin/universal -codesign "Developer ID"
|
||||
```
|
||||
|
||||
## Building for Linux
|
||||
|
||||
### From Any Platform
|
||||
|
||||
```bash
|
||||
# Build for Linux AMD64
|
||||
wails3 build -platform linux/amd64
|
||||
|
||||
# Build for Linux ARM64
|
||||
wails3 build -platform linux/arm64
|
||||
```
|
||||
|
||||
### Linux-Specific Options
|
||||
|
||||
```bash
|
||||
# With custom icon
|
||||
wails3 build -platform linux/amd64 -icon app.png
|
||||
```
|
||||
|
||||
## Build Matrix
|
||||
|
||||
### Build All Platforms
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# build-all.sh
|
||||
|
||||
platforms=("windows/amd64" "darwin/universal" "linux/amd64")
|
||||
|
||||
for platform in "${platforms[@]}"; do
|
||||
echo "Building for $platform..."
|
||||
wails3 build -platform "$platform" -o "dist/myapp-$platform"
|
||||
done
|
||||
```
|
||||
|
||||
### Using Taskfile
|
||||
|
||||
```yaml
|
||||
# Taskfile.yml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:all:
|
||||
desc: Build for all platforms
|
||||
cmds:
|
||||
- task: build:windows
|
||||
- task: build:macos
|
||||
- task: build:linux
|
||||
|
||||
build:windows:
|
||||
desc: Build for Windows
|
||||
cmds:
|
||||
- wails3 build -platform windows/amd64 -o dist/myapp-windows.exe
|
||||
|
||||
build:macos:
|
||||
desc: Build for macOS
|
||||
cmds:
|
||||
- wails3 build -platform darwin/universal -o dist/myapp-macos
|
||||
|
||||
build:linux:
|
||||
desc: Build for Linux
|
||||
cmds:
|
||||
- wails3 build -platform linux/amd64 -o dist/myapp-linux
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
```yaml
|
||||
name: Build
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.21'
|
||||
|
||||
- name: Install Wails
|
||||
run: go install github.com/wailsapp/wails/v3/cmd/wails3@latest
|
||||
|
||||
- name: Build
|
||||
run: wails3 build
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: app-${{ matrix.os }}
|
||||
path: build/bin/
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
- Test on target platforms
|
||||
- Use CI/CD for builds
|
||||
- Version your builds
|
||||
- Include platform in filename
|
||||
- Document build requirements
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
- Don't skip testing on target platform
|
||||
- Don't forget platform-specific icons
|
||||
- Don't hardcode paths
|
||||
- Don't ignore build warnings
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Creating Installers](/guides/installers) - Package your application
|
||||
- [Building](/guides/building) - Basic building guide
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue