docs: third-pass deep audit verifying struct fields, options, CLI flags, and examples

Deep audit of 23 documentation files against v3 source code:

- reference/window.mdx: Added 20+ missing methods (ToggleFullscreen, ToggleMaximise,
  ForceReload, Width/Height, Bounds, PhysicalBounds, SnapAssist, ToggleFrameless,
  ShowMenuBar/HideMenuBar/ToggleMenuBar), fixed EmitEvent return docs
- reference/dialogs.mdx: Added missing dialog methods (ResolvesAliases,
  AllowsOtherFileTypes, HideExtension, SetMessage, SetButtonText, AddButtons,
  OpenFileWithOptions, SaveFileWithOptions, full SaveFileDialogStruct methods)
- reference/menu.mdx: Added missing MenuItem methods and role constants
- reference/events.mdx: Fixed event constant names and struct fields
- guides/cli.mdx: Added missing CLI flags (-s, -mod, -tags, -noevents), fixed
  -clean default to true, added missing commands (tool lipo, tool capabilities,
  tool sign, setup signing, setup entitlements, doctor-ng, generate template)
- guides/building.mdx: Fixed fabricated build flags, corrected Taskfile structure
- concepts/lifecycle.mdx: Replaced hallucinated OnStartup/OnBeforeClose hooks with
  actual ServiceStartup interface, ShouldQuit option, RegisterHook pattern
- concepts/build-system.mdx: Fixed Vite port (5173->9245), corrected Taskfile.yml
  to match actual project structure with platform-specific includes
- features/screens/info.mdx: Fixed app.Screens->app.Screen, replaced fabricated
  Screen struct with real one (Bounds, PhysicalBounds, WorkArea, Rotation fields),
  removed non-existent GetCurrent/GetByID methods
- features/windows/options.mdx: Added missing platform option fields
- tutorials/03-notes-vanilla.mdx: Fixed package-level dialog functions to manager
  API, fixed service registration pattern with app reference passing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lea Anthony 2026-02-08 21:39:21 +11:00
commit 0cac85cc6b
23 changed files with 1264 additions and 650 deletions

View file

@ -348,17 +348,27 @@ Shutdown.Save -> End
**Lifecycle hooks:**
```go
// Services provide startup/shutdown hooks
type AppService struct{}
func (s *AppService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
// Initialise database, load config, etc.
return nil
}
func (s *AppService) ServiceShutdown() error {
// Save state, close connections, etc.
return nil
}
app := application.New(application.Options{
Name: "My App",
// Called before windows are created
OnStartup: func(ctx context.Context) {
// Initialise database, load config, etc.
Services: []application.Service{
application.NewService(&AppService{}),
},
// Called when app is about to quit
// Called when app is about to quit (before service shutdown)
OnShutdown: func() {
// Save state, close connections, etc.
// Additional cleanup
},
})
```

View file

