247 lines
5.7 KiB
Text
247 lines
5.7 KiB
Text
|
|
---
|
|||
|
|
title: Extending Wails
|
|||
|
|
description: Practical guide to adding new features and platforms to Wails v3
|
|||
|
|
sidebar:
|
|||
|
|
order: 8
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
> Wails is designed to be **hackable**.
|
|||
|
|
> Every major subsystem lives in Go code you can read, modify, and ship.
|
|||
|
|
> This page shows _where_ to start and _how_ to stay cross-platform when you:
|
|||
|
|
|
|||
|
|
* Add a **Service** (file server, KV store, custom IPC…)
|
|||
|
|
* Create a **new CLI command** (`wails3 <foo>`)
|
|||
|
|
* Extend the **Runtime** (window API, dialogs, events)
|
|||
|
|
* Introduce a **Platform capability** (Wayland, Apple Vision OS…)
|
|||
|
|
* Keep **cross-platform compatibility** without drowning in `//go:build` tags
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Adding a Service
|
|||
|
|
|
|||
|
|
Services are background Go components exposed to apps via the `application.Context`.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
internal/service/
|
|||
|
|
├── template/ # Boilerplate for new services
|
|||
|
|
│ ├── template.go
|
|||
|
|
│ └── README.md
|
|||
|
|
└── service.go # Registration + lifecycle interfaces
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.1 Define the Service
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
package chat
|
|||
|
|
|
|||
|
|
type Service struct {
|
|||
|
|
messages []string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func New() *Service { return &Service{} }
|
|||
|
|
|
|||
|
|
func (s *Service) Send(msg string) string {
|
|||
|
|
s.messages = append(s.messages, msg)
|
|||
|
|
return "ok"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.2 Implement `service.Service`
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
func (s *Service) Init(ctx *application.Context) error { return nil }
|
|||
|
|
func (s *Service) Shutdown() error { return nil }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.3 Register Generator
|
|||
|
|
|
|||
|
|
*Add to* `internal/generator/collect/services.go`
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
services.Register("chat", chat.New)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> Now `wails3 generate` emits bindings so JS can call
|
|||
|
|
> `window.backend.chat.Send("hi")`.
|
|||
|
|
|
|||
|
|
### 1.4 Ship an Example
|
|||
|
|
|
|||
|
|
Copy `v3/examples/services/` and adjust.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. Writing a New CLI Command
|
|||
|
|
|
|||
|
|
CLI logic lives under `internal/commands/`.
|
|||
|
|
Each command is **one file** that mounts itself in `init()`.
|
|||
|
|
|
|||
|
|
### 2.1 Create the File
|
|||
|
|
|
|||
|
|
`v3/internal/commands/hello.go`
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
package commands
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"github.com/spf13/cobra"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
func init() {
|
|||
|
|
Add(&cobra.Command{
|
|||
|
|
Use: "hello",
|
|||
|
|
Short: "Prints Hello World",
|
|||
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|||
|
|
cmd.Println("Hello Wails!")
|
|||
|
|
return nil
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`Add()` registers with the root in `cmd/wails3/main.go`.
|
|||
|
|
|
|||
|
|
### 2.2 Re-compile CLI
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
go install ./v3/cmd/wails3
|
|||
|
|
wails3 hello
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
If your command needs **Taskfile** integration, reuse helpers in
|
|||
|
|
`internal/commands/task_wrapper.go`.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. Modifying the Runtime
|
|||
|
|
|
|||
|
|
Common reasons:
|
|||
|
|
|
|||
|
|
* New window feature (`SetOpacity`, `Shake`, …)
|
|||
|
|
* Extra dialog (`ColorPicker`)
|
|||
|
|
* System-level API (screen brightness)
|
|||
|
|
|
|||
|
|
### 3.1 Public API
|
|||
|
|
|
|||
|
|
Add to `pkg/application/window.go`:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
func (w *WebviewWindow) SetOpacity(o float32) {
|
|||
|
|
w.ctx.Call("window:setOpacity", o)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 Message Processor
|
|||
|
|
|
|||
|
|
Create `messageprocessor_window_opacity.go`:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
const MsgSetOpacity = "window:setOpacity"
|
|||
|
|
|
|||
|
|
func init() { register(MsgSetOpacity, handleSetOpacity) }
|
|||
|
|
|
|||
|
|
func handleSetOpacity(ctx *Context, params json.RawMessage) ([]byte, error) {
|
|||
|
|
var req struct {
|
|||
|
|
ID uint64 `json:"id"`
|
|||
|
|
Opacity float32 `json:"o"`
|
|||
|
|
}
|
|||
|
|
json.Unmarshal(params, &req)
|
|||
|
|
return nil, runtime.SetOpacity(req.ID, req.Opacity)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.3 Platform Implementation
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
internal/runtime/
|
|||
|
|
webview_window_darwin.go
|
|||
|
|
webview_window_linux.go
|
|||
|
|
webview_window_windows.go
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Add `SetOpacity()` to each, guarded by build tags.
|
|||
|
|
If a platform can’t support it, return `ErrCapability`.
|
|||
|
|
|
|||
|
|
### 3.4 Capability Flag
|
|||
|
|
|
|||
|
|
Expose via `internal/capabilities`.
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
const CapOpacity = "window:opacity"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Runtime files `*_darwin.go` etc. should `registerCapability(CapOpacity)` when
|
|||
|
|
successfully initialised.
|
|||
|
|
|
|||
|
|
Apps can query:
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
if application.HasCapability(application.CapOpacity) { ... }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. Adding New Platform Capabilities
|
|||
|
|
|
|||
|
|
Example: **Wayland** clipboard on Linux.
|
|||
|
|
|
|||
|
|
1. Fork `internal/runtime/clipboard_linux.go`.
|
|||
|
|
2. Split file:
|
|||
|
|
* `clipboard_linux_x11.go` → `//go:build linux && !wayland`
|
|||
|
|
* `clipboard_linux_wayland.go` → `//go:build linux && wayland`
|
|||
|
|
3. Add CLI flag `--tags wayland` in `internal/commands/dev.go`
|
|||
|
|
(`goEnv.BuildTags += ",wayland"`).
|
|||
|
|
4. Document in **Asset Server** & README.
|
|||
|
|
|
|||
|
|
> Keep default build tags minimal; reserve opt-in tags for niche features.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. Cross-Platform Compatibility Checklist
|
|||
|
|
|
|||
|
|
| ✅ Step | Why |
|
|||
|
|
|---------|-----|
|
|||
|
|
| Provide **every** public method in all platform files (even if stub) | Keeps build green on all OS/arch |
|
|||
|
|
| Gate platform specifics behind **capabilities** | Let apps degrade gracefully |
|
|||
|
|
| Use **pure Go** first, CGO only when needed | Simplifies cross-compiles |
|
|||
|
|
| Test with `task matrix:test` | Runs `go test ./...` on linux/mac/windows in Docker |
|
|||
|
|
| Document new build tags in `docs/getting-started/installation.mdx` | Users must know flags |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. Debug Builds & Iteration Speed
|
|||
|
|
|
|||
|
|
* `wails3 dev -tags debug` adds extra log hooks (`logger_dev*.go`).
|
|||
|
|
* Use `task reload` to rebuild runtime without restarting the whole app.
|
|||
|
|
* Race detector: `wails3 dev -race` (see `pkg/application/RACE.md`).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. Upstream Contributions
|
|||
|
|
|
|||
|
|
1. Open an **issue** to discuss the idea & design.
|
|||
|
|
2. Follow the techniques above to implement.
|
|||
|
|
3. Add:
|
|||
|
|
* Unit tests (`*_test.go`)
|
|||
|
|
* Example (`v3/examples/<feat>/`)
|
|||
|
|
* Docs (this file or API reference)
|
|||
|
|
4. Ensure `go vet`, `golangci-lint`, and CI matrix pass.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Quick Links
|
|||
|
|
|
|||
|
|
| Area | Location |
|
|||
|
|
|------|----------|
|
|||
|
|
| Services framework | `internal/service/` |
|
|||
|
|
| CLI core | `internal/commands/` |
|
|||
|
|
| Runtime per-OS | `internal/runtime/` |
|
|||
|
|
| Capability helpers| `internal/capabilities/` |
|
|||
|
|
| Taskfile DSL | `tasks/Taskfile.yml` |
|
|||
|
|
| Dev matrix tests | `tasks/events/generate.go` |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
You now have a **roadmap** for bending Wails to your will—add services, sprinkle
|
|||
|
|
CLI magic, hack the runtime, or bring entirely new OS features.
|
|||
|
|
Happy extending!
|