@ -196,7 +196,7 @@ var assets embed.FS
```
**What happens:**
1. Starts frontend dev server (Vite on port 5173)
1. Starts frontend dev server (Vite on port 9245)
2. Compiles Go without optimisations
3. Launches app pointing to dev server
4. Enables hot reload
@ -245,118 +245,87 @@ var assets embed.FS
wails3 build
```
**Output:** `build/bin/myapp[.exe]`
**Output:** Binary in `bin/` directory.
### Build for Specific Platform
`wails3 build` is a wrapper for `wails3 task build`. It runs the `build` task defined in your project's `Taskfile.yml`, which dispatches to the appropriate platform-specific Taskfile.
### Build with Tags
```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
# Pass additional Go build tags
wails3 build -tags production
```
**Cross-compilation:** Build for any platform from any platform.
### Build with CLI Variables
### Build with Options
You can pass CLI variables to customize the build:
```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
wails3 build PRODUCTION=true
```
### Build Modes
These variables are forwarded to the underlying task and can be accessed in your `Taskfile.yml` using Go template syntax.
### Platform-Specific Tasks
The default project structure includes platform-specific Taskfiles. The main `Taskfile.yml` dispatches to the correct one based on `{{OS}}`:
```bash
# Debug build (includes symbols)
wails3 build -debug
# Production build (default, optimised)
wails3 build
# Development build (fast, unoptimised)
wails3 build -devbuild
# Run platform-specific tasks directly
wails3 task darwin:build
wails3 task windows:build
wails3 task linux:build
```
## Build Configuration
### Taskfile.yml
Wails uses [Taskfile](https://taskfile.dev/) for build configuration:
Wails uses [Taskfile](https://taskfile.dev/) for build configuration. The main `Taskfile.yml` in the project root includes platform-specific Taskfiles:
```yaml
# Taskfile.yml
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:
desc: Build the application
summary: Builds the application
cmds:
- wails3 build
build:windows:
desc: Build for Windows
- task: "{{OS}}:build"
package:
summary: Packages a production build of the application
cmds:
- wails3 build -platform windows/amd64
build:macos:
desc: Build for macOS (Universal)
- task: "{{OS}}:package"
run:
summary: Runs the application
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
- task: "{{OS}}:run"
dev:
summary: Runs the application in development mode
cmds:
- wails3 build -platform linux/amd64
- wails3 dev -config ./build/config.yml -port {{.VITE_PORT}}
```
**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}}"
}
}
wails3 task build
wails3 task package
wails3 task run
```
## Asset Embedding
@ -476,7 +445,7 @@ export default {
```bash
# After building
upx --best build/bin/myapp.exe
upx --best bin/myapp.exe
```
**Results:**
@ -497,12 +466,7 @@ upx --best build/bin/myapp.exe
**Icon:**
```bash
# Specify icon
wails3 build -icon build/appicon.png
```
Wails converts PNG to `.ico` automatically.
Icon generation is handled by the platform-specific Taskfile. The default Windows Taskfile generates an `.ico` file from `build/appicon.png` using the `wails3 generate icons` command.
**Manifest:**
@ -557,15 +521,10 @@ myapp.app/
**Universal Binary:**
```bash
# Build for both architectures
wails3 build -platform darwin/amd64
wails3 build -platform darwin/arm64
The macOS Taskfile supports building universal binaries. You can also use the `wails3 tool lipo` command to combine architecture-specific binaries:
# Combine into universal binary
lipo -create -output myapp-universal \
build/bin/myapp-amd64 \
build/bin/myapp-arm64
```bash
wails3 tool lipo -i bin/myapp-amd64 -i bin/myapp-arm64 -output bin/myapp-universal
```
### Linux
@ -625,24 +584,7 @@ sudo cp icon.png /usr/share/icons/hicolor/256x256/apps/myapp.png
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:**
**2. Use faster tools:**
```bash
# Use esbuild instead of webpack
@ -684,9 +626,7 @@ wait
**Solutions:**
1. **Strip debug symbols** (should be automatic)
```bash
wails3 build # Already includes -ldflags="-s -w"
```
The default Taskfiles include `-ldflags="-s -w"` in the build task.
2. **Check embedded assets**
```bash
@ -696,7 +636,7 @@ wait
3. **Use UPX compression**
```bash
upx --best build/bin/myapp.exe
upx --best bin/myapp.exe
```
### Slow Builds
@ -709,12 +649,7 @@ wait
- Go cache is automatic
- Frontend cache (Vite) is automatic
2. **Skip unchanged steps**
```bash
wails3 build -skipbindings
```
3. **Optimise frontend build**
2. **Optimise frontend build**
```javascript
// vite.config.js
export default {
@ -730,7 +665,7 @@ wait
- **Use `wails3 dev` during development** - Fast iteration
- **Use `wails3 build` for releases** - Optimised output
- **Version your builds** - Use `-ldflags` to embed version
- **Version your builds** - Use `-ldflags` in your Taskfile 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

View file

@ -33,7 +33,7 @@ PreInit: "Pre-Initialisation" {
}
}
OnStartup: "OnStartup Hook" {
ServiceStartup: "Service Startup" {
shape: rectangle
style.fill: "#3B82F6"
}
@ -59,12 +59,12 @@ QuitSignal: "Quit Signal" {
style.fill: "#F59E0B"
}
OnBeforeClose: "OnBeforeClose Hook" {
ShouldQuit: "ShouldQuit Check" {
shape: rectangle
style.fill: "#3B82F6"
}
OnShutdown: "OnShutdown Hook" {
OnShutdown: "Shutdown Hooks" {
shape: rectangle
style.fill: "#3B82F6"
}
@ -86,16 +86,16 @@ End: "Application End" {
Start -> PreInit.Parse
PreInit.Parse -> PreInit.Register
PreInit.Register -> PreInit.Validate
PreInit.Validate -> OnStartup
OnStartup -> CreateWindows
PreInit.Validate -> ServiceStartup
ServiceStartup -> 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"
QuitSignal -> ShouldQuit: "Can cancel?"
ShouldQuit -> EventLoop.Process: "Cancelled"
ShouldQuit -> OnShutdown: "Confirmed"
OnShutdown -> Cleanup.Close
Cleanup.Close -> Cleanup.Release
Cleanup.Release -> End
@ -111,34 +111,42 @@ Before your code runs, Wails:
**You don't control this phase** - it happens automatically.
### 2. OnStartup Hook
### 2. Service Startup
Your first opportunity to run code:
Your first opportunity to run code is through the `ServiceStartup` interface. Services that implement this interface receive a startup notification during `app.Run()`:
```go
type AppService struct {
db *sql.DB
config *Config
}
func (s *AppService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
// Initialise database
var err error
s.db, err = sql.Open("sqlite3", "app.db")
if err != nil {
return fmt.Errorf("failed to open database: %w", err)
}
// Load configuration
s.config, err = loadConfig()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
return nil
}
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)
Services: []application.Service{
application.NewService(&AppService{}),
},
})
```
**When it runs:** After Wails initialisation, before windows are created
**When it runs:** During `app.Run()`, before the event loop starts
**Use it for:**
- Database connections
@ -146,7 +154,7 @@ app := application.New(application.Options{
- Resource initialisation
- Authentication checks
**Context:** The `context.Context` is passed to all services and can store shared state.
**Context:** The `context.Context` is valid as long as the application is running and is cancelled right before shutdown.
### 3. Window Creation
@ -185,60 +193,89 @@ User triggers quit via:
- Cmd+Q / Alt+F4 / File → Quit
- Your code calling `app.Quit()`
### 6. OnBeforeClose Hook
### 6. ShouldQuit / Window Close Prevention
**Optional hook to prevent quit:**
**Application-level quit prevention with `ShouldQuit`:**
```go
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
OnBeforeClose: func() bool {
app := application.New(application.Options{
ShouldQuit: func() bool {
// Return false to cancel quit
// Return true to allow quit
if hasUnsavedChanges() {
result := showConfirmDialog("Unsaved changes. Quit anyway?")
return result == "yes"
return false
}
return true
},
})
```
**Window-level close prevention with `RegisterHook`:**
```go
window := app.Window.New()
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
if hasUnsavedChanges() {
e.Cancel() // Prevent the window from closing
}
})
```
**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. Shutdown Hooks
### 7. OnShutdown Hook
There are multiple hooks for shutdown:
Your last opportunity to run code:
**`OnShutdown` (application option)** - runs first during shutdown:
```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
**`app.OnShutdown()` (runtime registration)** - add shutdown tasks dynamically:
**Use it for:**
- Saving state
- Closing connections
- Releasing resources
- Final cleanup
```go
app.OnShutdown(func() {
// Additional cleanup
cleanup()
})
```
**Important:** Keep it fast (&lt;1 second). OS may force-kill if too slow.
**`ServiceShutdown` (service interface)** - services shut down in reverse registration order:
```go
func (s *MyService) ServiceShutdown() error {
// Close database, release resources
return s.db.Close()
}
```
**`PostShutdown` (application option)** - runs after everything else, just before process exit:
```go
app := application.New(application.Options{
PostShutdown: func() {
log.Println("Application has finished shutting down")
},
})
```
**Shutdown order:**
1. `OnShutdown` callbacks (in order added)
2. `ServiceShutdown` (reverse registration order)
3. Windows closed, system trays destroyed
4. `PostShutdown` callback
**Important:** Keep shutdown fast. OS may force-kill if too slow.
### 8. Cleanup & Exit
@ -251,34 +288,46 @@ Wails automatically:
| Hook | When | Can Cancel? | Use For |
|------|------|-------------|---------|
| `OnStartup` | Before windows created | No | Initialisation |
| `OnBeforeClose` | Window closing | Yes | Confirm quit |
| `ServiceStartup` | During `app.Run()`, before event loop | Yes (return error) | Initialisation |
| `ShouldQuit` | App quit requested | Yes (return false) | Confirm quit |
| `ShouldClose` | Window closing | Yes (return false) | Prevent window close |
| `OnShutdown` | After quit confirmed | No | Cleanup |
| `PostShutdown` | After shutdown complete | No | Logging, testing |
## Common Patterns
### Pattern 1: Database Lifecycle
```go
var db *sql.DB
type DatabaseService struct {
db *sql.DB
}
func (d *DatabaseService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
var err error
d.db, err = sql.Open("sqlite3", "app.db")
if err != nil {
return fmt.Errorf("failed to open database: %w", err)
}
// Run migrations
if err := runMigrations(d.db); err != nil {
return fmt.Errorf("migrations failed: %w", err)
}
return nil
}
func (d *DatabaseService) ServiceShutdown() error {
if d.db != nil {
return d.db.Close()
}
return nil
}
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()
}
Services: []application.Service{
application.NewService(&DatabaseService{}),
},
})
```
@ -287,69 +336,92 @@ app := application.New(application.Options{
```go
type Config struct {
Theme string
Language string
Theme string
Language string
WindowPos Point
}
var config *Config
type ConfigService struct {
config *Config
}
func (c *ConfigService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
var err error
c.config, err = loadConfig() // Load from disk
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
return nil
}
func (c *ConfigService) ServiceShutdown() error {
return saveConfig(c.config) // Save to disk
}
app := application.New(application.Options{
OnStartup: func(ctx context.Context) {
config = loadConfig() // Load from disk
},
OnShutdown: func() {
saveConfig(config) // Save to disk
Services: []application.Service{
application.NewService(&ConfigService{}),
},
})
```
### Pattern 3: Confirm Quit with Unsaved Changes
Use `RegisterHook` with `events.Common.WindowClosing` to prevent window close, or `ShouldQuit` on the application options:
```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"
// Application-level: prevent quit
app := application.New(application.Options{
ShouldQuit: func() bool {
if hasUnsavedChanges {
// Return false to prevent quit
return false
}
return true
},
})
// Window-level: prevent individual window close
window := app.Window.New()
window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) {
if hasUnsavedChanges {
e.Cancel() // Prevent window from closing
}
})
```
### 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
}
type SyncService struct{}
func (s *SyncService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
// 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
}
}()
}
}()
return nil
}
app := application.New(application.Options{
Services: []application.Service{
application.NewService(&SyncService{}),
},
})
```
**Important:** Use `ctx.Done()` to know when to stop background tasks.
**Important:** Use `ctx.Done()` to know when to stop background tasks. The context is cancelled right before shutdown.
## Window Lifecycle
@ -382,7 +454,7 @@ CloseRequest: "Close Request" {
style.fill: "#F59E0B"
}
OnBeforeClose: "OnBeforeClose" {
CloseHook: "WindowClosing Hook" {
shape: rectangle
style.fill: "#3B82F6"
}
@ -401,9 +473,9 @@ Load -> Show
Show -> Active.Events
Active.Events -> Active.Events: "Loop"
Active.Events -> CloseRequest: "User closes"
CloseRequest -> OnBeforeClose
OnBeforeClose -> Active.Events: "Cancelled"
OnBeforeClose -> Destroy: "Confirmed"
CloseRequest -> CloseHook
CloseHook -> Active.Events: "Cancelled"
CloseHook -> Destroy: "Confirmed"
Destroy -> End
```
@ -453,34 +525,38 @@ app := application.New(application.Options{
### Startup Errors
Return an error from `ServiceStartup` to abort application startup. The error is propagated from `app.Run()`:
```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()
}
},
})
func (s *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
if err := initialise(); err != nil {
return fmt.Errorf("initialisation failed: %w", err)
}
return nil
}
// In main:
err := app.Run()
if err != nil {
log.Fatal("Failed to start:", err)
}
```
### Shutdown Errors
Errors returned from `ServiceShutdown` are logged but do not prevent shutdown:
```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)
}
},
})
func (s *MyService) ServiceShutdown() error {
if err := saveState(); err != nil {
// Error will be logged by Wails
return fmt.Errorf("failed to save state: %w", err)
}
return nil
}
```
**Important:** `OnShutdown` runs during quit - don't show dialogs or try to cancel.
**Important:** Shutdown hooks run during quit - don't show dialogs or try to cancel.
## Platform Differences
@ -507,15 +583,14 @@ app := application.New(application.Options{
**Symptom:** Database connections left open, files not closed
**Solution:** Use `OnShutdown`:
**Solution:** Implement `ServiceShutdown` on your services:
```go
app := application.New(application.Options{
OnShutdown: func() {
log.Println("Cleaning up...")
// Your cleanup code
},
})
func (s *MyService) ServiceShutdown() error {
log.Println("Cleaning up...")
// Your cleanup code
return s.db.Close()
}
```
### Problem: Application Won't Quit
@ -523,28 +598,31 @@ app := application.New(application.Options{
**Symptom:** App hangs when trying to quit
**Causes:**
1. `OnBeforeClose` returning `false`
2. `OnShutdown` taking too long
3. Background goroutines not stopping
1. `ShouldQuit` returning `false`
2. Window close hook cancelling close events
3. Shutdown tasks taking too long
4. Background goroutines not stopping
**Solution:**
```go
// 1. Check OnBeforeClose logic
OnBeforeClose: func() bool {
log.Println("OnBeforeClose called")
return true // Allow quit
}
// 1. Check ShouldQuit logic
app := application.New(application.Options{
ShouldQuit: func() bool {
log.Println("ShouldQuit called")
return true // Allow quit
},
})
// 2. Keep OnShutdown fast
OnShutdown: func() {
log.Println("OnShutdown started")
// 2. Keep shutdown fast
app.OnShutdown(func() {
log.Println("Shutdown started")
// Fast cleanup only
log.Println("OnShutdown finished")
}
log.Println("Shutdown finished")
})
// 3. Stop background tasks
OnStartup: func(ctx context.Context) {
// 3. Stop background tasks using context
func (s *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
go func() {
for {
select {
@ -556,6 +634,7 @@ OnStartup: func(ctx context.Context) {
}
}
}()
return nil
}
```
@ -563,14 +642,19 @@ OnStartup: func(ctx context.Context) {
**Symptom:** App starts but doesn't work correctly
**Solution:** Check errors in `OnStartup`:
**Solution:** Return errors from `ServiceStartup` to abort startup:
```go
OnStartup: func(ctx context.Context) {
func (s *MyService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
if err := initialise(); err != nil {
log.Fatal("Initialisation failed:", err)
// Or show dialog and quit
return fmt.Errorf("initialisation failed: %w", err)
}
return nil
}
// In main - app.Run() returns the error:
if err := app.Run(); err != nil {
log.Fatal("Failed to start:", err)
}
```
@ -578,20 +662,20 @@ OnStartup: func(ctx context.Context) {
### ✅ Do
- **Initialise in OnStartup** - Database, config, resources
- **Clean up in OnShutdown** - Close connections, save state
- **Keep OnShutdown fast** - &lt;1 second
- **Initialise in ServiceStartup** - Database, config, resources
- **Clean up in ServiceShutdown** - Close connections, save state
- **Keep shutdown fast** - &lt;1 second
- **Use context for cancellation** - Stop background tasks
- **Handle errors gracefully** - Show dialogs, log errors
- **Handle errors gracefully** - Return errors from ServiceStartup
- **Test quit scenarios** - Unsaved changes, background tasks
### ❌ Don't
- **Don't block OnStartup** - Keep it fast (&lt;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
- **Don't block ServiceStartup** - Keep it fast (&lt;2 seconds)
- **Don't show dialogs during shutdown** - App is quitting
- **Don't ignore errors** - Log or return them
- **Don't leak resources** - Always clean up in ServiceShutdown
- **Don't forget background tasks** - Stop them using ctx.Done()
## Next Steps

View file

@ -107,6 +107,16 @@ func updateApplicationTheme(theme string) {
}
```
### Accent Colour
Get the system accent colour:
```go
accentColor := app.Env.GetAccentColor()
app.Logger.Info("System accent colour", "color", accentColor)
// Returns a CSS-compatible colour string, e.g. "rgb(0,122,255)"
```
## File Manager Integration
### Open File Manager

View file

@ -150,10 +150,13 @@ Wails provides **predefined menu roles** that create platform-appropriate menu s
| Role | Description | Platform Notes |
|------|-------------|----------------|
| `AppMenu` | Application menu with About, Preferences, Quit | **macOS only** |
| `AppMenu` | Application menu with About, Services, Hide/Show, and Quit | **macOS only** |
| `FileMenu` | File menu with Close Window (macOS) or Quit (Windows/Linux) | All platforms |
| `EditMenu` | Text editing (Undo, Redo, Cut, Copy, Paste) | All platforms |
| `EditMenu` | Text editing (Undo, Redo, Cut, Copy, Paste, etc.) | All platforms |
| `ViewMenu` | View menu with Reload, Zoom, and Fullscreen controls | All platforms |
| `WindowMenu` | Window management (Minimise, Zoom, etc.) | All platforms |
| `ServicesMenu` | macOS Services submenu | **macOS only** |
| `SpeechMenu` | Speech menu with Start/Stop Speaking | **macOS only** |
| `HelpMenu` | Help and information | All platforms |
### Using Roles
@ -421,7 +424,6 @@ menuItem.OnClick(func(ctx *application.Context) {
**Standard locations:**
- **About**: Application menu
- **Preferences**: Application menu (⌘,)
- **Quit**: Application menu (⌘Q)
- **Help**: Help menu
@ -429,8 +431,8 @@ menuItem.OnClick(func(ctx *application.Context) {
```go
if runtime.GOOS == "darwin" {
menu.AddRole(application.AppMenu) // Adds About, Preferences, Quit
menu.AddRole(application.AppMenu) // Adds About, Services, Hide/Show, Quit
// Don't add Quit to File menu on macOS
// Don't add About to Help menu on macOS
}
@ -522,10 +524,8 @@ func createMenu(app *application.App) {
// 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 {
// Settings location varies by platform convention
if runtime.GOOS != "darwin" {
toolsMenu.Add("Settings").SetAccelerator("CmdOrCtrl+,").OnClick(showSettings)
}

View file

@ -232,12 +232,21 @@ 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
**Accelerator format:** `Modifier+Key` joined by `+`. Modifiers are case-insensitive.
**Modifiers:**
- `CmdOrCtrl` (aliases: `Cmd`, `Command`) - Cmd on macOS, Ctrl on Windows/Linux
- `Ctrl` - Control key on all platforms
- `Alt` (aliases: `Option`, `OptionOrAlt`) - Alt on Windows/Linux, Option on macOS
- `Shift` - Shift key on all platforms
- `Super` - Cmd on macOS, Windows key on Windows/Linux
**Keys:**
- `A-Z`, `0-9` - Letter and number keys
- `F1-F35` - Function keys
- `Plus` - The `+` character (since `+` is the separator)
- Named keys: `Backspace`, `Tab`, `Return`, `Enter`, `Escape`, `Left`, `Right`, `Up`, `Down`, `Space`, `Delete`, `Home`, `End`, `Page Up`, `Page Down`, `NumLock`
- Any single printable character
**Examples:**
@ -306,10 +315,10 @@ menuItem.OnClick(func(ctx *application.Context) {
})
```
**Context provides:**
- `ctx.ClickedMenuItem()` - The menu item that was clicked
- Window context (if from window menu)
- Application context
**Context methods:**
- `ctx.ClickedMenuItem() *MenuItem` - Returns the menu item that was clicked
- `ctx.IsChecked() bool` - Returns the checked state of the clicked item (for checkbox/radio items)
- `ctx.ContextMenuData() string` - Returns the context data string from the HTML element (context menus only)
**Example: Access menu item in handler**
@ -317,7 +326,7 @@ menuItem.OnClick(func(ctx *application.Context) {
checkbox := menu.AddCheckbox("Feature", false)
checkbox.OnClick(func(ctx *application.Context) {
item := ctx.ClickedMenuItem()
isChecked := item.Checked()
isChecked := ctx.IsChecked()
fmt.Printf("Feature is now: %v\n", isChecked)
})
```
@ -433,12 +442,12 @@ onUndoStackChanged(updateEditMenu)
**macOS:**
- Has "Application" menu (with app name)
- "Preferences" in Application menu
- `AppMenu` role adds: About, Services, Hide/Show, Quit
- "Quit" in Application menu
**Windows/Linux:**
- No Application menu
- "Preferences" in Edit or Tools menu
- "Settings" typically in Edit or Tools menu
- "Exit" in File menu
**Example: Platform-appropriate structure**
@ -456,13 +465,11 @@ 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 {
// Settings location varies by platform convention
if runtime.GOOS != "darwin" {
// On Windows/Linux, add to Edit or Tools menu
editMenu := menu.AddSubmenu("Edit")
editMenu.Add("Preferences")
editMenu.Add("Settings")
}
```

View file

@ -67,7 +67,7 @@ notifier.SendNotification(notifications.NotificationOptions{
```
### Interactive Notifications
Send a notification with action buttons and text inputs. These notifications require a notification category to be resgistered first:
Send a notification with action buttons and text inputs. These notifications require a notification category to be registered first:
```go
// Define a unique category id
@ -155,7 +155,7 @@ notifier.SendNotification(notifications.NotificationOptions{
On macOS, notifications:
- Require user authorization
- Require the app to be notorized for distribution
- Require the app to be notarized for distribution
- Use system-standard notification appearances
- Support `subtitle`
- Support user text input
@ -178,7 +178,7 @@ notifier.SendNotification(notifications.NotificationOptions{
<TabItem label="Linux" icon="fa-brands:linux">
On Linux, dialog behaviour depends on the desktop environment:
On Linux, notification behaviour depends on the desktop environment:
- Use native notifications when available
- Follow desktop environment theme
@ -225,25 +225,25 @@ Explore this example:
| `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 |
| Method | Returns | Description |
|------------------------------------------------------------|---------|------------------------------------------------|
| `SendNotification(options NotificationOptions)` | `error` | Sends a basic notification |
| `SendNotificationWithActions(options NotificationOptions)` | `error` | 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 |
| Method | Returns | Description |
|---------------------------------------------------------------|---------|------------------------------------------- |
| `RegisterNotificationCategory(category NotificationCategory)` | `error` | Registers a reusable notification category |
| `RemoveNotificationCategory(categoryID string)` | `error` | 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) |
| Method | Returns | Description |
|-------------------------------------------------|---------|---------------------------------------------------------------------|
| `RemoveAllPendingNotifications()` | `error` | Removes all pending notifications (macOS and Linux only) |
| `RemovePendingNotification(identifier string)` | `error` | Removes a specific pending notification (macOS and Linux only) |
| `RemoveAllDeliveredNotifications()` | `error` | Removes all delivered notifications (macOS and Linux only) |
| `RemoveDeliveredNotification(identifier string)`| `error` | Removes a specific delivered notification (macOS and Linux only) |
| `RemoveNotification(identifier string)` | `error` | Removes a notification (Linux-specific) |
### Event Handling
| Method | Description |

View file

@ -15,15 +15,15 @@ Wails provides a **unified screen API** that works across all platforms. Get scr
```go
// Get all screens
screens := app.Screens.GetAll()
screens := app.Screen.GetAll()
for _, screen := range screens {
fmt.Printf("Screen: %s (%dx%d)\n",
screen.Name, screen.Width, screen.Height)
fmt.Printf("Screen: %s (%dx%d)\n",
screen.Name, screen.Size.Width, screen.Size.Height)
}
// Get primary screen
primary := app.Screens.GetPrimary()
primary := app.Screen.GetPrimary()
fmt.Printf("Primary: %s\n", primary.Name)
```
@ -34,12 +34,12 @@ fmt.Printf("Primary: %s\n", primary.Name)
### All Screens
```go
screens := app.Screens.GetAll()
screens := app.Screen.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("Size: %dx%d\n", screen.Size.Width, screen.Size.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)
@ -50,61 +50,58 @@ for _, screen := range screens {
### Primary Screen
```go
primary := app.Screens.GetPrimary()
primary := app.Screen.GetPrimary()
fmt.Printf("Primary screen: %s\n", primary.Name)
fmt.Printf("Resolution: %dx%d\n", primary.Width, primary.Height)
fmt.Printf("Resolution: %dx%d\n", primary.Size.Width, primary.Size.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?
ID string // A unique identifier for the display
Name string // The name of the display
ScaleFactor float32 // The scale factor of the display (DPI/96)
X int // The x-coordinate of the top-left corner
Y int // The y-coordinate of the top-left corner
Size Size // The size of the display
Bounds Rect // The bounds of the display
PhysicalBounds Rect // The physical bounds (before scaling)
WorkArea Rect // The work area of the display
PhysicalWorkArea Rect // The physical work area (before scaling)
IsPrimary bool // Whether this is the primary display
Rotation float32 // The rotation of the display
}
type Rect struct {
X int
Y int
Width int
Height int
}
type Size struct {
Width int
Height int
}
```
### Physical vs Logical Pixels
```go
screen := app.Screens.GetPrimary()
screen := app.Screen.GetPrimary()
// Logical pixels (what you use)
logicalWidth := screen.Width
logicalHeight := screen.Height
// Logical pixels (DIP - what you use)
logicalWidth := screen.Bounds.Width
logicalHeight := screen.Bounds.Height
// Physical pixels (actual display)
physicalWidth := int(float32(screen.Width) * screen.ScaleFactor)
physicalHeight := int(float32(screen.Height) * screen.ScaleFactor)
physicalWidth := screen.PhysicalBounds.Width
physicalHeight := screen.PhysicalBounds.Height
fmt.Printf("Logical: %dx%d\n", logicalWidth, logicalHeight)
fmt.Printf("Physical: %dx%d\n", physicalWidth, physicalHeight)
@ -125,10 +122,10 @@ fmt.Printf("Scale: %.2f\n", screen.ScaleFactor)
```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
x := screen.X + (screen.Size.Width-windowWidth)/2
y := screen.Y + (screen.Size.Height-windowHeight)/2
window.SetPosition(x, y)
}
```
@ -137,7 +134,7 @@ func centreOnScreen(window *application.WebviewWindow, screen *Screen) {
```go
func moveToScreen(window *application.WebviewWindow, screenIndex int) {
screens := app.Screens.GetAll()
screens := app.Screen.GetAll()
if screenIndex < 0 || screenIndex >= len(screens) {
return
@ -161,15 +158,15 @@ func positionTopLeft(window *application.WebviewWindow, screen *Screen) {
// Top-right corner
func positionTopRight(window *application.WebviewWindow, screen *Screen) {
windowWidth, _ := window.Size()
window.SetPosition(screen.X+screen.Width-windowWidth-10, screen.Y+10)
window.SetPosition(screen.X+screen.Size.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,
screen.X+screen.Size.Width-windowWidth-10,
screen.Y+screen.Size.Height-windowHeight-10,
)
}
```
@ -180,11 +177,11 @@ func positionBottomRight(window *application.WebviewWindow, screen *Screen) {
```go
func hasMultipleMonitors() bool {
return len(app.Screens.GetAll()) > 1
return len(app.Screen.GetAll()) > 1
}
func getMonitorCount() int {
return len(app.Screens.GetAll())
return len(app.Screen.GetAll())
}
```
@ -192,7 +189,7 @@ func getMonitorCount() int {
```go
func listMonitors() {
screens := app.Screens.GetAll()
screens := app.Screen.GetAll()
fmt.Printf("Found %d monitor(s):\n", len(screens))
@ -203,7 +200,7 @@ func listMonitors() {
}
fmt.Printf("%d. %s%s\n", i+1, screen.Name, primary)
fmt.Printf(" Resolution: %dx%d\n", screen.Width, screen.Height)
fmt.Printf(" Resolution: %dx%d\n", screen.Size.Width, screen.Size.Height)
fmt.Printf(" Position: %d,%d\n", screen.X, screen.Y)
fmt.Printf(" Scale: %.2fx\n", screen.ScaleFactor)
}
@ -214,7 +211,7 @@ func listMonitors() {
```go
func chooseMonitor() (*Screen, error) {
screens := app.Screens.GetAll()
screens := app.Screen.GetAll()
if len(screens) == 1 {
return screens[0], nil
@ -229,7 +226,7 @@ func chooseMonitor() (*Screen, error) {
}
options = append(options,
fmt.Sprintf("%d. %s%s - %dx%d",
i+1, screen.Name, primary, screen.Width, screen.Height))
i+1, screen.Name, primary, screen.Size.Width, screen.Size.Height))
}
// Use dialog to select
@ -257,7 +254,7 @@ func NewMultiMonitorManager(app *application.App) *MultiMonitorManager {
}
func (m *MultiMonitorManager) CreateWindowOnScreen(screenIndex int) error {
screens := m.app.Screens.GetAll()
screens := m.app.Screen.GetAll()
if screenIndex < 0 || screenIndex >= len(screens) {
return errors.New("invalid screen index")
@ -273,8 +270,8 @@ func (m *MultiMonitorManager) CreateWindowOnScreen(screenIndex int) error {
})
// Centre on screen
x := screen.X + (screen.Width-800)/2
y := screen.Y + (screen.Height-600)/2
x := screen.X + (screen.Size.Width-800)/2
y := screen.Y + (screen.Size.Height-600)/2
window.SetPosition(x, y)
window.Show()
@ -284,7 +281,7 @@ func (m *MultiMonitorManager) CreateWindowOnScreen(screenIndex int) error {
}
func (m *MultiMonitorManager) CreateWindowOnEachScreen() {
screens := m.app.Screens.GetAll()
screens := m.app.Screen.GetAll()
for i := range screens {
m.CreateWindowOnScreen(i)
@ -304,7 +301,7 @@ type ScreenMonitor struct {
func NewScreenMonitor(app *application.App) *ScreenMonitor {
return &ScreenMonitor{
app: app,
lastScreens: app.Screens.GetAll(),
lastScreens: app.Screen.GetAll(),
}
}
@ -323,7 +320,7 @@ func (sm *ScreenMonitor) Start() {
}
func (sm *ScreenMonitor) checkScreens() {
current := sm.app.Screens.GetAll()
current := sm.app.Screen.GetAll()
if len(current) != len(sm.lastScreens) {
sm.lastScreens = current
@ -341,22 +338,22 @@ 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
x := screen.X + (screen.Size.Width-width)/2
y := screen.Y + (screen.Size.Height-height)/2
window.SetPosition(x, y)
return window
}
```
@ -365,7 +362,7 @@ func createDPIAwareWindow(screen *Screen) *application.WebviewWindow {
```go
func visualiseScreenLayout() string {
screens := app.Screens.GetAll()
screens := app.Screen.GetAll()
var layout strings.Builder
layout.WriteString("Screen Layout:\n\n")
@ -378,11 +375,11 @@ func visualiseScreenLayout() string {
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(" Size: %dx%d\n", screen.Size.Width, screen.Size.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(fmt.Sprintf(" Physical: %dx%d\n",
screen.PhysicalBounds.Width,
screen.PhysicalBounds.Height))
layout.WriteString("\n")
}

View file

@ -386,8 +386,9 @@ Windows can communicate via the event system. See [Multiple Windows](/features/w
**Backdrop types:**
- `MacBackdropNormal` - Standard window
- `MacBackdropTranslucent` - Translucent background
- `MacBackdropTransparent` - Fully transparent
- `MacBackdropTranslucent` - Translucent background
- `MacBackdropLiquidGlass` - Liquid Glass effect (macOS 15.0+)
**Native fullscreen:**
macOS fullscreen creates a new Space (virtual desktop).

View file

@ -202,17 +202,27 @@ window.OnWindowEvent(events.Common.WindowDidResize, func(e *application.WindowEv
| Event | Description |
|-------|-------------|
| `events.Common.WindowClosing` | Window is about to close |
| `events.Common.WindowFocus` | Window gained focus |
| `events.Common.WindowLostFocus` | Window lost focus |
| `events.Common.WindowDidMove` | Window moved |
| `events.Common.WindowDidResize` | Window resized |
| `events.Common.WindowMinimise` | Window minimised |
| `events.Common.WindowUnMinimise` | Window restored from minimised |
| `events.Common.WindowMaximise` | Window maximised |
| `events.Common.WindowUnMaximise` | Window restored from maximised |
| `events.Common.WindowFullscreen` | Window entered fullscreen |
| `events.Common.WindowUnFullscreen` | Window exited fullscreen |
| `events.Common.WindowDPIChanged` | DPI scaling changed |
| `events.Common.WindowFilesDropped` | Files were dropped onto the window |
| `events.Common.WindowFocus` | Window gained focus |
| `events.Common.WindowFullscreen` | Window entered fullscreen |
| `events.Common.WindowHide` | Window was hidden |
| `events.Common.WindowLostFocus` | Window lost focus |
| `events.Common.WindowMaximise` | Window maximised |
| `events.Common.WindowMinimise` | Window minimised |
| `events.Common.WindowToggleFrameless` | Frameless mode toggled |
| `events.Common.WindowRestore` | Window restored from min/max |
| `events.Common.WindowRuntimeReady` | Wails runtime loaded in window |
| `events.Common.WindowShow` | Window became visible |
| `events.Common.WindowUnFullscreen` | Window exited fullscreen |
| `events.Common.WindowUnMaximise` | Window restored from maximised |
| `events.Common.WindowUnMinimise` | Window restored from minimised |
| `events.Common.WindowZoom` | Window zoom changed |
| `events.Common.WindowZoomIn` | Zoom level increased |
| `events.Common.WindowZoomOut` | Zoom level decreased |
| `events.Common.WindowZoomReset` | Zoom reset to default |
## Complete Example

View file

@ -444,17 +444,14 @@ func CreateWindow(name string) {
}
```
### Closing vs Destroying
### Closing Windows
```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.
**Note:** `Close()` emits a `WindowClosing` event. Use `RegisterHook` to intercept and optionally cancel the close.
### Resource Cleanup
@ -464,14 +461,14 @@ type ManagedWindow struct {
resources []io.Closer
}
func (mw *ManagedWindow) Destroy() {
func (mw *ManagedWindow) CleanupAndClose() {
// Close all resources
for _, resource := range mw.resources {
resource.Close()
}
// Destroy window
mw.window.Destroy()
// Close window
mw.window.Close()
}
```

View file

@ -514,22 +514,6 @@ 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
@ -638,10 +622,14 @@ Mac: application.MacWindow{
- `MacTitleBarHiddenInset` - Hidden with inset traffic lights
- `MacTitleBarHiddenInsetUnified` - Hidden with unified toolbar style
**DisableShadow** (`bool`)
- Disable the window shadow
**Backdrop** (`MacBackdrop`)
- `MacBackdropNormal` - Standard
- `MacBackdropTranslucent` - Blurred translucent
- `MacBackdropNormal` - Standard opaque background
- `MacBackdropTransparent` - Fully transparent
- `MacBackdropTranslucent` - Blurred translucent
- `MacBackdropLiquidGlass` - Apple's Liquid Glass effect (macOS 15.0+, falls back to translucent)
**InvisibleTitleBarHeight** (`int`)
- Height of invisible title bar (for dragging)
@ -650,6 +638,35 @@ Mac: application.MacWindow{
- `DefaultAppearance` - System default
- `NSAppearanceNameAqua` - Light appearance
- `NSAppearanceNameDarkAqua` - Dark appearance
- `NSAppearanceNameVibrantLight` - Light vibrant appearance
- `NSAppearanceNameAccessibilityHighContrastAqua` - High-contrast light
- `NSAppearanceNameAccessibilityHighContrastDarkAqua` - High-contrast dark
- `NSAppearanceNameAccessibilityHighContrastVibrantLight` - High-contrast vibrant light
- `NSAppearanceNameAccessibilityHighContrastVibrantDark` - High-contrast vibrant dark
**EnableFraudulentWebsiteWarnings** (`bool`)
- Enable warnings for fraudulent websites
**WebviewPreferences** (`MacWebviewPreferences`)
- `TabFocusesLinks` - Enable tabbing to links
- `TextInteractionEnabled` - Enable text interaction
- `FullscreenEnabled` - Enable fullscreen
- `AllowsBackForwardNavigationGestures` - Enable horizontal swipe gestures for back/forward navigation
**WindowLevel** (`MacWindowLevel`)
- Controls the window level ordering. Values: `MacWindowLevelNormal`, `MacWindowLevelFloating`, `MacWindowLevelTornOffMenu`, `MacWindowLevelModalPanel`, `MacWindowLevelMainMenu`, `MacWindowLevelStatus`, `MacWindowLevelPopUpMenu`, `MacWindowLevelScreenSaver`
**CollectionBehavior** (`MacWindowCollectionBehavior`)
- Controls how the window behaves across macOS Spaces and fullscreen. Values can be combined with bitwise OR. Includes: `MacWindowCollectionBehaviorCanJoinAllSpaces`, `MacWindowCollectionBehaviorMoveToActiveSpace`, `MacWindowCollectionBehaviorFullScreenPrimary`, `MacWindowCollectionBehaviorFullScreenAuxiliary`, etc.
**EventMapping** (`map[events.WindowEventType]events.WindowEventType`)
- Maps platform-specific events to common event types
**LiquidGlass** (`MacLiquidGlass`)
- Configuration for the Liquid Glass effect: `Style`, `Material`, `CornerRadius`, `TintColor`, `GroupID`, `GroupSpacing`
**ShowToolbarWhenFullscreen** (in `MacTitleBar`)
- Keep the toolbar visible when the window is in fullscreen mode
**Example:**
@ -696,6 +713,42 @@ Windows: application.WindowsWindow{
- Disable default frameless decorations (shadow and rounded corners)
- For custom window chrome
**WindowMask** (`[]byte`)
- Set the window shape using a PNG with an alpha channel
**WindowMaskDraggable** (`bool`)
- Make the window draggable by clicking on the window mask
**ResizeDebounceMS** (`uint16`)
- Amount of time in milliseconds to debounce redraws of webview2 when resizing
**WindowDidMoveDebounceMS** (`uint16`)
- Amount of time in milliseconds to debounce the WindowDidMove event when moving
**EventMapping** (`map[events.WindowEventType]events.WindowEventType`)
- Maps platform-specific events to common event types
**HiddenOnTaskbar** (`bool`)
- Hide the window from the taskbar
**EnableSwipeGestures** (`bool`)
- Enable swipe gestures for the window
**Menu** (`*Menu`)
- The menu to use for the window
**Permissions** (`map[CoreWebView2PermissionKind]CoreWebView2PermissionState`)
- WebView2 permissions map. If empty, default permissions will be granted.
**ExStyle** (`int`)
- Extended window style
**GeneralAutofillEnabled** (`bool`)
- Enable general autofill
**PasswordAutosaveEnabled** (`bool`)
- Enable autosaving passwords
**Example:**
```go
@ -728,6 +781,16 @@ Linux: application.LinuxWindow{
- `WebviewGpuPolicyOnDemand` - Enabled/disabled as requested by web contents
- `WebviewGpuPolicyNever` - Hardware acceleration always disabled
**WindowDidMoveDebounceMS** (`uint16`)
- Debounce time in milliseconds for the WindowDidMove event
**Menu** (`*Menu`)
- The window's menu
**MenuStyle** (`LinuxMenuStyle`) - GTK4 only, ignored on GTK3
- `LinuxMenuStyleMenuBar` - Traditional menu bar below the title bar (default)
- `LinuxMenuStylePrimaryMenu` - Primary menu button in the header bar (GNOME style)
**Example:**
```go

View file

@ -9,7 +9,7 @@ import { Card, CardGrid, Tabs, TabItem } from "@astrojs/starlight/components";
## Overview
Wails provides simple commands to build your application for development and production.
Wails uses [Task](https://taskfile.dev) as its build system. The `wails3 build`, `wails3 package`, and `wails3 dev` commands are wrappers around tasks defined in your project's `Taskfile.yml`. You can customize the build process by editing the Taskfile.
## Development Build
@ -28,16 +28,22 @@ wails3 dev
### Dev Options
```bash
# Specify frontend dev server
wails3 dev -devserver http://localhost:5173
# Custom config file
wails3 dev -config ./build/config.yml
# Skip frontend dev server
wails3 dev -nofrontend
# Custom vite dev server port
wails3 dev -port 3000
# Custom build flags
wails3 dev -tags dev
# Enable HTTPS for the dev server
wails3 dev -s
```
The dev server port defaults to `9245`. You can also set the port via the `WAILS_VITE_PORT` environment variable.
:::note
`wails3 dev` 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.
:::
## Production Build
### Basic Build
@ -46,105 +52,103 @@ wails3 dev -tags dev
wails3 build
```
**Output:** Optimized binary in `build/bin/`
**Output:** Binary in `bin/` directory.
### Build Options
### Build with Tags
```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"
# Pass additional Go build tags
wails3 build -tags production,debug
```
## Build Configuration
### Build with CLI Variables
### wails.json
You can pass CLI variables to customize the build via the underlying Taskfile:
```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"
}
}
```bash
wails3 build PRODUCTION=true
```
These variables can be accessed in your `Taskfile.yml` using Go template syntax.
:::note
`wails3 build` is equivalent to running `wails3 task build` which runs the `build` task in the project's main Taskfile. Any CLI variables are forwarded to the underlying task. You can customise the build process by editing the `Taskfile.yml` file.
:::
## Platform-Specific Builds
Platform-specific build tasks are defined in the Taskfiles under `build/<platform>/Taskfile.yml`. The main `Taskfile.yml` dispatches to the correct platform automatically based on the current OS.
<Tabs syncKey="platform">
<TabItem label="Windows" icon="seti:windows">
The Windows Taskfile handles:
- Building the binary with appropriate flags
- Generating `.ico` icon file
- Generating Windows `.syso` file
- Creating NSIS installers for packaging
```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
# Build for Windows (runs automatically on Windows)
wails3 build
```
</TabItem>
<TabItem label="macOS" icon="apple">
The macOS Taskfile handles:
- Building binaries for amd64, arm64, and universal architectures
- Generating `.icns` icon file
- Creating `.app` bundles
- Ad-hoc code signing
```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
# Build for macOS (runs automatically on macOS)
wails3 build
```
</TabItem>
<TabItem label="Linux" icon="linux">
The Linux Taskfile handles:
- Building the binary with appropriate flags
- Generating `.desktop` files
- Creating AppImage, deb, rpm, and Arch Linux packages
```bash
# Linux executable
wails3 build -platform linux/amd64
# With icon
wails3 build -platform linux/amd64 -icon icon.png
# Build for Linux (runs automatically on Linux)
wails3 build
```
</TabItem>
</Tabs>
## Packaging
```bash
wails3 package
```
This creates platform-specific packages for distribution. Like `build`, it is a wrapper around `wails3 task package`.
| Platform | Package Types |
|----------|-------------------------------------------|
| Windows | `.exe`, NSIS installer |
| macOS | `.app` bundle |
| Linux | `.AppImage`, `.deb`, `.rpm`, `.archlinux` |
## Optimization
### Binary Size
```bash
# Strip debug symbols
wails3 build -ldflags "-s -w"
# UPX compression (external tool)
upx --best --lzma build/bin/myapp
upx --best --lzma bin/myapp
```
### Performance
The default Taskfiles already include `-ldflags "-s -w"` to strip debug symbols.
### Build Tags
```bash
# Enable optimizations
# Pass build tags to the Go compiler
wails3 build -tags production
# Disable debug features
wails3 build -ldflags "-X main.debug=false"
```
## Troubleshooting
@ -157,20 +161,19 @@ wails3 build -ldflags "-X main.debug=false"
- Check `go.mod` is up to date
- Run `go mod tidy`
- Verify frontend builds: `cd frontend && npm run build`
- Check wails.json configuration
- Run `wails3 doctor` to check your environment
### Large Binary Size
**Problem:** Binary is too large
**Solutions:**
- Use `-ldflags "-s -w"` to strip symbols
- Ensure Taskfile includes `-ldflags "-s -w"` to strip symbols (default Taskfiles do this)
- 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
- [Build Customization](/guides/build/customization) - Customize the build process with Taskfiles
- [Build System](/concepts/build-system) - Understand how the build system works

View file

@ -37,7 +37,11 @@ wails3 init [flags]
| `-productcopyright` | Copyright notice | ` now, My Company` |
| `-productcomments` | File comments | `This is a comment` |
| `-productidentifier` | Product identifier | |
| `-s` | Skip warning for remote templates | `false` |
| `-skipgomodtidy` | Skip go mod tidy | `false` |
| `-mod` | Go module path | |
The `-mod` flag sets the Go module path for the project. If omitted and `-git` is provided, the module path will be computed from the Git URL.
The `-git` flag accepts various Git URL formats:
- HTTPS: `https://github.com/username/project`
@ -77,12 +81,22 @@ wails3 dev [flags]
Builds a debug version of your application. It defaults to building for the current platform and architecture.
```bash
wails3 build [CLI variables...]
wails3 build [flags] [CLI variables...]
```
#### Flags
| Flag | Description | Default |
|-----------|----------------------------------------------------------|---------|
| `-tags` | Additional build tags to pass to the Go compiler (comma-separated) | |
You can pass CLI variables to customize the build:
```bash
wails3 build PLATFORM=linux CONFIG=production
```
You can also pass additional Go build tags:
```bash
wails3 build -tags production,debug
```
:::note
@ -215,11 +229,12 @@ wails3 generate bindings [flags] [patterns...]
| `-i` | Use TS interfaces | `false` |
| `-b` | Use bundled runtime | `false` |
| `-names` | Use names instead of IDs | `false` |
| `-noevents` | Skip custom event types | `false` |
| `-noindex` | Skip index files | `false` |
| `-dry` | Dry run | `false` |
| `-silent` | Silent mode | `false` |
| `-v` | Debug output | `false` |
| `-clean` | Clean output directory | `false` |
| `-clean` | Clean output directory | `true` |
### `generate build-assets`
Generates build assets for your application.
@ -308,9 +323,15 @@ wails3 generate runtime
Generates JavaScript constants from Go code.
```bash
wails3 generate constants
wails3 generate constants [flags]
```
#### Flags
| Flag | Description | Default |
|------|--------------------------------|----------------|
| `-f` | The Go constants filename | `constants.go` |
| `-o` | The output JavaScript file | `constants.js` |
### `generate appimage`
Generates a Linux AppImage.
@ -435,20 +456,60 @@ 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 | `.` |
| Flag | Description | Default |
|----------------|------------------------------------------------|---------|
| `-format` | Package format (deb, rpm, archlinux, dmg) | `deb` |
| `-name` | Executable name | `myapp` |
| `-config` | Config file path | |
| `-out` | Output directory | `.` |
| `-background` | Path to optional background image for DMG | |
| `-create-dmg` | Create a DMG file (macOS only) | `false` |
### `tool lipo`
Creates a macOS universal binary from multiple architecture-specific binaries.
```bash
wails3 tool lipo [flags]
```
#### Flags
| Flag | Description | Default |
|-----------|--------------------------------------|---------|
| `-format` | Package format (deb, rpm, archlinux) | `deb` |
| `-name` | Executable name | `myapp` |
| `-config` | Config file path | |
| `-out` | Output directory | `.` |
| Flag | Description | Default |
|------------|-------------------------------------------------|---------|
| `-output` (`-o`) | Output path for the universal binary | |
| `-input` (`-i`) | Input binaries to combine (specify multiple times) | |
### `tool capabilities`
Checks system build capabilities (GTK4/GTK3 availability on Linux).
```bash
wails3 tool capabilities
```
### `tool sign`
Signs a binary or package directly.
```bash
wails3 tool sign [flags]
```
#### Flags
| Flag | Description | Default |
|----------------------|------------------------------------------------------|---------|
| `-input` | Path to the file to sign | |
| `-output` | Output path (defaults to in-place signing) | |
| `-verbose` | Enable verbose output | `false` |
| `-certificate` | Path to PKCS#12 (.pfx/.p12) certificate file | |
| `-password` | Certificate password | |
| `-thumbprint` | Certificate thumbprint in Windows certificate store | |
| `-timestamp` | Timestamp server URL | |
| `-identity` | macOS signing identity | |
| `-entitlements` | Path to entitlements plist file | |
| `-hardened-runtime` | Enable hardened runtime | `false` |
| `-notarize` | Submit for Apple notarization after signing | `false` |
| `-keychain-profile` | Keychain profile for notarization credentials | |
| `-pgp-key` | Path to PGP private key file | |
| `-pgp-password` | PGP key password | |
| `-role` | DEB signing role (origin, maint, archive, builder) | |
Base command: `wails3 update`
@ -537,3 +598,99 @@ Opens the Wails sponsorship page in your default browser.
```bash
wails3 sponsor
```
### `doctor-ng`
System status report using the new TUI interface.
```bash
wails3 doctor-ng
```
## Setup Commands
Setup commands provide project configuration wizards. All setup commands use the base command: `wails3 setup <command>`.
### `setup signing`
Configures code signing for your project.
```bash
wails3 setup signing [flags]
```
#### Flags
| Flag | Description | Default |
|--------------|--------------------------------------------------------------|---------|
| `-platform` | Platform(s) to configure (darwin, windows, linux). Auto-detects if not specified. | |
### `setup entitlements`
Configures macOS entitlements for your project.
```bash
wails3 setup entitlements [flags]
```
#### Flags
| Flag | Description | Default |
|------------|--------------------------------------|-------------------------------------|
| `-output` | Output path for entitlements.plist | `build/darwin/entitlements.plist` |
## Sign Command
### `sign`
Signs binaries and packages for the current or specified platform. This is a wrapper that calls platform-specific signing tasks.
```bash
wails3 sign [flags] [args...]
```
## Generate Commands (continued)
### `generate template`
Generates a new Wails template from an existing project.
```bash
wails3 generate template [flags]
```
#### Flags
| Flag | Description | Default |
|-----------------|--------------------------|----------|
| `-name` | Template name | |
| `-shortname` | Short name | |
| `-author` | Template author | |
| `-description` | Template description | |
| `-helpurl` | Help URL | |
| `-version` | Template version | `v0.0.1` |
| `-dir` | Output directory | `.` |
| `-frontend` | Frontend directory to migrate | |
### `generate webview2bootstrapper`
Generates the WebView2 bootstrapper executable for Windows.
```bash
wails3 generate webview2bootstrapper [flags]
```
#### Flags
| Flag | Description | Default |
|--------|---------------------------------|---------|
| `-dir` | Directory to write the file to | |
## iOS Commands
iOS commands provide tooling for iOS development. All iOS commands use the base command: `wails3 ios <command>`.
### `ios overlay:gen`
Generates a Go overlay for the iOS bridge shim.
```bash
wails3 ios overlay:gen
```
### `ios xcode:gen`
Generates an Xcode project in the output directory.
```bash
wails3 ios xcode:gen
```

View file

@ -35,13 +35,6 @@ func main() {
app := application.New(application.Options{
Name: "My Application",
Description: "My awesome application",
Protocols: []application.Protocol{
{
Scheme: "myapp",
Description: "My Application Protocol",
Role: "Editor", // macOS only
},
},
})
// Register handler for protocol events
@ -60,6 +53,12 @@ func handleCustomURL(url string) {
}
```
:::note
Custom protocol schemes are registered at the OS level through your application's build assets
(Info.plist on macOS, NSIS installer on Windows, .desktop files on Linux),
not through `application.Options`. See the [Platform Registration](#platform-registration) section below for details.
:::
## Protocol Handler
Listen for protocol events to handle incoming URLs:
@ -123,7 +122,7 @@ Custom protocols are registered differently on each platform.
#### Automatic Registration
When you build your application with `wails3 build`, the NSIS installer:
1. Automatically registers all protocols defined in `application.Options.Protocols`
1. Automatically registers all protocols defined in your build assets configuration
2. Associates protocols with your application executable
3. Sets up proper registry entries
4. Removes protocol associations during uninstall
@ -136,7 +135,7 @@ The NSIS template includes built-in macros:
- `wails.associateCustomProtocols` - Registers protocols during installation
- `wails.unassociateCustomProtocols` - Removes protocols during uninstall
These macros are automatically called based on your `Protocols` configuration.
These macros are automatically called based on your build assets protocol configuration.
#### Manual Registry (Advanced)
@ -174,7 +173,7 @@ On macOS, protocols are registered via your `Info.plist` file.
Wails automatically generates the `Info.plist` with your protocols when you build with `wails3 build`.
The protocols from `application.Options.Protocols` are added to:
The protocols from your build assets configuration are added to:
```xml
<key>CFBundleURLTypes</key>
@ -278,13 +277,6 @@ func main() {
app := application.New(application.Options{
Name: "DeepLink Demo",
Description: "Custom protocol demonstration",
Protocols: []application.Protocol{
{
Scheme: "deeplink",
Description: "DeepLink Demo Protocol",
Role: "Editor",
},
},
})
myApp := &App{app: app}
@ -556,8 +548,10 @@ Create a test HTML page:
**Check logs:**
```go
import "log/slog"
app := application.New(application.Options{
Logger: application.NewLogger(application.LogLevelDebug),
LogLevel: slog.LevelDebug,
// ...
})
```

View file

@ -168,22 +168,45 @@ Events.On('common:ThemeChanged', (event) => {
### 3. Handling File Drops
Make your app accept dragged files:
Handle file drops in Go using the `WindowEventContext`:
```go
import "github.com/wailsapp/wails/v3/pkg/events"
window.OnWindowEvent(events.Common.WindowFilesDropped, func(e *application.WindowEvent) {
files := e.Context().DroppedFiles()
for _, file := range files {
fmt.Println("File dropped:", file)
}
// Optionally get drop target details
details := e.Context().DropTargetDetails()
if details != nil {
fmt.Println("Dropped on element:", details.ID)
}
// Notify the frontend
window.EmitEvent("files-dropped", files)
})
```
Then listen in JavaScript:
```javascript
import { Events } from '@wailsio/runtime';
Events.On('common:WindowFilesDropped', (event) => {
const files = event.data.files;
files.forEach(file => {
Events.On('files-dropped', (event) => {
event.data.forEach(file => {
console.log('File dropped:', file);
// Process the dropped files
handleFileUpload(file);
});
});
```
:::note
The `common:WindowFilesDropped` system event is dispatched to the JS frontend but does not include the file paths in its data. Use a Go event handler to access the dropped files via `event.Context().DroppedFiles()` and forward them to the frontend with a custom event.
:::
### 4. Window Lifecycle Management
Respond to window state changes:
@ -194,9 +217,6 @@ import { Events } from '@wailsio/runtime';
Events.On('common:WindowClosing', () => {
// Save user data before closing
saveApplicationState();
// You could also prevent closing by returning false
// from a registered window close handler
});
Events.On('common:WindowMaximise', () => {
@ -327,12 +347,8 @@ func init() {
:::caution
`RegisterEvent` is meant to be called at init time and will panic if:
- Arguments are not valid
- The same event name is registered twice with different data types
:::
:::note
It is safe to register the same event multiple times as long as the data type is always the same. This can be useful to ensure an event is registered when any of multiple packages is loaded.
- The event name conflicts with a known system event name
- The same event name is registered more than once
:::
### Benefits of Event Registration
@ -421,18 +437,32 @@ These events work on all platforms:
| Event | Description | When to Use |
|-------|-------------|-------------|
| `common:ApplicationOpenedWithFile` | App launched with a file | Open file passed at launch |
| `common:ApplicationStarted` | Application has fully started | Initialize your app, load saved state |
| `common:WindowRuntimeReady` | Wails runtime is ready | Start making Wails API calls |
| `common:ApplicationLaunchedWithUrl` | App launched via URL scheme | Handle custom URL schemes |
| `common:ThemeChanged` | System theme changed | Update app appearance |
| `common:WindowFocus` | Window gained focus | Resume activities, refresh data |
| `common:WindowLostFocus` | Window lost focus | Pause activities, save state |
| `common:WindowMinimise` | Window was minimized | Pause rendering, reduce resource usage |
| `common:WindowMaximise` | Window was maximized | Adjust layout for full screen |
| `common:WindowRestore` | Window restored from min/max | Return to normal layout |
| `common:WindowClosing` | Window is about to close | Save data, cleanup resources |
| `common:WindowFilesDropped` | Files dropped on window | Handle file imports |
| `common:WindowDidResize` | Window was resized | Adjust layout, rerender charts |
| `common:WindowDidMove` | Window was moved | Update position-dependent features |
| `common:WindowDidResize` | Window was resized | Adjust layout, rerender charts |
| `common:WindowDPIChanged` | DPI scaling changed | Adjust rendering for new DPI |
| `common:WindowFilesDropped` | Files dropped on window | Handle file imports |
| `common:WindowFocus` | Window gained focus | Resume activities, refresh data |
| `common:WindowFullscreen` | Window entered fullscreen | Adjust layout for fullscreen |
| `common:WindowHide` | Window was hidden | Pause rendering |
| `common:WindowLostFocus` | Window lost focus | Pause activities, save state |
| `common:WindowMaximise` | Window was maximized | Adjust layout for full screen |
| `common:WindowMinimise` | Window was minimized | Pause rendering, reduce resource usage |
| `common:WindowToggleFrameless` | Frameless mode toggled | Update window chrome |
| `common:WindowRestore` | Window restored from min/max | Return to normal layout |
| `common:WindowRuntimeReady` | Wails runtime is ready | Start making Wails API calls |
| `common:WindowShow` | Window became visible | Resume rendering |
| `common:WindowUnFullscreen` | Window exited fullscreen | Restore normal layout |
| `common:WindowUnMaximise` | Window restored from maximized | Restore normal layout |
| `common:WindowUnMinimise` | Window restored from minimized | Resume rendering |
| `common:WindowZoom` | Window zoom changed | Adjust content scaling |
| `common:WindowZoomIn` | Zoom level increased | Update zoom indicator |
| `common:WindowZoomOut` | Zoom level decreased | Update zoom indicator |
| `common:WindowZoomReset` | Zoom reset to default | Update zoom indicator |
### Platform-Specific Events

View file

@ -533,7 +533,7 @@ menu.Append(menu.Text("File", nil, []*menu.MenuItem{
```go
menu := application.NewMenu()
fileMenu := menu.AddSubmenu("File")
fileMenu.Add("Quit").OnClick(func(ctx *application.Context) {
fileMenu.Add("Quit").OnClick(func(_ *application.Context) {
app.Quit()
})
```

View file

@ -112,6 +112,62 @@ Sets whether hidden files are shown.
func (d *OpenFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *OpenFileDialogStruct
```
#### ResolvesAliases()
Sets whether aliases (symlinks) are resolved.
```go
func (d *OpenFileDialogStruct) ResolvesAliases(resolvesAliases bool) *OpenFileDialogStruct
```
#### AllowsOtherFileTypes()
Sets whether file types other than those in filters are allowed.
```go
func (d *OpenFileDialogStruct) AllowsOtherFileTypes(allowsOtherFileTypes bool) *OpenFileDialogStruct
```
#### HideExtension()
Sets whether the file extension is hidden.
```go
func (d *OpenFileDialogStruct) HideExtension(hideExtension bool) *OpenFileDialogStruct
```
#### TreatsFilePackagesAsDirectories()
Sets whether file packages are treated as directories (macOS).
```go
func (d *OpenFileDialogStruct) TreatsFilePackagesAsDirectories(treatsFilePackagesAsDirectories bool) *OpenFileDialogStruct
```
#### CanSelectHiddenExtension()
Sets whether hidden extensions can be selected.
```go
func (d *OpenFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *OpenFileDialogStruct
```
#### SetMessage()
Sets the dialog message text.
```go
func (d *OpenFileDialogStruct) SetMessage(message string) *OpenFileDialogStruct
```
#### SetButtonText()
Sets the text for the dialog's confirmation button.
```go
func (d *OpenFileDialogStruct) SetButtonText(text string) *OpenFileDialogStruct
```
#### AttachToWindow()
Attaches the dialog to a specific window (sheet on macOS).
@ -200,6 +256,14 @@ if err != nil {
outputDir = folder
```
### app.Dialog.OpenFileWithOptions()
Creates a file open dialog with pre-set options.
```go
func (dm *DialogManager) OpenFileWithOptions(options *OpenFileDialogOptions) *OpenFileDialogStruct
```
### app.Dialog.SaveFile()
Creates a file save dialog.
@ -213,16 +277,16 @@ func (dm *DialogManager) SaveFile() *SaveFileDialogStruct
dialog := app.Dialog.SaveFile()
```
### SaveFileDialogStruct Methods
### app.Dialog.SaveFileWithOptions()
#### SetTitle()
Sets the dialog title.
Creates a file save dialog with pre-set options.
```go
func (d *SaveFileDialogStruct) SetTitle(title string) *SaveFileDialogStruct
func (dm *DialogManager) SaveFileWithOptions(options *SaveFileDialogOptions) *SaveFileDialogStruct
```
### SaveFileDialogStruct Methods
#### SetFilename()
Sets the default filename.
@ -252,6 +316,78 @@ Sets the initial directory.
func (d *SaveFileDialogStruct) SetDirectory(directory string) *SaveFileDialogStruct
```
#### SetMessage()
Sets the dialog message text.
```go
func (d *SaveFileDialogStruct) SetMessage(message string) *SaveFileDialogStruct
```
#### SetButtonText()
Sets the text for the dialog's confirmation button.
```go
func (d *SaveFileDialogStruct) SetButtonText(text string) *SaveFileDialogStruct
```
#### CanCreateDirectories()
Sets whether new directories can be created in the dialog.
```go
func (d *SaveFileDialogStruct) CanCreateDirectories(canCreateDirectories bool) *SaveFileDialogStruct
```
#### ShowHiddenFiles()
Sets whether hidden files are shown.
```go
func (d *SaveFileDialogStruct) ShowHiddenFiles(showHiddenFiles bool) *SaveFileDialogStruct
```
#### CanSelectHiddenExtension()
Sets whether hidden extensions can be selected.
```go
func (d *SaveFileDialogStruct) CanSelectHiddenExtension(canSelectHiddenExtension bool) *SaveFileDialogStruct
```
#### HideExtension()
Sets whether the file extension is hidden.
```go
func (d *SaveFileDialogStruct) HideExtension(hideExtension bool) *SaveFileDialogStruct
```
#### TreatsFilePackagesAsDirectories()
Sets whether file packages are treated as directories (macOS).
```go
func (d *SaveFileDialogStruct) TreatsFilePackagesAsDirectories(treatsFilePackagesAsDirectories bool) *SaveFileDialogStruct
```
#### AllowsOtherFileTypes()
Sets whether file types other than those in filters are allowed.
```go
func (d *SaveFileDialogStruct) AllowsOtherFileTypes(allowOtherFileTypes bool) *SaveFileDialogStruct
```
#### AttachToWindow()
Attaches the dialog to a specific window (sheet on macOS).
```go
func (d *SaveFileDialogStruct) AttachToWindow(window Window) *SaveFileDialogStruct
```
#### PromptForSingleSelection()
Shows the dialog and returns the save path.
@ -353,6 +489,14 @@ func (d *MessageDialog) AddButton(s string) *Button
**Returns:** `*Button` - The created button
#### AddButtons()
Sets all buttons for the dialog at once, replacing any existing buttons.
```go
func (d *MessageDialog) AddButtons(buttons []*Button) *MessageDialog
```
#### SetDefaultButton()
Sets the default button (activated by pressing Enter).

View file

@ -118,83 +118,83 @@ app.Event.Emit("global-update", data)
## Event Methods (Frontend)
### On()
### Events.On()
Listens for events from Go.
```javascript
import { On } from '@wailsio/runtime'
import { Events } from '@wailsio/runtime'
On(eventName, callback)
Events.On(eventName, callback)
```
**Parameters:**
- `eventName` - Name of the event to listen for
- `callback` - Function called when event is received
- `callback` - Function called with a `WailsEvent` object (`{ name, data, sender }`)
**Returns:** Cleanup function
**Example:**
```javascript
import { On } from '@wailsio/runtime'
import { Events } from '@wailsio/runtime'
// Listen for events
const cleanup = On('data-updated', (data) => {
console.log('Count:', data.count)
console.log('Status:', data.status)
updateUI(data)
const cleanup = Events.On('data-updated', (event) => {
console.log('Count:', event.data.count)
console.log('Status:', event.data.status)
updateUI(event.data)
})
// Later, remove listener
cleanup()
```
### Once()
### Events.Once()
Listens for a single event occurrence.
```javascript
import { Once } from '@wailsio/runtime'
import { Events } from '@wailsio/runtime'
Once(eventName, callback)
Events.Once(eventName, callback)
```
**Example:**
```javascript
import { Once } from '@wailsio/runtime'
import { Events } from '@wailsio/runtime'
// Listen for first occurrence only
Once('initialization-complete', (data) => {
console.log('App initialized!', data)
Events.Once('initialization-complete', (event) => {
console.log('App initialized!', event.data)
// This will only fire once
})
```
### Off()
### Events.Off()
Removes event listeners.
Removes event listeners for the specified event names.
```javascript
import { Off } from '@wailsio/runtime'
import { Events } from '@wailsio/runtime'
Off(...eventNames)
Events.Off(...eventNames)
```
**Example:**
```javascript
import { Off } from '@wailsio/runtime'
import { Events } from '@wailsio/runtime'
Off('my-event', 'other-event')
Events.Off('my-event', 'other-event')
```
### OffAll()
### Events.OffAll()
Removes all event listeners.
```javascript
import { OffAll } from '@wailsio/runtime'
import { Events } from '@wailsio/runtime'
OffAll()
Events.OffAll()
```
## Application Events
@ -326,14 +326,14 @@ func (s *Service) ProcessFiles(files []string) error {
**JavaScript:**
```javascript
import { On, Once } from '@wailsio/runtime'
import { Events } from '@wailsio/runtime'
On('progress', (data) => {
progressBar.style.width = `${data.percent}%`
statusText.textContent = `Processing ${data.file}... (${data.current}/${data.total})`
Events.On('progress', (event) => {
progressBar.style.width = `${event.data.percent}%`
statusText.textContent = `Processing ${event.data.file}... (${event.data.current}/${event.data.total})`
})
Once('processing-complete', () => {
Events.Once('processing-complete', () => {
progressBar.style.width = '100%'
statusText.textContent = 'Complete!'
})
@ -354,10 +354,10 @@ preferencesWindow.EmitEvent("settings-updated", settings)
**JavaScript:**
```javascript
import { On } from '@wailsio/runtime'
import { Events } from '@wailsio/runtime'
On('theme-changed', (theme) => {
document.body.className = theme
Events.On('theme-changed', (event) => {
document.body.className = event.data
})
```
@ -388,11 +388,11 @@ func (s *StateService) UpdateState(key string, value any) {
**JavaScript:**
```javascript
import { On } from '@wailsio/runtime'
import { Events } from '@wailsio/runtime'
On('state-updated', (data) => {
localState[data.key] = data.value
updateUI(data.key, data.value)
Events.On('state-updated', (event) => {
localState[event.data.key] = event.data.value
updateUI(event.data.key, event.data.value)
})
```
@ -462,7 +462,7 @@ app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.App
| `WindowClosing` | Window is about to close | Yes |
| `WindowDidMove` | Window moved | No |
| `WindowDidResize` | Window was resized | No |
| `WindowDPIChanged` | DPI scaling changed (Windows) | No |
| `WindowDPIChanged` | DPI scaling changed | No |
| `WindowFilesDropped` | Files dropped onto window | No |
| `WindowFocus` | Window gained focus | No |
| `WindowFullscreen` | Entered fullscreen | No |
@ -470,12 +470,14 @@ app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *application.App
| `WindowLostFocus` | Window lost focus | No |
| `WindowMaximise` | Window maximized | Yes (macOS) |
| `WindowMinimise` | Window minimized | Yes (macOS) |
| `WindowToggleFrameless` | Frameless mode toggled | No |
| `WindowRestore` | Restored from min/max | No |
| `WindowRuntimeReady` | Wails runtime loaded | No |
| `WindowShow` | Window became visible | No |
| `WindowUnFullscreen` | Exited fullscreen | No |
| `WindowUnMaximise` | Exited maximized state | Yes (macOS) |
| `WindowUnMinimise` | Exited minimized state | Yes (macOS) |
| `WindowZoom` | Zoom level changed | No |
| `WindowZoomIn` | Zoom increased | Yes (macOS) |
| `WindowZoomOut` | Zoom decreased | Yes (macOS) |
| `WindowZoomReset` | Zoom reset to 100% | Yes (macOS) |
@ -584,26 +586,26 @@ func main() {
**JavaScript:**
```javascript
import { On, Once } from '@wailsio/runtime'
import { Events } from '@wailsio/runtime'
import { StartLongTask, BroadcastMessage } from './bindings/EventDemoService'
// Task events
On('task-started', () => {
Events.On('task-started', () => {
document.getElementById('status').textContent = 'Running...'
})
On('task-progress', (data) => {
Events.On('task-progress', (event) => {
const progressBar = document.getElementById('progress')
progressBar.style.width = `${data.percent}%`
progressBar.style.width = `${event.data.percent}%`
})
Once('task-completed', (data) => {
document.getElementById('status').textContent = data.message
Events.Once('task-completed', (event) => {
document.getElementById('status').textContent = event.data.message
})
// Window state events
On('window-state', (state) => {
document.body.dataset.windowState = state
Events.On('window-state', (event) => {
document.body.dataset.windowState = event.data
})
// Trigger long task

View file

@ -37,6 +37,30 @@ You can also create a menu via the manager:
menu := app.Menu.New()
```
### NewMenuFromItems()
Creates a menu from one or more existing menu items.
```go
func NewMenuFromItems(item *MenuItem, items ...*MenuItem) *Menu
```
### NewSubmenu()
Creates a submenu menu item with an existing menu as its content.
```go
func NewSubmenu(label string, items *Menu) *MenuItem
```
### NewContextMenu()
Creates a new context menu and auto-registers it by name.
```go
func NewContextMenu(name string) *ContextMenu
```
## Menu Methods
### Add()
@ -167,7 +191,19 @@ editMenu.AddRole(application.Paste)
editMenu.AddRole(application.SelectAll)
```
**Available Roles:** `AppMenu`, `EditMenu`, `FileMenu`, `ViewMenu`, `WindowMenu`, `HelpMenu`, `Undo`, `Redo`, `Cut`, `Copy`, `Paste`, `PasteAndMatchStyle`, `SelectAll`, `Delete`, `Quit`, `CloseWindow`, `About`, `Reload`, `ForceReload`, `ToggleFullscreen`, `OpenDevTools`, `ResetZoom`, `ZoomIn`, `ZoomOut`, `Minimise`, `Print`, and more.
**Available Roles:**
| Category | Roles |
|----------|-------|
| **Complete Menus** | `AppMenu`, `EditMenu`, `FileMenu`, `ViewMenu`, `WindowMenu`, `SpeechMenu`, `HelpMenu`, `ServicesMenu` |
| **Window Actions** | `CloseWindow`, `Minimise`, `Zoom`, `FullScreen`, `ToggleFullscreen`, `Front`, `ShowAll`, `BringAllToFront` |
| **Edit Actions** | `Undo`, `Redo`, `Cut`, `Copy`, `Paste`, `PasteAndMatchStyle`, `SelectAll`, `Delete` |
| **File Actions** | `NewFile`, `Open`, `Save`, `SaveAs`, `Revert`, `Print`, `PageLayout` |
| **View Actions** | `Reload`, `ForceReload`, `OpenDevTools`, `ResetZoom`, `ZoomIn`, `ZoomOut` |
| **App Actions** | `About`, `Quit`, `Hide`, `HideOthers`, `UnHide` |
| **Find Actions** | `Find`, `FindAndReplace`, `FindNext`, `FindPrevious` |
| **Speech Actions** | `StartSpeaking`, `StopSpeaking` |
| **Other** | `Help`, `NoRole` |
### Update()
@ -341,9 +377,10 @@ func (mi *MenuItem) SetAccelerator(shortcut string) *MenuItem
**Parameters:**
- `shortcut` - Keyboard shortcut string
**Accelerator format:**
- **Modifiers:** `Ctrl`, `Cmd`, `Alt`, `Shift`, `CmdOrCtrl`
- **Keys:** `A-Z`, `0-9`, `F1-F12`, `Enter`, `Backspace`, etc.
**Accelerator format:** `Modifier+Key` joined by `+`. Modifiers are case-insensitive.
- **Modifiers:** `CmdOrCtrl` (aliases: `Cmd`, `Command`), `Ctrl`, `Alt` (aliases: `Option`, `OptionOrAlt`), `Shift`, `Super`
- **Keys:** `A-Z`, `0-9`, `F1-F35`, `Plus`, and named keys: `Backspace`, `Tab`, `Return`, `Enter`, `Escape`, `Space`, `Delete`, `Home`, `End`, `Page Up`, `Page Down`, `Left`, `Right`, `Up`, `Down`, `NumLock`
- **Cross-platform:** Use `CmdOrCtrl` for `Cmd` on macOS and `Ctrl` on Windows/Linux
**Example:**
@ -612,6 +649,16 @@ Sets the menu for the system tray. Returns `*SystemTray` for chaining.
func (s *SystemTray) SetMenu(menu *Menu) *SystemTray
```
#### SetIconPosition()
Sets the icon position relative to the label (macOS). Returns `*SystemTray` for chaining.
```go
func (s *SystemTray) SetIconPosition(iconPosition IconPosition) *SystemTray
```
**Icon position constants:** `NSImageNone`, `NSImageOnly`, `NSImageLeft`, `NSImageRight`, `NSImageBelow`, `NSImageAbove`, `NSImageOverlaps`, `NSImageLeading` (default), `NSImageTrailing`
#### SetLabel()
Sets the tray label text (shown next to icon on macOS).
@ -620,6 +667,14 @@ Sets the tray label text (shown next to icon on macOS).
func (s *SystemTray) SetLabel(label string)
```
#### Label()
Returns the current label text.
```go
func (s *SystemTray) Label() string
```
#### SetTooltip()
Sets the tooltip shown when hovering over the tray icon.
@ -928,7 +983,7 @@ func (e *Editor) updateMenuState() {
- Application menu automatically added with app name
- Use `Cmd` for accelerators (or `CmdOrCtrl` for cross-platform)
- "About", "Preferences", and "Quit" in application menu by default
- "About", "Services", "Hide/Show", and "Quit" in application menu by default
- Use `AddRole(application.AppMenu)` for the standard app menu
### Windows/Linux

View file

@ -290,6 +290,22 @@ window.Fullscreen()
window.UnFullscreen()
```
### ToggleFullscreen()
Toggles the window between fullscreen and normal.
```go
func (w *WebviewWindow) ToggleFullscreen()
```
### ToggleMaximise()
Toggles the window between maximised and normal.
```go
func (w *WebviewWindow) ToggleMaximise()
```
### Restore()
Restores the window to its previous state if it was minimised, maximised, or fullscreen.
@ -404,6 +420,90 @@ Reloads the current window content.
func (w *WebviewWindow) Reload()
```
### ForceReload()
Forces the window to reload the page assets.
```go
func (w *WebviewWindow) ForceReload()
```
## Size Helpers
### Width() / Height()
Returns the current window width or height individually.
```go
func (w *WebviewWindow) Width() int
func (w *WebviewWindow) Height() int
```
### Bounds() / SetBounds()
Gets or sets the DIP (device-independent pixel) bounds of the window as a `Rect`.
```go
func (w *WebviewWindow) Bounds() Rect
func (w *WebviewWindow) SetBounds(bounds Rect)
```
### PhysicalBounds() / SetPhysicalBounds()
Gets or sets the physical pixel bounds of the window.
```go
func (w *WebviewWindow) PhysicalBounds() Rect
func (w *WebviewWindow) SetPhysicalBounds(physicalBounds Rect)
```
### GetBorderSizes()
Returns the border sizes of the window.
```go
func (w *WebviewWindow) GetBorderSizes() *LRTB
```
### Resizable()
Returns `true` if the window is resizable.
```go
func (w *WebviewWindow) Resizable() bool
```
### DisableSizeConstraints() / EnableSizeConstraints()
Temporarily disables or re-enables the min/max size constraints (used internally during maximise/fullscreen).
```go
func (w *WebviewWindow) DisableSizeConstraints()
func (w *WebviewWindow) EnableSizeConstraints()
```
## Window Frame
### ToggleFrameless()
Toggles the window between frameless and normal.
```go
func (w *WebviewWindow) ToggleFrameless()
```
## Menu Bar
### ShowMenuBar() / HideMenuBar() / ToggleMenuBar()
Controls the visibility of the window menu bar.
```go
func (w *WebviewWindow) ShowMenuBar()
func (w *WebviewWindow) HideMenuBar()
func (w *WebviewWindow) ToggleMenuBar()
```
## Window Events
Wails provides two methods for handling window events:
@ -501,7 +601,7 @@ func (w *WebviewWindow) EmitEvent(name string, data ...any) bool
- `name` - Event name
- `data` - Optional data to send with the event
**Returns:** `true` if the event was cancelled
**Returns:** `true` if the event was emitted successfully (not cancelled by a hook)
**Example:**
```go
@ -600,12 +700,13 @@ performLongOperation()
window.SetEnabled(true)
```
### SetIgnoreMouseEvents()
### SetIgnoreMouseEvents() / IsIgnoreMouseEvents()
Makes the window ignore mouse events (click-through). Returns `Window` for chaining. Windows and Mac only.
```go
func (w *WebviewWindow) SetIgnoreMouseEvents(ignore bool) Window
func (w *WebviewWindow) IsIgnoreMouseEvents() bool
```
### SetContentProtection()
@ -656,6 +757,14 @@ Flashes the window's taskbar button/icon. Windows only.
func (w *WebviewWindow) Flash(enabled bool)
```
### SnapAssist()
Triggers the Windows Snap Assist feature by simulating Win+Z key combination. On Windows 11, this opens the snap layout options. On Linux and macOS, this is a no-op.
```go
func (w *WebviewWindow) SnapAssist()
```
### GetScreen()
Returns the screen that the window is on.

View file

@ -459,7 +459,7 @@ In this tutorial, we'll create a QR code generator service to demonstrate these
Description: "A demo of using raw HTML & CSS",
LogLevel: slog.LevelDebug,
Services: []application.Service{
application.NewService(NewQRService(), application.ServiceOptions{
application.NewServiceWithOptions(NewQRService(), application.ServiceOptions{
Route: "/qrservice",
}),
},
@ -549,7 +549,7 @@ In this tutorial, we'll create a QR code generator service to demonstrate these
```go title="main.go" ins={3}
// ...
application.NewService(NewQRService(), application.ServiceOptions{
application.NewServiceWithOptions(NewQRService(), application.ServiceOptions{
Route: "/services/qr",
}),
// ...

View file

@ -45,6 +45,7 @@ In this tutorial, you'll build a notes application that demonstrates file operat
package main
import (
"context"
"encoding/json"
"errors"
"os"
@ -63,11 +64,13 @@ In this tutorial, you'll build a notes application that demonstrates file operat
type NotesService struct {
notes []Note
app *application.App
}
func NewNotesService() *NotesService {
func NewNotesService(app *application.App) *NotesService {
return &NotesService{
notes: make([]Note, 0),
app: app,
}
}
@ -115,7 +118,7 @@ In this tutorial, you'll build a notes application that demonstrates file operat
// SaveToFile saves notes to a file
func (n *NotesService) SaveToFile() error {
path, err := application.SaveFileDialog().
path, err := n.app.Dialog.SaveFile().
SetFilename("notes.json").
AddFilter("JSON Files", "*.json").
PromptForSingleSelection()
@ -133,7 +136,7 @@ In this tutorial, you'll build a notes application that demonstrates file operat
return err
}
application.InfoDialog().
n.app.Dialog.Info().
SetTitle("Success").
SetMessage("Notes saved successfully!").
Show()
@ -143,7 +146,7 @@ In this tutorial, you'll build a notes application that demonstrates file operat
// LoadFromFile loads notes from a file
func (n *NotesService) LoadFromFile() error {
path, err := application.OpenFileDialog().
path, err := n.app.Dialog.OpenFile().
AddFilter("JSON Files", "*.json").
PromptForSingleSelection()
@ -163,7 +166,7 @@ In this tutorial, you'll build a notes application that demonstrates file operat
n.notes = notes
application.InfoDialog().
n.app.Dialog.Info().
SetTitle("Success").
SetMessage("Notes loaded successfully!").
Show()
@ -180,8 +183,8 @@ In this tutorial, you'll build a notes application that demonstrates file operat
- **Note struct**: Defines the data structure with JSON tags (lowercase) for proper serialization
- **CRUD operations**: GetAll, Create, Update, and Delete for managing notes in memory
- **File dialogs**: Uses package-level functions `application.SaveFileDialog()` and `application.OpenFileDialog()`
- **Info dialogs**: Shows success messages using `application.InfoDialog()`
- **File dialogs**: Uses `app.Dialog.SaveFile()` and `app.Dialog.OpenFile()` via the DialogManager
- **Info dialogs**: Shows success messages using `app.Dialog.Info()`
- **ID generation**: Simple timestamp-based ID generator
3. **Update main.go**
@ -206,9 +209,6 @@ In this tutorial, you'll build a notes application that demonstrates file operat
app := application.New(application.Options{
Name: "Notes App",
Description: "A simple notes application",
Services: []application.Service{
application.NewService(NewNotesService()),
},
Assets: application.AssetOptions{
Handler: application.AssetFileServerFS(assets),
},
@ -217,6 +217,9 @@ In this tutorial, you'll build a notes application that demonstrates file operat
},
})
// Register service after app creation so we can pass the app reference
app.RegisterService(application.NewService(NewNotesService(app)))
app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Notes App",
Width: 1000,
@ -234,7 +237,7 @@ In this tutorial, you'll build a notes application that demonstrates file operat
**What's happening here:**
- Registers `NotesService` with the application
- Creates the application, then registers `NotesService` with `app.RegisterService()` so the service can access the app for dialogs
- Creates a window with dimensions (1000x700) mimicking Apple Notes
- Sets up proper macOS behavior to quit when the last window closes
@ -650,19 +653,22 @@ In this tutorial, you'll build a notes application that demonstrates file operat
## Key Concepts
### Package-Level Dialog Functions
### Dialog Functions
In Wails v3, file and message dialogs are package-level functions, not methods on the app instance:
In Wails v3, file and message dialogs are accessed through the app's `Dialog` manager:
```go
// Correct - package-level function
path, err := application.SaveFileDialog().
// Use the DialogManager on the app instance
path, err := app.Dialog.SaveFile().
SetFilename("notes.json").
AddFilter("JSON Files", "*.json").
PromptForSingleSelection()
// Wrong - not available
path, err := app.Dialog.SaveFile() // This doesn't exist
// Info dialog
app.Dialog.Info().
SetTitle("Success").
SetMessage("Notes saved successfully!").
Show()
```
### JSON Tag Mapping