Merge branch 'v3-alpha' into v3-alpha-proposals/mobile

This commit is contained in:
Lea Anthony 2025-08-02 14:26:04 +10:00 committed by GitHub
commit fc968cbebc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
1323 changed files with 63913 additions and 18526 deletions

44
.github/file-labeler.yml vendored Normal file
View file

@ -0,0 +1,44 @@
# File path specific labels
v2-only:
- 'v2/**/*'
v3-alpha:
- 'v3/**/*'
windows:
- '**/*_windows.go'
- 'v2/internal/frontend/desktop/windows/**/*'
macos:
- '**/*_darwin.go'
- 'v2/internal/frontend/desktop/darwin/**/*'
linux:
- '**/*_linux.go'
- 'v2/internal/frontend/desktop/linux/**/*'
cli:
- 'v2/cmd/**/*'
- 'v3/cmd/**/*'
- '**/cli/**/*'
- '**/commands/**/*'
documentation:
- '**/*.md'
- 'docs/**/*'
- 'website/**/*'
- 'mkdocs-website/**/*'
templates:
- '**/templates/**/*'
- '**/template/**/*'
runtime:
- '**/runtime/**/*'
- 'v2/internal/runtime/**/*'
- 'v3/internal/runtime/**/*'
bindings:
- 'v2/internal/binding/**/*'
- 'v3/internal/generator/**/*'

144
.github/issue-labeler.yml vendored Normal file
View file

@ -0,0 +1,144 @@
# Version labels
v2-only:
- '\[v2\]'
- '\(v2\)'
- 'v2:'
- 'version 2'
- 'wails v2'
- 'using v2'
- 'master branch'
v3-alpha:
- '\[v3\]'
- '\(v3\)'
- 'v3:'
- '\[v3-alpha\]'
- '\(v3-alpha\)'
- 'version 3'
- 'wails v3'
- 'using v3'
- 'v3-alpha branch'
# Component labels
webview2:
- 'webview2'
- 'windows'
- 'microsoft edge'
- 'edge browser'
- 'IE'
- 'Explorer'
- 'browser crashes'
macos:
- 'macOS'
- 'mac OS'
- 'OS X'
- 'darwin'
- 'cocoa'
- 'Safari'
- 'Catalyst'
- 'Ventura'
- 'Sonoma'
- 'apple'
linux:
- 'linux'
- 'ubuntu'
- 'debian'
- 'fedora'
- 'gtk'
- 'webkitgtk'
- 'webkit2gtk'
- 'gnome'
- 'x11'
- 'wayland'
cli:
- 'cli'
- 'command line'
- 'wails doctor'
- 'wails init'
- 'wails build'
- 'wails dev'
- 'template'
- 'scaffolding'
# Type labels
bug:
- 'bug'
- 'crash'
- 'broken'
- 'failure'
- 'error'
- 'failed'
- 'panic'
- 'segfault'
- 'issue'
- 'not working'
- 'problem'
enhancement:
- 'feature'
- 'enhancement'
- 'request'
- 'add'
- 'new'
- 'improve'
- 'functionality'
- 'support for'
- 'please add'
- 'would be nice'
documentation:
- 'docs'
- 'documentation'
- 'readme'
- 'example'
- 'tutorial'
- 'guide'
- 'explanation'
- 'clarification'
- 'instructions'
security:
- 'security'
- 'vulnerability'
- 'exploit'
- 'hack'
- 'CVE'
- 'secure'
- 'encryption'
- 'hardening'
performance:
- 'performance'
- 'slow'
- 'speed'
- 'memory leak'
- 'cpu usage'
- 'high memory'
- 'lag'
- 'freeze'
- 'optimization'
# Priority labels
high-priority:
- 'urgent'
- 'critical'
- 'security'
- 'high priority'
- 'important'
- 'production'
- 'blocker'
- 'blocking'
question:
- 'how to'
- 'how do i'
- 'can I'
- 'is it possible'
- 'question'
- 'help me'
- 'need help'
- 'assistance'
- 'confused'

View file

@ -6,6 +6,7 @@
* YOUR PR MAY BE REJECTED IF IT DOES NOT FOLLOW THESE STEPS *
*********************************************************************
- *DO NOT* submit bugs for a source install of v3, ONLY tagged versions, e.g. v3.0.0-alpha.11
- *DO NOT* submit PRs for v3 alpha enhancements, unless you have opened a post on the discord channel.
All enhancements must be discussed first.
The feedback guide for v3 is here: https://v3alpha.wails.io/getting-started/feedback/
@ -47,7 +48,7 @@ Please paste the output of `wails doctor`. If you are unable to run this command
# Checklist:
- [ ] I have updated `website/src/pages/changelog.mdx` with details of this PR
- [ ] I have updated `v3/UNRELEASED_CHANGELOG.md` with details of this PR
- [ ] My code follows the general coding style of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas

26
.github/stale.yml vendored
View file

@ -1,7 +1,7 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 30
daysUntilStale: 45
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
daysUntilClose: 10
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
@ -9,14 +9,28 @@ exemptLabels:
- onhold
- inprogress
- "Selected For Development"
- bug
- enhancement
- v3-alpha
- high-priority
# Label to use when marking an issue as stale
staleLabel: "Wont Fix"
staleLabel: "stale"
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
recent activity. It will be closed if no further activity occurs within the next 10 days.
If this issue is still relevant, please add a comment to keep it open.
Thank you for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
closeComment: >
This issue has been automatically closed due to lack of activity.
Please feel free to reopen it if it's still relevant.
exemptMilestones: true
exemptAssignees: true
# Only mark issues (not PRs)
only: issues
# Exempt issues created before a certain date
exemptCreatedBefore: "2024-01-01T00:00:00Z"
# Starts checking issues only after the specified date
startDate: "2025-06-01T00:00:00Z"

33
.github/workflows/auto-label-issues.yml vendored Normal file
View file

@ -0,0 +1,33 @@
name: Auto Label Issues
on:
issues:
types: [opened, edited, reopened]
pull_request_target:
types: [opened, edited, reopened, synchronize]
jobs:
auto-label:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Label issues and PRs by content
uses: github/issue-labeler@v3.4
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
configuration-path: .github/issue-labeler.yml
enable-versioned-regex: 0
include-title: 1
- name: Label issues and PRs by file paths
uses: actions/labeler@v4
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
configuration-path: .github/file-labeler.yml
sync-labels: true

372
.github/workflows/automated-releases.yml vendored Normal file
View file

@ -0,0 +1,372 @@
name: Automated Nightly Releases
on:
workflow_dispatch:
inputs:
force_release:
description: 'Force release even if no changes detected'
required: false
default: false
type: boolean
dry_run:
description: 'Run in dry-run mode (no actual releases)'
required: false
default: false
type: boolean
# schedule:
# Run at 2 AM UTC every day - DISABLED for safety until ready
# - cron: '0 2 * * *'
env:
GO_VERSION: '1.24'
jobs:
check-permissions:
name: Check Release Permissions
runs-on: ubuntu-latest
outputs:
authorized: ${{ steps.check.outputs.authorized }}
steps:
- name: Check if user is authorized for releases
id: check
run: |
# Only allow specific users to trigger releases
AUTHORIZED_USERS="leaanthony"
if [[ "$AUTHORIZED_USERS" == *"${{ github.actor }}"* ]]; then
echo "✅ User ${{ github.actor }} is authorized for releases"
echo "authorized=true" >> $GITHUB_OUTPUT
else
echo "❌ User ${{ github.actor }} is not authorized for releases"
echo "authorized=false" >> $GITHUB_OUTPUT
fi
detect-v2-changes:
name: Detect v2 Changes
runs-on: ubuntu-latest
needs: check-permissions
if: needs.check-permissions.outputs.authorized == 'true'
outputs:
has_changes: ${{ steps.changes.outputs.has_changes }}
commits_since_last: ${{ steps.changes.outputs.commits_since_last }}
last_release_tag: ${{ steps.changes.outputs.last_release_tag }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Check for v2 changes since last release
id: changes
run: |
echo "🔍 Checking for v2 changes since last release..."
# Find the last v2 release tag
LAST_TAG=$(git tag -l "v2.*" --sort=-version:refname | head -n 1)
if [ -z "$LAST_TAG" ]; then
echo "No previous v2 tags found, assuming first release"
LAST_TAG=$(git rev-list --max-parents=0 HEAD)
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "commits_since_last=999" >> $GITHUB_OUTPUT
echo "last_release_tag=none" >> $GITHUB_OUTPUT
else
echo "Last v2 release tag: $LAST_TAG"
echo "last_release_tag=$LAST_TAG" >> $GITHUB_OUTPUT
# Count commits since last release affecting v2 or root files
COMMITS_COUNT=$(git rev-list --count ${LAST_TAG}..HEAD -- v2/ website/ README.md CHANGELOG.md || echo "0")
echo "Commits since last v2 release: $COMMITS_COUNT"
echo "commits_since_last=$COMMITS_COUNT" >> $GITHUB_OUTPUT
if [ "$COMMITS_COUNT" -gt 0 ] || [ "${{ github.event.inputs.force_release }}" == "true" ]; then
echo "✅ Changes detected or forced release"
echo "has_changes=true" >> $GITHUB_OUTPUT
else
echo " No changes detected since last release"
echo "has_changes=false" >> $GITHUB_OUTPUT
fi
fi
detect-v3-changes:
name: Detect v3-alpha Changes
runs-on: ubuntu-latest
needs: check-permissions
if: needs.check-permissions.outputs.authorized == 'true'
outputs:
has_changes: ${{ steps.changes.outputs.has_changes }}
commits_since_last: ${{ steps.changes.outputs.commits_since_last }}
last_release_tag: ${{ steps.changes.outputs.last_release_tag }}
steps:
- name: Checkout v3-alpha branch
uses: actions/checkout@v4
with:
ref: v3-alpha
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Check for v3-alpha changes since last release
id: changes
run: |
echo "🔍 Checking for v3-alpha changes since last release..."
# Find the last v3 alpha release tag
LAST_TAG=$(git tag -l "v3.*-alpha.*" --sort=-version:refname | head -n 1)
if [ -z "$LAST_TAG" ]; then
echo "No previous v3-alpha tags found, assuming first release"
LAST_TAG=$(git rev-list --max-parents=0 HEAD)
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "commits_since_last=999" >> $GITHUB_OUTPUT
echo "last_release_tag=none" >> $GITHUB_OUTPUT
else
echo "Last v3-alpha release tag: $LAST_TAG"
echo "last_release_tag=$LAST_TAG" >> $GITHUB_OUTPUT
# Count commits since last release affecting v3 or docs
COMMITS_COUNT=$(git rev-list --count ${LAST_TAG}..HEAD -- v3/ docs/ || echo "0")
echo "Commits since last v3-alpha release: $COMMITS_COUNT"
echo "commits_since_last=$COMMITS_COUNT" >> $GITHUB_OUTPUT
if [ "$COMMITS_COUNT" -gt 0 ] || [ "${{ github.event.inputs.force_release }}" == "true" ]; then
echo "✅ Changes detected or forced release"
echo "has_changes=true" >> $GITHUB_OUTPUT
else
echo " No changes detected since last release"
echo "has_changes=false" >> $GITHUB_OUTPUT
fi
fi
release-v2:
name: Create v2 Release
runs-on: ubuntu-latest
needs: [check-permissions, detect-v2-changes]
if: |
needs.check-permissions.outputs.authorized == 'true' &&
needs.detect-v2-changes.outputs.has_changes == 'true'
outputs:
version: ${{ steps.release.outputs.version }}
release_notes: ${{ steps.release.outputs.release_notes }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}
- name: Run v2 release script and extract notes
id: release
run: |
echo "🚀 Running v2 release script..."
cd v2/tools/release
# Run release script and capture output
RELEASE_OUTPUT=$(go run release.go 2>&1)
echo "$RELEASE_OUTPUT"
# Extract version from output or version file
NEW_VERSION=$(cat ../../cmd/wails/internal/version.txt)
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
# Extract release notes from delimited output
RELEASE_NOTES=$(echo "$RELEASE_OUTPUT" | sed -n '/=== RELEASE NOTES FOR/,/=== END RELEASE NOTES ===/p' | sed '1d;$d')
# Save release notes to file for multiline output
echo "$RELEASE_NOTES" > ../../../release_notes_v2.md
# Set output (escape for GitHub Actions)
{
echo "release_notes<<EOF"
echo "$RELEASE_NOTES"
echo "EOF"
} >> $GITHUB_OUTPUT
echo "✅ v2 release script completed - version: $NEW_VERSION"
- name: Create v2 git tag and release
if: github.event.inputs.dry_run != 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ steps.release.outputs.version }}"
echo "📝 Creating v2 release: $VERSION"
# Configure git
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
# Commit the changelog changes
git add website/src/pages/changelog.mdx v2/cmd/wails/internal/version.txt
git commit -m "chore: release $VERSION
Automated release created by GitHub Actions
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>"
# Create and push tag
git tag -a "$VERSION" -m "Release $VERSION"
git push origin master
git push origin "$VERSION"
# Create GitHub release with notes
gh release create "$VERSION" \
--title "Release $VERSION" \
--notes-file release_notes_v2.md \
--target master
- name: Log dry-run results for v2
if: github.event.inputs.dry_run == 'true'
run: |
echo "🧪 DRY RUN - Would have created v2 release:"
echo "Version: ${{ steps.release.outputs.version }}"
echo "Release Notes:"
cat release_notes_v2.md
release-v3:
name: Create v3-alpha Release
runs-on: ubuntu-latest
needs: [check-permissions, detect-v3-changes]
if: |
needs.check-permissions.outputs.authorized == 'true' &&
needs.detect-v3-changes.outputs.has_changes == 'true'
outputs:
version: ${{ steps.release.outputs.version }}
release_notes: ${{ steps.release.outputs.release_notes }}
steps:
- name: Checkout v3-alpha branch
uses: actions/checkout@v4
with:
ref: v3-alpha
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}
- name: Run v3 release script and extract notes
id: release
run: |
echo "🚀 Running v3-alpha release script..."
cd v3/tasks/release
# Run release script and capture output
RELEASE_OUTPUT=$(go run release.go 2>&1)
echo "$RELEASE_OUTPUT"
# Extract version from output or version file
NEW_VERSION=$(cat ../../internal/version/version.txt)
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
# Extract release notes from delimited output
RELEASE_NOTES=$(echo "$RELEASE_OUTPUT" | sed -n '/=== RELEASE NOTES FOR/,/=== END RELEASE NOTES ===/p' | sed '1d;$d')
# Save release notes to file for multiline output
echo "$RELEASE_NOTES" > ../../../release_notes_v3.md
# Set output (escape for GitHub Actions)
{
echo "release_notes<<EOF"
echo "$RELEASE_NOTES"
echo "EOF"
} >> $GITHUB_OUTPUT
echo "✅ v3-alpha release script completed - version: $NEW_VERSION"
- name: Create v3-alpha git tag and release
if: github.event.inputs.dry_run != 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ steps.release.outputs.version }}"
echo "📝 Creating v3-alpha release: $VERSION"
# Configure git
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
# Commit the changelog changes
git add docs/src/content/docs/changelog.mdx v3/internal/version/version.txt
git commit -m "chore: release $VERSION
Automated v3-alpha release created by GitHub Actions
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>"
# Create and push tag
git tag -a "$VERSION" -m "Release $VERSION"
git push origin v3-alpha
git push origin "$VERSION"
# Create GitHub release with notes
gh release create "$VERSION" \
--title "Release $VERSION" \
--notes-file release_notes_v3.md \
--target v3-alpha \
--prerelease
- name: Log dry-run results for v3-alpha
if: github.event.inputs.dry_run == 'true'
run: |
echo "🧪 DRY RUN - Would have created v3-alpha release:"
echo "Version: ${{ steps.release.outputs.version }}"
echo "Release Notes:"
cat release_notes_v3.md
summary:
name: Release Summary
runs-on: ubuntu-latest
needs: [check-permissions, detect-v2-changes, detect-v3-changes, release-v2, release-v3]
if: always() && needs.check-permissions.outputs.authorized == 'true'
steps:
- name: Create release summary
run: |
echo "# 🚀 Automated Release Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Repository**: ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY
echo "**Triggered by**: ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
echo "**Dry Run Mode**: ${{ github.event.inputs.dry_run || 'false' }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# v2 Summary
echo "## v2 Release" >> $GITHUB_STEP_SUMMARY
if [ "${{ needs.detect-v2-changes.outputs.has_changes }}" == "true" ]; then
if [ "${{ needs.release-v2.result }}" == "success" ]; then
echo "✅ **v2 Release**: Created successfully" >> $GITHUB_STEP_SUMMARY
echo " - Version: ${{ needs.release-v2.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo " - Commits since last: ${{ needs.detect-v2-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **v2 Release**: Failed" >> $GITHUB_STEP_SUMMARY
fi
else
echo "⏭️ **v2 Release**: Skipped (no changes)" >> $GITHUB_STEP_SUMMARY
echo " - Commits since last: ${{ needs.detect-v2-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY
fi
# v3 Summary
echo "## v3-alpha Release" >> $GITHUB_STEP_SUMMARY
if [ "${{ needs.detect-v3-changes.outputs.has_changes }}" == "true" ]; then
if [ "${{ needs.release-v3.result }}" == "success" ]; then
echo "✅ **v3-alpha Release**: Created successfully" >> $GITHUB_STEP_SUMMARY
echo " - Version: ${{ needs.release-v3.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo " - Commits since last: ${{ needs.detect-v3-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **v3-alpha Release**: Failed" >> $GITHUB_STEP_SUMMARY
fi
else
echo "⏭️ **v3-alpha Release**: Skipped (no changes)" >> $GITHUB_STEP_SUMMARY
echo " - Commits since last: ${{ needs.detect-v3-changes.outputs.commits_since_last }}" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "🤖 **Automated Release System** | Generated with [Claude Code](https://claude.ai/code)" >> $GITHUB_STEP_SUMMARY

View file

@ -3,13 +3,20 @@ name: Build + Test v3
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches:
- v3-alpha
paths:
- 'v3/**'
pull_request_review:
types: [submitted]
branches:
- v3-alpha
jobs:
check_approval:
name: Check PR Approval
runs-on: ubuntu-latest
if: github.base_ref == 'v3-alpha'
outputs:
approved: ${{ steps.check.outputs.approved }}
steps:
@ -22,59 +29,11 @@ jobs:
echo "approved=false" >> $GITHUB_OUTPUT
fi
test_go:
name: Run Go Tests
needs: check_approval
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
go-version: [1.23]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install linux dependencies
uses: awalsh128/cache-apt-pkgs-action@latest
if: matrix.os == 'ubuntu-latest'
with:
packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config
version: 1.0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache-dependency-path: "v3/go.sum"
- name: Install Task
uses: arduino/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Build Examples
working-directory: ./v3
run: task test:examples
- name: Run tests (mac)
if: matrix.os == 'macos-latest'
env:
CGO_LDFLAGS: -framework UniformTypeIdentifiers -mmacosx-version-min=10.13
working-directory: ./v3
run: go test -v ./...
- name: Run tests (!mac)
if: matrix.os != 'macos-latest'
working-directory: ./v3
run: go test -v ./...
test_js:
name: Run JS Tests
needs: check_approval
runs-on: ubuntu-latest
if: github.base_ref == 'v3-alpha'
strategy:
matrix:
node-version: [20.x]
@ -88,57 +47,158 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- name: Install Task
uses: arduino/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install dependencies
run: npm install
working-directory: v2/internal/frontend/runtime
working-directory: v3/internal/runtime/desktop/@wailsio/runtime
run: |
npm ci
npx --yes esbuild@latest --version
- name: Run tests
run: npm test
working-directory: v2/internal/frontend/runtime
- name: Clean build artifacts
working-directory: v3/internal/runtime/desktop/@wailsio/runtime
run: npm run clean
test_templates:
name: Test Templates
needs: test_go
- name: Type-check runtime
working-directory: v3
run: task runtime:check
- name: Test runtime
working-directory: v3
run: task runtime:test
- name: Check that the bundled runtime builds
working-directory: v3
run: task runtime:build
- name: Check that the npm package builds
working-directory: v3/internal/runtime/desktop/@wailsio/runtime
run: npm run build
- name: Store runtime build artifacts
uses: actions/upload-artifact@v4
with:
name: runtime-build-artifacts
path: |
v3/internal/runtime/desktop/@wailsio/runtime/dist/
v3/internal/runtime/desktop/@wailsio/runtime/types/
v3/internal/runtime/desktop/@wailsio/runtime/tsconfig.tsbuildinfo
test_go:
name: Run Go Tests v3
needs: [check_approval, test_js]
runs-on: ${{ matrix.os }}
if: github.base_ref == 'v3-alpha'
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
template:
[
svelte,
svelte-ts,
vue,
vue-ts,
react,
react-ts,
preact,
preact-ts,
lit,
lit-ts,
vanilla,
vanilla-ts,
]
go-version: [1.23]
os: [windows-latest, ubuntu-latest, macos-latest]
go-version: [1.24]
steps:
- name: Checkout
- name: Checkout code
uses: actions/checkout@v4
- name: Install linux dependencies
uses: awalsh128/cache-apt-pkgs-action@latest
if: matrix.os == 'ubuntu-latest'
with:
packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config xvfb x11-xserver-utils at-spi2-core xdg-desktop-portal-gtk
version: 1.0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache: true
cache-dependency-path: "v3/go.sum"
- name: Setup Golang caches
uses: actions/cache@v4
- name: Install Task
uses: arduino/setup-task@v2
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-golang-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-golang-
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Retrieve runtime build artifacts
uses: actions/download-artifact@v4
with:
name: runtime-build-artifacts
path: v3/internal/runtime/desktop/@wailsio/runtime/
- name: Build Examples
working-directory: v3
run: |
echo "Starting example compilation tests..."
task test:examples
echo "Example compilation tests completed successfully"
- name: Run tests (mac)
if: matrix.os == 'macos-latest'
env:
CGO_LDFLAGS: -framework UniformTypeIdentifiers -mmacosx-version-min=10.13
working-directory: v3
run: go test -v ./...
- name: Run tests (windows)
if: matrix.os == 'windows-latest'
working-directory: v3
run: go test -v ./...
- name: Run tests (ubuntu)
if: matrix.os == 'ubuntu-latest'
working-directory: v3
run: >
xvfb-run --auto-servernum
sh -c '
dbus-update-activation-environment --systemd --all &&
go test -v ./...
'
- name: Typecheck binding generator output
working-directory: v3
run: task generator:test:check
cleanup:
name: Cleanup build artifacts
if: always()
needs: [test_js, test_go]
runs-on: ubuntu-latest
steps:
- uses: geekyeggo/delete-artifact@v5
with:
name: runtime-build-artifacts
failOnError: false
test_templates:
name: Test Templates
needs: test_go
runs-on: ${{ matrix.os }}
if: github.base_ref == 'v3-alpha'
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
template:
- svelte
- svelte-ts
- vue
- vue-ts
- react
- react-ts
- preact
- preact-ts
- lit
- lit-ts
- vanilla
- vanilla-ts
go-version: [1.24]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install linux dependencies
uses: awalsh128/cache-apt-pkgs-action@latest
@ -147,17 +207,50 @@ jobs:
packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config
version: 1.0
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache: true
cache-dependency-path: "v3/go.sum"
- name: Install Task
uses: arduino/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Build Wails3 CLI
working-directory: v3
run: |
cd ./v3/cmd/wails3
go install
wails3 -help
task install
wails3 doctor
- name: Generate template '${{ matrix.template }}'
run: |
go install github.com/go-task/task/v3/cmd/task@latest
mkdir -p ./test-${{ matrix.template }}
cd ./test-${{ matrix.template }}
wails3 init -n ${{ matrix.template }} -t ${{ matrix.template }}
cd ${{ matrix.template }}
wails3 build
wails3 build
build_results:
if: ${{ always() }}
runs-on: ubuntu-latest
name: v3 Build Results
needs: [test_go, test_js, test_templates]
steps:
- run: |
go_result="${{ needs.test_go.result }}"
js_result="${{ needs.test_js.result }}"
templates_result="${{ needs.test_templates.result }}"
if [[ $go_result == "success" || $go_result == "skipped" ]] && \
[[ $js_result == "success" || $js_result == "skipped" ]] && \
[[ $templates_result == "success" || $templates_result == "skipped" ]]; then
echo "All required jobs succeeded or were skipped"
exit 0
else
echo "One or more required jobs failed"
exit 1
fi

View file

@ -0,0 +1,74 @@
name: Changelog Validation (v3)
on:
pull_request:
branches: [ v3-alpha ]
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to validate (for manual testing)'
required: true
type: string
jobs:
validate-changelog:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' || github.event.inputs.pr_number
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || format('refs/pull/{0}/head', github.event.inputs.pr_number) }}
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: '1.23'
- name: Get PR information
id: pr_info
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo "pr_number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
echo "base_ref=${{ github.event.pull_request.base.ref }}" >> $GITHUB_OUTPUT
else
echo "pr_number=${{ github.event.inputs.pr_number }}" >> $GITHUB_OUTPUT
echo "base_ref=v3-alpha" >> $GITHUB_OUTPUT
fi
- name: Check if changelog was modified
id: changelog_check
run: |
git fetch origin ${{ steps.pr_info.outputs.base_ref }}
if git diff --name-only origin/${{ steps.pr_info.outputs.base_ref }}..HEAD | grep -q "v3/UNRELEASED_CHANGELOG.md"; then
echo "changelog_modified=true" >> $GITHUB_OUTPUT
echo "✅ UNRELEASED_CHANGELOG.md was modified in this PR"
else
echo "changelog_modified=false" >> $GITHUB_OUTPUT
echo "⚠️ UNRELEASED_CHANGELOG.md was not modified"
fi
- name: Comment on PR about missing changelog
if: steps.changelog_check.outputs.changelog_modified == 'false' && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const author = context.payload.pull_request.user.login;
const message = '## ⚠️ Missing Changelog Update\n\n' +
`Hi @${author}, please update \`v3/UNRELEASED_CHANGELOG.md\` with a description of your changes.\n\n` +
'This helps us keep track of changes for the next release.';
await github.rest.issues.createComment({
issue_number: ${{ steps.pr_info.outputs.pr_number }},
owner: context.repo.owner,
repo: context.repo.repo,
body: message
});

View file

@ -29,7 +29,12 @@ jobs:
with:
commit-message: "chore: update sponsors.svg"
add-paths: "website/static/img/sponsors.svg"
title: Update Sponsor Image
body: Generated new image
title: "chore: update sponsors.svg"
body: |
Auto-generated by the sponsor image workflow
[skip ci] [skip actions]
branch: update-sponsors
base: master
delete-branch: true
draft: false

View file

@ -0,0 +1,77 @@
name: Issue Triage Automation
on:
issues:
types: [opened, reopened, labeled, unlabeled]
jobs:
triage:
runs-on: ubuntu-latest
permissions:
issues: write
contents: read
steps:
# Request more info for unclear bug reports
- name: Request more info
uses: actions/github-script@v6
if: |
contains(github.event.issue.labels.*.name, 'bug') &&
!contains(github.event.issue.body, 'wails doctor') &&
!contains(github.event.issue.body, 'reproduction')
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `👋 Thanks for reporting this issue! To help us investigate, could you please:
1. Add the output of \`wails doctor\` if not already included
2. Provide clear steps to reproduce the issue
3. If possible, create a minimal reproduction of the issue
This will help us resolve your issue much faster. Thank you!`
});
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['awaiting feedback']
});
# Prioritize security issues
- name: Prioritize security issues
uses: actions/github-script@v6
if: contains(github.event.issue.labels.*.name, 'security')
with:
script: |
github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['high-priority']
});
# Tag version-specific issues for project boards
- name: Add to v2 project
uses: actions/github-script@v6
if: |
contains(github.event.issue.labels.*.name, 'v2-only') &&
!contains(github.event.issue.labels.*.name, 'v3-alpha')
with:
script: |
// Replace PROJECT_ID with your actual GitHub project ID
// This is a placeholder as the actual implementation would require
// GraphQL API calls to add to a project board
console.log('Would add to v2 project board');
# Tag version-specific issues for project boards
- name: Add to v3 project
uses: actions/github-script@v6
if: contains(github.event.issue.labels.*.name, 'v3-alpha')
with:
script: |
// Replace PROJECT_ID with your actual GitHub project ID
// This is a placeholder as the actual implementation would require
// GraphQL API calls to add to a project board
console.log('Would add to v3 project board');

723
.github/workflows/nightly-release-v3.yml vendored Normal file
View file

@ -0,0 +1,723 @@
name: Nightly Release v3-alpha
on:
schedule:
- cron: '0 2 * * *' # 2 AM UTC daily
workflow_dispatch:
inputs:
force_release:
description: 'Force release even if no changes detected'
required: false
default: false
type: boolean
dry_run:
description: 'Run in dry-run mode (no actual release)'
required: false
default: true
type: boolean
jobs:
nightly-release:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: read
actions: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: v3-alpha
fetch-depth: 0
token: ${{ secrets.WAILS_REPO_TOKEN || github.token }}
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: '1.24'
cache: true
cache-dependency-path: 'v3/go.sum'
- name: Install Task
uses: arduino/setup-task@v2
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Git
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
# Configure git to use the token for authentication
git config --global url."https://x-access-token:${{ secrets.WAILS_REPO_TOKEN || github.token }}@github.com/".insteadOf "https://github.com/"
- name: Check for existing release tag
id: check_tag
run: |
if git describe --tags --exact-match HEAD 2>/dev/null; then
echo "has_tag=true" >> $GITHUB_OUTPUT
echo "tag=$(git describe --tags --exact-match HEAD)" >> $GITHUB_OUTPUT
else
echo "has_tag=false" >> $GITHUB_OUTPUT
echo "tag=" >> $GITHUB_OUTPUT
fi
- name: Check for unreleased changelog content
id: changelog_check
run: |
echo "🔍 Checking UNRELEASED_CHANGELOG.md for content..."
# Run the release script in check mode to see if there's content
cd v3/tasks/release
# Use the release script itself to check for content
if go run release.go --check-only 2>/dev/null; then
echo "has_unreleased_content=true" >> $GITHUB_OUTPUT
echo "✅ Found unreleased changelog content"
else
echo "has_unreleased_content=false" >> $GITHUB_OUTPUT
echo " No unreleased changelog content found"
fi
- name: Quick change detection and early exit
id: quick_check
run: |
echo "🔍 Quick check for changes to determine if we should continue..."
# First check if we have unreleased changelog content
if [ "${{ steps.changelog_check.outputs.has_unreleased_content }}" == "true" ]; then
echo "✅ Found unreleased changelog content, proceeding with release"
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "should_continue=true" >> $GITHUB_OUTPUT
echo "reason=Found unreleased changelog content" >> $GITHUB_OUTPUT
exit 0
fi
# If no unreleased changelog content, check for git changes as fallback
echo "No unreleased changelog content found, checking for git changes..."
# Check if current commit has a release tag
if git describe --tags --exact-match HEAD 2>/dev/null; then
CURRENT_TAG=$(git describe --tags --exact-match HEAD)
echo "Current commit has release tag: $CURRENT_TAG"
# For tagged commits, check if there are changes since the tag
COMMIT_COUNT=$(git rev-list ${CURRENT_TAG}..HEAD --count)
if [ "$COMMIT_COUNT" -eq 0 ]; then
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "should_continue=false" >> $GITHUB_OUTPUT
echo "reason=No changes since existing tag $CURRENT_TAG and no unreleased changelog content" >> $GITHUB_OUTPUT
else
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "should_continue=true" >> $GITHUB_OUTPUT
fi
else
# No current tag, check against latest release
LATEST_TAG=$(git tag --list "v3.0.0-alpha.*" | sort -V | tail -1)
if [ -z "$LATEST_TAG" ]; then
echo "No previous release found, proceeding with release"
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "should_continue=true" >> $GITHUB_OUTPUT
else
COMMIT_COUNT=$(git rev-list ${LATEST_TAG}..HEAD --count)
if [ "$COMMIT_COUNT" -gt 0 ]; then
echo "Found $COMMIT_COUNT commits since $LATEST_TAG"
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "should_continue=true" >> $GITHUB_OUTPUT
else
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "should_continue=false" >> $GITHUB_OUTPUT
echo "reason=No changes since latest release $LATEST_TAG and no unreleased changelog content" >> $GITHUB_OUTPUT
fi
fi
fi
- name: Early exit - No changes detected
if: |
steps.quick_check.outputs.should_continue == 'false' &&
github.event.inputs.force_release != 'true'
run: |
echo "🛑 EARLY EXIT: ${{ steps.quick_check.outputs.reason }}"
echo ""
echo " No changes detected since last release and force_release is not enabled."
echo " Workflow will exit early to save resources."
echo ""
echo " To force a release anyway, run this workflow with 'force_release=true'"
echo ""
echo "## 🛑 Early Exit Summary" >> $GITHUB_STEP_SUMMARY
echo "**Reason:** ${{ steps.quick_check.outputs.reason }}" >> $GITHUB_STEP_SUMMARY
echo "**Action:** Workflow exited early to save resources" >> $GITHUB_STEP_SUMMARY
echo "**Force Release:** Set 'force_release=true' to override this behavior" >> $GITHUB_STEP_SUMMARY
exit 0
- name: Continue with release process
if: |
steps.quick_check.outputs.should_continue == 'true' ||
github.event.inputs.force_release == 'true'
run: |
echo "✅ Proceeding with release process..."
if [ "${{ github.event.inputs.force_release }}" == "true" ]; then
echo "🔨 FORCE RELEASE: Overriding change detection"
fi
- name: Run release script
id: release
if: |
steps.quick_check.outputs.should_continue == 'true' ||
github.event.inputs.force_release == 'true'
run: |
cd v3
echo "🚀 Running release task..."
echo "======================================================="
# Initialize error tracking
RELEASE_ERRORS=""
RELEASE_SUCCESS=true
# Store the original version for comparison
ORIGINAL_VERSION=$(cat internal/version/version.txt 2>/dev/null || echo "unknown")
echo "📌 Current version: $ORIGINAL_VERSION"
# Run the release task and capture output with error handling
if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then
echo "🧪 DRY RUN MODE: Simulating release task execution"
# In dry run, we'll simulate the task without making actual changes
OUTPUT=$(task release 2>&1 || true)
RELEASE_EXIT_CODE=0 # Always succeed in dry run
echo "$OUTPUT"
else
echo "🚀 LIVE MODE: Executing release task"
OUTPUT=$(task release 2>&1)
RELEASE_EXIT_CODE=$?
echo "$OUTPUT"
if [ $RELEASE_EXIT_CODE -ne 0 ]; then
echo "❌ Release task failed with exit code $RELEASE_EXIT_CODE"
RELEASE_ERRORS="$RELEASE_ERRORS\n- Release task execution failed: $OUTPUT"
RELEASE_SUCCESS=false
else
echo "✅ Release task completed successfully"
fi
fi
# Verify version file exists and is readable
if [ ! -f "internal/version/version.txt" ]; then
echo "❌ Version file not found: internal/version/version.txt"
RELEASE_ERRORS="$RELEASE_ERRORS\n- Version file not found after release task execution"
RELEASE_SUCCESS=false
RELEASE_VERSION="unknown"
else
RELEASE_VERSION=$(cat internal/version/version.txt 2>/dev/null || echo "unknown")
if [ "$RELEASE_VERSION" == "unknown" ]; then
echo "❌ Failed to read version from file"
RELEASE_ERRORS="$RELEASE_ERRORS\n- Failed to read version from version.txt"
RELEASE_SUCCESS=false
else
echo "✅ Successfully read version: $RELEASE_VERSION"
fi
fi
# Check if version changed
VERSION_CHANGED="false"
if [ "$ORIGINAL_VERSION" != "$RELEASE_VERSION" ] && [ "$RELEASE_VERSION" != "unknown" ]; then
echo "✅ Version changed from $ORIGINAL_VERSION to $RELEASE_VERSION"
VERSION_CHANGED="true"
else
echo " Version unchanged: $RELEASE_VERSION"
fi
RELEASE_TAG="${RELEASE_VERSION}"
RELEASE_TITLE="Wails ${RELEASE_VERSION}"
# Set outputs for next steps
echo "version=$RELEASE_VERSION" >> $GITHUB_OUTPUT
echo "tag=$RELEASE_TAG" >> $GITHUB_OUTPUT
echo "title=$RELEASE_TITLE" >> $GITHUB_OUTPUT
echo "is_prerelease=true" >> $GITHUB_OUTPUT
echo "is_latest=false" >> $GITHUB_OUTPUT
echo "has_changes=${{ steps.changelog_check.outputs.has_unreleased_content }}" >> $GITHUB_OUTPUT
echo "success=$RELEASE_SUCCESS" >> $GITHUB_OUTPUT
echo "version_changed=$VERSION_CHANGED" >> $GITHUB_OUTPUT
# Generate release notes from UNRELEASED_CHANGELOG.md if it has content
if [ "${{ steps.changelog_check.outputs.has_unreleased_content }}" == "true" ] && [ "$RELEASE_SUCCESS" == "true" ]; then
echo "📝 Generating release notes from UNRELEASED_CHANGELOG.md..."
# Use the release script to extract changelog content
cd tasks/release
if CHANGELOG_CONTENT=$(go run release.go --extract-changelog 2>&1); then
if [ -n "$CHANGELOG_CONTENT" ] && [ "$CHANGELOG_CONTENT" != "No changelog content found." ]; then
echo "### Changes in this release:" > ../../release-notes.txt
echo "" >> ../../release-notes.txt
echo "$CHANGELOG_CONTENT" >> ../../release-notes.txt
echo "✅ Successfully extracted changelog content"
echo "release_notes_file=release-notes.txt" >> $GITHUB_OUTPUT
else
echo " No changelog content to extract"
echo "No detailed changelog available for this release." > ../../release-notes.txt
echo "release_notes_file=release-notes.txt" >> $GITHUB_OUTPUT
fi
else
echo "⚠️ Failed to extract changelog content: $CHANGELOG_CONTENT"
echo "No detailed changelog available for this release." > ../../release-notes.txt
RELEASE_ERRORS="$RELEASE_ERRORS\n- Failed to extract changelog content for release notes"
echo "release_notes_file=release-notes.txt" >> $GITHUB_OUTPUT
fi
cd ../..
else
echo "release_notes_file=" >> $GITHUB_OUTPUT
if [ "$RELEASE_SUCCESS" != "true" ]; then
echo "⚠️ Skipping release notes generation due to release task failure"
fi
fi
# Set error output for later steps
if [ -n "$RELEASE_ERRORS" ]; then
echo "release_errors<<EOF" >> $GITHUB_OUTPUT
echo -e "$RELEASE_ERRORS" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "has_release_errors=true" >> $GITHUB_OUTPUT
else
echo "has_release_errors=false" >> $GITHUB_OUTPUT
fi
- name: Create and push git tag
id: git_tag
if: |
(steps.quick_check.outputs.should_continue == 'true' || github.event.inputs.force_release == 'true') &&
steps.check_tag.outputs.has_tag == 'false' &&
github.event.inputs.dry_run != 'true' &&
steps.release.outputs.success == 'true' &&
steps.release.outputs.version_changed == 'true'
env:
GITHUB_TOKEN: ${{ secrets.WAILS_REPO_TOKEN || github.token }}
run: |
echo "🏷️ Creating and pushing git tag: ${{ steps.release.outputs.tag }}"
# Initialize error tracking
GIT_ERRORS=""
GIT_SUCCESS=true
# Create git tag with error handling
if git tag -a "${{ steps.release.outputs.tag }}" -m "Release ${{ steps.release.outputs.version }}" 2>&1; then
echo "✅ Successfully created git tag: ${{ steps.release.outputs.tag }}"
else
echo "❌ Failed to create git tag"
GIT_ERRORS="$GIT_ERRORS\n- Failed to create git tag: ${{ steps.release.outputs.tag }}"
GIT_SUCCESS=false
fi
# Push tag with retry logic and error handling
if [ "$GIT_SUCCESS" == "true" ]; then
RETRY_COUNT=0
MAX_RETRIES=3
PUSH_SUCCESS=false
while [ $RETRY_COUNT -lt $MAX_RETRIES ] && [ "$PUSH_SUCCESS" == "false" ]; do
RETRY_COUNT=$((RETRY_COUNT + 1))
echo "🔄 Attempting to push tag (attempt $RETRY_COUNT/$MAX_RETRIES)..."
if git push "https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git" "${{ steps.release.outputs.tag }}" 2>&1; then
echo "✅ Successfully pushed git tag to origin"
PUSH_SUCCESS=true
else
echo "❌ Failed to push git tag (attempt $RETRY_COUNT/$MAX_RETRIES)"
if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then
echo "⏳ Waiting 5 seconds before retry..."
sleep 5
fi
fi
done
if [ "$PUSH_SUCCESS" == "false" ]; then
echo "❌ Failed to push git tag after $MAX_RETRIES attempts"
GIT_ERRORS="$GIT_ERRORS\n- Failed to push git tag after $MAX_RETRIES attempts"
GIT_SUCCESS=false
fi
fi
# Set outputs for later steps
echo "success=$GIT_SUCCESS" >> $GITHUB_OUTPUT
if [ -n "$GIT_ERRORS" ]; then
echo "git_tag_errors<<EOF" >> $GITHUB_OUTPUT
echo -e "$GIT_ERRORS" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "has_git_errors=true" >> $GITHUB_OUTPUT
else
echo "has_git_errors=false" >> $GITHUB_OUTPUT
fi
- name: Commit and push changes
id: git_commit
if: |
(steps.quick_check.outputs.should_continue == 'true' || github.event.inputs.force_release == 'true') &&
github.event.inputs.dry_run != 'true' &&
steps.release.outputs.success == 'true' &&
steps.release.outputs.version_changed == 'true'
env:
GITHUB_TOKEN: ${{ secrets.WAILS_REPO_TOKEN || github.token }}
run: |
echo "📝 Committing and pushing changes..."
# Initialize error tracking
COMMIT_ERRORS=""
COMMIT_SUCCESS=true
# Add any changes made by the release script with error handling
if git add . 2>&1; then
echo "✅ Successfully staged changes"
else
echo "❌ Failed to stage changes"
COMMIT_ERRORS="$COMMIT_ERRORS\n- Failed to stage changes with git add"
COMMIT_SUCCESS=false
fi
# Check if there are changes to commit
if [ "$COMMIT_SUCCESS" == "true" ]; then
if ! git diff --cached --quiet; then
echo "📝 Changes detected, creating commit..."
# Create commit with error handling
if git commit -m "${{ steps.release.outputs.version }}" 2>&1; then
echo "✅ Successfully created commit"
# Push changes with retry logic
RETRY_COUNT=0
MAX_RETRIES=3
PUSH_SUCCESS=false
while [ $RETRY_COUNT -lt $MAX_RETRIES ] && [ "$PUSH_SUCCESS" == "false" ]; do
RETRY_COUNT=$((RETRY_COUNT + 1))
echo "🔄 Attempting to push changes (attempt $RETRY_COUNT/$MAX_RETRIES)..."
if git push "https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git" v3-alpha 2>&1; then
echo "✅ Successfully pushed changes to v3-alpha branch"
PUSH_SUCCESS=true
else
echo "❌ Failed to push changes (attempt $RETRY_COUNT/$MAX_RETRIES)"
if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then
echo "⏳ Waiting 5 seconds before retry..."
sleep 5
fi
fi
done
if [ "$PUSH_SUCCESS" == "false" ]; then
echo "❌ Failed to push changes after $MAX_RETRIES attempts"
COMMIT_ERRORS="$COMMIT_ERRORS\n- Failed to push changes after $MAX_RETRIES attempts"
COMMIT_SUCCESS=false
fi
else
echo "❌ Failed to create commit"
COMMIT_ERRORS="$COMMIT_ERRORS\n- Failed to create git commit"
COMMIT_SUCCESS=false
fi
else
echo " No changes to commit"
fi
fi
# Set outputs for later steps
echo "success=$COMMIT_SUCCESS" >> $GITHUB_OUTPUT
if [ -n "$COMMIT_ERRORS" ]; then
echo "commit_errors<<EOF" >> $GITHUB_OUTPUT
echo -e "$COMMIT_ERRORS" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "has_commit_errors=true" >> $GITHUB_OUTPUT
else
echo "has_commit_errors=false" >> $GITHUB_OUTPUT
fi
- name: Read release notes
id: read_notes
if: |
(steps.quick_check.outputs.should_continue == 'true' || github.event.inputs.force_release == 'true') &&
steps.release.outputs.release_notes_file != '' &&
steps.release.outputs.version_changed == 'true'
run: |
cd v3
if [ -f "release-notes.txt" ]; then
# Read the release notes and handle multiline content
RELEASE_NOTES=$(cat release-notes.txt)
echo "release_notes<<EOF" >> $GITHUB_OUTPUT
echo "$RELEASE_NOTES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
else
echo "release_notes=No release notes available" >> $GITHUB_OUTPUT
fi
- name: Test GitHub Release Creation (DRY RUN)
if: |
(steps.quick_check.outputs.should_continue == 'true' || github.event.inputs.force_release == 'true') &&
github.event.inputs.dry_run == 'true' &&
steps.release.outputs.version_changed == 'true'
run: |
echo "🧪 DRY RUN: Would create GitHub release with the following parameters:"
echo "======================================================================="
echo "Tag Name: ${{ steps.release.outputs.tag }}"
echo "Release Name: ${{ steps.release.outputs.title }}"
echo "Is Prerelease: ${{ steps.release.outputs.is_prerelease }}"
echo "Is Latest: ${{ steps.release.outputs.is_latest }}"
echo "Has Changes: ${{ steps.release.outputs.has_changes }}"
echo ""
echo "Release Body Preview:"
echo "## Wails v3 Alpha Release - ${{ steps.release.outputs.version }}"
echo ""
cat << 'RELEASE_NOTES_EOF'
${{ steps.read_notes.outputs.release_notes }}
RELEASE_NOTES_EOF
echo ""
echo ""
echo ""
echo "---"
echo ""
echo "🤖 This is an automated nightly release generated from the latest changes in the v3-alpha branch."
echo ""
echo "**Installation:**"
echo "\`\`\`bash"
echo "go install github.com/wailsapp/wails/v3/cmd/wails@${{ steps.release.outputs.tag }}"
echo "\`\`\`"
echo ""
echo "**⚠️ Alpha Warning:** This is pre-release software and may contain bugs or incomplete features."
echo ""
echo "✅ DRY RUN: GitHub release creation test completed successfully!"
- name: Create GitHub Release (LIVE)
id: github_release
if: |
(steps.quick_check.outputs.should_continue == 'true' || github.event.inputs.force_release == 'true') &&
github.event.inputs.dry_run != 'true' &&
steps.release.outputs.success == 'true' &&
steps.release.outputs.version_changed == 'true'
continue-on-error: true
run: |
echo "🚀 Creating GitHub release using gh CLI..."
# Create release notes in a temporary file
cat > release_notes.md << 'EOF'
## Wails v3 Alpha Release - ${{ steps.release.outputs.version }}
${{ steps.read_notes.outputs.release_notes }}
---
🤖 This is an automated nightly release generated from the latest changes in the v3-alpha branch.
**Installation:**
```bash
go install github.com/wailsapp/wails/v3/cmd/wails@${{ steps.release.outputs.tag }}
```
**⚠️ Alpha Warning:** This is pre-release software and may contain bugs or incomplete features.
EOF
# Create the release
if gh release create "${{ steps.release.outputs.tag }}" \
--title "${{ steps.release.outputs.title }}" \
--notes-file release_notes.md \
--target v3-alpha \
--prerelease; then
echo "✅ Successfully created GitHub release"
echo "outcome=success" >> $GITHUB_OUTPUT
else
echo "❌ Failed to create GitHub release"
echo "outcome=failure" >> $GITHUB_OUTPUT
fi
env:
GITHUB_TOKEN: ${{ secrets.WAILS_REPO_TOKEN || github.token }}
- name: Handle GitHub Release Creation Result
id: release_result
if: |
(steps.quick_check.outputs.should_continue == 'true' || github.event.inputs.force_release == 'true') &&
github.event.inputs.dry_run != 'true' &&
steps.release.outputs.success == 'true' &&
steps.release.outputs.version_changed == 'true'
run: |
echo "📋 Checking GitHub release creation result..."
# Initialize error tracking
GITHUB_ERRORS=""
GITHUB_SUCCESS=true
# Check if GitHub release creation succeeded
if [ "${{ steps.github_release.outcome }}" == "success" ]; then
echo "✅ GitHub release created successfully"
echo "🔗 Release URL: https://github.com/${{ github.repository }}/releases/tag/${{ steps.release.outputs.tag }}"
else
echo "❌ GitHub release creation failed"
GITHUB_ERRORS="$GITHUB_ERRORS\n- GitHub release creation failed with outcome: ${{ steps.github_release.outcome }}"
GITHUB_SUCCESS=false
fi
# Set outputs for summary
echo "success=$GITHUB_SUCCESS" >> $GITHUB_OUTPUT
if [ -n "$GITHUB_ERRORS" ]; then
echo "github_errors<<EOF" >> $GITHUB_OUTPUT
echo -e "$GITHUB_ERRORS" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "has_github_errors=true" >> $GITHUB_OUTPUT
else
echo "has_github_errors=false" >> $GITHUB_OUTPUT
fi
- name: Error Summary and Reporting
id: error_summary
if: always()
run: |
echo "📊 Generating comprehensive error summary..."
# Initialize error tracking
TOTAL_ERRORS=0
ERROR_SUMMARY=""
OVERALL_SUCCESS=true
# Check for changelog errors
if [ "${{ steps.changelog_check.outputs.has_errors }}" == "true" ]; then
echo "❌ Changelog processing errors detected"
ERROR_SUMMARY="$ERROR_SUMMARY\n### 📄 Changelog Processing Errors\n${{ steps.changelog_check.outputs.changelog_errors }}\n"
TOTAL_ERRORS=$((TOTAL_ERRORS + 1))
OVERALL_SUCCESS=false
fi
# Check for release script errors
if [ "${{ steps.release.outputs.has_release_errors }}" == "true" ]; then
echo "❌ Release script errors detected"
ERROR_SUMMARY="$ERROR_SUMMARY\n### 🚀 Release Script Errors\n${{ steps.release.outputs.release_errors }}\n"
TOTAL_ERRORS=$((TOTAL_ERRORS + 1))
OVERALL_SUCCESS=false
fi
# Check for git tag errors
if [ "${{ steps.git_tag.outputs.has_git_errors }}" == "true" ]; then
echo "❌ Git tag errors detected"
ERROR_SUMMARY="$ERROR_SUMMARY\n### 🏷️ Git Tag Errors\n${{ steps.git_tag.outputs.git_tag_errors }}\n"
TOTAL_ERRORS=$((TOTAL_ERRORS + 1))
OVERALL_SUCCESS=false
fi
# Check for git commit errors
if [ "${{ steps.git_commit.outputs.has_commit_errors }}" == "true" ]; then
echo "❌ Git commit errors detected"
ERROR_SUMMARY="$ERROR_SUMMARY\n### 📝 Git Commit Errors\n${{ steps.git_commit.outputs.commit_errors }}\n"
TOTAL_ERRORS=$((TOTAL_ERRORS + 1))
OVERALL_SUCCESS=false
fi
# Check for GitHub release errors
if [ "${{ steps.release_result.outputs.has_github_errors }}" == "true" ]; then
echo "❌ GitHub release errors detected"
ERROR_SUMMARY="$ERROR_SUMMARY\n### 🐙 GitHub Release Errors\n${{ steps.release_result.outputs.github_errors }}\n"
TOTAL_ERRORS=$((TOTAL_ERRORS + 1))
OVERALL_SUCCESS=false
fi
# Set outputs for final summary
echo "total_errors=$TOTAL_ERRORS" >> $GITHUB_OUTPUT
echo "overall_success=$OVERALL_SUCCESS" >> $GITHUB_OUTPUT
if [ -n "$ERROR_SUMMARY" ]; then
echo "error_summary<<EOF" >> $GITHUB_OUTPUT
echo -e "$ERROR_SUMMARY" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
fi
# Log summary
if [ "$OVERALL_SUCCESS" == "true" ]; then
echo "✅ Workflow completed successfully with no errors"
else
echo "⚠️ Workflow completed with $TOTAL_ERRORS error categories"
fi
- name: Summary
if: always()
run: |
if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then
echo "## 🧪 DRY RUN Release Test Summary" >> $GITHUB_STEP_SUMMARY
else
echo "## 🚀 Nightly Release Summary" >> $GITHUB_STEP_SUMMARY
fi
echo "================================" >> $GITHUB_STEP_SUMMARY
echo "- **Version:** ${{ steps.release.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "- **Tag:** ${{ steps.release.outputs.tag }}" >> $GITHUB_STEP_SUMMARY
echo "- **Version Changed:** ${{ steps.release.outputs.version_changed }}" >> $GITHUB_STEP_SUMMARY
echo "- **Has existing tag:** ${{ steps.check_tag.outputs.has_tag }}" >> $GITHUB_STEP_SUMMARY
echo "- **Has unreleased changelog content:** ${{ steps.changelog_check.outputs.has_unreleased_content }}" >> $GITHUB_STEP_SUMMARY
echo "- **Has changes:** ${{ steps.release.outputs.has_changes }}" >> $GITHUB_STEP_SUMMARY
echo "- **Is prerelease:** ${{ steps.release.outputs.is_prerelease }}" >> $GITHUB_STEP_SUMMARY
echo "- **Is latest:** ${{ steps.release.outputs.is_latest }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Overall status
if [ "${{ steps.error_summary.outputs.overall_success }}" == "true" ]; then
if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then
echo "- **Mode:** 🧪 DRY RUN (no actual release created)" >> $GITHUB_STEP_SUMMARY
echo "- **Status:** ✅ Test completed successfully" >> $GITHUB_STEP_SUMMARY
else
echo "- **Mode:** 🚀 Live release" >> $GITHUB_STEP_SUMMARY
echo "- **Status:** ✅ Release created successfully" >> $GITHUB_STEP_SUMMARY
fi
else
echo "- **Mode:** ${{ github.event.inputs.dry_run == 'true' && '🧪 DRY RUN' || '🚀 Live release' }}" >> $GITHUB_STEP_SUMMARY
echo "- **Status:** ⚠️ Completed with ${{ steps.error_summary.outputs.total_errors }} error(s)" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Release Processing" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.release.outputs.version_changed }}" == "true" ]; then
echo "✅ **Version was incremented** and release created" >> $GITHUB_STEP_SUMMARY
else
echo " **Version was not changed** - no release created" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Changelog Processing" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.changelog_check.outputs.has_unreleased_content }}" == "true" ]; then
echo "✅ **UNRELEASED_CHANGELOG.md** had content and was processed" >> $GITHUB_STEP_SUMMARY
echo "- Content moved to main changelog" >> $GITHUB_STEP_SUMMARY
echo "- UNRELEASED_CHANGELOG.md reset with template" >> $GITHUB_STEP_SUMMARY
else
echo " **UNRELEASED_CHANGELOG.md** had no content to process" >> $GITHUB_STEP_SUMMARY
fi
# Error reporting section
if [ "${{ steps.error_summary.outputs.total_errors }}" -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "## ⚠️ Error Report" >> $GITHUB_STEP_SUMMARY
echo "**Total Error Categories:** ${{ steps.error_summary.outputs.total_errors }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "${{ steps.error_summary.outputs.error_summary }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🔧 Troubleshooting Tips" >> $GITHUB_STEP_SUMMARY
echo "- Check the individual step logs above for detailed error messages" >> $GITHUB_STEP_SUMMARY
echo "- Verify GitHub token permissions (contents: write, pull-requests: read)" >> $GITHUB_STEP_SUMMARY
echo "- Ensure UNRELEASED_CHANGELOG.md follows the expected format" >> $GITHUB_STEP_SUMMARY
echo "- Check for network connectivity issues if git/GitHub operations failed" >> $GITHUB_STEP_SUMMARY
echo "- Re-run the workflow with 'force_release=true' if needed" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Release Notes Preview" >> $GITHUB_STEP_SUMMARY
if [ -n "${{ steps.read_notes.outputs.release_notes }}" ]; then
echo "${{ steps.read_notes.outputs.release_notes }}" >> $GITHUB_STEP_SUMMARY
else
echo "No specific release notes generated" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "*Generated by automated nightly release workflow with enhanced error handling and changelog integration*" >> $GITHUB_STEP_SUMMARY
# Set final workflow status
if [ "${{ steps.error_summary.outputs.overall_success }}" != "true" ]; then
echo "⚠️ Workflow completed with errors. Check the summary above for details."
exit 1
fi

View file

@ -1,20 +1,23 @@
name: PR Checks
name: PR Checks (master)
on:
pull_request:
branches:
- master
pull_request_review:
types: [submitted]
branches:
- master
jobs:
check_docs:
name: Check Docs
if: ${{github.repository == 'wailsapp/wails' && contains(github.head_ref,'feature/')}}
if: ${{github.repository == 'wailsapp/wails' && github.base_ref == 'master'}}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Verify Changed files
uses: tj-actions/verify-changed-files@v17
uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
id: verify-changed-files
with:
files: |
@ -26,47 +29,28 @@ jobs:
run: |
echo "::warning::Feature branch does not contain any changes to the website."
# lint_go:
# name: Run Go Linters
# runs-on: ubuntu-latest
# steps:
# - name: Checkout code
# uses: actions/checkout@v4
#
# - name: Setup Go
# uses: actions/setup-go@v4
# with:
# go-version: "1.21"
#
# - name: Update go modules
# working-directory: ./v2
# run: go mod tidy
#
# - name: Run Linter
# uses: golangci/golangci-lint-action@v3
# with:
# version: v1.54
# working-directory: ./v2
# args: --timeout=10m0s --config ./.golangci.yml
test_go:
name: Run Go Tests
runs-on: ${{ matrix.os }}
if: github.event.review.state == 'approved'
if: >
github.event.review.state == 'approved' &&
github.repository == 'wailsapp/wails' &&
github.base_ref == 'master' &&
github.event.pull_request.head.ref != 'update-sponsors'
strategy:
matrix:
os: [ubuntu-22.04, windows-latest, macos-latest, ubuntu-24.04]
go-version: ['1.21']
go-version: ['1.23']
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install linux dependencies ( 22.04 )
- name: Install linux dependencies (22.04)
if: matrix.os == 'ubuntu-22.04'
run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev build-essential pkg-config
- name: Install linux dependencies ( 24.04 )
- name: Install linux dependencies (24.04)
if: matrix.os == 'ubuntu-24.04'
run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config

View file

@ -1,63 +1,114 @@
on:
push:
branches: ['v3-alpha']
workflow_dispatch:
concurrency:
group: publish-npm-v3
cancel-in-progress: true
jobs:
publish:
detect:
name: Detect committed changes
if: github.event_name != 'workflow_dispatch'
outputs:
changed: ${{ steps.package-json-changes.outputs.any_modified == 'true' || steps.source-changes.outputs.any_modified == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.sha }}
persist-credentials: 'true'
- name: Detect committed package.json changes
id: package-json-changes
uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
with:
files: |
v3/internal/runtime/desktop/@wailsio/runtime/package.json
- name: Detect committed source changes
if: >-
steps.package-json-changes.outputs.any_modified != 'true'
id: source-changes
uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
with:
files: |
v3/internal/runtime/Taskfile.yaml
v3/internal/runtime/desktop/@wailsio/compiled/main.js
v3/internal/runtime/desktop/@wailsio/runtime/tsconfig.json
v3/internal/runtime/desktop/@wailsio/runtime/src/**
v3/pkg/events/events.txt
v3/tasks/events/**
rebuild_and_publish:
name: Rebuild and publish
needs: [detect]
if: >-
!failure() && !cancelled()
&& (github.event_name == 'workflow_dispatch' || needs.detect.outputs.changed == 'true')
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: 'v3-alpha'
ssh-key: ${{ secrets.DEPLOY_KEY }}
- name: Configure git
run: |
git config --local user.email "github-actions@github.com"
git config --local user.name "GitHub Actions"
- name: Setup go-task
uses: pnorton5432/setup-task@v1
- name: Install Task
uses: arduino/setup-task@v2
with:
task-version: 3.29.1
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/setup-node@v3
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: "20"
- run: |
npm ci
npm run build:types
npm run build:docs
- name: Install dependencies
working-directory: v3/internal/runtime/desktop/@wailsio/runtime
run: |
npm ci
npx --yes esbuild@latest --version
- name: Verify Changed files
uses: tj-actions/verify-changed-files@v20
id: verify-changed-files
with:
files: |
v3/internal/runtime/desktop/@wailsio/runtime/src/*.js
v3/internal/runtime/desktop/@wailsio/runtime/types/*.d.ts
v3/internal/runtime/desktop/@wailsio/runtime/docs/*.*
- name: Clean build artifacts
working-directory: v3/internal/runtime/desktop/@wailsio/runtime
run: npm run clean
- name: test action
if: steps.verify-changed-files.outputs.files_changed == 'true'
id: get-version
uses: beaconbrigade/package-json-version@v0.3.2
with:
path: v3/internal/runtime/desktop/@wailsio/runtime
- name: Build bundled runtime
working-directory: v3
run: task runtime:build
- name: Test+Build npm package
working-directory: v3/internal/runtime/desktop/@wailsio/runtime
run: |
npm test
npm run build
- name: Bump version
id: bump-version
working-directory: v3/internal/runtime/desktop/@wailsio/runtime
run: |
echo "version=$(npm --no-git-tag-version --force version prerelease)" >> "$GITHUB_OUTPUT"
- name: Commit changes
if: steps.verify-changed-files.outputs.files_changed == 'true'
run: |
git add .
git commit -m "[skip ci] Publish @wailsio/runtime ${{ steps.get-version.outputs.version }}"
git commit -m "[skip ci] Publish @wailsio/runtime ${{ steps.bump-version.outputs.version }}"
git push
fi
- uses: JS-DevTools/npm-publish@v3
if: steps.verify-changed-files.outputs.files_changed == 'true'
- name: Publish npm package
uses: JS-DevTools/npm-publish@v3
with:
package: v3/internal/runtime/desktop/@wailsio/runtime
access: public
token: ${{ secrets.NPM_TOKEN }}
token: ${{ secrets.NPM_TOKEN }}

View file

@ -5,6 +5,7 @@ on:
branches:
- main
- master
- v3-alpha
paths:
- .github/workflows/semgrep.yml
schedule:
@ -14,7 +15,7 @@ name: Semgrep
jobs:
semgrep:
name: semgrep/ci
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
container:

57
.github/workflows/stale-issues.yml vendored Normal file
View file

@ -0,0 +1,57 @@
name: Mark and Close Stale Issues
on:
schedule:
- cron: '0 1 * * *' # Run at 1 AM UTC every day
workflow_dispatch: # Allow manual triggering
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v9
with:
# General settings
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 45
days-before-close: 10
stale-issue-label: 'stale'
operations-per-run: 250 # Increased from 50 to 250
# Issue specific settings
stale-issue-message: |
This issue has been automatically marked as stale because it has not had recent activity.
It will be closed if no further activity occurs within the next 10 days.
If this issue is still relevant, please add a comment to keep it open.
Thank you for your contributions.
close-issue-message: |
This issue has been automatically closed due to lack of activity.
Please feel free to reopen it if it's still relevant.
# PR specific settings - We will not mark PRs as stale
days-before-pr-stale: -1 # Disable PR staling
days-before-pr-close: -1 # Disable PR closing
# Exemptions
exempt-issue-labels: 'pinned,security,onhold,inprogress,Selected For Development,bug,enhancement,v3-alpha,high-priority'
exempt-all-issue-milestones: true
exempt-all-issue-assignees: true
# Protection for existing issues
exempt-issue-created-before: '2024-01-01T00:00:00Z'
start-date: '2025-06-01T00:00:00Z' # Don't start checking until June 1, 2025
# Only process issues, not PRs
only-labels: ''
any-of-labels: ''
remove-stale-when-updated: true
# Debug options
debug-only: false # Set to true to test without actually marking issues
ascending: true # Process older issues first

View file

@ -0,0 +1,216 @@
name: Test Nightly Releases (Dry Run)
on:
workflow_dispatch:
inputs:
dry_run:
description: 'Run in dry-run mode (no actual releases)'
required: false
default: true
type: boolean
test_branch:
description: 'Branch to test against'
required: false
default: 'master'
type: string
env:
GO_VERSION: '1.24'
jobs:
test-permissions:
name: Test Release Permissions
runs-on: ubuntu-latest
outputs:
authorized: ${{ steps.check.outputs.authorized }}
steps:
- name: Check if user is authorized
id: check
run: |
# Test authorization logic
AUTHORIZED_USERS="leaanthony"
if [[ "$AUTHORIZED_USERS" == *"${{ github.actor }}"* ]]; then
echo "✅ User ${{ github.actor }} is authorized"
echo "authorized=true" >> $GITHUB_OUTPUT
else
echo "❌ User ${{ github.actor }} is not authorized"
echo "authorized=false" >> $GITHUB_OUTPUT
fi
test-changelog-extraction:
name: Test Changelog Extraction
runs-on: ubuntu-latest
needs: test-permissions
if: needs.test-permissions.outputs.authorized == 'true'
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.test_branch }}
fetch-depth: 0
- name: Test v2 changelog extraction
run: |
echo "🧪 Testing v2 changelog extraction..."
CHANGELOG_FILE="website/src/pages/changelog.mdx"
if [ ! -f "$CHANGELOG_FILE" ]; then
echo "❌ v2 changelog file not found"
exit 1
fi
# Extract unreleased section
awk '
/^## \[Unreleased\]/ { found=1; next }
found && /^## / { exit }
found && !/^$/ { print }
' $CHANGELOG_FILE > v2_release_notes.md
echo "📝 v2 changelog content (first 10 lines):"
head -10 v2_release_notes.md || echo "No content found"
echo "Total lines: $(wc -l < v2_release_notes.md)"
- name: Test v3 changelog extraction (if accessible)
run: |
echo "🧪 Testing v3 changelog extraction..."
if git show v3-alpha:docs/src/content/docs/changelog.mdx > /dev/null 2>&1; then
echo "✅ v3 changelog accessible"
git show v3-alpha:docs/src/content/docs/changelog.mdx | awk '
/^## \[Unreleased\]/ { found=1; next }
found && /^## / { exit }
found && !/^$/ { print }
' > v3_release_notes.md
echo "📝 v3 changelog content (first 10 lines):"
head -10 v3_release_notes.md || echo "No content found"
echo "Total lines: $(wc -l < v3_release_notes.md)"
else
echo "⚠️ v3 changelog not accessible from current context"
fi
test-version-detection:
name: Test Version Detection
runs-on: ubuntu-latest
needs: test-permissions
if: needs.test-permissions.outputs.authorized == 'true'
outputs:
v2_current_version: ${{ steps.versions.outputs.v2_current }}
v2_next_patch: ${{ steps.versions.outputs.v2_next_patch }}
v2_next_minor: ${{ steps.versions.outputs.v2_next_minor }}
v2_next_major: ${{ steps.versions.outputs.v2_next_major }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Test version detection logic
id: versions
run: |
echo "🧪 Testing version detection..."
# Test v2 version parsing
if [ -f "v2/cmd/wails/internal/version.txt" ]; then
CURRENT_V2=$(cat v2/cmd/wails/internal/version.txt | sed 's/^v//')
echo "Current v2 version: v$CURRENT_V2"
echo "v2_current=v$CURRENT_V2" >> $GITHUB_OUTPUT
# Parse and increment
IFS='.' read -ra VERSION_PARTS <<< "$CURRENT_V2"
MAJOR=${VERSION_PARTS[0]}
MINOR=${VERSION_PARTS[1]}
PATCH=${VERSION_PARTS[2]}
PATCH_VERSION="v$MAJOR.$MINOR.$((PATCH + 1))"
MINOR_VERSION="v$MAJOR.$((MINOR + 1)).0"
MAJOR_VERSION="v$((MAJOR + 1)).0.0"
echo "v2_next_patch=$PATCH_VERSION" >> $GITHUB_OUTPUT
echo "v2_next_minor=$MINOR_VERSION" >> $GITHUB_OUTPUT
echo "v2_next_major=$MAJOR_VERSION" >> $GITHUB_OUTPUT
echo "✅ Patch: v$CURRENT_V2 → $PATCH_VERSION"
echo "✅ Minor: v$CURRENT_V2 → $MINOR_VERSION"
echo "✅ Major: v$CURRENT_V2 → $MAJOR_VERSION"
else
echo "❌ v2 version file not found"
fi
test-commit-analysis:
name: Test Commit Analysis
runs-on: ubuntu-latest
needs: test-permissions
if: needs.test-permissions.outputs.authorized == 'true'
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Test commit analysis
run: |
echo "🧪 Testing commit analysis..."
# Get recent commits for testing
echo "Recent commits:"
git log --oneline -10
# Test conventional commit detection
RECENT_COMMITS=$(git log --oneline --since="7 days ago")
echo "Commits from last 7 days:"
echo "$RECENT_COMMITS"
# Analyze for release type
RELEASE_TYPE="patch"
if echo "$RECENT_COMMITS" | grep -q "feat!\|fix!\|BREAKING CHANGE:"; then
RELEASE_TYPE="major"
elif echo "$RECENT_COMMITS" | grep -q "feat\|BREAKING CHANGE"; then
RELEASE_TYPE="minor"
fi
echo "✅ Detected release type: $RELEASE_TYPE"
test-summary:
name: Test Summary
runs-on: ubuntu-latest
needs: [test-permissions, test-changelog-extraction, test-version-detection, test-commit-analysis]
if: always()
steps:
- name: Print test results
run: |
echo "# 🧪 Nightly Release Workflow Test Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ needs.test-permissions.result }}" == "success" ]; then
echo "✅ **Permissions Test**: Passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Permissions Test**: Failed" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ needs.test-changelog-extraction.result }}" == "success" ]; then
echo "✅ **Changelog Extraction**: Passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Changelog Extraction**: Failed" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ needs.test-version-detection.result }}" == "success" ]; then
echo "✅ **Version Detection**: Passed" >> $GITHUB_STEP_SUMMARY
echo " - Current v2: ${{ needs.test-version-detection.outputs.v2_current_version }}" >> $GITHUB_STEP_SUMMARY
echo " - Next patch: ${{ needs.test-version-detection.outputs.v2_next_patch }}" >> $GITHUB_STEP_SUMMARY
echo " - Next minor: ${{ needs.test-version-detection.outputs.v2_next_minor }}" >> $GITHUB_STEP_SUMMARY
echo " - Next major: ${{ needs.test-version-detection.outputs.v2_next_major }}" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Version Detection**: Failed" >> $GITHUB_STEP_SUMMARY
fi
if [ "${{ needs.test-commit-analysis.result }}" == "success" ]; then
echo "✅ **Commit Analysis**: Passed" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Commit Analysis**: Failed" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Note**: This was a dry-run test. No actual releases were created." >> $GITHUB_STEP_SUMMARY

11
.github/workflows/test-simple.yml vendored Normal file
View file

@ -0,0 +1,11 @@
name: Test Simple
on:
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Test
run: echo "Hello World"

View file

@ -15,7 +15,7 @@ jobs:
- name: Verify Changed files
id: changed-files
uses: tj-actions/changed-files@v41
uses: step-security/changed-files@3dbe17c78367e7d60f00d78ae6781a35be47b4a1 # v45.0.1
with:
files: |
website/**/*.mdx

49
.gitignore vendored
View file

@ -37,6 +37,55 @@ v2/cmd/wails/internal/commands/initialise/templates/testtemplates/
/websitev3/site/
/v3/examples/plugins/bin/testapp
# V3 Example binaries - ignore executables that match directory names
/v3/examples/badge-custom/badge-custom
/v3/examples/badge/badge
/v3/examples/binding/binding
/v3/examples/cancel-async/cancel-async
/v3/examples/cancel-chaining/cancel-chaining
/v3/examples/clipboard/clipboard
/v3/examples/contextmenus/contextmenus
/v3/examples/dev/dev
/v3/examples/dialogs-basic/dialogs-basic
/v3/examples/dialogs/dialogs
/v3/examples/drag-n-drop/drag-n-drop
/v3/examples/environment/environment
/v3/examples/events-bug/events-bug
/v3/examples/events/events
/v3/examples/file-association/file-association
/v3/examples/frameless/frameless
/v3/examples/gin-example/gin-example
/v3/examples/gin-routing/gin-routing
/v3/examples/gin-service/gin-service
/v3/examples/hide-window/hide-window
/v3/examples/html-dnd-api/html-dnd-api
/v3/examples/ignore-mouse/ignore-mouse
/v3/examples/keybindings/keybindings
/v3/examples/menu/menu
/v3/examples/notifications/notifications
/v3/examples/panic-handling/panic-handling
/v3/examples/plain/plain
/v3/examples/raw-message/raw-message
/v3/examples/screen/screen
/v3/examples/services/services
/v3/examples/show-macos-toolbar/show-macos-toolbar
/v3/examples/single-instance/single-instance
/v3/examples/systray-basic/systray-basic
/v3/examples/systray-custom/systray-custom
/v3/examples/systray-menu/systray-menu
/v3/examples/video/video
/v3/examples/window-api/window-api
/v3/examples/window-call/window-call
/v3/examples/window-menu/window-menu
/v3/examples/window/window
/v3/examples/wml/wml
# Common binary names in examples
/v3/examples/*/main
/v3/examples/*/app
/v3/examples/*/changeme
/v3/examples/*/testbuild-*
# Temporary called mkdocs, should be renamed to more standard -website or similar
/docs/site
.aider*

1240
docs/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -17,6 +17,7 @@
"@types/react-dom": "19.0.2",
"astro": "4.16.17",
"framer-motion": "11.14.4",
"mermaid": "^10.9.3",
"motion": "11.14.4",
"react": "19.0.0",
"react-dom": "19.0.0",

View file

@ -0,0 +1,59 @@
---
export interface Props {
title?: string;
}
const { title = "" } = Astro.props;
---
<script>
import mermaid from "mermaid";
// Postpone mermaid initialization
mermaid.initialize({ startOnLoad: false });
function extractMermaidCode() {
// Find all mermaid components
const mermaidElements = document.querySelectorAll("figure.expandable-diagram");
mermaidElements.forEach((element) => {
// Find the code content in the details section
const codeElement = element.querySelector("details pre code");
if (!codeElement) return;
// Extract the text content
let code = codeElement.textContent || "";
// Clean up the code
code = code.trim();
// Construct the `pre` element for the diagram code
const preElement = document.createElement("pre");
preElement.className = "mermaid not-prose";
preElement.innerHTML = code;
// Find the diagram content container and override its content
const diagramContainer = element.querySelector(".diagram-content");
if (diagramContainer) {
diagramContainer.innerHTML = "";
diagramContainer.appendChild(preElement);
}
});
}
// Wait for the DOM to be fully loaded
document.addEventListener("DOMContentLoaded", async () => {
extractMermaidCode();
mermaid.initialize({ startOnLoad: true });
});
</script>
<figure class="expandable-diagram">
<figcaption>{title}</figcaption>
<div class="diagram-content">Loading diagram...</div>
<details>
<summary>Source</summary>
<pre><code><slot /></code></pre>
</details>
</figure>

View file

@ -0,0 +1,52 @@
---
title: Alpha 10 Released - A New Chapter for Wails
description: Announcing Wails v3 Alpha 10 and our new daily release strategy
date: 2024-12-03
author: The Wails Team
---
# Alpha 10 Released - A New Chapter for Wails
We're thrilled to announce the release of **Wails v3 Alpha 10** - and it's big! While our release cadence may have seemed slow recently, there's been an incredible amount of work happening behind the scenes. Today marks not just another release, but a shift in how we approach the development of Wails.
## Why the Wait?
Like many development teams, we fell into the trap of trying to achieve perfection before each release. We wanted to squash every bug, polish every feature, and ensure everything was just right. But here's the thing - software is never truly bug-free, and waiting for that mythical state only delays getting improvements into your hands.
## Our New Release Strategy
Starting today, we're adopting a **daily release strategy**. Any new changes merged during the day will be released at the end of that day. This approach will:
- Get bug fixes and features to you faster
- Provide more frequent feedback loops
- Speed up our journey to Beta
- Make the development process more transparent
This means you might see more frequent, smaller releases rather than occasional large ones. We believe this will benefit everyone in the Wails community.
## How You Can Help
We've been overwhelmed by the number of people wanting to contribute to Wails! To make it easier for contributors to get involved, we're implementing a new system:
- Firstly, we are opening up bug reporting for alpha releases. The frequent releases allow us to do this.
- Issues marked with **"Ready for Work"** are open for community contributions
- These issues have clear requirements and are ready to be tackled
- Most importantly, **we need people testing PRs** - this is one of the most valuable contributions you can make
Testing doesn't require deep knowledge of the codebase, but it provides immense value by ensuring changes work across different environments and use cases.
## What's Next?
With our new daily release strategy, expect to see rapid progress toward Beta. We're committed to maintaining momentum and getting Wails v3 to a stable release as quickly as possible without sacrificing quality.
And who knows? There might be a few surprises along the way... 😉
## Thank You
To everyone who has contributed code, tested releases, reported bugs, or simply used Wails - thank you. Your support and feedback drive this project forward.
Here's to more frequent releases, faster iteration, and an exciting journey ahead!
---
*Want to get involved? Check out our [GitHub repository](https://github.com/wailsapp/wails) for issues marked "ready for work", or join our community to help test the latest changes.*

View file

@ -25,14 +25,81 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Breaking Changes
## v3.0.0-alpha.17 - 2025-07-31
## Fixed
- Fixed notification parsing on Windows @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4450)
## v3.0.0-alpha.16 - 2025-07-25
## Added
- Add Windows `getAccentColor` implementation by [@almas-x](https://github.com/almas-x) in [PR](https://github.com/wailsapp/wails/pull/4427)
## v3.0.0-alpha.15 - 2025-07-25
## Added
- Add Windows `getAccentColor` implementation by [@almas-x](https://github.com/almas-x) in [PR](https://github.com/wailsapp/wails/pull/4427)
## v3.0.0-alpha.14 - 2025-07-25
## Added
- Windows dark theme menus + menubar. By @leaanthony in [a29b4f0861b1d0a700e9eb213c6f1076ec40efd5](https://github.com/wailsapp/wails/commit/a29b4f0861b1d0a700e9eb213c6f1076ec40efd5)
- Rename built-in services for clearer JS/TS bindings by @popaprozac in [PR](https://github.com/wailsapp/wails/pull/4405)
## v3.0.0-alpha.12 - 2025-07-15
### Added
- `app.Env.GetAccentColor` to get the accent color of a user's system. Works on MacOS. by [@etesam913](https://github.com/etesam913)
- Add `window.ToggleFrameless()` api by [@atterpac](https://github.com/atterpac) in [#4137](https://github.com/wailsapp/wails/pull/4137)
### Fixed
- Fixed doctor command to check for Windows SDK dependencies by [@kodumulo](https://github.com/kodumulo) in [#4390](https://github.com/wailsapp/wails/issues/4390)
## v3.0.0-alpha.11 - 2025-07-12
## Added
- Add distribution-specific build dependencies for Linux by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/4345)
- Added bindings guide by @atterpac in [PR](https://github.com/wailsapp/wails/pull/4404)
## v3.0.0-alpha.10 - 2025-07-06
### Breaking Changes
- **Manager API Refactoring**: Reorganized application API from flat structure to organized managers for better code organization and discoverability by [@leaanthony](https://github.com/leaanthony) in [#4359](https://github.com/wailsapp/wails/pull/4359)
- `app.NewWebviewWindow()` → `app.Window.New()`
- `app.CurrentWindow()` → `app.Window.Current()`
- `app.GetAllWindows()` → `app.Window.GetAll()`
- `app.WindowByName()` → `app.Window.GetByName()`
- `app.EmitEvent()` → `app.Event.Emit()`
- `app.OnApplicationEvent()` → `app.Event.OnApplicationEvent()`
- `app.OnWindowEvent()` → `app.Event.OnWindowEvent()`
- `app.SetApplicationMenu()` → `app.Menu.SetApplicationMenu()`
- `app.OpenFileDialog()` → `app.Dialog.OpenFile()`
- `app.SaveFileDialog()` → `app.Dialog.SaveFile()`
- `app.MessageDialog()` → `app.Dialog.Message()`
- `app.InfoDialog()` → `app.Dialog.Info()`
- `app.WarningDialog()` → `app.Dialog.Warning()`
- `app.ErrorDialog()` → `app.Dialog.Error()`
- `app.QuestionDialog()` → `app.Dialog.Question()`
- `app.NewSystemTray()` → `app.SystemTray.New()`
- `app.GetSystemTray()` → `app.SystemTray.Get()`
- `app.ShowContextMenu()` → `app.ContextMenu.Show()`
- `app.RegisterKeybinding()` → `app.KeyBinding.Register()`
- `app.UnregisterKeybinding()` → `app.KeyBinding.Unregister()`
- `app.GetPrimaryScreen()` → `app.Screen.GetPrimary()`
- `app.GetAllScreens()` → `app.Screen.GetAll()`
- `app.BrowserOpenURL()` → `app.Browser.OpenURL()`
- `app.Environment()` → `app.Env.GetAll()`
- `app.ClipboardGetText()` → `app.Clipboard.Text()`
- `app.ClipboardSetText()` → `app.Clipboard.SetText()`
- Renamed Service methods: `Name` -> `ServiceName`, `OnStartup` -> `ServiceStartup`, `OnShutdown` -> `ServiceShutdown` by [@leaanthony](https://github.com/leaanthony)
- Moved `Path` and `Paths` methods to `application` package by [@leaanthony](https://github.com/leaanthony)
- The application menu is now macOS only by [@leaanthony](https://github.com/leaanthony)
### Added
- **Organized Testing Infrastructure**: Moved Docker test files to dedicated `test/docker/` directory with optimized images and enhanced build reliability by [@leaanthony](https://github.com/leaanthony) in [#4359](https://github.com/wailsapp/wails/pull/4359)
- **Improved Resource Management Patterns**: Added proper event handler cleanup and context-aware goroutine management in examples by [@leaanthony](https://github.com/leaanthony) in [#4359](https://github.com/wailsapp/wails/pull/4359)
- Support aarch64 AppImage builds by [@AkshayKalose](https://github.com/AkshayKalose) in [#3981](https://github.com/wailsapp/wails/pull/3981)
- Add diagnostics section to `wails doctor` by [@leaanthony](https://github.com/leaanthony)
- Add window to context when calling a service method by [@leaanthony](https://github.com/leaanthony)
@ -53,10 +120,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `//wails:ignore` directive to prevent binding generation for chosen service methods by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Add `//wails:internal` directive on services and models to allow for types that are exported in Go but not in JS/TS by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Add binding generator support for constants of alias type to allow for weakly typed enums by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Add binding generator tests for Go 1.24 features by [@fbbdev](https://github.com/fbbdev) in [#4068](https://github.com/wailsapp/wails/pull/4068)
- Add support for macOS 15 "Sequoia" to `OSInfo.Branding` for improved OS version detection in [#4065](https://github.com/wailsapp/wails/pull/4065)
- Add `PostShutdown` hook for running custom code after the shutdown process completes by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
- Add `FatalError` struct to support detection of fatal errors in custom error handlers by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
- Standardise and document service startup and shutdown order by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
- Add test harness for application startup/shutdown sequence and service startup/shutdown tests by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
- Add `RegisterService` method for registering services after the application has been created by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
- Add `MarshalError` field in application and service options for custom error handling in binding calls by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
- Add cancellable promise wrapper that propagates cancellation requests through promise chains by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
- Add the ability to tie binding call cancellation to an `AbortSignal` by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
- Support `data-wml-*` attributes for WML alongside the usual `wml-*` attributes by [@leaanthony](https://github.com/leaanthony)
- Add `Configure` method on all services for late configuration/dynamic reconfiguration by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- `fileserver` service sends a 503 Service Unavailable response when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- `kvstore` service provides an in-memory key-value store by default when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Add `Load` method on `kvstore` service to reload data from file after config changes by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Add `Clear` method on `kvstore` service to delete all keys by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Add type `Level` in `log` service to provide JS-side log-level constants by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Add `Log` method on `log` service to specify log-level dynamically by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- `sqlite` service provides an in-memory DB by default when unconfigured by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Add method `Close` on `sqlite` service to close the DB manually by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Add cancellation support for query methods on `sqlite` service by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Add prepared statement support to `sqlite` service with JS bindings by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Gin support by [Lea Anthony](https://github.com/leaanthony) in [PR](https://github.com/wailsapp/wails/pull/3537) based on the original work of [@AnalogJ](https://github.com/AnalogJ) in PR[https://github.com/wailsapp/wails/pull/3537]
- Fix auto save and password auto save always enabled by [@oSethoum](https://github.com/osethoum) in [#4134](https://github.com/wailsapp/wails/pull/4134)
- Add `SetMenu()` on window to allow for setting a menu on a window by [@leaanthony](https://github.com/leaanthony)
- Add Notification support by [@popaprozac](https://github.com/popaprozac) in [#4098](https://github.com/wailsapp/wails/pull/4098)
-  Add File Association support for mac by [@wimaha](https://github.com/wimaha) in [#4177](https://github.com/wailsapp/wails/pull/4177)
- Add `wails3 tool version` for semantic version bumping by [@leaanthony](https://github.com/leaanthony)
- Add badging support for macOS and Windows by [@popaprozac](https://github.com/popaprozac) in [#](https://github.com/wailsapp/wails/pull/4234)
### Fixed
- Fixed nil pointer dereference in processURLRequest for Mac by [@etesam913](https://github.com/etesam913) in [#4366](https://github.com/wailsapp/wails/pull/4366)
- Fixed a linux bug preventing filtered dialogs by [@bh90210](https://github.com/bh90210) in [#4287](https://github.com/wailsapp/wails/pull/4287)
- Fixed Windows+Linux Edit Menu issues by [@leaanthony](https://github.com/leaanthony) in [#3f78a3a](https://github.com/wailsapp/wails/commit/3f78a3a8ce7837e8b32242c8edbbed431c68c062)
- Updated the minimum system version in macOS .plist files from 10.13.0 to 10.15.0 by [@AkshayKalose](https://github.com/AkshayKalose) in [#3981](https://github.com/wailsapp/wails/pull/3981)
- Window ID skip issue by [@leaanthony](https://github.com/leaanthony)
@ -81,6 +178,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Suppressed warnings for services that define lifecycle or http methods but no other bound methods by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Fixed non-React templates failing to display Hello World footer when using light system colour scheme by [@marcus-crane](https://github.com/marcus-crane) in [#4056](https://github.com/wailsapp/wails/pull/4056)
- Fixed hidden menu items on macOS by [@leaanthony](https://github.com/leaanthony)
- Fixed handling and formatting of errors in message processors by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
-  Fixed skipped service shutdown when quitting application by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
-  Ensure menu updates occur on the main thread by [@leaanthony](https://github.com/leaanthony)
- The dragging and resizing mechanism is now more robust and matches expected platform behaviour more closely by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
- Fixed [#4097](https://github.com/wailsapp/wails/issues/4097) Webpack/angular discards runtime init code by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
- Fixed initially-hidden menu items by [@IanVS](https://github.com/IanVS) in [#4116](https://github.com/wailsapp/wails/pull/4116)
- Fixed assetFileServer not serving `.html` files when non-extension request when `[request]` doesn't exist but `[request].html` does
- Fixed icon generation paths by [@robin-samuel](https://github.com/robin-samuel) in [#4125](https://github.com/wailsapp/wails/pull/4125)
- Fixed `fullscreen`, `unfullscreen`, `unminimise` and `unmaximise` events not being emitted by [@oSethoum](https://github.com/osethoum) in [#4130](https://github.com/wailsapp/wails/pull/4130)
- Fixed NSIS Error because of incorrect prefix on default version in config by [@robin-samuel](https://github.com/robin-samuel) in [#4126](https://github.com/wailsapp/wails/pull/4126)
- Fixed Dialogs runtime function returning escaped paths on Windows by [TheGB0077](https://github.com/TheGB0077) in [#4188](https://github.com/wailsapp/wails/pull/4188)
- Fixed Webview2 detection path in HKCU by [@leaanthony](https://github.com/leaanthony).
- Fixed input issue with macOS by [@leaanthony](https://github.com/leaanthony).
- Fixed Windows icon generation task file name by [@yulesxoxo](https://github.com/yulesxoxo) in [#4219](https://github.com/wailsapp/wails/pull/4219).
- Fixed transparency issue for frameless windows by [@leaanthony](https://github.com/leaanthony) based on work by @kron.
- Fixed focus calls when window is disabled or minimised by [@leaanthony](https://github.com/leaanthony) based on work by @kron.
- Fixed system trays not showing after taskbar restarts by [@leaanthony](https://github.com/leaanthony) based on work by @kron.
- Fixed fallbackResponseWriter not implementing Flush() in [#4245](https://github.com/wailsapp/wails/pull/4245)
- Fixed fallbackResponseWriter not implementing Flush() by [@superDingda] in [#4236](https://github.com/wailsapp/wails/issues/4236)
- Fixed macOS window close with pending async Go-bound function call crashes by [@joshhardy](https://github.com/joshhardy) in [#4354](https://github.com/wailsapp/wails/pull/4354)
- Fixed Windows Efficiency mode startup race condition by [@leaanthony](https://github.com/leaanthony)
- Fixed Windows icon handle cleanup by [@leaanthony](https://github.com/leaanthony).
- Fixed `OpenFileManager` on Windows by [@PPTGamer](https://github.com/PPTGamer) in [#4375](https://github.com/wailsapp/wails/pull/4375).
### Changed
@ -94,9 +214,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- In JS/TS bindings, `internal.js/ts` model files have been removed; all models can now be found in `models.js/ts` by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- In JS/TS bindings, named types are never rendered as aliases for other named types; the old behaviour is now restricted to aliases by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- In JS/TS bindings, in class mode, struct fields whose type is a type parameter are marked optional and never initialised automatically by [@fbbdev](https://github.com/fbbdev) in [#4045](https://github.com/wailsapp/wails/pull/4045)
- Remove ESLint from templates by by [@IanVS](https://github.com/IanVS) in [#4059](https://github.com/wailsapp/wails/pull/4059)
- Update copyright date to 2025 by [@IanVS](https://github.com/IanVS) in [#4037](https://github.com/wailsapp/wails/pull/4037)
- Add docs for event.Sender by [@IanVS](https://github.com/IanVS) in [#4075](https://github.com/wailsapp/wails/pull/4075)
- Go 1.24 support by [@leaanthony](https://github.com/leaanthony)
- `ServiceStartup` hooks are now invoked when `App.Run` is called, not in `application.New` by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
- `ServiceStartup` errors are now returned from `App.Run` instead of terminating the process by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
- Binding and dialog calls from JS now reject with error objects instead of strings by [@fbbdev](https://github.com/fbbdev) in [#4066](https://github.com/wailsapp/wails/pull/4066)
- Improved systray menu positioning on Windows by [@leaanthony](https://github.com/leaanthony)
- The JS runtime has been ported to TypeScript by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
- The runtime initialises as soon as it is imported, no need to wait for the window to load by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
- The runtime does not export an init method anymore. A side effects import can be used to initialise it by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
- Bound methods now return a `CancellablePromise` that rejects with a `CancelError` if cancelled. The actual result of the call is discarded by [@fbbdev](https://github.com/fbbdev) in [#4100](https://github.com/wailsapp/wails/pull/4100)
- Built-in service types are now consistently called `Service` by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Built-in service creation functions with options are now consistently called `NewWithConfig` by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- `Select` method on `sqlite` service is now named `Query` for consistency with Go APIs by [@fbbdev](https://github.com/fbbdev) in [#4067](https://github.com/wailsapp/wails/pull/4067)
- Templates: moved runtime to "dependencies", organized package.json files by [@IanVS](https://github.com/IanVS) in [#4133](https://github.com/wailsapp/wails/pull/4133)
- Creates and ad-hoc signs app bundles in dev to enable certain macOS APIs by [@popaprozac](https://github.com/popaprozac) in [#4171](https://github.com/wailsapp/wails/pull/4171)
## v3.0.0-alpha.9 - 2025-01-13
@ -362,9 +496,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 🐧 add task `run:linux` by
[@marcus-crane](https://github.com/marcus-crane) in
[#3146](https://github.com/wailsapp/wails/pull/3146)
- Export `SetIcon` method by @almas1992 in
- Export `SetIcon` method by [@almas-x](https://github.com/almas-x) in
[PR](https://github.com/wailsapp/wails/pull/3147)
- Improve `OnShutdown` by @almas1992 in
- Improve `OnShutdown` by [@almas-x](https://github.com/almas-x) in
[PR](https://github.com/wailsapp/wails/pull/3189)
- Restore `ToggleMaximise` method in `Window` interface by
[@fbbdev](https://github.com/fbbdev) in
@ -435,7 +569,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed bug for linux in doctor in the event user doesn't have proper drivers
installed. Added by [@pylotlight](https://github.com/pylotlight) in
[PR](https://github.com/wailsapp/wails/pull/3032)
- Fix dpi scaling on start up (windows). Changed by @almas1992 in
- Fix dpi scaling on start up (windows). Changed by [@almas-x](https://github.com/almas-x) in
[PR](https://github.com/wailsapp/wails/pull/3145)
- Fix replace line in `go.mod` to use relative paths. Fixes Windows paths with
spaces - @leaanthony.

View file

@ -0,0 +1,9 @@
{
"label": "Technical Documentation",
"link": {
"type": "generated-index",
"description": "Deep dive into Wails v3 internals for developers who want to understand or contribute to the codebase."
},
"order": 50,
"collapsed": false
}

View file

@ -0,0 +1,165 @@
---
title: Wails v3 Architecture
description: Deep-dive diagrams and explanations of every moving part inside Wails v3
sidebar:
order: 1
---
import Mermaid from "../../components/Mermaid.astro";
Wails v3 is a **full-stack desktop framework** consisting of a Go runtime,
a JavaScript bridge, a task-driven tool-chain and a collection of templates that
let you ship native applications powered by modern web tech.
This page presents the *big picture* in four diagrams:
1. **Overall Architecture** how every subsystem connects
2. **Runtime Flow** what happens when JS calls Go and vice-versa
3. **Development vs Production** two modes of the asset server
4. **Platform Implementations** where OS-specific code lives
---
## 1 · Overall Architecture
<Mermaid title="Wails v3 High-Level Stack">
flowchart TD
subgraph Developer
CLI[wails3 CLI]
end
subgraph Build["Build-time Tool-chain"]
GEN["Binding Generator\n(static analysis)"]
TMP["Template Engine"]
ASSETDEV["Asset Server (dev)"]
PKG["Cross-compilation & Packaging"]
end
subgraph Runtime["Native Runtime"]
RT["Desktop Runtime\n(window, dialogs, tray, …)"]
BRIDGE["Message Bridge\n(JSON channel)"]
end
subgraph App["Your Application"]
BACKEND["Go Backend"]
FRONTEND["Web Frontend\n(React/Vue/…)"]
end
CLI -->|init| TMP
CLI -->|generate| GEN
CLI -->|dev| ASSETDEV
CLI -->|build| PKG
GEN -->|Go & TS stubs| BACKEND
GEN -->|bindings.json| FRONTEND
ASSETDEV <-->|HTTP| FRONTEND
BACKEND <--> BRIDGE <--> FRONTEND
BRIDGE <--> RT
RT <-->|serve assets| ASSETDEV
</Mermaid>
---
## 2 · Runtime Call Flow
<Mermaid title="Runtime JavaScript ⇄ Go Calling Path">
sequenceDiagram
participant JS as JavaScript (frontend)
participant Bridge as Bridge (WebView callback)
participant MP as Message Processor (Go)
participant Go as Bound Go Function
JS->>Bridge: invoke("Greet","Alice")
Bridge->>MP: JSON {t:c,id:42,...}
MP->>Go: call Greet("Alice")
Go-->>MP: "Hello Alice"
MP-->>Bridge: JSON {t:r,id:42,result:"Hello Alice"}
Bridge-->>JS: Promise.resolve("Hello Alice")
</Mermaid>
Key points:
* **No HTTP / IPC** the bridge uses the native WebViews in-memory channel
* **Method IDs** deterministic FNV-hash enables O(1) lookup in Go
* **Promises** errors propagate as rejections with stack & code
---
## 3 · Development vs Production Asset Flow
<Mermaid title="Dev ↔ Prod Asset Server">
flowchart LR
subgraph Dev["`wails3 dev`"]
VITE["Framework Dev Server\n(port 5173)"]
ASDEV["Asset Server (dev)\n(proxy + disk)"]
FRONTENDDEV[Browser]
end
subgraph Prod["`wails3 build`"]
EMBED["Embedded FS\n(go:embed)"]
ASPROD["Asset Server (prod)\n(read-only)"]
FRONTENDPROD[WebView Window]
end
VITE <-->|proxy / HMR| ASDEV
ASDEV <-->|http| FRONTENDDEV
EMBED --> ASPROD
ASPROD <-->|in-memory| FRONTENDPROD
</Mermaid>
* In **dev** the server proxies unknown paths to the frameworks live-reload
server and serves static assets from disk.
* In **prod** the same API is backed by `go:embed`, producing a zero-dependency
binary.
---
## 4 · Platform-Specific Runtime Split
<Mermaid title="Per-OS Runtime Files">
classDiagram
class runtime::Window {
+Show()
+Hide()
+Center()
}
runtime::Window <|-- Window_darwin
runtime::Window <|-- Window_linux
runtime::Window <|-- Window_windows
class Window_darwin {
//go:build darwin
+NSWindow* ptr
}
class Window_linux {
//go:build linux
+GtkWindow* ptr
}
class Window_windows {
//go:build windows
+HWND ptr
}
note for runtime::Window "Shared interface\nin pkg/application"
note for Window_darwin "Objective-C (Cgo)"
note for Window_linux "Pure Go GTK calls"
note for Window_windows "Win32 API via syscall"
</Mermaid>
Every feature follows this pattern:
1. **Common interface** in `pkg/application`
2. **Message processor** entry in `pkg/application/messageprocessor_*.go`
3. **Implementation** per OS under `internal/runtime/*.go` guarded by build tags
Missing functionality on an OS should return `ErrCapability` and register
availability via `internal/capabilities`.
---
## Summary
These diagrams outline **where the code lives**, **how data moves**, and
**which layers own which responsibilities**.
Keep them handy while exploring the detailed pages that follow they are your
map to the Wails v3 source tree.

View file

@ -0,0 +1,203 @@
---
title: Asset Server
description: How Wails v3 serves and embeds your web assets in development and production
sidebar:
order: 4
---
## Overview
Every Wails application ships a **single native executable** that combines:
1. Your *Go* backend
2. A *Web* frontend (HTML + JS + CSS)
The **Asset Server** is the glue that makes this possible.
It has **two operating modes** that are selected at compile-time via Go build
tags:
| Mode | Tag | Purpose |
|------|-----|---------|
| **Development** | `//go:build dev` | Fast iteration with hot-reload |
| **Production** | `//go:build !dev` | Zero-dependency, embedded assets |
The implementation lives in
`v3/internal/assetserver/` with clear file splits:
```
assetserver_dev.go # ⬅️ runtime dev server
assetserver_production.go # ⬅️ embedded server
assetserver_darwin.go # OS-specific helpers (same for linux/windows)
asset_fileserver.go # Shared static file logic
content_type_sniffer.go # MIME type detection
ringqueue.go # Tiny LRU for mime cache
```
---
## Development Mode
### Lifecycle
1. `wails3 dev` boots and **spawns your frontend dev server**
(Vite, SvelteKit, React-SWC …) by running the task defined in
`frontend/Taskfile.yml` (usually `npm run dev`).
2. Wails starts the **Dev Asset Server** listening on `localhost:<random>` and
tells the Go runtime to load `http://<host>:<port>` as the window URL.
3. Incoming requests are handled by `assetserver_dev.go`:
```
┌─────────┐ /runtime/... ┌─────────────┐
│ Browser │ ── native bridge ───▶ │ Runtime │
├─────────┤ └─────────────┘
│ JS │ / (index.html) proxy / -> Vite
└─────────┘ ◀─────────────┐
AssetServer │
┌────────────┐
│ Vite Dev │
│ Server │
└────────────┘
```
4. Static files (`/assets/logo.svg`) are **served directly from disk** via
`asset_fileserver.go` (for speed) while anything unknown is **proxied** to
the framework dev server, giving you *instant* hot-module replacement.
### Features
* **Live Reload** Vite/Snowpack/… injects HMR WebSocket; Wails only has to
proxy it.
* **Source Map Support** because assets are not bundled, your browser devtools
map errors back to original source.
* **No Go Re-compile** Only the frontend rebuilds; Go code stays running until
you change `.go` files.
### Switching Frameworks
The dev proxy is **framework-agnostic**:
* The `wails.json` template injects two env vars:
`FRONTEND_DEV_HOST` & `FRONTEND_DEV_PORT`.
* Taskfiles for each template emit those vars before running their dev servers.
* `assetserver_dev.go` simply proxies to that target.
Add a new template → define its dev task → Asset Server just works.
---
## Production Mode
When you run `wails3 build` the pipeline:
1. Runs the frontend **production build** (`npm run build`) producing
`/frontend/dist/**`.
2. **Embeds** that folder into `go:embed` FS at compile time (see
`bundled_assetserver.go` generated file).
3. Compiles the Go binary with `-tags production` (implicit).
### Request Handling
```go
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 1. Try embedded static assets (exact path)
// 2. Fallback to index.html for SPA routing
// 3. Sniff content-type if extension unknown
// 4. Set strong cache headers
}
```
* **MIME Detection** If the build tool produced extension-less files (e.g.
`/assets/manifest`) `content_type_sniffer.go` inspects the first 512 bytes and
caches the result in a tiny lock-free LRU.
* **Ring Queue Caching** Frequently accessed assets (logo, CSS) are kept
in-memory for the lifetime of the app, removing the need for disk or embed FS
lookups.
* **Security Headers** Disallows `file://` navigation, enables `nosniff`.
Because everything is embedded, the shipped binary has **no external
dependencies** (even on Windows).
---
## Bridging Dev ↔ Prod
Both modes expose the **same public interface**:
```go
type AssetServer interface {
URL() string // dev: http://localhost:34115, prod: wails://app
Open() error // start listening
Close() // graceful shutdown
}
```
`pkg/application` happily uses whichever implementation was compiled in, meaning
**your application code does not change** between `dev` and `build`.
---
## How Frontend Frameworks Integrate
### Templates
Each official template (React, Vue, Svelte, Solid…) contains:
* `frontend/Taskfile.yml`
* `frontend/vite.config.ts` (or equivalent)
They export two tasks:
| Task | Purpose |
|------|---------|
| `dev` | Starts the framework dev server on a **random free port** and prints it to stdout (`PORT=5173`). |
| `build` | Produces static assets into `dist/` + manifest for cache-busting. |
`internal/commands/dev.go` parses that stdout, sets `FRONTEND_DEV_*` env vars
and launches the **Dev Asset Server**.
Frameworks remain fully decoupled from Go:
* No need to import Wails JS SDK at build time the runtime injects it at
window creation.
* Any framework with an HTTP dev server can plug in.
---
## Extending / Customising
Need custom headers, auth, or gzip?
1. Implement `type Middleware func(http.Handler) http.Handler`
2. Register via `internal/assetserver/options.go`
3. For prod, remember to add the same middleware in `assetserver_production.go`.
---
## Key Source Files
| File | Role |
|------|------|
| `assetserver_dev.go` | Reverse proxy + disk file server |
| `assetserver_production.go` | Embedded FS handler |
| `options.go` | Config struct parsed from `pkg/options/assetserver` |
| `build_dev.go` / `build_production.go` | Build-tag wrappers selecting correct implementation |
| `bundled_assetserver.go` | Generated embed data (only present in production builds) |
---
## Gotchas & Debugging
* **White Screen in Prod** usually SPA routing: ensure `History API Fallback`
is enabled in dev and `index.html` fallback works in prod.
* **404 in Dev** mis-matched `FRONTEND_DEV_PORT`; run with
`WAILSDEV_VERBOSE=1` to print every proxied request.
* **Large Assets** they are embedded; consider
[`assetserver.WithExternalDir("/path")`](https://pkg.go.dev) to load from disk.
---
You now know how the Wails **Asset Server** feeds your web code to the native
window in both **development** and **production**.
Master this layer and you can debug loading issues, add middlewares, or even
swap in a completely different frontend tool-chain with confidence.

View file

@ -0,0 +1,240 @@
---
title: Binding System
description: How Wails v3 lets Go and JavaScript call each other with zero boilerplate
sidebar:
order: 5
---
> “Bindings” are the **type-safe contract** that lets you write:
```go
msg, err := chatService.Send("Hello")
```
in Go *and*
```ts
const msg = await window.backend.ChatService.Send("Hello")
```
in TypeScript **without manual glue code**.
This document breaks down *how* that magic happens, from **static analysis** at
build time, through **code generation**, to the **runtime bridge** that moves
bytes over the WebView.
---
## 1. 30-Second Overview
| Stage | Component | Output |
|-------|-----------|--------|
| **Analysis** | `internal/generator/analyse.go` | In-memory AST of exported Go structs, methods, params, return types |
| **Generation** | `internal/generator/render/*.tmpl` | • `pkg/application/bindings.go` (Go)<br>• `frontend/src/wailsjs/**.ts` (TS defs)<br>• `runtime/desktop/@wailsio/runtime/internal/bindings.json` |
| **Runtime** | `pkg/application/messageprocessor_call.go` + JS runtime (`invoke.ts`) | JSON messages over WebView native bridge |
The flow is orchestrated by the `wails3` CLI:
```
wails3 generate ─┬─> generator.Collect() // parse Go packages
├─> generator.Analyse() // build semantic model
└─> generator.Render() // write files + `go fmt`
```
---
## 2. Static Analysis
### Entry Point
```
internal/generator/analyse.go
```
`Analyse(cfg generator.Config)`:
1. Recursively loads provided Go packages with `go/packages`.
2. Walks the *type* and *value* specs of every AST file.
3. Filters **exported** symbols (capitalised) outside `internal/` packages.
4. Records:
* Receiver type (`struct`, `interface`)
* Method name
* Parameter list (name, type, pointer, variadic)
* Return tuple (values + error presence)
5. Normalises types to a **serialisable set**
(`int`, `float64`, `string`, `[]byte`, slices, maps, structs, pointers).
Unsupported types produce a **compile-time error** so mistakes are caught early.
### Model
```go
type Method struct {
ID uint32 // stable hash for runtime lookup
Name string
Params []Param
Results []Result
Receiver *Struct // nil for package funcs
}
```
A deterministic **FNV-1a** hash (`internal/hash/fnv.go`) of
`<Receiver>.<Name>(<Signature>)` becomes the *method ID* used at runtime.
---
## 3. Code Generation
### Templates
`internal/generator/render/` contains text/template files:
| Template | Target |
|----------|--------|
| `go_bindings.tmpl` | `pkg/application/bindings.go` |
| `ts_bindings.tmpl` | `frontend/wailsjs/go/models.d.ts` |
| `ts_index.tmpl` | `frontend/wailsjs/index.ts` |
Add or adjust templates here to customise generation.
### Go Output
`bindings.go` registers a lookup table:
```go
var bindings = []application.BoundMethod{
{ID: 0x7A1201D3, Name: "ChatService.Send", Call: chatServiceSend},
}
func chatServiceSend(ctx context.Context, in []byte) ([]byte, error) {
var req struct{ Msg string }
if err := json.Unmarshal(in, &req); err != nil { return nil, err }
res, err := chatService.Send(req.Msg)
return json.Marshal(res), err
}
```
Key points:
* **Zero reflection** at runtime → performance.
* Marshal/Unmarshal is **per-method** with generated struct wrappers.
### TypeScript Output
```ts
export namespace backend {
export namespace ChatService {
function Send(msg: string): Promise<string>;
}
}
```
* Emitted as **ES modules** so any bundler can tree-shake.
* Method IDs are embedded in an auto-generated `bindings.json` for the JS
runtime.
---
## 4. Runtime Invocation Protocol
### JavaScript Side
```ts
import { invoke } from "@wailsio/runtime";
await invoke(0x7a1201d3 /* ChatService.Send */, ["Hello"]);
```
Implementation: `runtime/desktop/@wailsio/runtime/invoke.ts`
1. Packs `{t:"c", id:<methodID>, p:[...args]}` into JSON.
2. Calls `window.external.invoke(payload)` (WebView2) or equivalent.
3. Returns a `Promise` that resolves/rejects based on the reply.
### Go Side
1. `messageprocessor_call.go` receives the JSON.
2. Looks up `methodID` in the `bindings` slice.
3. Executes the generated stub (`chatServiceSend`).
4. Serialises `{result, error}` back to JS.
### Error Mapping
| Go | JavaScript |
|----|------------|
| `error == nil` | `Promise` resolves with result |
| `errors.New(...)` | `Promise` rejects with `{message, stack, code}` |
The mapping code lives in `runtime/desktop/@wailsio/runtime/errors.ts`.
---
## 5. Calling JavaScript from Go
*Browser → Go* is covered above.
*Go → Browser* uses **Events** or **Eval**:
```go
window.Eval(`alert("Hi")`)
app.Publish("chat:new-message", msg)
```
Binding generation is one-way (Go methods).
For JS-exposed functions use `runtime.EventsOn` in JS and `application.Publish`
from Go.
---
## 6. Extending & Troubleshooting
### Adding Custom Serialisers
* Implement `generator.TypeConverter` interface.
* Register in `generator.Config.Converters`.
* Update JS runtime deserialisation if needed.
### Unsupported Type Error
```
error: field "Client" uses unsupported type: chan struct{}
```
→ wrap the channel in a struct with an exposed API or redesign.
### Version Skew
Bindings are regenerated on **every** `wails3 dev` / `wails3 build`.
If IDE intellisense shows stale stubs, delete `frontend/wailsjs` and rebuild.
### Performance Tips
* Prefer **value** receivers for small structs to reduce allocations.
* Avoid large byte slices over the bridge; use `application.FileServer` instead.
* Batch multiple quick calls into one method to minimise bridge latency.
---
## 7. Key Files Map
| Concern | File |
|---------|------|
| Static analysis entry | `internal/generator/analyse.go` |
| Render pipeline | `internal/generator/generate.go` |
| Template assets | `internal/generator/render/*.tmpl` |
| Go binding table | `pkg/application/bindings.go` (generated) |
| Call processor | `pkg/application/messageprocessor_call.go` |
| JS runtime | `runtime/desktop/@wailsio/runtime/invoke.ts` |
| Errors mapping | `runtime/desktop/@wailsio/runtime/errors.ts` |
Keep this cheat-sheet handy when you trace a bridge bug.
---
## 8. Recap
1. **Generator** scans your Go code → semantic model.
2. **Templates** emit **Go stubs** + **TypeScript definitions**.
3. **Message Processor** executes stubs at runtime.
4. **JS Runtime** wraps it all in idiomatic promises.
All without reflection, without IPC servers, and without you writing a single
line of boilerplate. Thats the Wails v3 binding system. Go forth and bind!

View file

@ -0,0 +1,211 @@
---
title: Build & Packaging Pipeline
description: What happens under the hood when you run `wails3 build`, how cross-platform binaries are produced, and how installers are generated for each OS.
sidebar:
order: 6
---
`wails3 build` is a **single command** that drives a _multi-stage_ pipeline:
1. **Frontend production build** (Vite / React / …)
2. **Asset embedding** into Go sources
3. **Native compilation** for the target OS/arch
4. **Post-processing** (icon injection, version info, codesign)
5. **Packaging** into a distributable artefact (AppImage, DMG, MSI, …)
This document follows every stage, shows where the code lives, and explains how
to customise or debug it.
---
## 1. Entry Point: `internal/commands/build-assets.go`
```
wails3 build # cmd/wails3/main.go
└─ internal/commands.Build() # build-assets.go
```
High-level tasks are delegated to **Taskfile** targets so the same logic runs in
CI or locally.
| Stage | Taskfile target | Go implementation |
|-------|-----------------|-------------------|
| Frontend build | `frontend:build` | `internal/commands/task.go` |
| Embed assets | `embed:assets` | generated `bundled_assetserver.go` |
| Go compile | `build:go` | `tool_buildinfo.go`, `tool_package.go` |
| Package | `package:*` | `internal/packager`, `internal/commands/*` |
---
## 2. Frontend Production Build
1. The CLI changes into `frontend/` and executes the `build` task found in the
project `Taskfile.yml` (`npm run build` by default).
2. Output is written to `frontend/dist/`.
3. A **content hash** manifest is produced (`manifest.json`) so cache-busting
works out of the box.
If the task fails the pipeline aborts early, returning the exit code of your
build tool.
---
## 3. Embedding Assets
Implemented in `internal/assetserver/build_production.go`:
* Walks `frontend/dist` and generates a `go:embed` file
`bundled_assetserver.go` (ignored by git).
* Adds the `production` build tag.
Result: the final binary contains every HTML/JS/CSS byte, so the executable is
self-contained.
---
## 4. Native Compilation
### Build Flags
| Flag | Purpose |
|------|---------|
| `-tags production` | Select prod asset server & runtime stubs |
| `-ldflags "-s -w"` | Strip symbol table & DWARF (smaller binaries) |
| `-X internal/buildinfo.BuildTime=$(date)` | Embed reproducible metadata |
`internal/commands/tool_buildinfo.go` collects version, git commit, and build
time then injects them using `go build -ldflags`.
### Cross Compilation Matrix
| OS | Arch | Build Tag | CGO | Notes |
|----|------|-----------|-----|-------|
| Windows | amd64 / arm64 | `windows` | cgo enabled for WebView2 | Generates `.syso` via `internal/commands/syso.go` |
| macOS | amd64 / arm64 | `darwin` | cgo enabled (Objective-C) | Codesign & notarisation tasks available |
| Linux | amd64 / arm64 | `linux` | pure Go (webkit option) | GTK/WebKitGTK headers required for CGO build |
`wails3 build -platform darwin/arm64` overrides GOOS/GOARCH.
If CGO is needed on the host that can't compile the target (e.g. building
Windows from Linux), the CLI falls back to **Docker** images (`ghcr.io/wailsapp/cross-compiler`).
---
## 5. Post-Processing
### Icons & Resources
* **Windows** `syso.go` merges your PNG/ICO into a `.syso` that `go build`
links automatically. Also writes `manifest.xml` enabling High-DPI support.
* **macOS** `icons.go` turns `icon.png` → `.icns`, injects into
`MyApp.app/Contents/Resources`.
* **Linux** `.desktop` files are generated in `/usr/share/applications`
by each packager backend.
### Code Signing (Optional)
* macOS: `codesign` + `xcrun altool --notarize-app`
* Windows: `signtool.exe`
* Linux: Not common (repository GPG handled externally)
Task targets: `sign:mac`, `sign:windows`.
---
## 6. Packaging Back-Ends
### Linux
| Artefact | Code Path | Tooling |
|----------|-----------|---------|
| **AppImage** (default) | `internal/commands/appimage.go` | `linuxdeploy` + `linuxdeploy-plugin-gtk` |
| **deb / rpm / pacman** | `internal/packager` | `fpm` (invoked via Go) |
Flags:
```
wails3 build -package deb # produces myapp_1.0.0_amd64.deb
wails3 build -package rpm
```
### macOS
* **DMG** `internal/commands/packager.go` calls `appdmg` to generate a
drag-&-drop installer.
* **ZIP** fallback if `appdmg` is missing.
* Sets CFBundle identifiers and version plist automatically.
### Windows
* **MSI** `internal/commands/packager.go` wraps **WiX** candle & light tools.
Heat autogenerates the component tree from the built `.exe`.
Extra resources:
* `internal/commands/windows_resources/` contains templated **.wxs** fragments.
* Version info injected via `rsrc` utility.
---
## 7. Build Artefact Layout
After a successful build the CLI prints:
```
build/bin/
├── myapp-linux-amd64
└── myapp-linux-amd64.AppImage
build/package/
└── myapp_1.0.0_amd64.deb
```
The exact files depend on `-platform` and `-package` flags.
---
## 8. Customising the Pipeline
| Need | Approach |
|------|----------|
| Run a linter before build | Add a `lint` task in **Taskfile.yml** and make `build` depend on it. |
| Extra linker flags | `wails3 build -ldflags "-H windowsgui"` |
| Skip packaging | `wails3 build -skip-package` (keeps raw binary). |
| Bring your own packager | Implement `internal/packager/<name>.go`, register in `packager.go`. |
All Taskfile targets use environment variables exported by the CLI, so you can
reference values like `$WAILS_VERSION` or `$WAILS_PLATFORM` inside custom tasks.
---
## 9. Troubleshooting
| Symptom | Likely Cause | Fix |
|---------|--------------|-----|
| **`ld: framework not found WebKit` (mac)** | Xcode CLI tools missing | `xcode-select --install` |
| **Blank window in prod build** | Frontend build failed or SPA routing | Check `frontend/dist/index.html` exists and `History API Fallback` is set. |
| **`wixl` not found** (Linux MSI cross-build) | WiX toolset not installed in Docker image | Use `--docker` build or install WiX manually. |
| **Duplicated symbol `_WinMain`** | Mixed `windowsgui` flag and syso | Remove custom `-ldflags` or let Wails manage resources. |
Verbose mode: `wails3 build -verbose` dumps every command executed.
---
## 10. Key Source Map
| Concern | File |
|---------|------|
| Task runner glue | `internal/commands/task_wrapper.go` |
| Build dispatcher | `internal/commands/build-assets.go` |
| AppImage builder | `internal/commands/appimage.go` |
| Generic packager interface | `internal/packager/packager.go` |
| Windows resource generator | `internal/commands/syso.go` |
| Build info injector | `internal/commands/tool_buildinfo.go` |
| Version constants | `internal/version/version.go` |
Keep this table handy when you trace a build failure.
---
You now have the full picture from **source code** to **installer**. Armed with
this knowledge you can tweak the pipeline, add new packaging targets, or debug
cross-compilation issues without fear. Happy shipping!

View file

@ -0,0 +1,210 @@
---
title: Codebase Layout
description: How the Wails v3 repository is organised and how the pieces fit together
sidebar:
order: 2
---
Wails v3 lives in a **monorepo** that contains the framework runtime, CLI,
examples, documentation, and build tool-chain.
This page walks through the *directory structure* that matters to anyone digging
into the internals.
## Top-Level View
```
wails/
├── v3/ # ⬅️ Everything specific to Wails v3 lives here
├── v2/ # Legacy v2 implementation (can be ignored for v3 work)
├── docs/ # Astro-powered v3 docs site (this page!)
├── website/ # Docusaurus v2 site and marketing pages (main site)
├── scripts/ # Misc helper scripts (e.g. sponsor image generator)
├── mkdocs-website/ # Deprecated v3 docs prototype
└── *.md # Project-wide meta files (CHANGELOG, LICENSE, …)
```
From here on, we zoom into the **`v3/`** tree.
## `v3/` Root
```
v3/
├── cmd/ # Compilable commands (currently only wails3 CLI)
├── examples/ # Hands-on sample apps testing every feature
├── internal/ # Framework implementation (not public API)
├── pkg/ # Public Go packages the API surface
├── tasks/ # Taskfile-based release utilities
├── templates/ # RFC-style proposals (WEP) + common assets
├── go.mod
└── go.sum
```
### Mental Model
1. **`pkg/`** exposes *what application developers import*
2. **`internal/`** contains *how the magic is implemented*
3. **`cmd/wails3`** drives *project lifecycle & builds*
4. **`examples/`** double as *living tests* and *reference code*
Everything else supports those three pillars.
---
## `cmd/` Commands
| Path | Notes |
|------|-------|
| `v3/cmd/wails3` | The **CLI entrypoint**. A tiny `main.go` delegates all logic to packages in `internal/commands`. |
| `internal/commands/*` | Sub-commands (init, dev, build, doctor, …). Each lives in its own file for easy discoverability. |
| `internal/commands/task_wrapper.go` | Bridges between CLI flags and the Taskfile build pipeline. |
The CLI owns:
* **Project scaffolding** (`init`, template generation)
* **Dev server orchestration** (`dev`, live-reload)
* **Production builds & packaging** (`build`, `package`, platform wrappers)
* **Diagnostics** (`doctor`)
---
## `internal/` The Engine Room
```
internal/
├── assetserver/ # Serving & embedding web assets
├── buildinfo/ # Reproducible build metadata
├── commands/ # CLI mechanics (see above)
├── runtime/ # Desktop runtime (per-platform)
├── generator/ # Static analysis & binding generator
├── templates/ # Project templates (frontend stacks)
├── packager/ # Linux AppImage/deb/rpm, Windows MSI, macOS DMG
├── capabilities/ # Host OS capability probing
├── dbus/ # Linux system tray integration
├── service/ # IPC micro-services framework
└── ... # [other helper sub-packages]
```
### Key Sub-Packages
| Package | Responsibility | Where It Connects |
|---------|----------------|-------------------|
| `runtime` | Window/event loop, clipboard, dialogs, system tray. One file per OS with build-tags (`*_darwin.go`, `*_linux.go`, …). | Called by `pkg/application` and message processor. |
| `assetserver` | Dual-mode file server:<br>• Dev: serves from disk & proxies Vite<br>• Prod: embeds assets via `go:embed` | Initialized by `pkg/application` during startup. |
| `generator` | Parses Go source to build **binding metadata** which later produces TypeScript stub files and Go marshal/unmarshal code. | Triggered by `wails3 init` / `wails3 generate`. |
| `packager` | Wraps platform-specific packaging CLIs (eg. `electron-builder` equivalents) into Go for cross-platform automation. | Invoked by `wails3 package`. |
Supporting utilities (eg. `s/`, `hash/`, `flags/`) keep internal concerns decoupled.
---
## `pkg/` Public API
```
pkg/
├── application/ # Core API: windows, menus, dialogs, events, …
├── runtime/ # JS-side helpers (bridge, events, screen, ...)
├── options/ # Strongly-typed configuration structs
├── menu/ # Declarative menu DSL
└── ... # Platform helpers (mac/, windows/, assetserver/, …)
```
`pkg/application` bootstraps a Wails program:
```go
func main() {
app := application.New(application.Options{
Name: "MyApp",
Assets: application.NewAssetsFS(assetsFS),
})
window := app.NewWebviewWindow(&application.WebviewWindowOptions{
Title: "Hello",
Width: 1024,
Height: 768,
})
app.Run()
}
```
Under the hood it:
1. Spins up `internal.runtime.*`
2. Sets up an `assetserver`
3. Registers bindings generated by `internal.generator`
4. Enters the OS main thread
---
## `examples/` Living Specs
Each sub-folder is a **self-contained application** exercising one feature.
Patterns youll see mirrored in real code:
* Binding services (`examples/binding/`)
* Multi-window (`examples/window/`)
* System tray (`examples/systray-*`)
* Packaging (`examples/file-association/`)
When in doubt, start here and drill into implementation.
---
## `templates/` Scaffolding Blueprints
`internal/templates` ships **base templates** (Go layout) and **frontend skins**
(React, Svelte, Vue, …).
At `wails3 init -t react`, the CLI:
1. Copies `_common` Go files
2. Merges desired frontend pack
3. Runs `npm install` + `task deps`
Editing templates **does not** affect existing apps, only future `init`s.
---
## `tasks/` Release Automation
Taskfiles wrap complex cross-compilation, version bumping, and changelog
generation. They are consumed programmatically by `internal/commands/task.go`
so the same logic powers **CLI** and **CI**.
---
## How the Pieces Interact
```mermaid
flowchart TD
A[wails3 CLI] -- build/generate --> B[internal.generator]
A -- dev --> C[assetserver (dev)]
A -- package --> P[internal.packager]
subgraph App runtime
E[pkg.application] --> F[internal.runtime]
F --> G[OS APIs]
E --> C
end
B --> E %% generated bindings registered at init
```
*CLI → generator → runtime* forms the core path from **source** to **running
desktop app**.
---
## Orientation Tips
| Need to understand… | Look at… |
|---------------------|----------|
| Platform shims | `internal/runtime/*_DARWIN.go`, `*_windows.go`, … |
| Bridge protocol | `pkg/application/messageprocessor_*.go` |
| Asset workflow | `internal/assetserver`, `v3/templates/base/assets` |
| Packaging flow | `internal/commands/appimage.go`, `internal/packager` |
| Template engine | `internal/templates/templates.go` |
| Static analysis | `internal/generator/*` |
---
You now have a **mental map** of the repository.
Use it with `ripgrep`, your IDEs “Go to File/Symbol”, and the example apps to
navigate deeper into any feature. Happy hacking!

View file

@ -0,0 +1,246 @@
---
title: Extending Wails
description: Practical guide to adding new features and platforms to Wails v3
sidebar:
order: 8
---
> Wails is designed to be **hackable**.
> Every major subsystem lives in Go code you can read, modify, and ship.
> This page shows _where_ to start and _how_ to stay cross-platform when you:
* Add a **Service** (file server, KV store, custom IPC…)
* Create a **new CLI command** (`wails3 <foo>`)
* Extend the **Runtime** (window API, dialogs, events)
* Introduce a **Platform capability** (Wayland, Apple Vision OS…)
* Keep **cross-platform compatibility** without drowning in `//go:build` tags
---
## 1. Adding a Service
Services are background Go components exposed to apps via the `application.Context`.
```
internal/service/
├── template/ # Boilerplate for new services
│ ├── template.go
│ └── README.md
└── service.go # Registration + lifecycle interfaces
```
### 1.1 Define the Service
```go
package chat
type Service struct {
messages []string
}
func New() *Service { return &Service{} }
func (s *Service) Send(msg string) string {
s.messages = append(s.messages, msg)
return "ok"
}
```
### 1.2 Implement `service.Service`
```go
func (s *Service) Init(ctx *application.Context) error { return nil }
func (s *Service) Shutdown() error { return nil }
```
### 1.3 Register Generator
*Add to* `internal/generator/collect/services.go`
```go
services.Register("chat", chat.New)
```
> Now `wails3 generate` emits bindings so JS can call
> `window.backend.chat.Send("hi")`.
### 1.4 Ship an Example
Copy `v3/examples/services/` and adjust.
---
## 2. Writing a New CLI Command
CLI logic lives under `internal/commands/`.
Each command is **one file** that mounts itself in `init()`.
### 2.1 Create the File
`v3/internal/commands/hello.go`
```go
package commands
import (
"github.com/spf13/cobra"
)
func init() {
Add(&cobra.Command{
Use: "hello",
Short: "Prints Hello World",
RunE: func(cmd *cobra.Command, args []string) error {
cmd.Println("Hello Wails!")
return nil
},
})
}
```
`Add()` registers with the root in `cmd/wails3/main.go`.
### 2.2 Re-compile CLI
```
go install ./v3/cmd/wails3
wails3 hello
```
If your command needs **Taskfile** integration, reuse helpers in
`internal/commands/task_wrapper.go`.
---
## 3. Modifying the Runtime
Common reasons:
* New window feature (`SetOpacity`, `Shake`, …)
* Extra dialog (`ColorPicker`)
* System-level API (screen brightness)
### 3.1 Public API
Add to `pkg/application/window.go`:
```go
func (w *WebviewWindow) SetOpacity(o float32) {
w.ctx.Call("window:setOpacity", o)
}
```
### 3.2 Message Processor
Create `messageprocessor_window_opacity.go`:
```go
const MsgSetOpacity = "window:setOpacity"
func init() { register(MsgSetOpacity, handleSetOpacity) }
func handleSetOpacity(ctx *Context, params json.RawMessage) ([]byte, error) {
var req struct {
ID uint64 `json:"id"`
Opacity float32 `json:"o"`
}
json.Unmarshal(params, &req)
return nil, runtime.SetOpacity(req.ID, req.Opacity)
}
```
### 3.3 Platform Implementation
```
internal/runtime/
webview_window_darwin.go
webview_window_linux.go
webview_window_windows.go
```
Add `SetOpacity()` to each, guarded by build tags.
If a platform cant 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!

View file

@ -0,0 +1,128 @@
---
title: Technical Overview
description: High-level architecture and roadmap to the Wails v3 codebase
sidebar:
order: 1
---
import { Card, CardGrid } from "@astrojs/starlight/components";
import Mermaid from "../../components/Mermaid.astro";
## Welcome to the Wails v3 Technical Documentation
This section is **not** about community guidelines or how to open a pull-request.
Instead, it dives into **how Wails v3 is built** so that you can quickly orient
yourself in the codebase and start hacking with confidence.
Whether you plan to patch the runtime, extend the CLI, craft new templates, or
simply understand the internals, the pages that follow provide the technical
context you need.
---
## High-Level Architecture
<CardGrid>
<Card title="Go Backend" icon="seti:go">
The heart of every Wails app is Go code compiled into a native executable.
It owns application logic, system integration and performance-critical
operations.
</Card>
<Card title="Web Frontend" icon="seti:html">
UI is written with standard web tech (React, Vue, Svelte, Vanilla, …)
rendered by a lightweight system WebView (WebKit on Linux/macOS, WebView2 on
Windows).
</Card>
<Card title="Bridging Layer" icon="puzzle">
A zero-copy, in-memory bridge enables **Go⇄JavaScript** calls with automatic
type conversion, event propagation and error forwarding.
</Card>
<Card title="CLI & Tooling" icon="terminal">
`wails3` orchestrates project creation, live-reload dev server, asset
bundling, cross-compilation and packaging (deb, rpm, AppImage, msi, dmg…).
</Card>
</CardGrid>
---
## Architectural Overview
<Mermaid title="Wails v3 End-to-End Flow">
```mermaid
flowchart TD
subgraph Developer Environment
CLI[wails3 CLI<br/>Init · Dev · Build · Package]
end
subgraph Build-Time
GEN[Binding System<br/>(Static Analysis & Codegen)]
ASSET[Asset Server<br/>(Dev Proxy · Embed FS)]
PKG[Build & Packaging<br/>Pipeline]
end
subgraph Runtime
RUNTIME[Desktop Runtime<br/>(Window · Events · Dialogs)]
BIND[Bridge<br/>(Message Processor)]
end
subgraph Application
GO[Go Backend<br/>(App Logic)]
WEB[Web Frontend<br/>(React/Vue/...)]
end
%% Relationships
CLI --> |"generate"| GEN
CLI --> |"dev / build"| ASSET
CLI --> |"compile & package"| PKG
GEN --> |"Code Stubs + TS"| GO
GEN --> |"Bindings JSON"| WEB
PKG --> |"Final Binary + Installer"| GO
GO --> |"Function Calls"| BIND
WEB --> |"Invoke / Events"| BIND
RUNTIME <-->|native messages| BIND
RUNTIME --> |"Display Assets"| ASSET
WEB <-->|HTTP / In-Memory| ASSET
```
</Mermaid>
The diagram shows the **end-to-end flow**:
1. **CLI** drives generation, dev server, compilation, and packaging.
2. **Binding System** produces glue code that lets the **Web Frontend** call into the **Go Backend**.
3. During development the **Asset Server** proxies to the framework dev server; in production it serves embedded files.
4. At runtime the **Desktop Runtime** manages windows and OS APIs, while the **Bridge** shuttles messages between Go and JavaScript.
---
## What This Documentation Covers
| Topic | Why It Matters |
| ----- | -------------- |
| **Codebase Layout** | Map of `/v3` directories and how modules interact. |
| **Runtime Internals** | Window management, system APIs, message processor, and platform shims. |
| **Asset & Dev Server** | How web assets are served in dev and embedded in production. |
| **Build & Packaging Pipeline** | Taskfile-based workflow, cross-platform compilation, and installer generation. |
| **Binding System** | Static analysis pipeline that generates type-safe Go⇄TS bindings. |
| **Template System** | Generator architecture that powers `wails init -t <framework>`. |
| **Testing & CI** | Unit/integration test harness, GitHub Actions, race detector guidance. |
| **Extending Wails** | Adding services, templates, or CLI sub-commands. |
Each subsequent page drills into these areas with concrete code samples,
diagrams, and references to the relevant source files.
---
:::note
Prerequisites: You should be comfortable with **Go 1.23+**, basic TypeScript,
and modern frontend build tools. If you are new to Go, consider skimming the
official tour first.
:::
Happy exploring — and welcome to the Wails v3 internals!

View file

@ -0,0 +1,175 @@
---
title: Runtime Internals
description: Deep-dive into how Wails v3 boots, runs, and talks to the OS
sidebar:
order: 3
---
The **runtime** is the layer that transforms ordinary Go functions into a
cross-platform desktop application.
This document explains the moving parts you will meet when tracing through the
source code.
---
## 1. Application Lifecycle
| Phase | Code Path | What Happens |
|-------|-----------|--------------|
| **Bootstrap** | `pkg/application/application.go:init()` | Registers build-time data, creates a global `application` singleton. |
| **New()** | `application.New(...)` | Validates `Options`, spins up the **AssetServer**, initialises logging. |
| **Run()** | `application.(*App).Run()` | 1. Calls platform `mainthread.X()` to enter the OS UI thread.<br>2. Boots the **runtime** (`internal/runtime`).<br>3. Blocks until the last window closes or `Quit()` is called. |
| **Shutdown** | `application.(*App).Quit()` | Broadcasts `application:shutdown` event, flushes log, tears down windows & services. |
The lifecycle is strictly **single-entry**: you may create many windows, but the
application object itself is initialised once.
---
## 2. Window Management
### Public API
```go
win := app.NewWebviewWindow(&application.WebviewWindowOptions{
Title: "Dashboard",
Width: 1280,
Height: 720,
})
win.Show()
```
`NewWebviewWindow` delegates to `internal/runtime/webview_window_*.go` where
platform-specific constructors live:
```
internal/runtime/
├── webview_window_darwin.go
├── webview_window_linux.go
└── webview_window_windows.go
```
Each file:
1. Creates a native webview (WKWebView, WebKitGTK, WebView2).
2. Registers a **Message Processor** callback.
3. Maps Wails events (`WindowResized`, `Focus`, `DropFile`, …) to runtime
event IDs.
Windows are tracked by `screenmanager.go` which offers **query APIs** (all
displays, DPI, active window) and centralises resize / move bookkeeping.
---
## 3. Message Processing Pipeline
The bridge between JavaScript and Go is implemented by the **Message
Processor** family in `pkg/application/messageprocessor_*.go`.
Flow:
1. **JavaScript** calls `window.runtime.Invoke("Greet", "Alice")`.
2. The runtime serialises the request:
`{"t":"c","id":"123","m":"Greet","p":["Alice"]}`
(`t` = type, `c` = call).
3. **Go** receives this JSON via the webview callback.
4. `messageprocessor_call.go` looks up the bound method in the
generated table (`application.bindings.go`) and executes it.
5. The result or error is marshalled back to JS where a `Promise` resolves.
Specialised processors:
| File | Purpose |
|------|---------|
| `messageprocessor_window.go` | Window actions (hide, maximize, …) |
| `messageprocessor_dialog.go` | Native dialogs (`OpenFile`, `MessageBox`, …) |
| `messageprocessor_clipboard.go` | Clipboard read/write |
| `messageprocessor_events.go` | Event subscribe / emit |
| `messageprocessor_browser.go` | Browser navigation, devtools |
Processors are **stateless** they pull everything they need from the
`ApplicationContext` passed with each message.
---
## 4. Events System
Events are namespaced strings dispatched across three layers:
1. **Application events**: global lifecycle (`application:ready`,
`application:shutdown`).
2. **Window events**: per-window (`window:focus`, `window:resize`).
3. **Custom events**: user-defined (`chat:new-message`).
Implementation details:
* Constants are generated from `internal/events/defaults.go`.
* Go side:
`app.On("window:focus", func(e application.WindowEvent) {...})`
* JS side:
`window.runtime.EventsOn("chat:new-message", cb)`
Under the hood both map to the same **EventBus** in
`pkg/application/events.go`.
Subscribers are reference-counted; when a window closes, its callbacks are
auto-unregistered to avoid leaks.
---
## 5. Platform-Specific Implementations
Conditional compilation keeps the public API identical while hiding OS wrinkles.
| Concern | Darwin | Linux | Windows |
|---------|--------|-------|---------|
| Main Thread | `mainthread_darwin.go` (Cgo to Foundation) | `mainthread_linux.go` (GTK) | `mainthread_windows.go` (Win32 `AttachThreadInput`) |
| Dialogs | `dialogs_darwin.*` (NSAlert) | `dialogs_linux.go` (GtkFileChooser) | `dialogs_windows.go` (IFileOpenDialog) |
| Clipboard | `clipboard_darwin.go` | `clipboard_linux.go` | `clipboard_windows.go` |
| Tray Icons | `systemtray_darwin.*` | `systemtray_linux.go` (DBus) | `systemtray_windows.go` (Shell_NotifyIcon) |
Key principles:
* **No Cgo on Windows/Linux** unless unavoidable (performance, portability).
* Use **build tags** (`//go:build darwin && !production`) to keep files readable.
* Expose **capabilities** via `internal/capabilities` so higher layers can
degrade gracefully.
---
## 6. File Guide
| File | Why Youd Touch It |
|------|--------------------|
| `internal/runtime/runtime_*.go` | Change global startup logic, add debug hooks. |
| `internal/runtime/webview_window_*.go` | Implement a new window hint or behaviour. |
| `pkg/application/messageprocessor_*.go` | Add a new bridge command callable from JS. |
| `pkg/application/events_*.go` | Extend built-in event definitions. |
| `internal/assetserver/*` | Tweak dev/production asset handling. |
---
## 7. Debugging Tips
* Launch with `WAILS_LOG_LEVEL=debug` to print every message crossing the bridge.
* Use `wails3 dev -verbose` to see live reload & asset requests.
* On macOS run under `lldb --` to catch Objective-C exceptions early.
* For Windows Chromium issues, enable WebView2 debug logs:
`set WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=9222`
---
## 8. Extending the Runtime
1. Define a **capability flag** in `internal/capabilities`.
2. Implement the feature in each platform file using build tags.
3. Add public API in `pkg/application`.
4. Register a new message type or event if JS needs to call it.
5. Update at least one example in `v3/examples/` exercising the feature.
Follow this checklist and you'll keep the cross-platform contract intact.
---
You now have a guided tour of the runtime internals. Combine this knowledge with
the **Codebase Layout** map and the **Asset Server** docs to navigate confidently
and make impactful contributions. Happy coding!

View file

@ -0,0 +1,222 @@
---
title: Template System
description: How Wails v3 scaffolds new projects, how templates are organised, and how to build your own.
sidebar:
order: 7
---
Wails ships with a **template system** that lets `wails3 init` produce a ready-to-run
project for **any** modern frontend stack (React, Svelte, Vue, Solid, Vanilla
JS …).
This page covers:
1. Template directory layout
2. How the CLI chooses & renders templates
3. Creating a new template step-by-step
4. Updating or overriding existing templates
5. Troubleshooting and best practices
---
## 1. Where Templates Live
```
v3/
└── internal/templates/
├── _common/ # Files copied into EVERY project (main.go, .gitignore)
├── base/ # Backend-only “plain Go” template
├── react/ # React + Vite
├── react-ts/
├── svelte/
├── vue/
├── ... (others)
└── templates.go # Registry + helper functions
```
* **`_common/`** universal boilerplate (Go modules, Taskfile, README stub,
VS Code settings).
* **Framework folders** contain **frontend/** and any stack-specific config.
* Folder names match the **template ID** you pass to the CLI
(`wails3 init -t react-ts`).
> The whole directory is compiled into the CLI binary via `go:embed`, so users
> can scaffold projects offline.
---
## 2. How `wails3 init` Uses Templates
Call chain:
```
cmd/wails3/init.go
internal/commands/init.go ← parses flags, target dir
internal/templates.Load() ← templates.go
Template.CopyTo(dest) ← filesystem copy + text substitutions
```
### Substitutions
Placeholders wrapped in `{{ }}` are replaced at copy time:
| Placeholder | Example | Source |
|-------------|---------|--------|
| `{{ProjectName}}` | `myapp` | CLI flag/dir name |
| `{{ModulePath}}` | `github.com/me/myapp` | if `--module` provided |
| `{{WailsVersion}}`| `v3.0.0` | compiled constant |
You can add **any** placeholder; just ensure it gets a value in
`internal/commands/init.go`.
### Post-Copy Hooks
Each template may ship a **Taskfile** with a `deps` task.
After copy, the CLI runs:
```
task --taskfile Taskfile.yml deps
```
Typical work:
* `go mod tidy`
* `npm install`
* git init & initial commit (optional)
Hook behaviour defined in `_common/Taskfile.yml` but override-able per template.
---
## 3. Creating a New Template
> Example: Add **Qwik-TS** template
### 3.1 Folder & ID
```
internal/templates/qwik-ts/
```
The folder name = template ID. Keep it **kebab-case**.
### 3.2 Minimal File Set
```
qwik-ts/
├── README.md # Shown in CLI with -list
├── frontend/ # Your web project
│ ├── src/
│ ├── package.json
│ └── vite.config.ts
├── Taskfile.yml # deps & dev tasks
└── go/ # Optional extra Go files
```
Start by copying `react-ts` and pruning files.
### 3.3 Update Placeholders
Search/replace:
* `myapp` → `{{ProjectName}}`
* `github.com/you/myapp` → `{{ModulePath}}`
### 3.4 Register (optional)
If the folder exists, **templates.go auto-detects it** via `embed`.
Only add explicit code if you need **custom validation**:
```go
func (t *Template) Validate() error {
// check Node ≥ 20 for this template
}
```
### 3.5 Test
```bash
wails3 init -n demo -t qwik-ts
cd demo
wails3 dev
```
Ensure:
* Dev server starts
* `wailsjs` bindings generate
* Hot-reload works
---
## 4. Modifying Existing Templates
1. Edit files under `internal/templates/<id>/`.
2. Run `go generate ./v3/...` **or** just `go run ./v3/cmd/wails3 help`; the
embed directive recompiles automatically.
3. Bump any **dependency versions** in `frontend/package.json` & `Taskfile.yml`.
4. Update `README.md` inside the template — users see it via
`wails3 init -t react --help`.
### Common Tweaks
| Task | Where |
|------|-------|
| Change dev server port | `frontend/Taskfile.yml` (`vite dev --port {{DevPort}}`) |
| Add environment variables | same Taskfile or `.env` |
| Replace JS package manager | Switch `npm` → `pnpm` in Taskfile |
---
## 5. Template Authoring Tips
* **Keep frontend generic** avoid referencing Wails-specific globals; runtime
is injected automatically.
* **No compiled artefacts** exclude `node_modules`, `dist`, `.DS_Store` via
`.tmplignore`.
* **Document prerequisites** Node version, extra CLI tools, etc.
* **Avoid breaking changes** create a *new* template ID if overhaul is big.
---
## 6. Troubleshooting
| Symptom | Cause | Fix |
|---------|-------|-----|
| `unknown template id` | Folder name mismatch | Check `wails3 init -list` |
| Placeholders not replaced | Missing entry in `Data` map | Edit `internal/commands/init.go` |
| Dev server opens blank page | Wrong `DevPort` env | Ensure Taskfile echoes port to stdout |
| Frontend fails to build in prod | Forgotten vite `base` path | Set `base: "./"` in `vite.config.ts` |
Add `--verbose` to `wails3 init` to print every copied file and substitution.
---
## 7. Key Source File Map
| File | Responsibility |
|------|----------------|
| `internal/templates/templates.go` | Embeds template FS, returns `[]Template` |
| `internal/templates/<id>/**` | Actual template content |
| `internal/commands/init.go` | CLI glue: picks template, fills placeholders |
| `internal/commands/generate_template.go` | Utility to *export* a live project back into a template (handy for updates) |
---
## 8. Recap
* Templates live in **`internal/templates/`** and are baked into the CLI.
* `wails3 init -t <id>` copies `_common` + selected template, performs
substitutions, then runs a **deps** Taskfile hook.
* Creating a template is as simple as **making a folder**, adding files, and
using placeholders.
* The system is **extensible** and **self-contained** — perfect for sharing
custom stacks with your team or the community.
Happy templating!

View file

@ -0,0 +1,216 @@
---
title: Testing & Continuous Integration
description: How Wails v3 ensures quality with unit tests, integration suites, race detection, and GitHub Actions CI.
sidebar:
order: 9
---
Robust desktop frameworks demand rock-solid testing.
Wails v3 employs a **layered strategy**:
| Layer | Goal | Tooling |
|-------|------|---------|
| Unit tests | Fast feedback for isolated functions | `go test ./...` |
| Integration / Example tests | Validate whole features across packages | Example apps + headless assertions |
| Race detection | Catch data races in the runtime & bridge | `go test -race`, `wails3 dev -race` |
| CI matrix | Cross-OS confidence on every PR | GitHub Actions |
This document explains **where tests live**, **how to run them**, and **what happens in CI**.
---
## 1. Directory Conventions
```
v3/
├── internal/.../_test.go # Unit tests for internal packages
├── pkg/.../_test.go # Public API tests
├── examples/... # Example apps double as integration tests
├── tasks/events/... # Generated code tests run in CI
└── pkg/application/RACE.md # Race test guidance & known safe suppressions
```
Guidelines:
* **Keep unit tests next to code** (`foo.go` ↔ `foo_test.go`)
* Use the **black-box style** for `pkg/` packages (`package application_test`)
* Integration helpers go in `internal/testutil/` (shared mocks, fixtures)
---
## 2. Unit Tests
### Writing Tests
```go
func TestColourParseHex(t *testing.T) {
c, err := colour.Parse("#FF00FF")
require.NoError(t, err)
assert.Equal(t, 255, c.R)
}
```
Recommendations:
* Use [`stretchr/testify`](https://github.com/stretchr/testify) already vendor-imported.
* Follow **table-driven** style for branches.
* Stub platform quirks behind build tags (`_test_windows.go` etc.) when needed.
### Running Locally
```bash
cd v3
go test ./... -cover
```
Taskfile shortcut:
```
task test # same as above + vet + lint
```
---
## 3. Integration & Example Tests
Every folder under `v3/examples/` is a **self-contained app**.
Three techniques turn them into tests:
| Technique | File | What it checks |
|-----------|------|----------------|
| `go test ./...` inside example | `*_test.go` spins up `wails dev` in headless mode | Build & startup |
| CLI golden tests | `internal/commands/appimage_test.go` | Generated artefact hashes |
| Scripted e2e | `tasks/events/generate.go` | Emits Go that launches example, asserts runtime logs |
Run all with:
```
task integration
```
> Headless mode uses **Xvfb** on Linux runners; macOS & Windows start minimized.
---
## 4. Race Detection
Data races are fatal in GUI runtimes.
### Dedicated Doc
See `v3/pkg/application/RACE.md` for:
* Known benign races and suppression rationale
* How to interpret stack-traces crossing Cgo boundaries
### Local Race Suite
```
go test -race ./...
```
or
```
wails3 dev -race
```
*The latter rebuilds runtime with race flags and launches a demo window.
Close the window; any races print after exit.*
CI runs the race suite on **linux/amd64** (fastest) for every PR.
---
## 5. GitHub Actions Workflows
### 5.1 `build-and-test-v3.yml`
Location: `.github/workflows/build-and-test-v3.yml`
Key features:
* **Matrix** over `os: [ubuntu-latest, windows-latest, macos-latest]`
* Caches Go & npm deps for speed
* Steps:
1. `setup-go` @ 1.23
2. `go vet ./...`
3. `go test ./... -v -coverprofile=cover.out`
4. Build CLI + sample app (`wails3 build -skip-package`)
5. Upload coverage to Codecov
* Conditional **race** job on Ubuntu:
```yaml
- name: Race Detector
if: matrix.os == 'ubuntu-latest'
run: go test -race ./...
```
### 5.2 Static Analysis
* `semgrep.yml` security/lint scanning (`.github/workflows/semgrep.yml`)
* `qodana.yaml` JetBrains Qodana cloud inspection (optional in PR)
### 5.3 Release Pipeline
`runtime.yml` builds and publishes the JS runtime to npm on tag push; out of testing scope but referenced by build matrix.
---
## 6. Local CI Parity
Run the **exact** CI task set via Taskfile:
```
task ci
```
It executes:
1. `lint` (golangci-lint, govet)
2. `test` (unit + race)
3. `integration`
4. `build` (for host platform)
---
## 7. Troubleshooting Failing Tests
| Symptom | Likely Cause | Fix |
|---------|--------------|-----|
| **Race in `runtime_darwin.go`** | Forgot to lock `mainthread.Call` | Use `mainthread.Sync` helper |
| **Windows headless hangs** | Console window waiting for input | Pass `-verbose` and ensure no dialogs open |
| **CI example build fails** | Template changed without bumping example deps | `task update-examples` regenerates lockfiles |
| **Coverpkg errors** | Integration test importing `main` | Switch to build tags `//go:build integration` |
---
## 8. Adding New Tests
1. **Unit** create `*_test.go`, run `go test ./...`
2. **Integration** update or add example app + test harness
3. **CI** commit; the workflow auto-discovers tests
If OS-specific, add skip tags:
```go
//go:build !windows
```
---
## 9. Key File Map
| What | Path |
|------|------|
| Unit test example | `internal/colour/colour_test.go` |
| Integration harness | `internal/commands/appimage_test.go` |
| Race guide | `pkg/application/RACE.md` |
| Taskfile test targets | `v3/Taskfile.yaml` |
| CI workflow | `.github/workflows/build-and-test-v3.yml` |
---
Quality isnt an afterthought in Wails v3.
With unit tests, integration suites, race detection, and a cross-platform CI
matrix you can contribute confidently, knowing your changes run green on every
OS we support.
Happy testing!

View file

@ -174,13 +174,13 @@ wails3 generate icons [flags]
```
#### Flags
| Flag | Description | Default |
|------------|------------------------------|-----------------------|
| `-input` | Input PNG file | Required |
| `-windows` | Windows output filename | |
| `-mac` | macOS output filename | |
| `-sizes` | Icon sizes (comma-separated) | `256,128,64,48,32,16` |
| `-example` | Generate example icon | `false` |
| Flag | Description | Default |
|--------------------|------------------------------|-----------------------|
| `-input` | Input PNG file | Required |
| `-windowsfilename` | Windows output filename | |
| `-macfilename` | macOS output filename | |
| `-sizes` | Icon sizes (comma-separated) | `256,128,64,48,32,16` |
| `-example` | Generate example icon | `false` |
### `generate syso`
Generates Windows .syso file.
@ -325,6 +325,32 @@ Shows build information about the application.
wails3 tool buildinfo
```
### `tool version`
Bumps a semantic version based on the provided flags.
```bash
wails3 tool version [flags]
```
#### Flags
| Flag | Description | Default |
|---------------|--------------------------------------------------|---------|
| `-v` | Current version to bump | |
| `-major` | Bump major version | `false` |
| `-minor` | Bump minor version | `false` |
| `-patch` | Bump patch version | `false` |
| `-prerelease` | Bump prerelease version (e.g., alpha.5 to alpha.6) | `false` |
The command follows the precedence order: major > minor > patch > prerelease. It preserves the "v" prefix if present in the input version, as well as any prerelease and metadata components.
Example usage:
```bash
wails3 tool version -v 1.2.3 -major # Output: 2.0.0
wails3 tool version -v v1.2.3 -minor # Output: v1.3.0
wails3 tool version -v 1.2.3-alpha -patch # Output: 1.2.4-alpha
wails3 tool version -v v3.0.0-alpha.5 -prerelease # Output: v3.0.0-alpha.6
```
### `tool package`
Generates Linux packages (deb, rpm, archlinux).

View file

@ -0,0 +1,167 @@
---
title: Custom Protocol Schemes (Deep Linking)
description: Guide to implementing custom URL schemes for deep linking in Wails applications across macOS, Windows, and Linux.
---
import { Aside } from '@astrojs/starlight/components';
# Custom Protocol Schemes (Deep Linking)
Custom protocol schemes (also known as custom URL schemes or deep linking) allow your Wails application to be launched or brought to the foreground by clicking a URL with a scheme you define (e.g., `myapp://some/data`). This is useful for various purposes, such as:
- OAuth authentication flows.
- Inter-application communication.
- Launching your app with a specific context or to perform a particular action.
Wails provides a unified way to handle these custom URL invocations across macOS, Windows, and Linux through the `events.Common.ApplicationLaunchedWithUrl` event.
## Defining Your Protocols
First, you need to define the custom protocol schemes your application will use. This is done in your `wails.json` project configuration file. Wails reads this file during the build process (`wails build`) to configure the necessary platform-specific assets like `Info.plist` for macOS, NSIS installer scripts for Windows, and `.desktop` files for Linux.
**Example: `wails.json`**
```json title="wails.json"
{
"name": "My App",
"description": "An amazing Wails app!",
"info": {
"companyName": "My Company",
"productName": "My Product",
// ... other info fields ...
"protocols": [
{
"scheme": "myapp",
"description": "My Application Custom Protocol"
},
{
"scheme": "anotherprotocol",
"description": "Another protocol for specific actions"
}
]
}
// ... other wails.json fields ...
}
```
This `info.protocols` array is what Wails uses to generate the necessary entries in platform-specific files. For example, in template files, you might access this via a path like `{{.Info.Protocols}}`.
<Aside type="note">
While `application.Options` in your `main.go` is used for runtime application settings, the definition of custom protocols for build-time asset generation (like `Info.plist`, NSIS scripts, `.desktop` files) should be managed in `wails.json`.
</Aside>
## Handling the Event in Your Application
When your application is launched or activated via a custom URL, Wails emits an `events.Common.ApplicationLaunchedWithUrl` event. You can listen for this event and retrieve the URL that triggered the launch.
```go title="main.go"
import (
"log"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/events"
)
func main() {
app := application.New(application.Options{
Name: "My App", // Ensure this matches relevant info from wails.json if needed
Description: "An amazing Wails app!",
// ... other runtime options ...
})
app.OnApplicationEvent(events.Common.ApplicationLaunchedWithUrl, func(e *application.ApplicationEvent) {
launchedURL := e.Context().URL() // Retrieve the URL from the event context
log.Printf("Application launched with URL: %s", launchedURL)
// TODO: Process the URL (e.g., navigate, perform action, etc.)
// Example: app.EmitEvent("frontend:ShowURL", launchedURL)
})
// ... rest of your main function ...
err := app.Run()
if err != nil {
log.Fatal(err)
}
}
```
<Aside type="note">
The `e.Context().URL()` method returns the full URL string that was used to launch the application (e.g., `myapp://some/data?param=value`).
</Aside>
## Platform-Specific Setup and Behavior
While Wails aims for a unified event, the underlying mechanism for custom protocol registration and URL delivery varies by operating system.
### macOS
- **Setup:** Wails automatically configures your application's `Info.plist` file during the build process. It adds `CFBundleURLTypes` entries based on the `info.protocols` defined in your `wails.json` file.
```xml title="Info.plist (excerpt generated by Wails)"
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>My Application Custom Protocol</string> <!-- From Protocol.Description in wails.json -->
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string> <!-- From Protocol.Scheme in wails.json -->
</array>
</dict>
<!-- ... other protocols ... -->
</array>
```
- **How it Works:** When a URL like `myapp://` is opened, macOS uses LaunchServices to find the application registered for that scheme and sends it an Apple Event (`kAEGetURL`). Wails intercepts this event and translates it into the common `events.Common.ApplicationLaunchedWithUrl` Wails event, providing the URL via `e.Context().URL()`.
### Windows
- **Setup:** Custom protocol schemes on Windows are registered in the Windows Registry. Wails facilitates this through its NSIS installer template.
- When you build your application with the `-nsis` flag, Wails uses the `v3/internal/commands/updatable_build_assets/windows/nsis/wails_tools.nsh.tmpl` file.
- This template contains macros like `CUSTOM_PROTOCOL_ASSOCIATE` and `wails.associateCustomProtocols` which use the `info.protocols` from your `wails.json` (passed as `{{.Info.Protocols}}` to the template) to create the necessary registry entries during installation.
```nsis title="wails_tools.nsh.tmpl (excerpt)"
!macro wails.associateCustomProtocols
; Create custom protocols associations
{{range .Info.Protocols}}
!insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\""
{{end}}
!macroend
```
- **How it Works:** The installer registers your application executable to be called with the URL as a command-line argument (`%1`). For example, `your_app.exe "myapp://some/data"`.
- The Wails runtime for Windows (`v3/pkg/application/application_windows.go`) has been updated to check `os.Args` upon startup. If it detects an argument that looks like a URL (e.g., `os.Args[1]` contains `"://"`), it now emits the `events.Common.ApplicationLaunchedWithUrl` event with this URL.
<Aside type="important">
For Windows, custom protocol schemes are typically only registered when your application is installed via an installer (like the one generated by Wails with NSIS). Running the bare executable might not have the schemes registered system-wide.
</Aside>
### Linux
- **Setup:** On Linux, custom protocol handling is typically managed via `.desktop` files and the MIME type system.
- Wails uses a `.desktop` file template (e.g., `v3/internal/commands/updatable_build_assets/linux/desktop.tmpl`) which is populated during the build using information from `wails.json`.
```desktop title="desktop.tmpl (excerpt)"
[Desktop Entry]
Name={{.ProductName}}
Exec=/usr/local/bin/{{.BinaryName}} %u
MimeType={{range $index, $protocol := .Info.Protocols}}x-scheme-handler/{{$protocol.Scheme}};{{end}}
```
The `Exec` line uses `%u` which gets replaced by the URL. The `MimeType` line registers your application as a handler for `x-scheme-handler/your-scheme` for each protocol defined in `wails.json` (via `{{.Info.Protocols}}`).
- When packaging for Linux (e.g., using `nfpm`), this `.desktop` file is installed to `/usr/share/applications/`.
- A `postinstall.sh` script (e.g., `v3/internal/commands/build_assets/linux/nfpm/scripts/postinstall.sh`) is used to update the system's application and MIME databases:
```sh title="postinstall.sh (excerpt)"
#!/bin/sh
update-desktop-database -q /usr/share/applications
update-mime-database -n /usr/share/mime
```
- **How it Works:** When a URL like `myapp://` is opened, the desktop environment uses the MIME database to find the associated `.desktop` file and executes the command specified in its `Exec` line, substituting `%u` with the URL. Your application receives this URL as a command-line argument.
- The Wails runtime for Linux (`v3/pkg/application/application_linux.go`) checks `os.Args` on startup. If it detects an argument that looks like a URL, it emits the `events.Common.ApplicationLaunchedWithUrl` event.
## Testing Your Custom Protocols
- **macOS:** Open Terminal and type `open "your-scheme://your/data"`.
- **Linux:** Open a terminal and type `xdg-open "your-scheme://your/data"` (requires `xdg-utils` to be installed and the app to be properly packaged and registered).
- **Windows:** After installation via NSIS:
- You can try running `start your-scheme://your/data` from Command Prompt or PowerShell.
- Create a simple HTML file with a link `<a href="your-scheme://your/data">Test Link</a>` and open it in a browser.
<Aside type="tip">
Always ensure your application is properly built and installed (especially for Windows and Linux) for the system to recognize the custom protocol schemes.
</Aside>
By following this guide, you can effectively use custom protocol schemes to enhance your Wails application's interactivity and integration with other applications or web services.

View file

@ -0,0 +1,400 @@
---
title: Events Guide
description: A practical guide to using events in Wails v3 for application communication and lifecycle management
---
**NOTE: This guide is a work in progress**
# Events Guide
Events are the heartbeat of communication in Wails applications. They allow different parts of your application to talk to each other without being tightly coupled. This guide will walk you through everything you need to know about using events effectively in your Wails application.
## Understanding Wails Events
Think of events as messages that get broadcast throughout your application. Any part of your application can listen for these messages and react accordingly. This is particularly useful for:
- **Responding to window changes**: Know when your window is minimized, maximized, or moved
- **Handling system events**: React to theme changes or power events
- **Custom application logic**: Create your own events for features like data updates or user actions
- **Cross-component communication**: Let different parts of your app communicate without direct dependencies
## Event Naming Convention
All Wails events follow a namespace pattern to clearly indicate their origin:
- `common:` - Cross-platform events that work on Windows, macOS, and Linux
- `windows:` - Windows-specific events
- `mac:` - macOS-specific events
- `linux:` - Linux-specific events
For example:
- `common:WindowFocus` - Window gained focus (works everywhere)
- `windows:APMSuspend` - System is suspending (Windows only)
- `mac:ApplicationDidBecomeActive` - App became active (macOS only)
## Getting Started with Events
### Listening to Events (Frontend)
The most common use case is listening for events in your frontend code:
```javascript
import { Events } from '@wailsio/runtime';
// Listen for when the window gains focus
Events.On('common:WindowFocus', () => {
console.log('Window is now focused!');
// Maybe refresh some data or resume animations
});
// Listen for theme changes
Events.On('common:ThemeChanged', (event) => {
console.log('Theme changed:', event.data);
// Update your app's theme accordingly
});
// Listen for custom events from your Go backend
Events.On('my-app:data-updated', (event) => {
console.log('Data updated:', event.data);
// Update your UI with the new data
});
```
### Emitting Events (Backend)
From your Go code, you can emit events that your frontend can listen to:
```go
package main
import (
"github.com/wailsapp/wails/v3/pkg/events"
"github.com/wailsapp/wails/v3/pkg/application"
)
func (a *App) UpdateData() {
// Do some data processing...
// Notify the frontend
a.app.EmitEvent("my-app:data-updated", map[string]interface{}{
"timestamp": time.Now(),
"count": 42,
})
}
```
### Removing Event Listeners
Always clean up your event listeners when they're no longer needed:
```javascript
// Store the handler reference
const focusHandler = () => {
console.log('Window focused');
};
// Add the listener
Events.On('common:WindowFocus', focusHandler);
// Later, remove it when no longer needed
Events.Off('common:WindowFocus', focusHandler);
// Or remove all listeners for an event
Events.Off('common:WindowFocus');
```
## Common Use Cases
### 1. Pause/Resume on Window Focus
Many applications need to pause certain activities when the window loses focus:
```javascript
let animationRunning = true;
Events.On('common:WindowLostFocus', () => {
animationRunning = false;
pauseBackgroundTasks();
});
Events.On('common:WindowFocus', () => {
animationRunning = true;
resumeBackgroundTasks();
});
```
### 2. Responding to Theme Changes
Keep your app in sync with the system theme:
```javascript
Events.On('common:ThemeChanged', (event) => {
const isDarkMode = event.data.isDark;
if (isDarkMode) {
document.body.classList.add('dark-theme');
document.body.classList.remove('light-theme');
} else {
document.body.classList.add('light-theme');
document.body.classList.remove('dark-theme');
}
});
```
### 3. Handling File Drops
Make your app accept dragged files:
```javascript
Events.On('common:WindowFilesDropped', (event) => {
const files = event.data.files;
files.forEach(file => {
console.log('File dropped:', file);
// Process the dropped files
handleFileUpload(file);
});
});
```
### 4. Window Lifecycle Management
Respond to window state changes:
```javascript
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', () => {
// Adjust UI for maximized view
adjustLayoutForMaximized();
});
Events.On('common:WindowRestore', () => {
// Return UI to normal state
adjustLayoutForNormal();
});
```
### 5. Platform-Specific Features
Handle platform-specific events when needed:
```javascript
// Windows-specific power management
Events.On('windows:APMSuspend', () => {
console.log('System is going to sleep');
saveState();
});
Events.On('windows:APMResumeSuspend', () => {
console.log('System woke up');
refreshData();
});
// macOS-specific app lifecycle
Events.On('mac:ApplicationWillTerminate', () => {
console.log('App is about to quit');
performCleanup();
});
```
## Creating Custom Events
You can create your own events for application-specific needs:
### Backend (Go)
```go
// Emit a custom event when data changes
func (a *App) ProcessUserData(userData UserData) error {
// Process the data...
// Notify all listeners
a.app.EmitEvent("user:data-processed", map[string]interface{}{
"userId": userData.ID,
"status": "completed",
"timestamp": time.Now(),
})
return nil
}
// Emit periodic updates
func (a *App) StartMonitoring() {
ticker := time.NewTicker(5 * time.Second)
go func() {
for range ticker.C {
stats := a.collectStats()
a.app.EmitEvent("monitor:stats-updated", stats)
}
}()
}
```
### Frontend (JavaScript)
```javascript
// Listen for your custom events
Events.On('user:data-processed', (event) => {
const { userId, status, timestamp } = event.data;
showNotification(`User ${userId} processing ${status}`);
updateUIWithNewData();
});
Events.On('monitor:stats-updated', (event) => {
updateDashboard(event.data);
});
```
## Event Reference
### Common Events (Cross-platform)
These events work on all platforms:
| Event | Description | When to Use |
|-------|-------------|-------------|
| `common:ApplicationStarted` | Application has fully started | Initialize your app, load saved state |
| `common:WindowRuntimeReady` | Wails runtime is ready | Start making Wails API calls |
| `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 |
### Platform-Specific Events
#### Windows Events
Key events for Windows applications:
| Event | Description | Use Case |
|-------|-------------|----------|
| `windows:SystemThemeChanged` | Windows theme changed | Update app colors |
| `windows:APMSuspend` | System suspending | Save state, pause operations |
| `windows:APMResumeSuspend` | System resumed | Restore state, refresh data |
| `windows:APMPowerStatusChange` | Power status changed | Adjust performance settings |
#### macOS Events
Important macOS application events:
| Event | Description | Use Case |
|-------|-------------|----------|
| `mac:ApplicationDidBecomeActive` | App became active | Resume operations |
| `mac:ApplicationDidResignActive` | App became inactive | Pause operations |
| `mac:ApplicationWillTerminate` | App will quit | Final cleanup |
| `mac:WindowDidEnterFullScreen` | Entered fullscreen | Adjust UI for fullscreen |
| `mac:WindowDidExitFullScreen` | Exited fullscreen | Restore normal UI |
#### Linux Events
Core Linux window events:
| Event | Description | Use Case |
|-------|-------------|----------|
| `linux:SystemThemeChanged` | Desktop theme changed | Update app theme |
| `linux:WindowFocusIn` | Window gained focus | Resume activities |
| `linux:WindowFocusOut` | Window lost focus | Pause activities |
## Best Practices
### 1. Use Event Namespaces
When creating custom events, use namespaces to avoid conflicts:
```javascript
// Good - namespaced events
Events.Emit('myapp:user:login');
Events.Emit('myapp:data:updated');
Events.Emit('myapp:network:connected');
// Avoid - generic names that might conflict
Events.Emit('login');
Events.Emit('update');
```
### 2. Clean Up Listeners
Always remove event listeners when components unmount:
```javascript
// React example
useEffect(() => {
const handler = (event) => {
// Handle event
};
Events.On('common:WindowResize', handler);
// Cleanup
return () => {
Events.Off('common:WindowResize', handler);
};
}, []);
```
### 3. Handle Platform Differences
Check platform availability when using platform-specific events:
```javascript
import { Platform } from '@wailsio/runtime';
if (Platform.isWindows) {
Events.On('windows:APMSuspend', handleSuspend);
} else if (Platform.isMac) {
Events.On('mac:ApplicationWillTerminate', handleTerminate);
}
```
### 4. Don't Overuse Events
While events are powerful, don't use them for everything:
- ✅ Use events for: System notifications, lifecycle changes, broadcast updates
- ❌ Avoid events for: Direct function returns, single component updates, synchronous operations
## Debugging Events
To debug event issues:
```javascript
// Log all events (development only)
if (isDevelopment) {
const originalOn = Events.On;
Events.On = function(eventName, handler) {
console.log(`[Event Registered] ${eventName}`);
return originalOn.call(this, eventName, function(event) {
console.log(`[Event Fired] ${eventName}`, event);
return handler(event);
});
};
}
```
## Source of Truth
The complete list of available events can be found in the Wails source code:
- Frontend events: [`v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts`](https://github.com/wailsapp/wails/blob/main/v3/internal/runtime/desktop/@wailsio/runtime/src/event_types.ts)
- Backend events: [`v3/pkg/events/events.go`](https://github.com/wailsapp/wails/blob/main/v3/pkg/events/events.go)
Always refer to these files for the most up-to-date event names and availability.
## Summary
Events in Wails provide a powerful, decoupled way to handle communication in your application. By following the patterns and practices in this guide, you can build responsive, platform-aware applications that react smoothly to system changes and user interactions.
Remember: start with common events for cross-platform compatibility, add platform-specific events when needed, and always clean up your event listeners to prevent memory leaks.

View file

@ -62,6 +62,7 @@ fileAssociations:
| description | Description shown in file properties | Windows |
| iconName | Name of the icon file (without extension) in the build folder | All |
| role | Application's role for this file type (e.g., `Editor`, `Viewer`) | macOS |
| mimeType | MIME type for the file (e.g., `image/jpeg`) | macOS |
## Listening for File Open Events
@ -105,6 +106,8 @@ Let's walk through setting up file associations for a simple text editor:
Run `wails3 generate icons --help` for more information.
:::
- For macOS add copy statement like `cp build/darwin/documenticon.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources` in the `create:app:bundle:` task.
2. ### Configure File Associations
Edit the `build/config.yml` file to add your file associations:

View file

@ -0,0 +1,584 @@
---
title: Using Gin for Routing
description: A comprehensive guide to integrating Gin web framework with Wails v3 applications
---
This guide demonstrates how to integrate the [Gin web framework](https://github.com/gin-gonic/gin) with Wails v3. Gin is a high-performance HTTP web framework written in Go that makes it easy to build web applications and APIs.
## Introduction
Wails v3 provides a flexible asset system that allows you to use any HTTP handler, including popular web frameworks like Gin. This integration enables you to:
- Serve web content using Gin's powerful routing and middleware capabilities
- Create RESTful APIs that can be accessed from your Wails application
- Leverage Gin's extensive feature set whilst maintaining the benefits of Wails
## Setting Up Gin with Wails
To integrate Gin with Wails, you need to create a Gin router and configure it as the asset handler in your Wails application. Here's a step-by-step guide:
### 1. Install Dependencies
First, ensure you have the Gin package installed:
```bash
go get -u github.com/gin-gonic/gin
```
### 2. Create a Middleware for Gin
Create a middleware function that will handle the integration between Wails and Gin:
```go
// GinMiddleware creates a middleware that passes requests to Gin if they're not handled by Wails
func GinMiddleware(ginEngine *gin.Engine) application.Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Let Wails handle the `/wails` route
if strings.HasPrefix(r.URL.Path, "/wails") {
next.ServeHTTP(w, r)
return
}
// Let Gin handle everything else
ginEngine.ServeHTTP(w, r)
})
}
}
```
This middleware passes all HTTP requests to the Gin router.
### 3. Configure Your Gin Router
Set up your Gin router with routes, middlewares, and handlers:
```go
// Create a new Gin router
ginEngine := gin.New() // Using New() instead of Default() to add custom middleware
// Add middlewares
ginEngine.Use(gin.Recovery())
ginEngine.Use(LoggingMiddleware()) // Your custom middleware
// Define routes
ginEngine.GET("/", func(c *gin.Context) {
// Serve your main page
})
ginEngine.GET("/api/hello", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Hello from Gin API!",
"time": time.Now().Format(time.RFC3339),
})
})
```
### 4. Integrate with Wails Application
Configure your Wails application to use the Gin router as its asset handler:
```go
// Create a new Wails application
app := application.New(application.Options{
Name: "Gin Example",
Description: "A demo of using Gin with Wails",
Mac: application.MacOptions{
ApplicationShouldTerminateAfterLastWindowClosed: true,
},
Assets: application.AssetOptions{
Handler: ginEngine,
Middleware: GinMiddleware(ginEngine),
},
})
// Create window
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Wails + Gin Example",
Width: 900,
Height: 700,
Centered: true,
URL: "/", // This will load the route handled by Gin
})
```
## Serving Static Content
There are several ways to serve static content with Gin in a Wails application:
### Option 1: Using Go's embed Package
The recommended approach is to use Go's `embed` package to embed static files into your binary:
```go
//go:embed static
var staticFiles embed.FS
// In your main function:
ginEngine.StaticFS("/static", http.FS(staticFiles))
// Serve index.html
ginEngine.GET("/", func(c *gin.Context) {
file, err := staticFiles.ReadFile("static/index.html")
if err != nil {
c.String(http.StatusInternalServerError, "Error reading index.html")
return
}
c.Data(http.StatusOK, "text/html; charset=utf-8", file)
})
```
### Option 2: Serving from Disk
For development purposes, you might prefer to serve files directly from disk:
```go
// Serve static files from the "static" directory
ginEngine.Static("/static", "./static")
// Serve index.html
ginEngine.GET("/", func(c *gin.Context) {
c.File("./static/index.html")
})
```
## Custom Middleware
Gin allows you to create custom middleware for various purposes. Here's an example of a logging middleware:
```go
// LoggingMiddleware is a Gin middleware that logs request details
func LoggingMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Start timer
startTime := time.Now()
// Process request
c.Next()
// Calculate latency
latency := time.Since(startTime)
// Log request details
log.Printf("[GIN] %s | %s | %s | %d | %s",
c.Request.Method,
c.Request.URL.Path,
c.ClientIP(),
c.Writer.Status(),
latency,
)
}
}
```
## Handling API Requests
Gin makes it easy to create RESTful APIs. Here's how to define API endpoints:
```go
// GET endpoint
ginEngine.GET("/api/users", func(c *gin.Context) {
c.JSON(http.StatusOK, users)
})
// POST endpoint with JSON binding
ginEngine.POST("/api/users", func(c *gin.Context) {
var newUser User
if err := c.ShouldBindJSON(&newUser); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Process the new user...
c.JSON(http.StatusCreated, newUser)
})
// Path parameters
ginEngine.GET("/api/users/:id", func(c *gin.Context) {
id := c.Param("id")
// Find user by ID...
c.JSON(http.StatusOK, user)
})
// Query parameters
ginEngine.GET("/api/search", func(c *gin.Context) {
query := c.DefaultQuery("q", "")
limit := c.DefaultQuery("limit", "10")
// Perform search...
c.JSON(http.StatusOK, results)
})
```
## Event Communication
One of the powerful features of Wails is its event system, which allows for communication between the frontend and backend. When using Gin as a service, you can still leverage this event system by serving the Wails runtime.js file to your frontend.
### Serving the Wails Runtime
To enable event communication, you need to serve the Wails runtime.js file at the `/wails/runtime.js` path. Here's how to implement this in your Gin service:
```go
import (
"io"
"net/http"
"github.com/gin-gonic/gin"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/runtime"
)
// GinService implements a Wails service that uses Gin for HTTP handling
type GinService struct {
ginEngine *gin.Engine
app *application.App
// Other fields...
}
// ServeHTTP implements the http.Handler interface
func (s *GinService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Special handling for Wails runtime.js
if r.URL.Path == "/wails/runtime.js" {
s.serveWailsRuntime(w, r)
return
}
// All other requests go to the Gin router
s.ginEngine.ServeHTTP(w, r)
}
// serveWailsRuntime serves the Wails runtime.js file
func (s *GinService) serveWailsRuntime(w http.ResponseWriter, r *http.Request) {
// Open the runtime.js file from the public runtime package
runtimeFile, err := runtime.Assets.Open(runtime.RuntimeJSPath)
if err != nil {
http.Error(w, "Failed to access runtime assets", http.StatusInternalServerError)
return
}
defer runtimeFile.Close()
// Set the content type
w.Header().Set("Content-Type", "application/javascript")
// Copy the file to the response
_, err = io.Copy(w, runtimeFile)
if err != nil {
http.Error(w, "Failed to serve runtime.js", http.StatusInternalServerError)
}
}
```
### Handling Events
You'll also need to add an endpoint to handle events from the frontend. This endpoint will bridge the gap between the HTTP requests and the Wails event system:
```go
// In your setupRoutes method
func (s *GinService) setupRoutes() {
// Event handling endpoint
s.ginEngine.POST("/events/emit", func(c *gin.Context) {
var eventData struct {
Name string `json:"name" binding:"required"`
Data interface{} `json:"data"`
}
if err := c.ShouldBindJSON(&eventData); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Process the event using the Wails event system
s.app.EmitEvent(eventData.Name, eventData.Data)
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "Event processed successfully",
})
})
// Other routes...
}
```
### Using Events in the Frontend
In your frontend HTML, include the Wails runtime.js script and use the event API:
```html
<!DOCTYPE html>
<html>
<head>
<script src="/wails/runtime.js"></script>
</head>
<body>
<button id="triggerEvent">Trigger Event</button>
<pre id="eventResponse"></pre>
<script>
// Emit an event to the backend
document.getElementById('triggerEvent').addEventListener('click', () => {
ce.Events.Emit("my-event", {
message: "Hello from the frontend!",
timestamp: new Date().toISOString()
});
});
// Listen for events from the backend
ce.Events.On("response-event", (data) => {
document.getElementById('eventResponse').textContent =
JSON.stringify(data, null, 2);
});
// For the runtime.js stub implementation, add this polyfill
if (window.ce && !window.ce._isNative) {
const originalFetch = window.fetch;
window.fetch = async function(url, options) {
if (typeof url === 'string' && url.includes('/wails/events/emit')) {
const req = new Request(url, options);
const data = await req.json();
// Forward the event to the backend through a regular API call
await fetch('/api/events/emit', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
return new Response(JSON.stringify({ success: true }), {
headers: { 'Content-Type': 'application/json' }
});
}
return originalFetch.apply(this, arguments);
};
}
</script>
</body>
</html>
```
This approach allows you to use the Wails event system seamlessly with your Gin service, providing a consistent experience across your application.
## Interacting with Wails
Your Gin-served web content can interact with Wails features like events and bindings. To enable this interaction, you
must use the JavaScript API package `@wailsio/runtime`.
### Handling Wails Events in Go
```go
// Register event handler
app.OnEvent("my-event", func(event *application.CustomEvent) {
log.Printf("Received event from frontend: %v", event.Data)
// Process the event...
})
```
### Emitting Events from JavaScript
```html
<script>
document.getElementById('callApi').addEventListener('click', async () => {
try {
const response = await fetch('/api/hello');
const data = await response.json();
document.getElementById('apiResult').textContent = JSON.stringify(data, null, 2);
} catch (error) {
console.error('Error calling API:', error);
document.getElementById('apiResult').textContent = 'Error: ' + error.message;
}
});
</script>
```
## Advanced Configuration
### Customising Gin's Mode
Gin has three modes: debug, release, and test. For production applications, you should use release mode:
```go
// Set Gin to release mode
gin.SetMode(gin.ReleaseMode)
// Create a new Gin router
ginEngine := gin.New()
```
You can use Go's build tags to set the mode based on the build environment:
```go[main_prod.go]
// +build production
var ginMode = gin.ReleaseMode
```
```go[main_dev.go]
// +build !production
var ginMode = gin.DebugMode
```
```go [main.go]
// In your main function:
gin.SetMode(ginMode)
```
### Handling WebSockets
You can integrate WebSockets with Gin using libraries like Gorilla WebSocket:
```go
import "github.com/gorilla/websocket"
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true // Allow all connections
},
}
// In your route handler:
ginEngine.GET("/ws", func(c *gin.Context) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Println(err)
return
}
defer conn.Close()
// Handle WebSocket connection...
})
```
## Complete Example
Here's a complete example of integrating Gin with Wails:
```go
package main
import (
"embed"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/wailsapp/wails/v3/pkg/application"
)
//go:embed static
var staticFiles embed.FS
// GinMiddleware creates a middleware that passes requests to Gin if they're not handled by Wails
func GinMiddleware(ginEngine *gin.Engine) application.Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Let Wails handle the `/wails` route
if r.URL.Path == "/wails" {
next.ServeHTTP(w, r)
return
}
// Let Gin handle everything else
ginEngine.ServeHTTP(w, r)
})
}
}
// LoggingMiddleware is a Gin middleware that logs request details
func LoggingMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Start timer
startTime := time.Now()
// Process request
c.Next()
// Calculate latency
latency := time.Since(startTime)
// Log request details
log.Printf("[GIN] %s | %s | %s | %d | %s",
c.Request.Method,
c.Request.URL.Path,
c.ClientIP(),
c.Writer.Status(),
latency,
)
}
}
func main() {
// Create a new Gin router
ginEngine := gin.New() // Using New() instead of Default() to add our own middleware
// Add middlewares
ginEngine.Use(gin.Recovery())
ginEngine.Use(LoggingMiddleware())
// Serve embedded static files
ginEngine.StaticFS("/static", http.FS(staticFiles))
// Define routes
ginEngine.GET("/", func(c *gin.Context) {
file, err := staticFiles.ReadFile("static/index.html")
if err != nil {
c.String(http.StatusInternalServerError, "Error reading index.html")
return
}
c.Data(http.StatusOK, "text/html; charset=utf-8", file)
})
ginEngine.GET("/api/hello", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Hello from Gin API!",
"time": time.Now().Format(time.RFC3339),
})
})
// Create a new Wails application
app := application.New(application.Options{
Name: "Gin Example",
Description: "A demo of using Gin with Wails",
Mac: application.MacOptions{
ApplicationShouldTerminateAfterLastWindowClosed: true,
},
Assets: application.AssetOptions{
Handler: ginEngine,
Middleware: GinMiddleware(ginEngine),
},
})
// Register event handler
app.OnEvent("gin-button-clicked", func(event *application.CustomEvent) {
log.Printf("Received event from frontend: %v", event.Data)
})
// Create window
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Wails + Gin Example",
Width: 900,
Height: 700,
URL: "/",
})
// Run the app
err := app.Run()
if err != nil {
log.Fatal(err)
}
}
```
## Best Practices
- **Use Go's embed Package:** Embed static files into your binary for better distribution.
- **Separate Concerns:** Keep your API logic separate from your UI logic.
- **Error Handling:** Implement proper error handling in both Gin routes and frontend code.
- **Security:** Be mindful of security considerations, especially when handling user input.
- **Performance:** Use Gin's release mode in production for better performance.
- **Testing:** Write tests for your Gin routes using Gin's testing utilities.
## Conclusion
Integrating Gin with Wails provides a powerful combination for building desktop applications with web technologies. Gin's performance and feature set complement Wails' desktop integration capabilities, allowing you to create sophisticated applications that leverage the best of both worlds.
For more information, refer to the [Gin documentation](https://github.com/gin-gonic/gin) and the [Wails documentation](https://wails.io).

View file

@ -0,0 +1,552 @@
---
title: Using Gin for Services
description: A guide to integrating the Gin web framework with Wails v3 Services
---
# Using Gin for Services
The Gin web framework is a popular choice for building HTTP services in Go. With Wails v3, you can easily integrate Gin-based services into your application, providing a powerful way to handle HTTP requests, implement RESTful APIs, and serve web content.
This guide will walk you through creating a Gin-based service that can be mounted at a specific route in your Wails application. We'll build a complete example that demonstrates how to:
1. Create a Gin-based service
2. Implement the Wails Service interface
3. Set up routes and middleware
4. Integrate with the Wails event system
5. Interact with the service from the frontend
## Prerequisites
Before you begin, make sure you have:
- Wails v3 installed
- Basic knowledge of Go and the Gin framework
- Familiarity with HTTP concepts and RESTful APIs
You'll need to add the Gin framework to your project:
```bash
go get github.com/gin-gonic/gin
```
## Creating a Gin-Based Service
Let's start by creating a Gin service that implements the Wails Service interface. Our service will manage a collection of users and provide API endpoints for retrieving and creating user records.
### 1. Define Your Data Models
First, define the data structures your service will work with:
```go
package services
import (
"context"
"net/http"
"strconv"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/wailsapp/wails/v3/pkg/application"
)
// User represents a user in the system
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"createdAt"`
}
// EventData represents data sent in events
type EventData struct {
Message string `json:"message"`
Timestamp string `json:"timestamp"`
}
```
### 2. Create Your Service Structure
Next, define the service structure that will hold your Gin router and any state your service needs to maintain:
```go
// GinService implements a Wails service that uses Gin for HTTP handling
type GinService struct {
ginEngine *gin.Engine
users []User
nextID int
mu sync.RWMutex
app *application.App
}
// NewGinService creates a new GinService instance
func NewGinService() *GinService {
// Create a new Gin router
ginEngine := gin.New()
// Add middlewares
ginEngine.Use(gin.Recovery())
ginEngine.Use(LoggingMiddleware())
service := &GinService{
ginEngine: ginEngine,
users: []User{
{ID: 1, Name: "Alice", Email: "alice@example.com", CreatedAt: time.Now().Add(-72 * time.Hour)},
{ID: 2, Name: "Bob", Email: "bob@example.com", CreatedAt: time.Now().Add(-48 * time.Hour)},
{ID: 3, Name: "Charlie", Email: "charlie@example.com", CreatedAt: time.Now().Add(-24 * time.Hour)},
},
nextID: 4,
}
// Define routes
service.setupRoutes()
return service
}
```
### 3. Implement the Service Interface
Implement the required methods for the Wails Service interface:
```go
// ServiceName returns the name of the service
func (s *GinService) ServiceName() string {
return "Gin API Service"
}
// ServiceStartup is called when the service starts
func (s *GinService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
// Store the application instance for later use
s.app = application.Get()
// Register an event handler that can be triggered from the frontend
s.app.OnEvent("gin-api-event", func(event *application.CustomEvent) {
// Log the event data
s.app.Logger.Info("Received event from frontend", "data", event.Data)
// Emit an event back to the frontend
s.app.EmitEvent("gin-api-response", map[string]interface{}{
"message": "Response from Gin API Service",
"time": time.Now().Format(time.RFC3339),
})
})
return nil
}
// ServiceShutdown is called when the service shuts down
func (s *GinService) ServiceShutdown(ctx context.Context) error {
// Clean up resources if needed
return nil
}
```
### 3. Implement the http.Handler Interface
To make your service mountable at a specific route, implement the `http.Handler` interface. This single method, `ServeHTTP`, is the gateway for all HTTP requests to your service. It delegates the request handling to the Gin router, allowing you to use all of Gin's powerful features for routing and middleware.
```go
// ServeHTTP implements the http.Handler interface
func (s *GinService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// All requests go to the Gin router
s.ginEngine.ServeHTTP(w, r)
}
```
### 4. Set Up Your Routes
Define your API routes in a separate method for better organisation. This approach keeps your code clean and makes it easier to understand the structure of your API. The Gin router provides a fluent API for defining routes, including support for route groups, which help organise related endpoints.
```go
// setupRoutes configures the API routes
func (s *GinService) setupRoutes() {
// Basic info endpoint
s.ginEngine.GET("/info", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"service": "Gin API Service",
"version": "1.0.0",
"time": time.Now().Format(time.RFC3339),
})
})
// Users group
users := s.ginEngine.Group("/users")
{
// Get all users
users.GET("", func(c *gin.Context) {
s.mu.RLock()
defer s.mu.RUnlock()
c.JSON(http.StatusOK, s.users)
})
// Get user by ID
users.GET("/:id", func(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
return
}
s.mu.RLock()
defer s.mu.RUnlock()
for _, user := range s.users {
if user.ID == id {
c.JSON(http.StatusOK, user)
return
}
}
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
})
// Create a new user
users.POST("", func(c *gin.Context) {
var newUser User
if err := c.ShouldBindJSON(&newUser); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
s.mu.Lock()
defer s.mu.Unlock()
// Set the ID and creation time
newUser.ID = s.nextID
newUser.CreatedAt = time.Now()
s.nextID++
// Add to the users slice
s.users = append(s.users, newUser)
c.JSON(http.StatusCreated, newUser)
// Emit an event to notify about the new user
s.app.EmitEvent("user-created", newUser)
})
// Delete a user
users.DELETE("/:id", func(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
return
}
s.mu.Lock()
defer s.mu.Unlock()
for i, user := range s.users {
if user.ID == id {
// Remove the user from the slice
s.users = append(s.users[:i], s.users[i+1:]...)
c.JSON(http.StatusOK, gin.H{"message": "User deleted"})
return
}
}
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
})
}
}
```
### 5. Create Custom Middleware
You can create custom Gin middleware to enhance your service. Middleware functions in Gin are executed in the order they are added to the router and can perform tasks such as logging, authentication, and error handling. This example shows a simple logging middleware that records request details.
```go
// LoggingMiddleware is a Gin middleware that logs request details
func LoggingMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Start timer
start := time.Now()
// Process request
c.Next()
// Calculate latency
latency := time.Since(start)
// Log request details
log.Printf("[GIN] %s %s %d %s", c.Request.Method, c.Request.URL.Path, c.Writer.Status(), latency)
}
}
```
## Registering Your Service
To use your Gin-based service in a Wails application, you need to register it with the application and specify the route where it should be mounted. This is done when creating the Wails application instance. The route you specify becomes the base path for all endpoints defined in your Gin router.
```go
app := application.New(application.Options{
Name: "Gin Service Demo",
Description: "A demo of using Gin in Wails services",
Mac: application.MacOptions{
ApplicationShouldTerminateAfterLastWindowClosed: true,
},
LogLevel: slog.LevelDebug,
Services: []application.Service{
application.NewServiceWithOptions(services.NewGinService(), application.ServiceOptions{
Route: "/api",
}),
},
Assets: application.AssetOptions{
Handler: application.BundledAssetFileServer(assets),
},
})
```
In this example, the Gin service is mounted at the `/api` route. This means that if your Gin router has an endpoint defined as `/info`, it will be accessible at `/api/info` in your application. This approach allows you to organise your API endpoints logically and avoid conflicts with other parts of your application.
## Integrating with the Wails Event System
One of the powerful features of using Gin with Wails Services is the ability to seamlessly integrate with the Wails event system. This allows for real-time communication between your backend service and the frontend.
In your service's `ServiceStartup` method, you can register event handlers to process events from the frontend:
```go
s.app.OnEvent("gin-api-event", func(event *application.CustomEvent) {
// Log the event data
s.app.Logger.Info("Received event from frontend", "data", event.Data)
// Emit an event back to the frontend
s.app.EmitEvent("gin-api-response", map[string]interface{}{
"message": "Response from Gin API Service",
"time": time.Now().Format(time.RFC3339),
})
})
```
You can also emit events to the frontend from your Gin routes or other parts of your service:
```go
// After creating a new user
s.app.EmitEvent("user-created", newUser)
```
## Frontend Integration
To interact with your Gin service from the frontend, you'll need to import the Wails runtime, make HTTP requests to your API endpoints, and use the Wails event system for real-time communication.
For production use, it's recommended to use the `@wailsio/runtime` package instead of directly importing `/wails/runtime.js`. This ensures type safety, better IDE support, version management through npm, and compatibility with modern JavaScript tooling.
Install the package:
```bash
npm install @wailsio/runtime
```
Then use it in your code:
```javascript
import * as wails from '@wailsio/runtime';
// Event emission
wails.Events.Emit('gin-api-event', eventData);
```
Here's an example of how to set up frontend integration:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gin Service Example</title>
<!-- Styles omitted for brevity -->
</head>
<body>
<h1>Gin Service Example</h1>
<div class="card">
<h2>API Endpoints</h2>
<p>Try the Gin API endpoints mounted at /api:</p>
<button id="getInfo">Get Service Info</button>
<button id="getUsers">Get All Users</button>
<button id="getUser">Get User by ID</button>
<button id="createUser">Create User</button>
<button id="deleteUser">Delete User</button>
<div id="apiResult">
<pre id="apiResponse">Results will appear here...</pre>
</div>
</div>
<div class="card">
<h2>Event Communication</h2>
<p>Trigger an event to communicate with the Gin service:</p>
<button id="triggerEvent">Trigger Event</button>
<div id="eventResult">
<pre id="eventResponse">Event responses will appear here...</pre>
</div>
</div>
<div class="card" id="createUserForm" style="display: none; border: 2px solid #0078d7;">
<h2>Create New User</h2>
<div>
<label for="userName">Name:</label>
<input type="text" id="userName" placeholder="Enter name">
</div>
<div>
<label for="userEmail">Email:</label>
<input type="email" id="userEmail" placeholder="Enter email">
</div>
<button id="submitUser">Submit</button>
<button id="cancelCreate">Cancel</button>
</div>
<script type="module">
// Import the Wails runtime
// Note: In production, use '@wailsio/runtime' instead
import * as wails from "/wails/runtime.js";
// Helper function to fetch API endpoints
async function fetchAPI(endpoint, options = {}) {
try {
const response = await fetch(`/api${endpoint}`, options);
const data = await response.json();
document.getElementById('apiResponse').textContent = JSON.stringify(data, null, 2);
return data;
} catch (error) {
document.getElementById('apiResponse').textContent = `Error: ${error.message}`;
console.error('API Error:', error);
}
}
// Event listeners for API buttons
document.getElementById('getInfo').addEventListener('click', () => {
fetchAPI('/info');
});
document.getElementById('getUsers').addEventListener('click', () => {
fetchAPI('/users');
});
document.getElementById('getUser').addEventListener('click', async () => {
const userId = prompt('Enter user ID:');
if (userId) {
await fetchAPI(`/users/${userId}`);
}
});
document.getElementById('createUser').addEventListener('click', () => {
const form = document.getElementById('createUserForm');
form.style.display = 'block';
form.scrollIntoView({ behavior: 'smooth' });
});
document.getElementById('cancelCreate').addEventListener('click', () => {
document.getElementById('createUserForm').style.display = 'none';
});
document.getElementById('submitUser').addEventListener('click', async () => {
const name = document.getElementById('userName').value;
const email = document.getElementById('userEmail').value;
if (!name || !email) {
alert('Please enter both name and email');
return;
}
try {
await fetchAPI('/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name, email })
});
document.getElementById('createUserForm').style.display = 'none';
document.getElementById('userName').value = '';
document.getElementById('userEmail').value = '';
// Automatically fetch the updated user list
await fetchAPI('/users');
// Show a success message
const apiResponse = document.getElementById('apiResponse');
const currentData = JSON.parse(apiResponse.textContent);
apiResponse.textContent = JSON.stringify({
message: "User created successfully!",
users: currentData
}, null, 2);
} catch (error) {
console.error('Error creating user:', error);
}
});
document.getElementById('deleteUser').addEventListener('click', async () => {
const userId = prompt('Enter user ID to delete:');
if (userId) {
try {
await fetchAPI(`/users/${userId}`, {
method: 'DELETE'
});
// Show success message
document.getElementById('apiResponse').textContent = JSON.stringify({
message: `User with ID ${userId} deleted successfully`
}, null, 2);
// Refresh the user list
setTimeout(() => fetchAPI('/users'), 1000);
} catch (error) {
console.error('Error deleting user:', error);
}
}
});
// Using Wails Events API for event communication
document.getElementById('triggerEvent').addEventListener('click', async () => {
// Display the event being sent
document.getElementById('eventResponse').textContent = JSON.stringify({
status: "Sending event to backend...",
data: { timestamp: new Date().toISOString() }
}, null, 2);
// Use the Wails runtime to emit an event
const eventData = {
message: "Hello from the frontend!",
timestamp: new Date().toISOString()
};
wails.Events.Emit({name: 'gin-api-event', data: eventData});
});
// Set up event listener for responses from the backend
window.addEventListener('DOMContentLoaded', () => {
// Register event listener using Wails runtime
wails.Events.On("gin-api-response", (data) => {
document.getElementById('eventResponse').textContent = JSON.stringify(data, null, 2);
});
// Also listen for user-created events
wails.Events.On("user-created", (data) => {
document.getElementById('eventResponse').textContent = JSON.stringify({
event: "user-created",
user: data
}, null, 2);
});
// Initial API call to get service info
fetchAPI('/info');
});
</script>
</body>
</html>
```
## Closing Thoughts
Integrating the Gin web framework with Wails v3 Services provides a powerful and flexible approach to building modular, maintainable web applications. By leveraging Gin's routing and middleware capabilities alongside the Wails event system, you can create rich, interactive applications with clean separation of concerns.
The complete example code for this guide can be found in the Wails repository under `v3/examples/gin-service`.

View file

@ -7,10 +7,10 @@ Wails v3 provides a powerful menu system that allows you to create both applicat
### Creating a Menu
To create a new menu, use the `NewMenu()` method from your application instance:
To create a new menu, use the `New()` method from the Menus manager:
```go
menu := application.NewMenu()
menu := app.Menus.New()
```
### Adding Menu Items
@ -59,10 +59,10 @@ submenu.Add("Save")
#### Combining menus
A menu can be added into another menu by appending or prepending it.
```go
menu := application.NewMenu()
menu := app.Menus.New()
menu.Add("First Menu")
secondaryMenu := application.NewMenu()
secondaryMenu := app.Menus.New()
secondaryMenu.Add("Second Menu")
// insert 'secondaryMenu' after 'menu'
@ -88,7 +88,7 @@ In some cases it'll be better to construct a whole new menu if you are working w
This will clear all items on an existing menu and allows you to add items again.
```go
menu := application.NewMenu()
menu := app.Menus.New()
menu.Add("Waiting for update...")
// after certain logic, the menu has to be updated
@ -107,7 +107,7 @@ so be sure to manage your menus carefully.
If you want to clear and release a menu, use the `Destroy()` method:
```go
menu := application.NewMenu()
menu := app.Menus.New()
menu.Add("Waiting for update...")
// after certain logic, the menu has to be destroyed
@ -265,7 +265,7 @@ These roles can be used to add individual menu items:
Here's an example showing how to use both complete menus and individual roles:
```go
menu := application.NewMenu()
menu := app.Menus.New()
// Add complete menu structures
menu.AddRole(application.AppMenu) // macOS only
@ -287,8 +287,8 @@ Application menus are the menus that appear at the top of your application windo
### Application Menu Behaviour
When you set an application menu using `app.SetMenu()`, it becomes the main menu on macOS.
Menus are set on a pre-window basis for Windows/Linux.
When you set an application menu using `app.Menus.Set()`, it becomes the main menu on macOS.
Menus are set on a per-window basis for Windows/Linux.
```go
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
@ -306,19 +306,19 @@ func main() {
app := application.New(application.Options{})
// Create application menu
appMenu := application.NewMenu()
appMenu := app.Menus.New()
fileMenu := appMenu.AddSubmenu("File")
fileMenu.Add("New").OnClick(func(ctx *application.Context) {
// This will be available in all windows unless overridden
window := app.CurrentWindow()
window := app.Windows.Current()
window.SetTitle("New Window")
})
// Set as application menu - this is for macOS
app.SetMenu(appMenu)
app.Menus.Set(appMenu)
// Window with custom menu on Windows
customMenu := application.NewMenu()
customMenu := app.Menus.New()
customMenu.Add("Custom Action")
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Custom Menu",
@ -366,6 +366,10 @@ You can control when the default context menu appears using the `--default-conte
</div>
```
:::note
This feature will only work as expected after the runtime [has been initialised](../../learn/runtime#initialisation).
:::
#### Nested Context Menu Behavior
When using the `--default-contextmenu` property on nested elements, the following rules apply:
@ -411,7 +415,8 @@ When creating a custom context menu, you provide a unique identifier (name) that
```go
// Create a context menu with identifier "imageMenu"
contextMenu := application.NewContextMenu("imageMenu")
contextMenu := application.NewContextMenu()
app.ContextMenus.Add("imageMenu", contextMenu)
```
The name parameter ("imageMenu" in this example) serves as a unique identifier that will be used to:
@ -470,7 +475,8 @@ Here's a complete example of implementing a custom context menu for an image gal
```go
// Backend: Create the context menu
imageMenu := application.NewContextMenu("imageMenu")
imageMenu := application.NewContextMenu()
app.ContextMenus.Add("imageMenu", imageMenu)
// Add relevant operations
imageMenu.Add("View Full Size").OnClick(func(ctx *application.Context) {

View file

@ -0,0 +1,138 @@
---
title: MSIX Packaging (Windows)
description: Guide for creating MSIX packages with Wails v3
---
# MSIX Packaging (Windows)
Wails v3 can generate modern **MSIX** installers for Windows applications, providing a cleaner, safer and Store-ready alternative to traditional **NSIS** or plain `.exe` bundles.
This guide walks through:
* Prerequisites & tool installation
* Building your app as an MSIX package
* Signing the package
* Command-line reference
* Troubleshooting
---
## 1. Prerequisites
| Requirement | Notes |
|-------------|-------|
| **Windows 10 1809+ / Windows 11** | MSIX is only supported on Windows. |
| **Windows SDK** (for `MakeAppx.exe` & `signtool.exe`) | Install from the [Windows SDK download page](https://developer.microsoft.com/windows/downloads/windows-sdk/). |
| **Microsoft MSIX Packaging Tool** (optional) | Available from the Microsoft Store provides a GUI & CLI. |
| **Code-signing certificate** (recommended) | A `.pfx` file generated by your CA or `New-SelfSignedCertificate`. |
> **Tip:** Wails ships a Task that opens the download pages for you:
```bash
# installs MakeAppx / signtool (via Windows SDK) and the MSIX Packaging Tool
wails3 task windows:install:msix:tools
```
---
## 2. Building an MSIX package
### 2.1 Quick CLI
```bash
# Production build + MSIX
wails3 tool msix \
--name MyApp \ # executable name
--executable build/bin/MyApp.exe \
--arch x64 \ # x64, x86 or arm64
--out build/bin/MyApp-x64.msix
```
The command will:
1. Create a temporary layout (`AppxManifest.xml`, `Assets/`).
2. Call **MakeAppx.exe** (default) or **MsixPackagingTool.exe** if `--use-msix-tool` is passed.
3. Optionally sign the package (see §3).
### 2.2 Using the generated Taskfile
When you ran `wails init`, Wails created `build/windows/Taskfile.yml`.
Packaging with MSIX is a one-liner:
```bash
# default=nsis, override FORMAT
wails3 task windows:package FORMAT=msix
```
Output goes to `build/bin/MyApp-<arch>.msix`.
---
## 3. Signing the package
Windows will refuse unsigned MSIX packages unless you enable developer-mode, so signing is strongly recommended.
```bash
wails3 tool msix \
--cert build/cert/CodeSign.pfx \
--cert-password "pfx-password" \
--publisher "CN=MyCompany" \
--out build/bin/MyApp.msix
```
* If you pass `--cert`, Wails automatically runs `signtool sign …`.
* `--publisher` sets the `Publisher` field inside `AppxManifest.xml`.
It **must** match the subject of your certificate.
---
## 4. Command-line reference
| Flag | Default | Description |
|------|---------|-------------|
| `--config` | `wails.json` | Project config with **Info** & `fileAssociations`. |
| `--name` | — | Executable name inside the package (no spaces). |
| `--executable` | — | Path to the built `.exe`. |
| `--arch` | `x64` | `x64`, `x86`, or `arm64`. |
| `--out` | `<name>.msix` | Output path / filename. |
| `--publisher` | `CN=<CompanyName>` | Publisher string in the manifest. |
| `--cert` | ― | Path to `.pfx` certificate for signing. |
| `--cert-password` | ― | Password for the `.pfx`. |
| `--use-msix-tool` | `false` | Use **MsixPackagingTool.exe** instead of **MakeAppx.exe**. |
| `--use-makeappx` | `true` | Force MakeAppx even if the MSIX Tool is installed. |
---
## 5. File associations
Wails automatically injects file associations declared in `wails.json` into the package manifest:
```json
"fileAssociations": [
{ "ext": "wails", "name": "Wails Project", "description": "Wails file", "role": "Editor" }
]
```
After installation, Windows will offer your app as a handler for these extensions.
---
## 6. Troubleshooting
| Problem | Solution |
|---------|----------|
| `MakeAppx.exe not found` | Install the Windows SDK and restart the terminal. |
| `signtool.exe not found` | Same as above both live in the SDKs *bin* folder. |
| *Package cannot be installed because publisher mismatch* | The certificate subject (CN) must match `--publisher`. |
| *The certificate is not trusted* | Import the certificate into **Trusted Root Certification Authorities** or use a publicly trusted code-signing cert. |
| Need GUI | Install **MSIX Packaging Tool** from the store and run `MsixPackagingTool.exe`. The template generated by Wails is fully compatible. |
---
## 7. Next steps
* [Windows Installer (NSIS) guide](./windows-installer.mdx) legacy format.
* [Cross-platform update mechanism](../updates.mdx) coming soon.
* Join the community on Discord to share feedback!
Happy packaging!

View file

@ -0,0 +1,151 @@
---
title: Windows UAC Configuration
sidebar:
order: 11
---
import {Badge} from '@astrojs/starlight/components';
Relevant Platforms: <Badge text="Windows" variant="note" />
<br/>
Windows User Account Control (UAC) determines the execution privileges of your Wails application. By default, Wails v3 applications include explicit UAC configuration in their Windows manifest, ensuring consistent behavior across different machines.
## UAC Execution Levels
Windows applications can request different execution levels through their manifest file. Wails v3 automatically includes UAC configuration with a default execution level that you can customize based on your application's needs.
### Available Execution Levels
| Level | Description | Use Case |
|-------|-------------|----------|
| `asInvoker` | Runs with the same privileges as the parent process | Default for most applications |
| `highestAvailable` | Runs with the highest privileges available to the user | Applications that may need elevated access |
| `requireAdministrator` | Always requires administrator privileges | System utilities, installers |
### Default Configuration
Wails v3 applications include a default UAC configuration in their Windows manifest:
```xml
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
```
This configuration ensures your application:
- Runs with the same privileges as the launching process
- Does not require elevation by default
- Works consistently across different machines
- Does not trigger UAC prompts for normal users
## Customizing UAC Configuration
Since Wails v3 encourages users to customize their build assets, you can modify the UAC configuration by editing your Windows manifest template directly.
### Locating the Manifest Template
The Windows manifest template is located at:
```
build/windows/wails.exe.manifest
```
### Modifying the Execution Level
To change the execution level, edit the `level` attribute in the `requestedExecutionLevel` element:
```xml title="build/windows/wails.exe.manifest"
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
```
### Examples
#### Standard Application (Default)
Most applications should use the default `asInvoker` level:
```xml
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
```
#### System Utility
Applications that need elevated access when available:
```xml
<requestedExecutionLevel level="highestAvailable" uiAccess="false"/>
```
#### Administrative Tool
Applications that always require administrator privileges:
```xml
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
```
## UI Access
The `uiAccess` attribute controls whether your application can interact with higher-privilege UI elements. In most cases, this should remain `false`.
Set to `true` only if your application needs to:
- Send input to other applications
- Drive the UI of other applications
- Access UI elements of higher-privilege processes
:::caution[UI Access Requirements]
Setting `uiAccess="true"` requires your application to be:
- Digitally signed with a certificate from a trusted certificate authority
- Installed in a secure location (Program Files or Windows\System32)
:::
## Building with Custom UAC Settings
After modifying your manifest template, build your application normally:
```bash
wails3 build
```
The build process will automatically embed your custom UAC configuration into the executable.
## Verifying UAC Configuration
You can verify that your UAC settings are properly embedded using the `go-winres` tool:
```bash
go-winres extract --in your-app.exe --out extracted-resources/
```
Then examine the extracted manifest file to confirm your UAC configuration is present.
:::tip[Manifest Persistence]
Unlike some other frameworks, Wails v3's UAC configuration is embedded directly into the executable during compilation, ensuring it persists when the application is copied to other machines.
:::
## Troubleshooting
### UAC Prompts Not Appearing
If you set `requireAdministrator` but don't see UAC prompts:
- Verify the manifest is properly embedded in your executable
- Check that you're not running from an already-elevated process
- Ensure the manifest syntax is valid XML
### Application Not Starting
If your application fails to start after UAC changes:
- Check the manifest syntax for XML errors
- Verify the execution level value is valid
- Try reverting to `asInvoker` to isolate the issue
### Inconsistent Behavior Across Machines
If UAC behavior differs between machines:
- Ensure the manifest is embedded in the executable (not external)
- Check that the executable wasn't modified after building
- Verify Windows UAC settings are enabled on the target machine

View file

@ -64,11 +64,11 @@ import CardAnimation from '../../components/CardAnimation.astro';
go install github.com/wailsapp/wails/v3/cmd/wails3@latest
# Create a new project
wails init -n myproject
wails3 init -n myproject
# Run your project
cd myproject
wails dev
wails3 dev
```
</Card>

View file

@ -0,0 +1,342 @@
---
title: Advanced Binding Techniques
sidebar:
order: 22
---
import { FileTree } from "@astrojs/starlight/components";
This guide covers advanced techniques for customizing and optimizing the binding generation process in Wails v3.
## Customizing Generated Code with Directives
### Injecting Custom Code
The `//wails:inject` directive allows you to inject custom JavaScript/TypeScript code into the generated bindings:
```go
//wails:inject console.log("Hello from Wails!");
type MyService struct {}
func (s *MyService) Greet(name string) string {
return "Hello, " + name
}
```
This will inject the specified code into the generated JavaScript/TypeScript file for the `MyService` service.
You can also use conditional injection to target specific output formats:
```go
//wails:inject j*:console.log("Hello JS!"); // JavaScript only
//wails:inject t*:console.log("Hello TS!"); // TypeScript only
```
### Including Additional Files
The `//wails:include` directive allows you to include additional files with the generated bindings:
```go
//wails:include js/*.js
package mypackage
```
This directive is typically used in package documentation comments to include additional JavaScript/TypeScript files with the generated bindings.
### Marking Internal Types and Methods
The `//wails:internal` directive marks a type or method as internal, preventing it from being exported to the frontend:
```go
//wails:internal
type InternalModel struct {
Field string
}
//wails:internal
func (s *MyService) InternalMethod() {}
```
This is useful for types and methods that are only used internally by your Go code and should not be exposed to the frontend.
### Ignoring Methods
The `//wails:ignore` directive completely ignores a method during binding generation:
```go
//wails:ignore
func (s *MyService) IgnoredMethod() {}
```
This is similar to `//wails:internal`, but it completely ignores the method rather than marking it as internal.
### Custom Method IDs
The `//wails:id` directive specifies a custom ID for a method, overriding the default hash-based ID:
```go
//wails:id 42
func (s *MyService) CustomIDMethod() {}
```
This can be useful for maintaining compatibility when refactoring code.
## Working with Complex Types
### Nested Structs
The binding generator handles nested structs automatically:
```go
type Address struct {
Street string
City string
State string
Zip string
}
type Person struct {
Name string
Address Address
}
func (s *MyService) GetPerson() Person {
return Person{
Name: "John Doe",
Address: Address{
Street: "123 Main St",
City: "Anytown",
State: "CA",
Zip: "12345",
},
}
}
```
The generated JavaScript/TypeScript code will include classes for both `Person` and `Address`.
### Maps and Slices
Maps and slices are also handled automatically:
```go
type Person struct {
Name string
Attributes map[string]string
Friends []string
}
func (s *MyService) GetPerson() Person {
return Person{
Name: "John Doe",
Attributes: map[string]string{
"hair": "brown",
"eyes": "blue",
},
Friends: []string{"Jane", "Bob", "Alice"},
}
}
```
In JavaScript, maps are represented as objects and slices as arrays. In TypeScript, maps are represented as `Record<K, V>` and slices as `T[]`.
### Generic Types
The binding generator supports generic types:
```go
type Result[T any] struct {
Data T
Error string
}
func (s *MyService) GetResult() Result[string] {
return Result[string]{
Data: "Hello, World!",
Error: "",
}
}
```
The generated TypeScript code will include a generic class for `Result`:
```typescript
export class Result<T> {
"Data": T;
"Error": string;
constructor(source: Partial<Result<T>> = {}) {
if (!("Data" in source)) {
this["Data"] = null as any;
}
if (!("Error" in source)) {
this["Error"] = "";
}
Object.assign(this, source);
}
static createFrom<T>(source: string | object = {}): Result<T> {
let parsedSource = typeof source === "string" ? JSON.parse(source) : source;
return new Result<T>(parsedSource as Partial<Result<T>>);
}
}
```
### Interfaces
The binding generator can generate TypeScript interfaces instead of classes using the `-i` flag:
```bash
wails3 generate bindings -ts -i
```
This will generate TypeScript interfaces for all models:
```typescript
export interface Person {
Name: string;
Attributes: Record<string, string>;
Friends: string[];
}
```
## Optimizing Binding Generation
### Using Names Instead of IDs
By default, the binding generator uses hash-based IDs for method calls. You can use the `-names` flag to use names instead:
```bash
wails3 generate bindings -names
```
This will generate code that uses method names instead of IDs:
```javascript
export function Greet(name) {
let $resultPromise = $Call.ByName("Greet", name);
return $resultPromise;
}
```
This can make the generated code more readable and easier to debug, but it may be slightly less efficient.
### Bundling the Runtime
By default, the generated code imports the Wails runtime from the `@wailsio/runtime` npm package. You can use the `-b` flag to bundle the runtime with the generated code:
```bash
wails3 generate bindings -b
```
This will include the runtime code directly in the generated files, eliminating the need for the npm package.
### Disabling Index Files
If you don't need the index files, you can use the `-noindex` flag to disable their generation:
```bash
wails3 generate bindings -noindex
```
This can be useful if you prefer to import services and models directly from their respective files.
## Real-World Examples
### Authentication Service
Here's an example of an authentication service with custom directives:
```go
package auth
//wails:inject console.log("Auth service initialized");
type AuthService struct {
// Private fields
users map[string]User
}
type User struct {
Username string
Email string
Role string
}
type LoginRequest struct {
Username string
Password string
}
type LoginResponse struct {
Success bool
User User
Token string
Error string
}
// Login authenticates a user
func (s *AuthService) Login(req LoginRequest) LoginResponse {
// Implementation...
}
// GetCurrentUser returns the current user
func (s *AuthService) GetCurrentUser() User {
// Implementation...
}
// Internal helper method
//wails:internal
func (s *AuthService) validateCredentials(username, password string) bool {
// Implementation...
}
```
### Data Processing Service
Here's an example of a data processing service with generic types:
```go
package data
type ProcessingResult[T any] struct {
Data T
Error string
}
type DataService struct {}
// Process processes data and returns a result
func (s *DataService) Process(data string) ProcessingResult[map[string]int] {
// Implementation...
}
// ProcessBatch processes multiple data items
func (s *DataService) ProcessBatch(data []string) ProcessingResult[[]map[string]int] {
// Implementation...
}
// Internal helper method
//wails:internal
func (s *DataService) parseData(data string) (map[string]int, error) {
// Implementation...
}
```
### Conditional Code Injection
Here's an example of conditional code injection for different output formats:
```go
//wails:inject j*:/**
//wails:inject j*: * @param {string} arg
//wails:inject j*: * @returns {Promise<void>}
//wails:inject j*: */
//wails:inject j*:export async function CustomMethod(arg) {
//wails:inject t*:export async function CustomMethod(arg: string): Promise<void> {
//wails:inject await InternalMethod("Hello " + arg + "!");
//wails:inject }
type Service struct{}
```
This injects different code for JavaScript and TypeScript outputs, providing appropriate type annotations for each language.

View file

@ -10,12 +10,49 @@ Application menus provide the main menu bar interface for your application. They
## Creating an Application Menu
Create a new application menu using the `NewMenu` method:
Create a new application menu using the `New` method from the Menu manager:
```go
menu := app.NewMenu()
menu := app.Menu.New()
```
## Setting the Menu
The way to set the menu varies on the platform:
<Tabs>
<TabItem label="macOS" icon="fa-brands:apple">
On macOS, there is only one menu bar per application. Set the menu using the `Set` method of the Menu manager:
```go
app.Menu.Set(menu)
```
</TabItem>
<TabItem label="Windows" icon="fa-brands:windows">
On Windows, there is a menu bar per window. Set the menu using the `SetMenu` method of the window:
```go
app.Window.Current().SetMenu(menu)
```
</TabItem>
<TabItem label="Linux" icon="fa-brands:linux">
On Linux, the menu bar is typically per window. Set the menu using the `SetMenu` method of the window:
```go
app.Window.Current().SetMenu(menu)
```
</TabItem>
</Tabs>
## Menu Roles
Wails provides predefined menu roles that automatically create platform-appropriate menu structures:
@ -70,7 +107,7 @@ Menu items can control the application windows:
```go
viewMenu := menu.AddSubmenu("View")
viewMenu.Add("Toggle Fullscreen").OnClick(func(ctx *application.Context) {
window := app.CurrentWindow()
window := app.Window.Current()
if window.Fullscreen() {
window.SetFullscreen(false)
} else {
@ -153,7 +190,7 @@ Always test menu functionality across all target platforms to ensure consistent
:::
:::tip[Pro Tip]
Consider using the `application.CurrentWindow()` method in menu handlers to affect the active window, rather than storing window references.
Consider using the `app.Window.Current()` method in menu handlers to affect the active window, rather than storing window references.
:::
## Complete Example
@ -174,7 +211,7 @@ func main() {
})
// Create main menu
menu := app.NewMenu()
menu := app.Menu.New()
// Add platform-specific application menu
if runtime.GOOS == "darwin" {
@ -208,7 +245,7 @@ func main() {
})
// Set the menu
app.SetMenu(menu)
app.Menu.Set(menu)
// Create main window
app.NewWebviewWindow()

View file

@ -0,0 +1,195 @@
---
title: Badges
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
## Introduction
Wails provides a cross-platform badge service for desktop applications. This service allows you to display badges on your application tile or dock icon, which is useful for indicating unread messages, notifications, or other status information.
## Basic Usage
### Creating the Service
First, initialize the badge service:
```go
import "github.com/wailsapp/wails/v3/pkg/application"
import "github.com/wailsapp/wails/v3/pkg/services/badge"
// Create a new badge service
badgeService := badge.New()
// Register the service with the application
app := application.New(application.Options{
Services: []application.Service{
application.NewService(badgeService),
},
})
```
### Creating the Service with Custom Options (Windows Only)
On Windows, you can customize the badge appearance with various options:
```go
import "github.com/wailsapp/wails/v3/pkg/application"
import "github.com/wailsapp/wails/v3/pkg/services/badge"
import "image/color"
// Create a badge service with custom options
options := badge.Options{
TextColour: color.RGBA{255, 255, 255, 255}, // White text
BackgroundColour: color.RGBA{0, 0, 255, 255}, // Blue background
FontName: "consolab.ttf", // Bold Consolas font
FontSize: 20, // Font size for single character
SmallFontSize: 14, // Font size for multiple characters
}
badgeService := badge.NewWithOptions(options)
// Register the service with the application
app := application.New(application.Options{
Services: []application.Service{
application.NewService(badgeService),
},
})
```
## Badge Operations
### Setting a Badge
Set a badge on the application tile/dock icon:
```go
// Set a default badge
badgeService.SetBadge("")
// Set a numeric badge
badgeService.SetBadge("3")
// Set a text badge
badgeService.SetBadge("New")
```
### Setting a Custom Badge
Set a badge on the application tile/dock icon with one-off options applied:
#### Go
```go
options := badge.Options{
BackgroundColour: color.RGBA{0, 255, 255, 255},
FontName: "arialb.ttf", // System font
FontSize: 16,
SmallFontSize: 10,
TextColour: color.RGBA{0, 0, 0, 255},
}
// Set a default badge
badgeService.SetCustomBadge("", options)
// Set a numeric badge
badgeService.SetCustomBadge("3", options)
// Set a text badge
badgeService.SetCustomBadge("New", options)
```
### Removing a Badge
Remove the badge from the application icon:
```go
badgeService.RemoveBadge()
```
## Platform Considerations
<Tabs>
<TabItem label="macOS" icon="fa-brands:apple">
On macOS, badges:
- Are displayed directly on the dock icon
- Support text values
- Automatically handle dark/light mode appearance
- Use the standard macOS dock badge styling
- Automatically handle label overflow
- Do not support customization options (any options passed to NewWithOptions will be ignored)
- Will display "●" as the default badge if an empty label is provided
</TabItem>
<TabItem label="Windows" icon="fa-brands:windows">
On Windows, badges:
- Are displayed as an overlay icon in the taskbar
- Support text values
- Allow customization of colors, font, and font sizes
- Adapt to Windows theme settings
- Require the application to have a window
- Use smaller font size automatically for badges with multiple characters
- Do not handle label overflow
- Support the following customization options:
- TextColour: Text color (default: white)
- BackgroundColour: Badge background color (default: red)
- FontName: Font file name (default: "segoeuib.ttf")
- FontSize: Font size for single character (default: 18)
- SmallFontSize: Font size for multiple characters (default: 14)
</TabItem>
<TabItem label="Linux" icon="fa-brands:linux">
On Linux:
- Badge functionality is not available
</TabItem>
</Tabs>
## Best Practices
1. Use badges sparingly:
- Too many badge updates can distract users
- Reserve badges for important notifications
2. Keep badge text short:
- Numeric badges are most effective
- On macOS, text badges should be brief
3. For Windows customization:
- Ensure high contrast between text and background colors
- Test with different text lengths as font size decreases with length
- Use common system fonts to ensure availability
## API Reference
### Service Management
| Method | Description |
|--------------------------------------------|-------------------------------------------------------|
| `New()` | Creates a new badge service with default options |
| `NewWithOptions(options Options)` | Creates a new badge service with custom options (Windows only, options are ignored on macOS) |
### Badge Operations
| Method | Description |
|----------------------------------------------|------------------------------------------------------------|
| `SetBadge(label string) error` | Sets a badge with the specified label |
| `SetCustomBadge(label string, options Options) error` | Sets a badge with the specified label and custom styling options (Windows only) |
| `RemoveBadge() error` | Removes the badge from the application icon |
### Structs and Types
```go
// Options for customizing badge appearance (Windows only)
type Options struct {
TextColour color.RGBA // Color of the badge text
BackgroundColour color.RGBA // Color of the badge background
FontName string // Font file name (e.g., "segoeuib.ttf")
FontSize int // Font size for single character
SmallFontSize int // Font size for multiple characters
}
```

View file

@ -0,0 +1,115 @@
---
title: Binding Best Practices
sidebar:
order: 23
---
import { FileTree } from "@astrojs/starlight/components";
This guide provides best practices and patterns for using the Wails binding system effectively in your applications.
## Service Design Patterns
### Service Organization
Organize your services based on functionality rather than technical concerns. For example, instead of having a single large service, split it into smaller, focused services:
```go
// Instead of this:
type AppService struct {}
func (s *AppService) GetUser() User { /* ... */ }
func (s *AppService) UpdateUser(user User) error { /* ... */ }
func (s *AppService) GetProducts() []Product { /* ... */ }
func (s *AppService) AddProduct(product Product) error { /* ... */ }
// Do this:
type UserService struct {}
func (s *UserService) GetUser() User { /* ... */ }
func (s *UserService) UpdateUser(user User) error { /* ... */ }
type ProductService struct {}
func (s *ProductService) GetProducts() []Product { /* ... */ }
func (s *ProductService) AddProduct(product Product) error { /* ... */ }
```
This makes your code more maintainable and easier to understand.
### Use JSON Tags
Use JSON tags to control how your models are serialized:
```go
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"-"` // Exclude from JSON
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
```
### Separate Frontend and Backend Models
Consider using different models for the frontend and backend:
```go
// Backend model
type User struct {
ID int
Name string
Email string
Password string // Sensitive data
CreatedAt time.Time
UpdatedAt time.Time
}
// Frontend model
type UserDTO struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
}
func (s *UserService) GetUser() UserDTO {
user := getUserFromDatabase()
return UserDTO{
ID: user.ID,
Name: user.Name,
Email: user.Email,
CreatedAt: user.CreatedAt,
}
}
```
This gives you more control over what data is exposed to the frontend.
### Use Context for Cancellation
Use context for cancellation to avoid wasting resources on abandoned requests:
```go
func (s *ProductService) GetProducts(ctx context.Context, req ProductsRequest) (ProductsResponse, error) {
// Check if the request has been cancelled
select {
case <-ctx.Done():
return ProductsResponse{}, ctx.Err()
default:
// Continue processing
}
products, total, err := getProductsFromDatabase(ctx, req.Page, req.PageSize, req.Filter)
if err != nil {
return ProductsResponse{}, err
}
return ProductsResponse{
Products: products,
Total: total,
Page: req.Page,
PageSize: req.PageSize,
}, nil
}
```

View file

@ -0,0 +1,155 @@
---
title: Binding System Internals
sidebar:
order: 21
---
import { FileTree } from "@astrojs/starlight/components";
This guide explains how the Wails binding system works internally, providing insights for developers who want to understand the mechanics behind the automatic code generation.
## Architecture Overview
The Wails binding system consists of three main components:
1. **Collection**: Analyzes Go code to extract information about services, models, and other declarations
2. **Configuration**: Manages settings and options for the binding generation process
3. **Rendering**: Generates JavaScript/TypeScript code based on the collected information
<FileTree>
- internal/generator/
- collect/ # Package analysis and information extraction
- config/ # Configuration structures and interfaces
- render/ # Code generation for JS/TS
</FileTree>
## Collection Process
The collection process is responsible for analyzing Go packages and extracting information about services, models, and other declarations. This is handled by the `collect` package.
### Key Components
- **Collector**: Manages package information and caches collected data
- **Package**: Represents a Go package being analyzed and stores collected services, models, and directives
- **Service**: Collects information about service types and their methods
- **Model**: Collects detailed information about model types, including fields, values, and type parameters
- **Directive**: Parses and interprets `//wails:` directives in Go source code
### Collection Flow
1. The collector scans the Go packages specified in the project
2. It identifies service types (structs with methods that will be exposed to the frontend)
3. For each service, it collects information about its methods
4. It identifies model types (structs used as parameters or return values in service methods)
5. For each model, it collects information about its fields and type parameters
6. It processes any `//wails:` directives found in the code
## Rendering Process
The rendering process is responsible for generating JavaScript/TypeScript code based on the collected information. This is handled by the `render` package.
### Key Components
- **Renderer**: Orchestrates the rendering of service, model, and index files
- **Module**: Represents a single generated JavaScript/TypeScript module
- **Templates**: Text templates used for code generation
### Rendering Flow
1. For each service, the renderer generates a JavaScript/TypeScript file with functions that mirror the service methods
2. For each model, the renderer generates a JavaScript/TypeScript class that mirrors the model struct
3. The renderer generates index files that re-export all services and models
4. The renderer applies any custom code injections specified by `//wails:inject` directives
## Type Mapping
One of the most important aspects of the binding system is how Go types are mapped to JavaScript/TypeScript types. Here's a summary of the mapping:
| Go Type | JavaScript Type | TypeScript Type |
|---------|----------------|----------------|
| `bool` | `boolean` | `boolean` |
| `int`, `int8`, `int16`, `int32`, `int64`, `uint`, `uint8`, `uint16`, `uint32`, `uint64`, `float32`, `float64` | `number` | `number` |
| `string` | `string` | `string` |
| `[]byte` | `Uint8Array` | `Uint8Array` |
| `[]T` | `Array<T>` | `T[]` |
| `map[K]V` | `Object` | `Record<K, V>` |
| `struct` | `Object` | Custom class |
| `interface{}` | `any` | `any` |
| `*T` | `T \| null` | `T \| null` |
| `func` | Not supported | Not supported |
| `chan` | Not supported | Not supported |
## Directives System
The binding system supports several directives that can be used to customize the generated code. These directives are added as comments in your Go code.
### Available Directives
- `//wails:inject`: Injects custom JavaScript/TypeScript code into the generated bindings
- `//wails:include`: Includes additional files with the generated bindings
- `//wails:internal`: Marks a type or method as internal, preventing it from being exported to the frontend
- `//wails:ignore`: Completely ignores a method during binding generation
- `//wails:id`: Specifies a custom ID for a method, overriding the default hash-based ID
### Directive Processing
1. During the collection phase, the collector identifies and parses directives in the Go code
2. The directives are stored with the corresponding declarations (services, methods, models, etc.)
3. During the rendering phase, the renderer applies the directives to customize the generated code
## Advanced Features
### Conditional Code Generation
The binding system supports conditional code generation using a two-character condition prefix for `include` and `inject` directives:
```
<language><style>:<content>
```
Where:
- `<language>` can be:
- `*` - Both JavaScript and TypeScript
- `j` - JavaScript only
- `t` - TypeScript only
- `<style>` can be:
- `*` - Both classes and interfaces
- `c` - Classes only
- `i` - Interfaces only
For example:
```go
//wails:inject j*:console.log("JavaScript only");
//wails:inject t*:console.log("TypeScript only");
```
### Custom Method IDs
By default, methods are identified by a hash-based ID. However, you can specify a custom ID using the `//wails:id` directive:
```go
//wails:id 42
func (s *Service) CustomIDMethod() {}
```
This can be useful for maintaining compatibility when refactoring code.
## Performance Considerations
The binding generator is designed to be efficient, but there are a few things to keep in mind:
1. The first run will be slower as it builds up a cache of packages to scan
2. Subsequent runs will be faster as they use the cached information
3. The generator processes all packages in the project, which can be time-consuming for large projects
4. You can use the `-clean` flag to clean the output directory before generation
## Debugging
If you encounter issues with the binding generation, you can use the `-v` flag to enable debug output:
```bash
wails3 generate bindings -v
```
This will provide detailed information about the collection and rendering process, which can help identify the source of the issue.

View file

@ -183,7 +183,6 @@ func main() {
},
})
// ....
app.NewWebviewWindow()
err := app.Run()
if err != nil {
log.Fatal(err)
@ -437,8 +436,9 @@ func (s *MyService) WindowAwareMethod(ctx context.Context) (string, error) {
```
From the frontend, these methods can be called normally. If you need to cancel a
long-running operation, the Promise will be rejected with the cancellation
error:
long-running operation, you can call the special `cancel` method on the promise
and it will reject immediately with a special cancellation error;
the Go context will be cancelled and the actual result of the call will be discarded:
```javascript
// Call the method
@ -448,3 +448,227 @@ const promise = MyService.LongRunningTask("input");
// This will cause the context to be cancelled in the Go method
promise.cancel();
```
In fact, the runtime returns a special promise wrapper
that provides cancellation support for arbitrarily long promise chains.
For example:
```javascript
import { CancelError } from "@wailsio/runtime";
// Call the method and process its output
const promise = MyService.LongRunningTask("input").then((result) => {
console.log(result);
}).catch((err) => {
if (err instanceof CancelError) {
console.log("Cancelled.", err.cause);
} else {
console.error("Failed.", err);
}
});
// Later...
// cancel() accepts an optional cause parameter
// that will be attached to the cancellation error:
promise.cancel("I'm tired of waiting!").then(() => {
// Cancellation has been requested successfully
// and all handlers attached above have run.
console.log("Ready for the next adventure!");
});
```
The `cancel` method returns a promise that fulfills always (and never rejects)
after the cancellation request has been submitted successfully
and all previously attached handlers have run.
:::note
Calling the `cancel` method on a settled promise is safe and has no effect;
if the task completes before the call to `cancel`, the code above is going to log:
```
completed
Ready for the next adventure!
```
However, if `cancel` is called before the task finishes, the output will be:
```
Cancelled. I'm tired of waiting!
Ready for the next adventure!
```
:::
The approach discussed above requires storing and chaining promises manually,
which can be cumbersome for code written in `async`/`await` style.
If you target plaforms that support the `AbortController`/`AbortSignal` idiom,
you can call the `cancelOn` method and tie call cancellation to an `AbortSignal` instead:
```javascript
async function callBinding(signal) {
try {
await MyService.LongRunningTask("input").cancelOn(signal);
} catch (err) {
if (err instanceof CancelError) {
console.log("Cancelled! Cause: ", err.cause);
} else {
console.error("Failed! Error: ", err);
}
}
}
let controller = new AbortController();
callBinding(controller.signal);
// Later...
controller.abort("I'm tired of waiting!");
```
:::caution
On the macOS platform, `AbortSignal` is only supported from macOS 10.15 Catalina onwards.
:::
### Handling errors
As you may have noticed above, bound methods can return errors, which are handled specially.
When a result field has type `error`, it is omitted by default from the values returned to JS.
When such a field is _non-nil_, the promise rejects with a `RuntimeError` exception
that wraps the Go error message:
```go
func (*MyService) FailingMethod(name string) error {
return fmt.Errorf("Welcome to an imperfect world, %s", name)
}
```
```js
import { MyService } from './bindings/changeme';
try {
await MyService.FailingMethod("CLU")
} catch (err) {
if (err.name === 'RuntimeError') {
console.log(err.message); // Prints 'Welcome to an imperfect world, CLU'
}
}
```
The exception will be an instance of the `Call.RuntimeError` class from the wails runtime,
hence you can also test its type like this:
```js
import { Call } from '@wailsio/runtime';
try {
// ...
} catch (err) {
if (err instanceof Call.RuntimeError) {
// ...
}
}
```
If the Go error value supports JSON marshaling, the exception's `cause` property
will hold the marshaled version of the error:
```go
type ImperfectWorldError struct {
Name string `json:"name"`
}
func (err *ImperfectWorldError) Error() {
return fmt.Sprintf("Welcome to an imperfect world, %s", err.Name)
}
func (*MyService) FailingMethod(name string) error {
return &ImperfectWorldError{
Name: name,
}
}
```
```js
import { MyService } from './bindings/changeme';
try {
await MyService.FailingMethod("CLU")
} catch (err) {
if (err.name === 'RuntimeError') {
console.log(err.cause.name); // Prints 'CLU'
}
}
```
Generally, many Go error values will only have limited or no support for marshaling to JSON.
If you so wish, you can customise the value provided as cause
by specifying either a global or per-service error marshaling function:
```go
app := application.New(application.Options{
MarshalError: func(err error) []byte {
// ...
},
Services: []application.Service{
application.NewServiceWithOptions(&MyService{}, application.ServiceOptions{
MarshalError: func(err error) []byte {
// ...
},
}),
},
})
```
Per-service functions override the global function,
which in turn overrides the default behaviour of using `json.Marshal`.
If a marshaling function returns `nil`, it falls back to the outer function:
per-service functions fall back to the global function,
which in turn falls back to the default behaviour.
:::tip
If you wish to omit the `cause` property on the resulting exception,
let the marshaling function return a falsy JSON value like `[]byte("null")`.
:::
Here's an example marshaling function that unwraps path errors and reports the file path:
```go
app := application.New(application.Options{
MarshalError: func(err error) []byte {
var perr *fs.PathError
if !errors.As(err, &perr) {
// Not a path error, fall back to default handling.
return nil
}
// Marshal path string
path, err := json.Marshal(&perr.Path)
if err != nil {
// String marshaling failed, fall back to default handling.
return nil
}
return []byte(fmt.Sprintf(`{"path":%s}`, path))
},
})
```
:::note
Error marshaling functions are not allowed to fail.
If they are not able to process a given error and return valid JSON,
they should return `nil` and fall back to a more generic handler.
If no strategy succeeds, the exception will not have a `cause` property.
:::
Binding call promises may also reject with a `TypeError`
when the method has been passed the wrong number of arguments,
when the conversion of arguments from JSON to their Go types fails,
or when the conversion of results to JSON fails.
These problems will usually be caught early by the type system.
If your code typechecks but you still get type errors,
it might be that some of your Go types are not supported by the `encoding/json` package:
look for warnings from the binding generator to catch these.
:::caution
If you see a `ReferenceError` complaining about unknown methods,
it could mean that your JS bindings have gotten out of sync with Go code
and must be regenerated.
:::

View file

@ -0,0 +1,510 @@
---
title: Browser Integration
sidebar:
order: 58
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
Wails provides simple browser integration through the BrowserManager API, allowing your application to open URLs and files in the user's default web browser. This is useful for opening external links, documentation, or files that should be handled by the browser.
## Accessing the Browser Manager
The browser manager is accessed through the `Browser` property on your application instance:
```go
app := application.New(application.Options{
Name: "Browser Integration Demo",
})
// Access the browser manager
browser := app.Browser
```
## Opening URLs
### Open Web URLs
Open URLs in the user's default web browser:
```go
// Open a website
err := app.Browser.OpenURL("https://wails.io")
if err != nil {
app.Logger.Error("Failed to open URL", "error", err)
}
// Open specific pages
err = app.Browser.OpenURL("https://github.com/wailsapp/wails")
if err != nil {
app.Logger.Error("Failed to open GitHub", "error", err)
}
```
### Open Local URLs
Open local development servers or local network resources:
```go
// Open local development server
err := app.Browser.OpenURL("http://localhost:3000")
if err != nil {
app.Logger.Error("Failed to open local server", "error", err)
}
// Open network resource
err = app.Browser.OpenURL("http://192.168.1.100:8080")
if err != nil {
app.Logger.Error("Failed to open network resource", "error", err)
}
```
## Opening Files
### Open HTML Files
Open local HTML files in the browser:
```go
// Open an HTML file
err := app.Browser.OpenFile("/path/to/documentation.html")
if err != nil {
app.Logger.Error("Failed to open HTML file", "error", err)
}
// Open generated reports
reportPath := "/tmp/report.html"
err = app.Browser.OpenFile(reportPath)
if err != nil {
app.Logger.Error("Failed to open report", "error", err)
}
```
### Open Other File Types
Open various file types that browsers can handle:
```go
// Open PDF files
err := app.Browser.OpenFile("/path/to/document.pdf")
if err != nil {
app.Logger.Error("Failed to open PDF", "error", err)
}
// Open image files
err = app.Browser.OpenFile("/path/to/image.png")
if err != nil {
app.Logger.Error("Failed to open image", "error", err)
}
// Open text files
err = app.Browser.OpenFile("/path/to/readme.txt")
if err != nil {
app.Logger.Error("Failed to open text file", "error", err)
}
```
## Common Use Cases
### Help and Documentation
Provide easy access to help resources:
```go
// Create help menu
func setupHelpMenu(app *application.App) {
menu := app.Menu.New()
helpMenu := menu.AddSubmenu("Help")
helpMenu.Add("Online Documentation").OnClick(func(ctx *application.Context) {
err := app.Browser.OpenURL("https://docs.yourapp.com")
if err != nil {
app.Dialog.Error().
SetTitle("Error").
SetMessage("Could not open documentation").
Show()
}
})
helpMenu.Add("GitHub Repository").OnClick(func(ctx *application.Context) {
app.Browser.OpenURL("https://github.com/youruser/yourapp")
})
helpMenu.Add("Report Issue").OnClick(func(ctx *application.Context) {
app.Browser.OpenURL("https://github.com/youruser/yourapp/issues/new")
})
}
```
### External Links in Content
Handle external links from your application content:
```go
func handleExternalLink(app *application.App, url string) {
// Validate the URL before opening
if !isValidURL(url) {
app.Logger.Warn("Invalid URL", "url", url)
return
}
// Optionally confirm with user
dialog := app.Dialog.Question()
dialog.SetTitle("Open External Link")
dialog.SetMessage(fmt.Sprintf("Open %s in your browser?", url))
dialog.AddButton("Open").OnClick(func() {
err := app.Browser.OpenURL(url)
if err != nil {
app.Logger.Error("Failed to open URL", "url", url, "error", err)
}
})
dialog.AddButton("Cancel")
dialog.Show()
}
func isValidURL(url string) bool {
parsed, err := url.Parse(url)
return err == nil && (parsed.Scheme == "http" || parsed.Scheme == "https")
}
```
### Export and View Reports
Generate and open reports in the browser:
```go
import (
"html/template"
"os"
"path/filepath"
)
func generateAndOpenReport(app *application.App, data interface{}) error {
// Create temporary file for the report
tmpDir := os.TempDir()
reportPath := filepath.Join(tmpDir, "report.html")
// Generate HTML report
tmpl := `
<!DOCTYPE html>
<html>
<head>
<title>Application Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.header { border-bottom: 2px solid #333; padding-bottom: 10px; }
.data { margin-top: 20px; }
</style>
</head>
<body>
<div class="header">
<h1>Application Report</h1>
<p>Generated: {{.Timestamp}}</p>
</div>
<div class="data">
<!-- Report content here -->
{{range .Items}}
<p>{{.}}</p>
{{end}}
</div>
</body>
</html>`
// Write report to file
file, err := os.Create(reportPath)
if err != nil {
return err
}
defer file.Close()
t, err := template.New("report").Parse(tmpl)
if err != nil {
return err
}
err = t.Execute(file, data)
if err != nil {
return err
}
// Open in browser
return app.Browser.OpenFile(reportPath)
}
```
### Development Tools
Open development resources during development:
```go
func setupDevelopmentMenu(app *application.App) {
if !app.Environment.Info().Debug {
return // Only show in debug mode
}
menu := app.Menu.New()
devMenu := menu.AddSubmenu("Development")
devMenu.Add("Open DevTools").OnClick(func(ctx *application.Context) {
// This would open browser devtools if available
window := app.Window.Current()
if window != nil {
window.OpenDevTools()
}
})
devMenu.Add("View Source").OnClick(func(ctx *application.Context) {
// Open source code repository
app.Browser.OpenURL("https://github.com/youruser/yourapp")
})
devMenu.Add("API Documentation").OnClick(func(ctx *application.Context) {
// Open local API docs
app.Browser.OpenURL("http://localhost:8080/docs")
})
}
```
## Error Handling
### Graceful Error Handling
Always handle potential errors when opening URLs or files:
```go
func openURLWithFallback(app *application.App, url string, fallbackMessage string) {
err := app.Browser.OpenURL(url)
if err != nil {
app.Logger.Error("Failed to open URL", "url", url, "error", err)
// Show fallback dialog with URL
dialog := app.Dialog.Info()
dialog.SetTitle("Unable to Open Link")
dialog.SetMessage(fmt.Sprintf("%s\n\nURL: %s", fallbackMessage, url))
dialog.Show()
}
}
// Usage
openURLWithFallback(app,
"https://docs.example.com",
"Please open the following URL manually in your browser:")
```
### User Feedback
Provide feedback when operations succeed or fail:
```go
func openURLWithFeedback(app *application.App, url string) {
err := app.Browser.OpenURL(url)
if err != nil {
// Show error dialog
app.Dialog.Error().
SetTitle("Browser Error").
SetMessage(fmt.Sprintf("Could not open URL: %s", err.Error())).
Show()
} else {
// Optionally show success notification
app.Logger.Info("URL opened successfully", "url", url)
}
}
```
## Platform Considerations
<Tabs>
<TabItem label="macOS" icon="fa-brands:apple">
On macOS:
- Uses the `open` command to launch the default browser
- Respects user's default browser setting in System Preferences
- May prompt for permission if the application is sandboxed
- Handles `file://` URLs correctly for local files
</TabItem>
<TabItem label="Windows" icon="fa-brands:windows">
On Windows:
- Uses Windows Shell API to open URLs
- Respects default browser setting in Windows Settings
- Handles Windows path formats correctly
- May show security warnings for untrusted URLs
</TabItem>
<TabItem label="Linux" icon="fa-brands:linux">
On Linux:
- Attempts to use `xdg-open` first, falls back to other methods
- Behavior varies by desktop environment
- Respects `BROWSER` environment variable if set
- May require additional packages in minimal installations
</TabItem>
</Tabs>
## Best Practices
1. **Always Handle Errors**: Browser operations can fail for various reasons:
```go
if err := app.Browser.OpenURL(url); err != nil {
app.Logger.Error("Failed to open browser", "error", err)
// Provide fallback or user notification
}
```
2. **Validate URLs**: Ensure URLs are well-formed before opening:
```go
func isValidHTTPURL(str string) bool {
u, err := url.Parse(str)
return err == nil && (u.Scheme == "http" || u.Scheme == "https")
}
```
3. **User Confirmation**: For external links, consider asking user permission:
```go
// Show confirmation dialog before opening external links
confirmAndOpen(app, "https://external-site.com")
```
4. **Secure File Paths**: When opening files, ensure paths are safe:
```go
func openSafeFile(app *application.App, filename string) error {
// Ensure file exists and is readable
if _, err := os.Stat(filename); err != nil {
return err
}
return app.Browser.OpenFile(filename)
}
```
## Complete Example
Here's a complete example showing various browser integration patterns:
```go
package main
import (
"fmt"
"os"
"path/filepath"
"github.com/wailsapp/wails/v3/pkg/application"
)
func main() {
app := application.New(application.Options{
Name: "Browser Integration Demo",
})
// Setup menu with browser actions
setupMenu(app)
// Create main window
window := app.Window.New()
window.SetTitle("Browser Integration")
err := app.Run()
if err != nil {
panic(err)
}
}
func setupMenu(app *application.App) {
menu := app.Menu.New()
// File menu
fileMenu := menu.AddSubmenu("File")
fileMenu.Add("Generate Report").OnClick(func(ctx *application.Context) {
generateHTMLReport(app)
})
// Help menu
helpMenu := menu.AddSubmenu("Help")
helpMenu.Add("Documentation").OnClick(func(ctx *application.Context) {
openWithConfirmation(app, "https://docs.example.com")
})
helpMenu.Add("Support").OnClick(func(ctx *application.Context) {
openWithConfirmation(app, "https://support.example.com")
})
app.Menu.Set(menu)
}
func openWithConfirmation(app *application.App, url string) {
dialog := app.Dialog.Question()
dialog.SetTitle("Open External Link")
dialog.SetMessage(fmt.Sprintf("Open %s in your browser?", url))
dialog.AddButton("Open").OnClick(func() {
if err := app.Browser.OpenURL(url); err != nil {
showError(app, "Failed to open URL", err)
}
})
dialog.AddButton("Cancel")
dialog.Show()
}
func generateHTMLReport(app *application.App) {
// Create temporary HTML file
tmpDir := os.TempDir()
reportPath := filepath.Join(tmpDir, "demo_report.html")
html := `
<!DOCTYPE html>
<html>
<head>
<title>Demo Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.header { color: #333; border-bottom: 1px solid #ccc; }
</style>
</head>
<body>
<div class="header">
<h1>Application Report</h1>
<p>This is a sample report generated by the application.</p>
</div>
<div class="content">
<h2>Report Details</h2>
<p>This report was generated to demonstrate browser integration.</p>
</div>
</body>
</html>`
err := os.WriteFile(reportPath, []byte(html), 0644)
if err != nil {
showError(app, "Failed to create report", err)
return
}
// Open in browser
err = app.Browser.OpenFile(reportPath)
if err != nil {
showError(app, "Failed to open report", err)
}
}
func showError(app *application.App, message string, err error) {
app.Dialog.Error().
SetTitle("Error").
SetMessage(fmt.Sprintf("%s: %v", message, err)).
Show()
}
```
:::tip[Pro Tip]
Consider providing fallback mechanisms for when browser operations fail, such as copying URLs to clipboard or showing them in a dialog for manual opening.
:::
:::danger[Warning]
Always validate URLs and file paths before opening them to prevent security issues. Be cautious about opening user-provided URLs without validation.
:::

View file

@ -123,6 +123,7 @@ the application on macOS. Key features include:
- Building binaries for amd64, arm64 and universal (both) architectures
- Generating `.icns` icon file
- Creating an `.app` bundle for distributing
- Ad-hoc signing `.app` bundles
- Setting macOS-specific build flags and environment variables
## Task Execution and Command Aliases

View file

@ -11,7 +11,7 @@ The Wails Clipboard API provides a simple interface for interacting with the sys
The clipboard can be accessed through the application instance:
```go
clipboard := app.Clipboard()
clipboard := app.Clipboard
```
## Setting Text
@ -19,7 +19,7 @@ clipboard := app.Clipboard()
To set text to the clipboard, utilise the `SetText` method:
```go
success := app.Clipboard().SetText("Hello World")
success := app.Clipboard.SetText("Hello World")
if !success {
// Handle error
}
@ -36,7 +36,7 @@ Setting an empty string (`""`) effectively clears the text content from the clip
To retrieve text from the clipboard, utilise the `Text` method:
```go
text, ok := app.Clipboard().Text()
text, ok := app.Clipboard.Text()
if !ok {
// Handle error
} else {
@ -75,7 +75,7 @@ func main() {
})
// Create a custom menu
menu := app.NewMenu()
menu := app.Menu.New()
if runtime.GOOS == "darwin" {
menu.AddRole(application.AppMenu)
}
@ -83,33 +83,33 @@ func main() {
// Add clipboard operations to menu
setClipboardMenu := menu.AddSubmenu("Set Clipboard")
setClipboardMenu.Add("Set Text 'Hello'").OnClick(func(ctx *application.Context) {
success := app.Clipboard().SetText("Hello")
success := app.Clipboard.SetText("Hello")
if !success {
application.InfoDialog().SetMessage("Failed to set clipboard text").Show()
app.Dialog.Info().SetMessage("Failed to set clipboard text").Show()
}
})
getClipboardMenu := menu.AddSubmenu("Get Clipboard")
getClipboardMenu.Add("Get Text").OnClick(func(ctx *application.Context) {
result, ok := app.Clipboard().Text()
result, ok := app.Clipboard.Text()
if !ok {
application.InfoDialog().SetMessage("Failed to get clipboard text").Show()
app.Dialog.Info().SetMessage("Failed to get clipboard text").Show()
} else {
application.InfoDialog().SetMessage("Got:\n\n" + result).Show()
app.Dialog.Info().SetMessage("Got:\n\n" + result).Show()
}
})
clearClipboardMenu := menu.AddSubmenu("Clear Clipboard")
clearClipboardMenu.Add("Clear Text").OnClick(func(ctx *application.Context) {
success := app.Clipboard().SetText("")
success := app.Clipboard.SetText("")
if success {
application.InfoDialog().SetMessage("Clipboard text cleared").Show()
app.Dialog.Info().SetMessage("Clipboard text cleared").Show()
} else {
application.InfoDialog().SetMessage("Clipboard text not cleared").Show()
app.Dialog.Info().SetMessage("Clipboard text not cleared").Show()
}
})
app.SetMenu(menu)
app.Menu.Set(menu)
app.NewWebviewWindow()
err := app.Run()

View file

@ -10,10 +10,10 @@ Context menus are popup menus that appear when right-clicking elements in your a
## Creating a Context Menu
To create a context menu, utilise the `NewContextMenu` method from your application instance:
To create a context menu, use the `Add` method from the ContextMenu manager:
```go
contextMenu := application.NewContextMenu("menu-id")
contextMenu := app.ContextMenu.Add("menu-id", application.NewContextMenu())
```
The `menu-id` parameter is a unique identifier for the menu that will be used to associate it with HTML elements.
@ -23,7 +23,7 @@ The `menu-id` parameter is a unique identifier for the menu that will be used to
You can add items to your context menu using the same methods as application menus. Here's a simple example:
```go
contextMenu := application.NewContextMenu("editor-menu")
contextMenu := application.NewContextMenu()
contextMenu.Add("Cut").OnClick(func(ctx *application.Context) {
// Handle cut action
})
@ -33,6 +33,9 @@ contextMenu.Add("Copy").OnClick(func(ctx *application.Context) {
contextMenu.Add("Paste").OnClick(func(ctx *application.Context) {
// Handle paste action
})
// Register the context menu with the manager
app.ContextMenu.Add("editor-menu", contextMenu)
```
:::tip[Menu Items]
@ -44,12 +47,15 @@ For detailed information about available menu item types and properties, refer t
Context menus can receive data from the HTML element that triggered them. This data can be accessed in the click handlers:
```go
contextMenu := application.NewContextMenu("image-menu")
contextMenu := app.ContextMenu.New()
menuItem := contextMenu.Add("Process Image")
menuItem.OnClick(func(ctx *application.Context) {
imageID := ctx.ContextMenuData()
// Process the image using the ID
})
// Register the context menu with the manager
app.ContextMenu.Add("image-menu", contextMenu)
```
## Associating with HTML Elements
@ -65,6 +71,10 @@ To associate a context menu with an HTML element, use the `--custom-contextmenu`
- `--custom-contextmenu`: Specifies the menu ID (must match the ID used in `NewContextMenu`)
- `--custom-contextmenu-data`: Optional data that will be passed to the click handlers
:::note
This feature will only work as expected after the runtime [has been initialised](../runtime#initialisation).
:::
## Default Context Menu
The default context menu is the webview's built-in context menu that provides system-level operations. You can control its visibility using the `--default-contextmenu` CSS property:
@ -99,9 +109,12 @@ The `auto` setting enables "smart" context menu behaviour:
Menu items can be updated dynamically using the `SetLabel` method and other property setters. After making changes, call `Update` on the menu to apply them:
```go
contextMenu := application.NewContextMenu("dynamic-menu")
contextMenu := application.NewContextMenu()
menuItem := contextMenu.Add("Initial Label")
// Register the context menu with the manager
app.ContextMenu.Add("dynamic-menu", contextMenu)
// Later, update the menu item
menuItem.SetLabel("New Label")
contextMenu.Update()
@ -182,7 +195,7 @@ func main() {
})
// Create a context menu
contextMenu := application.NewContextMenu("test")
contextMenu := app.ContextMenu.New()
// Add items that respond to context data
clickMe := contextMenu.Add("Click to show context data")
@ -194,6 +207,9 @@ func main() {
contextMenu.Update()
})
// Register the context menu with the manager
app.ContextMenu.Add("test", contextMenu)
window := app.NewWebviewWindow()
window.SetTitle("Context Menu Demo")

View file

@ -15,7 +15,7 @@ Wails provides a comprehensive dialog system for displaying native system dialog
Display simple informational messages to users:
```go
dialog := application.InfoDialog()
dialog := app.Dialog.Info()
dialog.SetTitle("Welcome")
dialog.SetMessage("Welcome to our application!")
dialog.Show()
@ -26,7 +26,7 @@ dialog.Show()
Present users with questions and customisable buttons:
```go
dialog := application.QuestionDialog()
dialog := app.Dialog.Question()
dialog.SetTitle("Save Changes")
dialog.SetMessage("Do you want to save your changes?")
dialog.AddButton("Save").OnClick(func() {
@ -42,7 +42,7 @@ dialog.Show()
Display error messages:
```go
dialog := application.ErrorDialog()
dialog := app.Dialog.Error()
dialog.SetTitle("Error")
dialog.SetMessage("Failed to save file")
dialog.Show()
@ -55,7 +55,7 @@ dialog.Show()
Allow users to select files to open:
```go
dialog := application.OpenFileDialog()
dialog := app.Dialog.OpenFile()
dialog.SetTitle("Select Image")
dialog.SetFilters([]*application.FileFilter{
{
@ -80,7 +80,7 @@ if paths, err := dialog.PromptForMultipleSelection(); err == nil {
Allow users to choose where to save files:
```go
dialog := application.SaveFileDialog()
dialog := app.Dialog.SaveFile()
dialog.SetTitle("Save Document")
dialog.SetDefaultFilename("document.txt")
dialog.SetFilters([]*application.FileFilter{
@ -102,7 +102,7 @@ if path, err := dialog.PromptForSingleSelection(); err == nil {
Dialogs can use custom icons from the built-in icon set:
```go
dialog := application.InfoDialog()
dialog := app.Dialog.Info()
dialog.SetIcon(icons.ApplicationDarkMode256)
```
@ -111,8 +111,8 @@ dialog.SetIcon(icons.ApplicationDarkMode256)
Dialogs can be attached to specific windows:
```go
dialog := application.QuestionDialog()
dialog.AttachToWindow(app.CurrentWindow())
dialog := app.Dialog.Question()
dialog.AttachToWindow(app.Window.Current())
dialog.Show()
```
@ -121,7 +121,7 @@ dialog.Show()
Create buttons with custom labels and actions:
```go
dialog := application.QuestionDialog()
dialog := app.Dialog.Question()
dialog.SetMessage("Choose an action")
// Add buttons with custom handlers
@ -183,7 +183,9 @@ dialog.SetDefaultButton(cancelButton) // Set default button
Allow users to select directories:
```go
dialog := application.DirectoryDialog()
dialog := app.Dialog.OpenFile()
dialog.CanChooseDirectories(true)
dialog.CanChooseFiles(false)
dialog.SetTitle("Select Project Directory")
if path, err := dialog.PromptForSingleSelection(); err == nil {
// Use selected directory path
@ -195,7 +197,7 @@ if path, err := dialog.PromptForSingleSelection(); err == nil {
Display application information:
```go
app.ShowAboutDialog()
app.Menu.ShowAbout()
```
## Best Practices

View file

@ -0,0 +1,620 @@
---
title: Environment
sidebar:
order: 59
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
Wails provides comprehensive environment information through the EnvironmentManager API. This allows your application to detect system properties, theme preferences, and integrate with the operating system's file manager.
## Accessing the Environment Manager
The environment manager is accessed through the `Env` property on your application instance:
```go
app := application.New(application.Options{
Name: "Environment Demo",
})
// Access the environment manager
env := app.Env
```
## System Information
### Get Environment Information
Retrieve comprehensive information about the runtime environment:
```go
envInfo := app.Env.Info()
app.Logger.Info("Environment information",
"os", envInfo.OS, // "windows", "darwin", "linux"
"arch", envInfo.Arch, // "amd64", "arm64", etc.
"debug", envInfo.Debug, // Debug mode flag
)
// Operating system details
if envInfo.OSInfo != nil {
app.Logger.Info("OS details",
"name", envInfo.OSInfo.Name,
"version", envInfo.OSInfo.Version,
)
}
// Platform-specific information
for key, value := range envInfo.PlatformInfo {
app.Logger.Info("Platform info", "key", key, "value", value)
}
```
### Environment Structure
The environment information includes several important fields:
```go
type EnvironmentInfo struct {
OS string // Operating system: "windows", "darwin", "linux"
Arch string // Architecture: "amd64", "arm64", "386", etc.
Debug bool // Whether running in debug mode
OSInfo *operatingsystem.OS // Detailed OS information
PlatformInfo map[string]any // Platform-specific details
}
```
## Theme Detection
### Dark Mode Detection
Detect whether the system is using dark mode:
```go
if app.Env.IsDarkMode() {
app.Logger.Info("System is in dark mode")
// Apply dark theme to your application
applyDarkTheme()
} else {
app.Logger.Info("System is in light mode")
// Apply light theme to your application
applyLightTheme()
}
```
### Theme Change Monitoring
Listen for theme changes to update your application dynamically:
```go
import "github.com/wailsapp/wails/v3/pkg/events"
// Listen for theme changes
app.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) {
if app.Env.IsDarkMode() {
app.Logger.Info("Switched to dark mode")
updateApplicationTheme("dark")
} else {
app.Logger.Info("Switched to light mode")
updateApplicationTheme("light")
}
})
func updateApplicationTheme(theme string) {
// Update your application's theme
// This could emit an event to the frontend
app.Event.Emit("theme:changed", theme)
}
```
## File Manager Integration
### Open File Manager
Open the system's file manager at a specific location:
```go
// Open file manager at a directory
err := app.Env.OpenFileManager("/Users/username/Documents", false)
if err != nil {
app.Logger.Error("Failed to open file manager", "error", err)
}
// Open file manager and select a specific file
err = app.Env.OpenFileManager("/Users/username/Documents/report.pdf", true)
if err != nil {
app.Logger.Error("Failed to open file manager with selection", "error", err)
}
```
### Common Use Cases
Show files or folders in the file manager from your application:
```go
func showInFileManager(app *application.App, path string) {
err := app.Env.OpenFileManager(path, true)
if err != nil {
// Fallback: try opening just the directory
dir := filepath.Dir(path)
err = app.Env.OpenFileManager(dir, false)
if err != nil {
app.Logger.Error("Could not open file manager", "path", path, "error", err)
// Show error to user
app.Dialog.Error().
SetTitle("File Manager Error").
SetMessage("Could not open file manager").
Show()
}
}
}
// Usage examples
func setupFileMenu(app *application.App) {
menu := app.Menu.New()
fileMenu := menu.AddSubmenu("File")
fileMenu.Add("Show Downloads Folder").OnClick(func(ctx *application.Context) {
homeDir, _ := os.UserHomeDir()
downloadsDir := filepath.Join(homeDir, "Downloads")
showInFileManager(app, downloadsDir)
})
fileMenu.Add("Show Application Data").OnClick(func(ctx *application.Context) {
configDir, _ := os.UserConfigDir()
appDir := filepath.Join(configDir, "MyApp")
showInFileManager(app, appDir)
})
}
```
## Platform-Specific Behavior
### Adaptive Application Behavior
Use environment information to adapt your application's behavior:
```go
func configureForPlatform(app *application.App) {
envInfo := app.Env.Info()
switch envInfo.OS {
case "darwin":
configureMacOS(app)
case "windows":
configureWindows(app)
case "linux":
configureLinux(app)
}
// Adapt to architecture
if envInfo.Arch == "arm64" {
app.Logger.Info("Running on ARM architecture")
// Potentially optimize for ARM
}
}
func configureMacOS(app *application.App) {
app.Logger.Info("Configuring for macOS")
// macOS-specific configuration
menu := app.Menu.New()
menu.AddRole(application.AppMenu) // Add standard macOS app menu
// Handle dark mode
if app.Env.IsDarkMode() {
setMacOSDarkTheme()
}
}
func configureWindows(app *application.App) {
app.Logger.Info("Configuring for Windows")
// Windows-specific configuration
// Set up Windows-style menus, key bindings, etc.
}
func configureLinux(app *application.App) {
app.Logger.Info("Configuring for Linux")
// Linux-specific configuration
// Adapt to different desktop environments
}
```
## Debug Mode Handling
### Development vs Production
Use debug mode information to enable development features:
```go
func setupApplicationMode(app *application.App) {
envInfo := app.Env.Info()
if envInfo.Debug {
app.Logger.Info("Running in debug mode")
setupDevelopmentFeatures(app)
} else {
app.Logger.Info("Running in production mode")
setupProductionFeatures(app)
}
}
func setupDevelopmentFeatures(app *application.App) {
// Enable development-only features
menu := app.Menu.New()
// Add development menu
devMenu := menu.AddSubmenu("Development")
devMenu.Add("Reload Application").OnClick(func(ctx *application.Context) {
// Reload the application
window := app.Window.Current()
if window != nil {
window.Reload()
}
})
devMenu.Add("Open DevTools").OnClick(func(ctx *application.Context) {
window := app.Window.Current()
if window != nil {
window.OpenDevTools()
}
})
devMenu.Add("Show Environment").OnClick(func(ctx *application.Context) {
showEnvironmentDialog(app)
})
}
func setupProductionFeatures(app *application.App) {
// Production-only features
// Disable debug logging, enable analytics, etc.
}
```
## Environment Information Dialog
### Display System Information
Create a dialog showing environment information:
```go
func showEnvironmentDialog(app *application.App) {
envInfo := app.Env.Info()
details := fmt.Sprintf(`Environment Information:
Operating System: %s
Architecture: %s
Debug Mode: %t
Dark Mode: %t
Platform Information:`,
envInfo.OS,
envInfo.Arch,
envInfo.Debug,
app.Env.IsDarkMode())
// Add platform-specific details
for key, value := range envInfo.PlatformInfo {
details += fmt.Sprintf("\n%s: %v", key, value)
}
if envInfo.OSInfo != nil {
details += fmt.Sprintf("\n\nOS Details:\nName: %s\nVersion: %s",
envInfo.OSInfo.Name,
envInfo.OSInfo.Version)
}
dialog := app.Dialog.Info()
dialog.SetTitle("Environment Information")
dialog.SetMessage(details)
dialog.Show()
}
```
## Platform Considerations
<Tabs>
<TabItem label="macOS" icon="fa-brands:apple">
On macOS:
- Dark mode detection uses system appearance settings
- File manager operations use Finder
- Platform info includes macOS version details
- Architecture may be "arm64" on Apple Silicon Macs
```go
if envInfo.OS == "darwin" {
// macOS-specific handling
if envInfo.Arch == "arm64" {
app.Logger.Info("Running on Apple Silicon")
}
}
```
</TabItem>
<TabItem label="Windows" icon="fa-brands:windows">
On Windows:
- Dark mode detection uses Windows theme settings
- File manager operations use Windows Explorer
- Platform info includes Windows version details
- May include additional Windows-specific information
```go
if envInfo.OS == "windows" {
// Windows-specific handling
for key, value := range envInfo.PlatformInfo {
if key == "windows_version" {
app.Logger.Info("Windows version", "version", value)
}
}
}
```
</TabItem>
<TabItem label="Linux" icon="fa-brands:linux">
On Linux:
- Dark mode detection varies by desktop environment
- File manager operations use system default file manager
- Platform info includes distribution details
- Behavior may vary between different Linux distributions
```go
if envInfo.OS == "linux" {
// Linux-specific handling
if distro, ok := envInfo.PlatformInfo["distribution"]; ok {
app.Logger.Info("Linux distribution", "distro", distro)
}
}
```
</TabItem>
</Tabs>
## Best Practices
1. **Cache Environment Information**: Environment info rarely changes during runtime:
```go
type App struct {
envInfo *application.EnvironmentInfo
}
func (a *App) getEnvInfo() application.EnvironmentInfo {
if a.envInfo == nil {
info := a.app.Env.Info()
a.envInfo = &info
}
return *a.envInfo
}
```
2. **Handle Theme Changes**: Listen for system theme changes:
```go
app.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) {
updateTheme(app.Env.IsDarkMode())
})
```
3. **Graceful File Manager Failures**: Always handle file manager errors:
```go
func openFileManagerSafely(app *application.App, path string) {
err := app.Env.OpenFileManager(path, false)
if err != nil {
// Provide fallback or user notification
app.Logger.Warn("Could not open file manager", "path", path)
}
}
```
4. **Platform-Specific Features**: Use environment info to enable platform features:
```go
envInfo := app.Env.Info()
if envInfo.OS == "darwin" {
// Enable macOS-specific features
}
```
## Complete Example
Here's a complete example demonstrating environment management:
```go
package main
import (
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/events"
)
func main() {
app := application.New(application.Options{
Name: "Environment Demo",
})
// Setup application based on environment
setupForEnvironment(app)
// Monitor theme changes
monitorThemeChanges(app)
// Create menu with environment features
setupEnvironmentMenu(app)
// Create main window
window := app.Window.New()
window.SetTitle("Environment Demo")
err := app.Run()
if err != nil {
panic(err)
}
}
func setupForEnvironment(app *application.App) {
envInfo := app.Env.Info()
app.Logger.Info("Application environment",
"os", envInfo.OS,
"arch", envInfo.Arch,
"debug", envInfo.Debug,
"darkMode", app.Env.IsDarkMode(),
)
// Configure for platform
switch envInfo.OS {
case "darwin":
app.Logger.Info("Configuring for macOS")
// macOS-specific setup
case "windows":
app.Logger.Info("Configuring for Windows")
// Windows-specific setup
case "linux":
app.Logger.Info("Configuring for Linux")
// Linux-specific setup
}
// Apply initial theme
if app.Env.IsDarkMode() {
applyDarkTheme(app)
} else {
applyLightTheme(app)
}
}
func monitorThemeChanges(app *application.App) {
app.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) {
if app.Env.IsDarkMode() {
app.Logger.Info("System switched to dark mode")
applyDarkTheme(app)
} else {
app.Logger.Info("System switched to light mode")
applyLightTheme(app)
}
})
}
func setupEnvironmentMenu(app *application.App) {
menu := app.Menu.New()
// Add platform-specific app menu
if runtime.GOOS == "darwin" {
menu.AddRole(application.AppMenu)
}
// Tools menu
toolsMenu := menu.AddSubmenu("Tools")
toolsMenu.Add("Show Environment Info").OnClick(func(ctx *application.Context) {
showEnvironmentInfo(app)
})
toolsMenu.Add("Open Downloads Folder").OnClick(func(ctx *application.Context) {
openDownloadsFolder(app)
})
toolsMenu.Add("Toggle Theme").OnClick(func(ctx *application.Context) {
// This would typically be handled by the system
// but shown here for demonstration
toggleTheme(app)
})
app.Menu.Set(menu)
}
func showEnvironmentInfo(app *application.App) {
envInfo := app.Env.Info()
message := fmt.Sprintf(`Environment Information:
Operating System: %s
Architecture: %s
Debug Mode: %t
Dark Mode: %t
Platform Details:`,
envInfo.OS,
envInfo.Arch,
envInfo.Debug,
app.Env.IsDarkMode())
for key, value := range envInfo.PlatformInfo {
message += fmt.Sprintf("\n%s: %v", key, value)
}
if envInfo.OSInfo != nil {
message += fmt.Sprintf("\n\nOS Information:\nName: %s\nVersion: %s",
envInfo.OSInfo.Name,
envInfo.OSInfo.Version)
}
dialog := app.Dialog.Info()
dialog.SetTitle("Environment Information")
dialog.SetMessage(message)
dialog.Show()
}
func openDownloadsFolder(app *application.App) {
homeDir, err := os.UserHomeDir()
if err != nil {
app.Logger.Error("Could not get home directory", "error", err)
return
}
downloadsDir := filepath.Join(homeDir, "Downloads")
err = app.Env.OpenFileManager(downloadsDir, false)
if err != nil {
app.Logger.Error("Could not open Downloads folder", "error", err)
app.Dialog.Error().
SetTitle("File Manager Error").
SetMessage("Could not open Downloads folder").
Show()
}
}
func applyDarkTheme(app *application.App) {
app.Logger.Info("Applying dark theme")
// Emit theme change to frontend
app.Event.Emit("theme:apply", "dark")
}
func applyLightTheme(app *application.App) {
app.Logger.Info("Applying light theme")
// Emit theme change to frontend
app.Event.Emit("theme:apply", "light")
}
func toggleTheme(app *application.App) {
// This is just for demonstration
// Real theme changes should come from the system
currentlyDark := app.Env.IsDarkMode()
if currentlyDark {
applyLightTheme(app)
} else {
applyDarkTheme(app)
}
}
```
:::tip[Pro Tip]
Use environment information to provide platform-appropriate user experiences. For example, use Command key shortcuts on macOS and Control key shortcuts on Windows/Linux.
:::
:::danger[Warning]
Environment information is generally stable during application runtime, but theme preferences can change. Always listen for theme change events to keep your UI synchronized.
:::

View file

@ -422,33 +422,85 @@ app.OnApplicationEvent(events.Windows.WebViewNavigationCompleted, func(event *ap
## Custom Events
You can emit and listen for custom events to enable communication between different parts of your application.
You can emit and listen for custom events to enable communication between different parts of your application. Wails v3 provides both a traditional API and a new [Manager API](/learn/manager-api) for better organization.
### Emitting Events
You can emit custom events from anywhere in your application:
<Tabs>
<TabItem label="Manager API (Recommended)">
```go
// Emit an event with data from the application
// NEW: Using the Event Manager (recommended)
app.Event.Emit("myevent", "hello")
// Emit from a specific window
window.EmitEvent("windowevent", "window specific data")
```
</TabItem>
<TabItem label="Traditional API">
```go
// Traditional API (still supported)
app.EmitEvent("myevent", "hello")
// Emit from a specific window
window.EmitEvent("windowevent", "window specific data")
```
</TabItem>
</Tabs>
### Handling Custom Events
Listen for custom events using the `OnEvent` method:
Listen for custom events using the event management methods:
<Tabs>
<TabItem label="Manager API (Recommended)">
```go
app.OnEvent("myevent", func(e *application.CustomEvent) {
// NEW: Using the Event Manager (recommended)
cancelFunc := app.Event.On("myevent", func(e *application.CustomEvent) {
// Access event information
name := e.Name // Event name
data := e.Data // Event data
sender := e.Sender // Name of the window sending the event, or "" if sent from application
cancelled := e.IsCancelled() // Event cancellation status
})
// Remove specific event listener
app.Event.Off("myevent")
// Remove all event listeners
app.Event.Reset()
// Listen for events a limited number of times
app.Event.OnMultiple("myevent", func(e *application.CustomEvent) {
// This will only be called 3 times
}, 3)
```
</TabItem>
<TabItem label="Traditional API">
```go
// Traditional API (still supported)
cancelFunc := app.OnEvent("myevent", func(e *application.CustomEvent) {
// Access event information
name := e.Name // Event name
data := e.Data // Event data
sender := e.Sender // Name of the window sending the event, or "" if sent from application
cancelled := e.IsCancelled() // Event cancellation status
})
// Remove specific event listener
app.OffEvent("myevent")
// Remove all event listeners
app.ResetEvents()
// Listen for events a limited number of times
app.OnMultipleEvent("myevent", func(e *application.CustomEvent) {
// This will only be called 3 times
}, 3)
```
</TabItem>
</Tabs>
## Event Cancellation
@ -479,7 +531,7 @@ window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent
})
// For custom events
app.OnEvent("myevent", func(e *application.CustomEvent) {
app.Event.On("myevent", func(e *application.CustomEvent) {
if e.IsCancelled() {
app.Logger.Info("Event was cancelled")
return

View file

@ -0,0 +1,437 @@
---
title: Key Bindings
sidebar:
order: 56
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
Wails provides a powerful key binding system that allows you to register global keyboard shortcuts that work across all windows in your application. This enables users to quickly access functionality without navigating through menus.
## Accessing the Key Binding Manager
The key binding manager is accessed through the `KeyBindings` property on your application instance:
```go
app := application.New(application.Options{
Name: "Keyboard Shortcuts Demo",
})
// Access the key binding manager
keyBindings := app.KeyBinding
```
## Adding Key Bindings
### Basic Key Binding
Register a simple keyboard shortcut:
```go
app.KeyBinding.Add("Ctrl+S", func(window *application.WebviewWindow) {
// Handle save action
app.Logger.Info("Save shortcut triggered")
// Perform save operation...
})
```
### Multiple Key Bindings
Register multiple shortcuts for common operations:
```go
// File operations
app.KeyBinding.Add("Ctrl+N", func(window *application.WebviewWindow) {
// New file
window.EmitEvent("file:new", nil)
})
app.KeyBinding.Add("Ctrl+O", func(window *application.WebviewWindow) {
// Open file
dialog := app.Dialog.OpenFile()
if file, err := dialog.PromptForSingleSelection(); err == nil {
window.EmitEvent("file:open", file)
}
})
app.KeyBinding.Add("Ctrl+S", func(window *application.WebviewWindow) {
// Save file
window.EmitEvent("file:save", nil)
})
// Edit operations
app.KeyBinding.Add("Ctrl+Z", func(window *application.WebviewWindow) {
// Undo
window.EmitEvent("edit:undo", nil)
})
app.KeyBinding.Add("Ctrl+Y", func(window *application.WebviewWindow) {
// Redo (Windows/Linux)
window.EmitEvent("edit:redo", nil)
})
app.KeyBinding.Add("Cmd+Shift+Z", func(window *application.WebviewWindow) {
// Redo (macOS)
window.EmitEvent("edit:redo", nil)
})
```
## Key Binding Accelerators
### Accelerator Format
Key bindings use a standard accelerator format with modifiers and keys:
```go
// Modifier keys
"Ctrl+S" // Control + S
"Cmd+S" // Command + S (macOS)
"Alt+F4" // Alt + F4
"Shift+Ctrl+Z" // Shift + Control + Z
// Function keys
"F1" // F1 key
"Ctrl+F5" // Control + F5
// Special keys
"Escape" // Escape key
"Enter" // Enter key
"Space" // Spacebar
"Tab" // Tab key
"Backspace" // Backspace key
"Delete" // Delete key
// Arrow keys
"Up" // Up arrow
"Down" // Down arrow
"Left" // Left arrow
"Right" // Right arrow
```
### Platform-Specific Accelerators
Handle platform differences for common shortcuts:
```go
import "runtime"
// Cross-platform save shortcut
if runtime.GOOS == "darwin" {
app.KeyBinding.Add("Cmd+S", saveHandler)
} else {
app.KeyBinding.Add("Ctrl+S", saveHandler)
}
// Or register both
app.KeyBinding.Add("Ctrl+S", saveHandler)
app.KeyBinding.Add("Cmd+S", saveHandler)
```
## Managing Key Bindings
### Removing Key Bindings
Remove key bindings when they're no longer needed:
```go
// Remove a specific key binding
app.KeyBinding.Remove("Ctrl+S")
// Example: Temporary key binding for a modal
app.KeyBinding.Add("Escape", func(window *application.WebviewWindow) {
// Close modal
window.EmitEvent("modal:close", nil)
// Remove this temporary binding
app.KeyBinding.Remove("Escape")
})
```
### Getting All Key Bindings
Retrieve all registered key bindings:
```go
allBindings := app.KeyBinding.GetAll()
for _, binding := range allBindings {
app.Logger.Info("Key binding", "accelerator", binding.Accelerator)
}
```
## Advanced Usage
### Context-Aware Key Bindings
Make key bindings context-aware by checking application state:
```go
app.KeyBinding.Add("Ctrl+S", func(window *application.WebviewWindow) {
// Check current application state
if isEditMode() {
// Save document
saveDocument()
} else if isInSettings() {
// Save settings
saveSettings()
} else {
app.Logger.Info("Save not available in current context")
}
})
```
### Window-Specific Actions
Key bindings receive the active window, allowing window-specific behavior:
```go
app.KeyBinding.Add("F11", func(window *application.WebviewWindow) {
// Toggle fullscreen for the active window
if window.Fullscreen() {
window.SetFullscreen(false)
} else {
window.SetFullscreen(true)
}
})
app.KeyBinding.Add("Ctrl+W", func(window *application.WebviewWindow) {
// Close the active window
window.Close()
})
```
### Dynamic Key Binding Management
Dynamically add and remove key bindings based on application state:
```go
func enableEditMode() {
// Add edit-specific key bindings
app.KeyBinding.Add("Ctrl+B", func(window *application.WebviewWindow) {
window.EmitEvent("format:bold", nil)
})
app.KeyBinding.Add("Ctrl+I", func(window *application.WebviewWindow) {
window.EmitEvent("format:italic", nil)
})
app.KeyBinding.Add("Ctrl+U", func(window *application.WebviewWindow) {
window.EmitEvent("format:underline", nil)
})
}
func disableEditMode() {
// Remove edit-specific key bindings
app.KeyBinding.Remove("Ctrl+B")
app.KeyBinding.Remove("Ctrl+I")
app.KeyBinding.Remove("Ctrl+U")
}
```
## Platform Considerations
<Tabs>
<TabItem label="macOS" icon="fa-brands:apple">
On macOS:
- Use `Cmd` instead of `Ctrl` for standard shortcuts
- `Cmd+Q` is typically reserved for quitting the application
- `Cmd+H` hides the application
- `Cmd+M` minimizes windows
- Consider standard macOS keyboard shortcuts
Common macOS patterns:
```go
app.KeyBinding.Add("Cmd+N", newFileHandler) // New
app.KeyBinding.Add("Cmd+O", openFileHandler) // Open
app.KeyBinding.Add("Cmd+S", saveFileHandler) // Save
app.KeyBinding.Add("Cmd+Z", undoHandler) // Undo
app.KeyBinding.Add("Cmd+Shift+Z", redoHandler) // Redo
app.KeyBinding.Add("Cmd+C", copyHandler) // Copy
app.KeyBinding.Add("Cmd+V", pasteHandler) // Paste
```
</TabItem>
<TabItem label="Windows" icon="fa-brands:windows">
On Windows:
- Use `Ctrl` for standard shortcuts
- `Alt+F4` closes applications
- `F1` typically opens help
- Consider Windows keyboard conventions
Common Windows patterns:
```go
app.KeyBinding.Add("Ctrl+N", newFileHandler) // New
app.KeyBinding.Add("Ctrl+O", openFileHandler) // Open
app.KeyBinding.Add("Ctrl+S", saveFileHandler) // Save
app.KeyBinding.Add("Ctrl+Z", undoHandler) // Undo
app.KeyBinding.Add("Ctrl+Y", redoHandler) // Redo
app.KeyBinding.Add("Ctrl+C", copyHandler) // Copy
app.KeyBinding.Add("Ctrl+V", pasteHandler) // Paste
app.KeyBinding.Add("F1", helpHandler) // Help
```
</TabItem>
<TabItem label="Linux" icon="fa-brands:linux">
On Linux:
- Generally follows Windows conventions with `Ctrl`
- May vary by desktop environment
- Consider GNOME/KDE standard shortcuts
- Some desktop environments reserve certain shortcuts
Common Linux patterns:
```go
app.KeyBinding.Add("Ctrl+N", newFileHandler) // New
app.KeyBinding.Add("Ctrl+O", openFileHandler) // Open
app.KeyBinding.Add("Ctrl+S", saveFileHandler) // Save
app.KeyBinding.Add("Ctrl+Z", undoHandler) // Undo
app.KeyBinding.Add("Ctrl+Shift+Z", redoHandler) // Redo
app.KeyBinding.Add("Ctrl+C", copyHandler) // Copy
app.KeyBinding.Add("Ctrl+V", pasteHandler) // Paste
```
</TabItem>
</Tabs>
## Best Practices
1. **Use Standard Shortcuts**: Follow platform conventions for common operations:
```go
// Cross-platform save
if runtime.GOOS == "darwin" {
app.KeyBinding.Add("Cmd+S", saveHandler)
} else {
app.KeyBinding.Add("Ctrl+S", saveHandler)
}
```
2. **Provide Visual Feedback**: Let users know when shortcuts are triggered:
```go
app.KeyBinding.Add("Ctrl+S", func(window *application.WebviewWindow) {
saveDocument()
// Show brief notification
window.EmitEvent("notification:show", "Document saved")
})
```
3. **Handle Conflicts**: Be careful not to override important system shortcuts:
```go
// Avoid overriding system shortcuts like:
// Ctrl+Alt+Del (Windows)
// Cmd+Space (macOS Spotlight)
// Alt+Tab (Window switching)
```
4. **Document Shortcuts**: Provide help or documentation for available shortcuts:
```go
app.KeyBinding.Add("F1", func(window *application.WebviewWindow) {
// Show help dialog with available shortcuts
showKeyboardShortcutsHelp()
})
```
5. **Clean Up**: Remove temporary key bindings when they're no longer needed:
```go
func enterEditMode() {
app.KeyBinding.Add("Escape", exitEditModeHandler)
}
func exitEditModeHandler(window *application.WebviewWindow) {
exitEditMode()
app.KeyBinding.Remove("Escape") // Clean up temporary binding
}
```
## Complete Example
Here's a complete example of a text editor with keyboard shortcuts:
```go
package main
import (
"runtime"
"github.com/wailsapp/wails/v3/pkg/application"
)
func main() {
app := application.New(application.Options{
Name: "Text Editor with Shortcuts",
})
// File operations
if runtime.GOOS == "darwin" {
app.KeyBinding.Add("Cmd+N", func(window *application.WebviewWindow) {
window.EmitEvent("file:new", nil)
})
app.KeyBinding.Add("Cmd+O", func(window *application.WebviewWindow) {
openFile(app, window)
})
app.KeyBinding.Add("Cmd+S", func(window *application.WebviewWindow) {
window.EmitEvent("file:save", nil)
})
} else {
app.KeyBinding.Add("Ctrl+N", func(window *application.WebviewWindow) {
window.EmitEvent("file:new", nil)
})
app.KeyBinding.Add("Ctrl+O", func(window *application.WebviewWindow) {
openFile(app, window)
})
app.KeyBinding.Add("Ctrl+S", func(window *application.WebviewWindow) {
window.EmitEvent("file:save", nil)
})
}
// View operations
app.KeyBinding.Add("F11", func(window *application.WebviewWindow) {
window.SetFullscreen(!window.Fullscreen())
})
app.KeyBinding.Add("F1", func(window *application.WebviewWindow) {
showKeyboardShortcuts(window)
})
// Create main window
window := app.Window.New()
window.SetTitle("Text Editor")
err := app.Run()
if err != nil {
panic(err)
}
}
func openFile(app *application.App, window *application.WebviewWindow) {
dialog := app.Dialog.OpenFile()
dialog.AddFilter("Text Files", "*.txt;*.md")
if file, err := dialog.PromptForSingleSelection(); err == nil {
window.EmitEvent("file:open", file)
}
}
func showKeyboardShortcuts(window *application.WebviewWindow) {
shortcuts := `
Keyboard Shortcuts:
- Ctrl/Cmd+N: New file
- Ctrl/Cmd+O: Open file
- Ctrl/Cmd+S: Save file
- F11: Toggle fullscreen
- F1: Show this help
`
window.EmitEvent("help:show", shortcuts)
}
```
:::tip[Pro Tip]
Test your key bindings on all target platforms to ensure they work correctly and don't conflict with system shortcuts.
:::
:::danger[Warning]
Be careful not to override critical system shortcuts. Some key combinations are reserved by the operating system and cannot be captured by applications.
:::

View file

@ -0,0 +1,265 @@
---
title: Manager API
sidebar:
order: 25
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
The Wails v3 Manager API provides an organized and discoverable way to access application functionality through focused manager structs. This new API structure groups related methods together while maintaining full backward compatibility with the traditional App API.
## Overview
The Manager API organizes application functionality into eleven focused areas:
- **`app.Window`** - Window creation, management, and callbacks
- **`app.ContextMenu`** - Context menu registration and management
- **`app.KeyBinding`** - Global key binding management
- **`app.Browser`** - Browser integration (opening URLs and files)
- **`app.Env`** - Environment information and system state
- **`app.Dialog`** - File and message dialog operations
- **`app.Event`** - Custom event handling and application events
- **`app.Menu`** - Application menu management
- **`app.Screen`** - Screen management and coordinate transformations
- **`app.Clipboard`** - Clipboard text operations
- **`app.SystemTray`** - System tray icon creation and management
## Benefits
- **Better discoverability** - IDE autocomplete shows organized API surface
- **Improved code organization** - Related methods grouped together
- **Enhanced maintainability** - Separation of concerns across managers
- **Future extensibility** - Easier to add new features to specific areas
## Usage
The Manager API provides organized access to all application functionality:
```go
// Events and custom event handling
app.Event.Emit("custom", data)
app.Event.On("custom", func(e *CustomEvent) { ... })
// Window management
window, _ := app.Window.GetByName("main")
app.Window.OnCreate(func(window Window) { ... })
// Browser integration
app.Browser.OpenURL("https://wails.io")
// Menu management
menu := app.Menu.New()
app.Menu.Set(menu)
// System tray
systray := app.SystemTray.New()
```
## Manager Reference
### Window Manager
Manages window creation, retrieval, and lifecycle callbacks.
```go
// Create windows
window := app.Window.New()
window := app.Window.NewWithOptions(options)
current := app.Window.Current()
// Find windows
window, exists := app.Window.GetByName("main")
windows := app.Window.GetAll()
// Window callbacks
app.Window.OnCreate(func(window Window) {
// Handle window creation
})
```
### Event Manager
Handles custom events and application event listening.
```go
// Custom events
app.Event.Emit("userAction", data)
cancelFunc := app.Event.On("userAction", func(e *CustomEvent) {
// Handle event
})
app.Event.Off("userAction")
app.Event.Reset() // Remove all listeners
// Application events
app.Event.OnApplicationEvent(events.Common.ThemeChanged, func(e *ApplicationEvent) {
// Handle system theme change
})
```
### Browser Manager
Provides browser integration for opening URLs and files.
```go
// Open URLs and files in default browser
err := app.Browser.OpenURL("https://wails.io")
err := app.Browser.OpenFile("/path/to/document.pdf")
```
### Environment Manager
Access to system environment information.
```go
// Get environment info
env := app.Env.Info()
fmt.Printf("OS: %s, Arch: %s\n", env.OS, env.Arch)
// Check system theme
if app.Env.IsDarkMode() {
// Dark mode is active
}
// Open file manager
err := app.Env.OpenFileManager("/path/to/folder", false)
```
### Dialog Manager
Organized access to file and message dialogs.
```go
// File dialogs
result, err := app.Dialog.OpenFile().
AddFilter("Text Files", "*.txt").
PromptForSingleSelection()
result, err = app.Dialog.SaveFile().
SetDefaultFilename("document.txt").
PromptForSingleSelection()
// Message dialogs
app.Dialog.Info().
SetTitle("Information").
SetMessage("Operation completed successfully").
Show()
app.Dialog.Error().
SetTitle("Error").
SetMessage("An error occurred").
Show()
```
### Menu Manager
Application menu creation and management.
```go
// Create and set application menu
menu := app.Menu.New()
fileMenu := menu.AddSubmenu("File")
fileMenu.Add("New").OnClick(func(ctx *Context) {
// Handle menu click
})
app.Menu.Set(menu)
// Show about dialog
app.Menu.ShowAbout()
```
### Key Binding Manager
Dynamic management of global key bindings.
```go
// Add key bindings
app.KeyBinding.Add("ctrl+n", func(window *WebviewWindow) {
// Handle Ctrl+N
})
app.KeyBinding.Add("ctrl+q", func(window *WebviewWindow) {
app.Quit()
})
// Remove key bindings
app.KeyBinding.Remove("ctrl+n")
// Get all bindings
bindings := app.KeyBinding.GetAll()
```
### Context Menu Manager
Advanced context menu management (for library authors).
```go
// Create and register context menu
menu := app.ContextMenu.New()
app.ContextMenu.Add("myMenu", menu)
// Retrieve context menu
menu, exists := app.ContextMenu.Get("myMenu")
// Remove context menu
app.ContextMenu.Remove("myMenu")
```
### Screen Manager
Screen management and coordinate transformations for multi-monitor setups.
```go
// Get screen information
screens := app.Screen.GetAll()
primary := app.Screen.GetPrimary()
// Coordinate transformations
physicalPoint := app.Screen.DipToPhysicalPoint(logicalPoint)
logicalPoint := app.Screen.PhysicalToDipPoint(physicalPoint)
// Screen detection
screen := app.Screen.ScreenNearestDipPoint(point)
screen = app.Screen.ScreenNearestDipRect(rect)
```
### Clipboard Manager
Clipboard operations for reading and writing text.
```go
// Set text to clipboard
success := app.Clipboard.SetText("Hello World")
if !success {
// Handle error
}
// Get text from clipboard
text, ok := app.Clipboard.Text()
if !ok {
// Handle error
} else {
// Use the text
}
```
### SystemTray Manager
System tray icon creation and management.
```go
// Create system tray
systray := app.SystemTray.New()
systray.SetLabel("My App")
systray.SetIcon(iconBytes)
// Add menu to system tray
menu := app.Menu.New()
menu.Add("Open").OnClick(func(ctx *Context) {
// Handle click
})
systray.SetMenu(menu)
// Destroy system tray when done
systray.Destroy()
```

View file

@ -0,0 +1,304 @@
---
title: Notifications
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
## Introduction
Wails provides a comprehensive cross-platform notification system for desktop applications. This service allows you to display native system notifications, with support for interactive elements like action buttons and text input fields.
## Basic Usage
### Creating the Service
First, initialize the notifications service:
```go
import "github.com/wailsapp/wails/v3/pkg/application"
import "github.com/wailsapp/wails/v3/services/notifications"
// Create a new notification service
notifier := notifications.New()
//Register the service with the application
app := application.New(application.Options{
Services: []application.Service{
application.NewService(notifier),
},
})
```
## Notification Authorization
Notifications on macOS require user authorization. Request and check authorization:
```go
authorized, err := notifier.CheckNotificationAuthorization()
if err != nil {
// Handle authorization error
}
if authorized {
// Send notifications
} else {
// Request authorization
authorized, err = notifier.RequestNotificationAuthorization()
}
```
On Windows and Linux this always returns `true`.
## Notification Types
### Basic Notifications
Send a basic notification with a unique id, title, optional subtitle (macOS and Linux), and body text to users:
```go
notifier.SendNotification(notifications.NotificationOptions{
ID: "unique-id",
Title: "New Calendar Invite",
Subtitle: "From: Jane Doe", // Optional
Body: "Tap to view the event",
})
```
### Interactive Notifications
Send a notification with action buttons and text inputs. These notifications require a notification category to be resgistered first:
```go
// Define a unique category id
categoryID := "unique-category-id"
// Define a category with actions
category := notifications.NotificationCategory{
ID: categoryID,
Actions: []notifications.NotificationAction{
{
ID: "OPEN",
Title: "Open",
},
{
ID: "ARCHIVE",
Title: "Archive",
Destructive: true, /* macOS-specific */
},
},
HasReplyField: true,
ReplyPlaceholder: "message...",
ReplyButtonTitle: "Reply",
}
// Register the category
notifier.RegisterNotificationCategory(category)
// Send an interactive notification with the actions registered in the provided category
notifier.SendNotificationWithActions(notifications.NotificationOptions{
ID: "unique-id",
Title: "New Message",
Subtitle: "From: Jane Doe",
Body: "Are you able to make it?",
CategoryID: categoryID,
})
```
## Notification Responses
Process user interactions with notifications:
```go
notifier.OnNotificationResponse(func(result notifications.NotificationResult) {
response := result.Response
fmt.Printf("Notification %s was actioned with: %s\n", response.ID, response.ActionIdentifier)
if response.ActionIdentifier == "TEXT_REPLY" {
fmt.Printf("User replied: %s\n", response.UserText)
}
if data, ok := response.UserInfo["sender"].(string); ok {
fmt.Printf("Original sender: %s\n", data)
}
// Emit an event to the frontend
app.Event.Emit("notification", result.Response)
})
```
## Notification Customisation
### Custom Metadata
Basic and interactive notifications can include custom data:
```go
notifier.SendNotification(notifications.NotificationOptions{
ID: "unique-id",
Title: "New Calendar Invite",
Subtitle: "From: Jane Doe", // Optional
Body: "Tap to view the event",
Data: map[string]interface{}{
"sender": "jane.doe@example.com",
"timestamp": "2025-03-10T15:30:00Z",
}
})
```
## Platform Considerations
<Tabs>
<TabItem label="macOS" icon="fa-brands:apple">
On macOS, notifications:
- Require user authorization
- Require the app to be notorized for distribution
- Use system-standard notification appearances
- Support `subtitle`
- Support user text input
- Support the `Destructive` action option
- Automatically handle dark/light mode
</TabItem>
<TabItem label="Windows" icon="fa-brands:windows">
On Windows, notifications:
- Use Windows system toast styles
- Adapt to Windows theme settings
- Support user text input
- Support high DPI displays
- Do not support `subtitle`
</TabItem>
<TabItem label="Linux" icon="fa-brands:linux">
On Linux, dialog behaviour depends on the desktop environment:
- Use native notifications when available
- Follow desktop environment theme
- Position according to desktop environment rules
- Support `subtitle`
- Do not support user text input
</TabItem>
</Tabs>
## Best Practices
1. Check and request for authorization:
- macOS requires user authorization
2. Provide clear and concise notifications:
- Use descriptive titles, subtitles, text, and action titles
3. Handle dialog responses appropriately:
- Check for errors in notification responses
- Provide feedback for user actions
4. Consider platform conventions:
- Follow platform-specific notification patterns
- Respect system settings
## Examples
Explore this example:
- [Notifications](/examples/notifications)
## API Reference
### Service Management
| Method | Description |
|--------------------------------------------|-------------------------------------------------------|
| `New()` | Creates a new notifications service |
### Notification Authorization
| Method | Description |
|----------------------------------------------|------------------------------------------------------------|
| `RequestNotificationAuthorization()` | Requests permission to display notifications (macOS) |
| `CheckNotificationAuthorization()` | Checks current notification authorization status (macOS) |
### Sending Notifications
| Method | Description |
|------------------------------------------------------------|---------------------------------------------------|
| `SendNotification(options NotificationOptions)` | Sends a basic notification |
| `SendNotificationWithActions(options NotificationOptions)` | Sends an interactive notification with actions |
### Notification Categories
| Method | Description |
|---------------------------------------------------------------|---------------------------------------------------|
| `RegisterNotificationCategory(category NotificationCategory)` | Registers a reusable notification category |
| `RemoveNotificationCategory(categoryID string)` | Removes a previously registered category |
### Managing Notifications
| Method | Description |
|-------------------------------------------------|---------------------------------------------------------------------|
| `RemoveAllPendingNotifications()` | Removes all pending notifications (macOS and Linux only) |
| `RemovePendingNotification(identifier string)` | Removes a specific pending notification (macOS and Linux only) |
| `RemoveAllDeliveredNotifications()` | Removes all delivered notifications (macOS and Linux only) |
| `RemoveDeliveredNotification(identifier string)`| Removes a specific delivered notification (macOS and Linux only) |
| `RemoveNotification(identifier string)` | Removes a notification (Linux-specific) |
### Event Handling
| Method | Description |
|--------------------------------------------------------------------|-------------------------------------------------|
| `OnNotificationResponse(callback func(result NotificationResult))` | Registers a callback for notification responses |
### Structs and Types
#### NotificationOptions
```go
type NotificationOptions struct {
ID string // Unique identifier for the notification
Title string // Main notification title
Subtitle string // Subtitle text (macOS and Linux only)
Body string // Main notification content
CategoryID string // Category identifier for interactive notifications
Data map[string]interface{} // Custom data to associate with the notification
}
```
#### NotificationCategory
```go
type NotificationCategory struct {
ID string // Unique identifier for the category
Actions []NotificationAction // Button actions for the notification
HasReplyField bool // Whether to include a text input field
ReplyPlaceholder string // Placeholder text for the input field
ReplyButtonTitle string // Text for the reply button
}
```
#### NotificationAction
```go
type NotificationAction struct {
ID string // Unique identifier for the action
Title string // Button text
Destructive bool // Whether the action is destructive (macOS-specific)
}
```
#### NotificationResponse
```go
type NotificationResponse struct {
ID string // Notification identifier
ActionIdentifier string // Action that was triggered
CategoryID string // Category of the notification
Title string // Title of the notification
Subtitle string // Subtitle of the notification
Body string // Body text of the notification
UserText string // Text entered by the user
UserInfo map[string]interface{} // Custom data from the notification
}
```
#### NotificationResult
```go
type NotificationResult struct {
Response NotificationResponse // Response data
Error error // Any error that occurred
}
```

View file

@ -22,12 +22,12 @@ The runtime is required for integration between Go and the frontend. There are 2
ways to integrate the runtime:
- Using the `@wailsio/runtime` package
- Using a pre-built version of the runtime
- Using a pre-built bundle
## Using the npm package
The `@wailsio/runtime` package is a JavaScript package that provides access to
the Wails runtime from the frontend. It is used in by all the standard templates
the Wails runtime from the frontend. It is used by all standard templates
and is the recommended way to integrate the runtime into your application.
By using the `@wailsio/runtime` package, you will only include the parts of the runtime that you use.
@ -37,25 +37,55 @@ The package is available on npm and can be installed using:
npm install --save @wailsio/runtime
```
## Using a pre-built local version of the runtime
## Using a pre-built bundle
Some projects will not use a Javascript bundler and may prefer to use a
pre-built version of the runtime. This is the default for the examples in
`v3/examples`. The pre-built version of the runtime can be generated using the
following command:
pre-built bundled version of the runtime. This version can be generated locally
using the following command:
```shell
wails3 generate runtime
```
This will generate a `runtime.js` (and `runtime.debug.js`) file in the current
directory. This file can be used by your application by adding it to your frontend project:
The command will output a `runtime.js` (and `runtime.debug.js`) file in the current
directory. This file is an ES module that can be imported by your application scripts
just like the npm package, but the API is also exported to the global window object,
so for simpler applications you can use it as follows:
```html
<html>
<head>
<script src="./runtime.js"></script>
<script type="module" src="./runtime.js"></script>
<script>
window.onload = function () {
wails.Window.SetTitle("A new window title");
}
</script>
</head>
<!--- ... -->
</>
</html>
```
:::caution
It is important to include the `type="module"` attribute on the `<script>` tag that loads the runtime
and to wait for the page to be fully loaded before calling the API,
because scripts with the `type="module"` attribute run asynchronously.
:::
## Initialisation
Apart from the API functions, the runtime provides support for context menus and window dragging.
These features will only work as expected after the runtime has been initialised.
Even if you don't use the API, make sure to include a side-effect import statement
somewhere in your frontend code:
```javascript
import "@wailsio/runtime";
```
Your bundler should detect the presence of side-effects and include
all required initialisation code in the build.
:::note
If you prefer the pre-built bundle, adding a script tag as shown above suffices.
:::

View file

@ -0,0 +1,505 @@
---
title: Screen Management
sidebar:
order: 57
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
Wails provides comprehensive screen management capabilities for multi-monitor setups and high-DPI displays. The ScreenManager API allows you to query screen information, handle coordinate transformations, and manage window placement across multiple displays.
## Accessing the Screen Manager
The screen manager is accessed through the `Screens` property on your application instance:
```go
app := application.New(application.Options{
Name: "Multi-Monitor App",
})
// Access the screen manager
screens := app.Screen
```
## Getting Screen Information
### Get All Screens
Retrieve information about all connected displays:
```go
allScreens := app.Screen.GetAll()
for i, screen := range allScreens {
app.Logger.Info("Screen info",
"index", i,
"name", screen.Name,
"id", screen.ID,
"primary", screen.IsPrimary,
"width", screen.Size.Width,
"height", screen.Size.Height,
"scaleFactor", screen.ScaleFactor,
)
}
```
### Get Primary Screen
Get the primary (main) display:
```go
primaryScreen := app.Screen.GetPrimary()
if primaryScreen != nil {
app.Logger.Info("Primary screen",
"name", primaryScreen.Name,
"resolution", fmt.Sprintf("%dx%d", primaryScreen.Size.Width, primaryScreen.Size.Height),
"workArea", primaryScreen.WorkArea,
)
}
```
## Screen Properties
### Screen Structure
Each screen provides comprehensive information about the display:
```go
type Screen struct {
ID string // Unique identifier for the display
Name string // Display name (e.g., "Built-in Retina Display")
ScaleFactor float32 // DPI scale factor (1.0 = 96 DPI, 2.0 = 192 DPI)
X int // X coordinate of top-left corner
Y int // Y coordinate of top-left corner
Size Size // Logical size of the display
Bounds Rect // Display bounds in logical coordinates
PhysicalBounds Rect // Physical bounds before scaling
WorkArea Rect // Available area (excluding taskbars/docks)
PhysicalWorkArea Rect // Physical work area
IsPrimary bool // Whether this is the primary display
Rotation float32 // Display rotation in degrees
}
```
### Coordinate Systems
Wails handles two coordinate systems:
- **Logical (DIP)**: Device-independent pixels, scaled for readability
- **Physical**: Actual pixel coordinates on the hardware
```go
screen := app.Screen.GetPrimary()
// Logical coordinates (what you typically work with)
logicalWidth := screen.Size.Width
logicalHeight := screen.Size.Height
// Physical coordinates (actual hardware pixels)
physicalWidth := screen.PhysicalBounds.Width
physicalHeight := screen.PhysicalBounds.Height
// Scale factor relationship
scaleFactor := screen.ScaleFactor // e.g., 2.0 for Retina displays
// physicalWidth = logicalWidth * scaleFactor
```
## Coordinate Transformations
### Point Transformations
Convert coordinates between logical and physical systems:
```go
// Convert logical point to physical coordinates
logicalPoint := application.Point{X: 100, Y: 50}
physicalPoint := app.Screen.DipToPhysicalPoint(logicalPoint)
// Convert physical point to logical coordinates
physicalPoint2 := application.Point{X: 200, Y: 100}
logicalPoint2 := app.Screen.PhysicalToDipPoint(physicalPoint2)
app.Logger.Info("Coordinate conversion",
"logical", logicalPoint,
"physical", physicalPoint,
"backToLogical", logicalPoint2,
)
```
### Rectangle Transformations
Convert rectangular areas between coordinate systems:
```go
// Define a logical rectangle
logicalRect := application.Rect{
X: 100, Y: 100,
Width: 400, Height: 300,
}
// Convert to physical coordinates
physicalRect := app.Screen.DipToPhysicalRect(logicalRect)
// Convert back to logical coordinates
logicalRect2 := app.Screen.PhysicalToDipRect(physicalRect)
app.Logger.Info("Rectangle conversion",
"original", logicalRect,
"physical", physicalRect,
"converted", logicalRect2,
)
```
## Screen Detection and Positioning
### Find Screen by Point
Determine which screen contains a specific point:
```go
// Find screen containing a logical point
point := application.Point{X: 500, Y: 300}
screen := app.Screen.ScreenNearestDipPoint(point)
// Find screen containing a physical point
physicalPoint := application.Point{X: 1000, Y: 600}
screenPhys := app.Screen.ScreenNearestPhysicalPoint(physicalPoint)
app.Logger.Info("Screen detection",
"point", point,
"screen", screen.Name,
"physicalPoint", physicalPoint,
"physicalScreen", screenPhys.Name,
)
```
### Find Screen by Rectangle
Determine which screen best contains or overlaps with a rectangle:
```go
// Find screen for a logical rectangle
rect := application.Rect{X: 200, Y: 150, Width: 800, Height: 600}
screen := app.Screen.ScreenNearestDipRect(rect)
// Find screen for a physical rectangle
physicalRect := application.Rect{X: 400, Y: 300, Width: 1600, Height: 1200}
screenPhys := app.Screen.ScreenNearestPhysicalRect(physicalRect)
app.Logger.Info("Screen detection by area",
"rect", rect,
"screen", screen.Name,
"physicalRect", physicalRect,
"physicalScreen", screenPhys.Name,
)
```
## Window Positioning
### Position Window on Specific Screen
Place a window on a particular screen:
```go
func positionWindowOnScreen(window *application.WebviewWindow, targetScreen *application.Screen) {
// Calculate center position on target screen
centerX := targetScreen.WorkArea.X + (targetScreen.WorkArea.Width-800)/2
centerY := targetScreen.WorkArea.Y + (targetScreen.WorkArea.Height-600)/2
// Position the window
window.SetPosition(centerX, centerY)
window.SetSize(800, 600)
}
// Usage
primaryScreen := app.Screen.GetPrimary()
if primaryScreen != nil {
window := app.Window.New()
positionWindowOnScreen(window, primaryScreen)
}
```
### Multi-Monitor Window Management
Manage windows across multiple screens:
```go
func distributeWindowsAcrossScreens(app *application.App) {
screens := app.Screen.GetAll()
if len(screens) == 0 {
return
}
// Create one window per screen
for i, screen := range screens {
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: fmt.Sprintf("window-%d", i),
Title: fmt.Sprintf("Window on %s", screen.Name),
Width: 800,
Height: 600,
})
// Position window in center of screen's work area
x := screen.WorkArea.X + (screen.WorkArea.Width-800)/2
y := screen.WorkArea.Y + (screen.WorkArea.Height-600)/2
window.SetPosition(x, y)
}
}
```
## High-DPI Support
### Handling Different Scale Factors
Properly handle displays with different DPI settings:
```go
func analyzeDisplayScaling(app *application.App) {
screens := app.Screen.GetAll()
for _, screen := range screens {
scaleFactor := screen.ScaleFactor
switch {
case scaleFactor == 1.0:
app.Logger.Info("Standard DPI display", "screen", screen.Name)
case scaleFactor == 1.25:
app.Logger.Info("125% scaled display", "screen", screen.Name)
case scaleFactor == 1.5:
app.Logger.Info("150% scaled display", "screen", screen.Name)
case scaleFactor == 2.0:
app.Logger.Info("High DPI display (200%)", "screen", screen.Name)
default:
app.Logger.Info("Custom scale display",
"screen", screen.Name,
"scale", scaleFactor,
)
}
}
}
```
### DPI-Aware Measurements
Convert measurements appropriately for different displays:
```go
func getDPIAwareSizes(screen *application.Screen, logicalSize int) (int, int) {
// Physical size in actual pixels
physicalSize := int(float32(logicalSize) * screen.ScaleFactor)
return logicalSize, physicalSize
}
// Usage
screen := app.Screen.GetPrimary()
logicalWidth, physicalWidth := getDPIAwareSizes(screen, 400)
app.Logger.Info("Size conversion",
"logical", logicalWidth,
"physical", physicalWidth,
"scaleFactor", screen.ScaleFactor,
)
```
## Platform Considerations
<Tabs>
<TabItem label="macOS" icon="fa-brands:apple">
On macOS:
- Screen coordinates start from bottom-left (different from Windows/Linux)
- Built-in Retina displays typically have 2.0 scale factor
- External displays may have different scale factors
- Mission Control and Spaces affect screen management
- Screen arrangement can be configured in System Preferences
```go
// macOS-specific screen handling
if runtime.GOOS == "darwin" {
// Handle coordinate system differences
screen := app.Screen.GetPrimary()
// Y coordinates are flipped on macOS
}
```
</TabItem>
<TabItem label="Windows" icon="fa-brands:windows">
On Windows:
- Screen coordinates start from top-left
- Scale factors vary: 100%, 125%, 150%, 175%, 200%, etc.
- Multiple monitor configurations are common
- Taskbar affects work area calculations
- Windows display settings control scaling
```go
// Windows-specific screen handling
if runtime.GOOS == "windows" {
screen := app.Screen.GetPrimary()
// Account for taskbar in work area
workArea := screen.WorkArea
}
```
</TabItem>
<TabItem label="Linux" icon="fa-brands:linux">
On Linux:
- Screen coordinates start from top-left
- Scale factors depend on desktop environment
- X11 and Wayland have different behaviors
- Panel/dock configurations affect work areas
- Multi-monitor support varies by environment
```go
// Linux-specific screen handling
if runtime.GOOS == "linux" {
screen := app.Screen.GetPrimary()
// Handle different desktop environments
}
```
</TabItem>
</Tabs>
## Best Practices
1. **Always Check for Screens**: Ensure screens are available before using them:
```go
screens := app.Screen.GetAll()
if len(screens) == 0 {
app.Logger.Warn("No screens detected")
return
}
```
2. **Use Work Areas**: Position windows within work areas to avoid taskbars/docks:
```go
screen := app.Screen.GetPrimary()
workArea := screen.WorkArea
// Position within workArea, not full screen bounds
```
3. **Handle Scale Factors**: Always consider DPI scaling for proper sizing:
```go
screen := app.Screen.GetPrimary()
if screen.ScaleFactor > 1.0 {
// Adjust UI elements for high-DPI displays
}
```
4. **Monitor Screen Changes**: Listen for screen configuration changes:
```go
app.OnApplicationEvent(events.Common.SystemDisplayChanged, func(event *application.ApplicationEvent) {
// Refresh screen information
screens := app.Screen.GetAll()
// Reposition windows if needed
})
```
## Complete Example
Here's a complete example demonstrating screen management:
```go
package main
import (
"fmt"
"runtime"
"github.com/wailsapp/wails/v3/pkg/application"
)
func main() {
app := application.New(application.Options{
Name: "Screen Management Demo",
})
// Analyze connected screens
analyzeScreens(app)
// Create windows strategically positioned
createPositionedWindows(app)
// Setup screen change monitoring
monitorScreenChanges(app)
err := app.Run()
if err != nil {
panic(err)
}
}
func analyzeScreens(app *application.App) {
screens := app.Screen.GetAll()
app.Logger.Info("Screen analysis", "count", len(screens))
primary := app.Screen.GetPrimary()
if primary != nil {
app.Logger.Info("Primary screen",
"name", primary.Name,
"size", fmt.Sprintf("%dx%d", primary.Size.Width, primary.Size.Height),
"scaleFactor", primary.ScaleFactor,
"workArea", primary.WorkArea,
)
}
for i, screen := range screens {
app.Logger.Info("Screen details",
"index", i,
"name", screen.Name,
"primary", screen.IsPrimary,
"bounds", screen.Bounds,
"scaleFactor", screen.ScaleFactor,
)
}
}
func createPositionedWindows(app *application.App) {
screens := app.Screen.GetAll()
for i, screen := range screens {
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: fmt.Sprintf("screen-%d", i),
Title: fmt.Sprintf("Window on %s", screen.Name),
Width: 600,
Height: 400,
})
// Center window in screen's work area
x := screen.WorkArea.X + (screen.WorkArea.Width-600)/2
y := screen.WorkArea.Y + (screen.WorkArea.Height-400)/2
window.SetPosition(x, y)
app.Logger.Info("Created window",
"screen", screen.Name,
"position", fmt.Sprintf("%d,%d", x, y),
)
}
}
func monitorScreenChanges(app *application.App) {
// Monitor for screen configuration changes
app.OnApplicationEvent(events.Common.SystemDisplayChanged, func(event *application.ApplicationEvent) {
app.Logger.Info("Screen configuration changed")
// Re-analyze screens
screens := app.Screen.GetAll()
app.Logger.Info("Updated screen count", "count", len(screens))
// Could reposition windows here if needed
})
}
```
:::tip[Pro Tip]
When developing multi-monitor applications, test on various configurations including different DPI settings and monitor arrangements to ensure proper behavior.
:::
:::danger[Warning]
Screen configurations can change during application runtime (monitors connected/disconnected, resolution changes). Always handle these changes gracefully and avoid storing screen references long-term.
:::

View file

@ -40,9 +40,9 @@ greeting.
## Registering a Service
To register a service with the application, you need to provide an instance of
the service to the `Services` field of the `application.Options` struct (All
services need to be wrapped by an `application.NewService` call. Here's an
example:
the service to the `Services` field of the `application.Options` struct.
All services need to be wrapped by an `application.NewService` call.
Here's an example:
```go
app := application.New(application.Options{
@ -70,6 +70,25 @@ ServiceOptions has the following fields:
- Name - Specify a custom name for the Service
- Route - A route to bind the Service to the frontend (more on this below)
After the application has been created but not yet started,
you can register more services using the `RegisterService` method.
This is useful when you need to feed a service some value
that is only available after the application has been created.
For example, let's wire application's logger into your own service:
```go
app := application.New(application.Options{})
app.RegisterService(application.NewService(NewMyService(app.Logger)))
// ...
err := app.Run()
```
Services may only be registered before running the application:
`RegisterService` will panic if called after the `Run` method.
## Optional Methods
Services can implement optional methods to hook into the application lifecycle.
@ -98,8 +117,12 @@ func (s *Service) ServiceStartup(ctx context.Context, options application.Servic
This method is called when the application is starting up. You can use it to
initialize resources, set up connections, or perform any necessary setup tasks.
The context is the application context, and the `options` parameter provides
additional information about the service.
The context is the application context that will be canceled upon shutdown,
and the `options` parameter provides additional information about the service.
Services are initialised in the exact order of registration:
first those listed in the `Services` field of the `application.Options` struct,
then those added through the `RegisterService` method.
### ServiceShutdown
@ -110,6 +133,9 @@ func (s *Service) ServiceShutdown() error
This method is called when the application is shutting down. Use it to clean up
resources, close connections, or perform any necessary cleanup tasks.
Services are shut down in reverse registration order.
The application context will be canceled before `ServiceShutdown` is called.
### ServeHTTP
```go
@ -133,14 +159,16 @@ application.NewServiceWithOptions(fileserver.New(&fileserver.Config{
Let's look at a simplified version of the `fileserver` service as an example:
```go
type Service struct {
config *Config
fs http.Handler
type Config struct {
RootPath string
}
func New(config *Config) *Service {
type Service struct {
fs http.Handler
}
func NewWithConfig(config *Config) *Service {
return &Service{
config: config,
fs: http.FileServer(http.Dir(config.RootPath)),
}
}
@ -149,11 +177,6 @@ func (s *Service) ServiceName() string {
return "github.com/wailsapp/wails/v3/services/fileserver"
}
func (s *Service) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
// Any initialization code here
return nil
}
func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.fs.ServeHTTP(w, r)
}

View file

@ -15,10 +15,10 @@ To create a basic system tray icon:
```go
app := application.New(options)
systray := app.NewSystemTray()
systray := app.SystemTray.New()
systray.SetLabel("My App")
systray.SetIcon(iconBytes)
systray.Run()
app.Run()
```
## Setting the Icon
@ -85,10 +85,10 @@ You can add a menu to your system tray icon:
```go
menu := application.NewMenu()
menu.Add("Open").OnClick(func() {
menu.Add("Open").OnClick(func(ctx *application.Context) {
// Handle click
})
menu.Add("Quit").OnClick(func() {
menu.Add("Quit").OnClick(func(ctx *application.Context) {
app.Quit()
})
@ -108,7 +108,7 @@ Here's a complete example:
app := application.New()
// Create system tray
systray := app.NewSystemTray()
systray := app.SystemTray.New()
systray.SetLabel("My App")
// Create a window
@ -133,7 +133,7 @@ menu.Add("Quit").OnClick(func() {
})
systray.SetMenu(menu)
systray.Run()
app.Run()
```
## Icon Position <Badge text="macOS" variant="success" />
@ -182,7 +182,7 @@ Explore these examples for more advanced usage:
### Core Methods
| Method | Description |
|------------------------------------------|--------------------------------------------|
| `NewSystemTray()` | Creates a new system tray instance |
| `app.SystemTray.New()` | Creates a new system tray instance |
| `Run()` | Starts the system tray |
| `SetLabel(label string)` | Sets the text label |
| `SetTooltip(tooltip string)` | Sets the tooltip text (Windows) |

View file

@ -0,0 +1,352 @@
---
title: Windows
sidebar:
order: 52
---
import { Tabs, TabItem } from "@astrojs/starlight/components";
Wails provides a comprehensive window management system through the WindowManager API. This allows you to create, manage, and control multiple windows in your application.
## Accessing the Window Manager
The window manager is accessed through the `Windows` property on your application instance:
```go
app := application.New(application.Options{
Name: "Multi-Window App",
})
// Access the window manager
windowManager := app.Window
```
## Creating Windows
### Basic Window Creation
Create a new window with default settings:
```go
window := app.Window.New()
```
### Window Creation with Options
Create a window with custom configuration:
```go
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Custom Window",
Width: 800,
Height: 600,
Mac: application.MacOptions{
TitleBarAppearsTransparent: true,
},
Windows: application.WindowsOptions{
DisableWindowIcon: true,
},
Linux: application.LinuxOptions{
Icon: []byte{/* icon data */},
},
})
```
## Finding Windows
### Get Window by Name
Retrieve a window using its name:
```go
window, exists := app.Window.GetByName("main-window")
if exists {
// Work with the window
window.SetTitle("Found Window")
}
```
### Get Window by ID
Retrieve a window using its unique ID:
```go
window, exists := app.Window.GetByID(123)
if exists {
// Work with the window
}
```
### Get Current Window
Get the currently active/focused window:
```go
// Get current window (may be nil)
currentWindow := app.Window.Current()
if currentWindow != nil {
currentWindow.SetTitle("Active Window")
}
// Get current window with existence check
currentWindow, exists := app.Window.GetCurrent()
if exists {
currentWindow.SetTitle("Active Window")
}
```
### Get All Windows
Retrieve all windows managed by the application:
```go
allWindows := app.Window.GetAll()
for i, window := range allWindows {
window.SetTitle(fmt.Sprintf("Window %d", i+1))
}
```
## Window Lifecycle Management
### Window Creation Callbacks
Register callbacks to be notified when new windows are created:
```go
app.Window.OnCreate(func(window application.Window) {
app.Logger.Info("New window created", "name", window.Name())
// Configure the window
if webviewWindow, ok := window.(*application.WebviewWindow); ok {
webviewWindow.SetMinSize(400, 300)
}
})
```
### Removing Windows
Remove windows from the manager:
```go
// Remove by ID
app.Window.Remove(windowID)
// Remove by name
removed := app.Window.RemoveByName("window-name")
if removed {
app.Logger.Info("Window removed successfully")
}
```
## Window Types
### WebView Windows
The primary window type in Wails v3 is the WebView window, which embeds a web browser:
```go
window := app.Window.New()
// WebView-specific operations
window.SetURL("https://example.com")
window.SetHTML("<h1>Hello World</h1>")
window.Reload()
```
### Window Interface
All windows implement the `Window` interface, providing common functionality:
```go
type Window interface {
ID() uint
Name() string
SetTitle(title string)
SetSize(width, height int)
SetPosition(x, y int)
Show()
Hide()
Close()
// ... other methods
}
```
## Advanced Window Management
### Multi-Window Applications
Create and manage multiple windows for complex applications:
```go
// Create main window
mainWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Main Application",
Width: 1200,
Height: 800,
})
// Create settings window
settingsWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Settings",
Width: 600,
Height: 400,
WindowState: application.WindowStateHidden, // Start hidden
})
// Show settings when needed
settingsWindow.Show()
```
### Window Communication
Windows can communicate through events:
```go
// In window A
app.Event.Emit("data-updated", newData)
// In window B
app.Event.On("data-updated", func(event *application.CustomEvent) {
// Update UI with new data
data := event.Data
// Process data...
})
```
## Platform-Specific Considerations
<Tabs>
<TabItem label="macOS" icon="fa-brands:apple">
On macOS, windows:
- Support native title bar customization
- Can be configured with transparent title bars
- Follow macOS window management conventions
- Support native fullscreen mode
- Integrate with Mission Control and Spaces
</TabItem>
<TabItem label="Windows" icon="fa-brands:windows">
On Windows, windows:
- Support custom window icons
- Can be configured without system title bars
- Follow Windows window management conventions
- Support Windows 11 snap layouts
- Integrate with Windows taskbar features
</TabItem>
<TabItem label="Linux" icon="fa-brands:linux">
On Linux, windows:
- Support custom icons
- Follow desktop environment conventions
- Work with various window managers
- Support X11 and Wayland protocols
- Integrate with desktop environment features
</TabItem>
</Tabs>
## Best Practices
1. **Window Naming**: Always provide meaningful names for windows to make them easy to find:
```go
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "settings-window",
Title: "Application Settings",
})
```
2. **Resource Management**: Properly clean up windows when they're no longer needed:
```go
window.OnEvent(events.Common.WindowClosing, func(e *application.WindowEvent) {
app.Window.Remove(window.ID())
})
```
3. **Window State**: Consider starting secondary windows as hidden and showing them when needed:
```go
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
WindowState: application.WindowStateHidden,
})
```
4. **Error Handling**: Always check if windows exist before using them:
```go
if window, exists := app.Window.GetByName("main"); exists {
window.SetTitle("New Title")
}
```
## Complete Example
Here's a complete example of a multi-window application:
```go
package main
import (
"github.com/wailsapp/wails/v3/pkg/application"
)
func main() {
app := application.New(application.Options{
Name: "Multi-Window Demo",
})
// Create main window
mainWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "main",
Title: "Main Window",
Width: 800,
Height: 600,
})
// Create hidden settings window
settingsWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "settings",
Title: "Settings",
Width: 400,
Height: 300,
WindowState: application.WindowStateHidden,
})
// Setup window creation callback
app.Window.OnCreate(func(window application.Window) {
app.Logger.Info("Window created", "name", window.Name())
})
// Setup menu to show settings
menu := app.Menu.New()
menu.AddRole(application.AppMenu)
settingsMenu := menu.AddSubmenu("Settings")
settingsMenu.Add("Open Settings").OnClick(func(ctx *application.Context) {
if window, exists := app.Window.GetByName("settings"); exists {
window.Show()
window.Focus()
}
})
app.Menu.Set(menu)
err := app.Run()
if err != nil {
panic(err)
}
}
```
:::tip[Pro Tip]
Use the window manager's callback system to implement application-wide window policies, such as automatically setting minimum sizes or configuring window behaviors.
:::
:::danger[Warning]
Always check if windows exist before performing operations on them, as they may have been closed or removed by the user or system.
:::

View file

@ -2,7 +2,9 @@
title: Roadmap
---
## Current Status: Alpha 9
## Current Status: Alpha
Check out the [Changelog](./changelog) for the latest status.
Our goal is to reach Beta status. This roadmap outlines the key features and
improvements we need to implement before transitioning to Beta. Please note that
@ -28,7 +30,3 @@ direction. Your input is valuable in shaping the future of Wails! The roadmap is
a living document and is subject to change. If you have any suggestions, please
open an issue. Each milestone will have a set of goals that we are aiming to
achieve. These are subject to change.
## Alpha 10 Status
Check out the [Changelog](./changelog) for the latest status

View file

@ -40,8 +40,8 @@ This will show you how to organize your code into reusable services and handle e
return &QRService{}
}
// GenerateQRCode creates a QR code from the given text
func (s *QRService) GenerateQRCode(text string, size int) ([]byte, error) {
// Generate creates a QR code from the given text
func (s *QRService) Generate(text string, size int) ([]byte, error) {
// Generate the QR code
qr, err := qrcode.New(text, qrcode.Medium)
if err != nil {
@ -149,21 +149,21 @@ This will show you how to organize your code into reusable services and handle e
// This file is automatically generated. DO NOT EDIT
/**
* QRService handles QR code generation
* @module
*/
* QRService handles QR code generation
* @module
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
/**
* GenerateQRCode creates a QR code from the given text
* @param {string} text
* @param {number} size
* @returns {Promise<string> & { cancel(): void }}
*/
export function GenerateQRCode(text, size) {
* Generate creates a QR code from the given text
* @param {string} text
* @param {number} size
* @returns {Promise<string> & { cancel(): void }}
*/
export function Generate(text, size) {
let $resultPromise = /** @type {any} */($Call.ByID(3576998831, text, size));
let $typingPromise = /** @type {any} */($resultPromise.then(($result) => {
return $Create.ByteSlice($result);
@ -173,7 +173,7 @@ This will show you how to organize your code into reusable services and handle e
}
```
We can see that the bindings are generated for the `GenerateQRCode` method. The parameter names have been preserved,
We can see that the bindings are generated for the `Generate` method. The parameter names have been preserved,
as well as the comments. JSDoc has also been generated for the method to provide type information to your IDE.
:::note
@ -190,14 +190,36 @@ This will show you how to organize your code into reusable services and handle e
The bindings generator also supports generating Typescript bindings. You can do this by running `wails3 generate bindings -ts`.
:::
<br/>
The generated service is re-exported by an `index.js` file:
```js title="bindings/changeme/index.js"
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import * as QRService from "./qrservice.js";
export {
QRService
};
```
You may then access it through the simplified import path
`./bindings/changeme` consisting just of your Go package path,
without specifying any file name.
:::note
Simplified import paths are only available when using frontend bundlers.
If you prefer a vanilla frontend that does not employ a bundler,
you will have to import either `index.js` or `qrservice.js` manually.
:::
<br/>
6. ## Use Bindings in Frontend
Firstly, update `frontend/src/main.js` to use the new bindings:
```js title="frontend/src/main.js"
import { GenerateQRCode } from './bindings/changeme/qrservice.js';
import { QRService } from './bindings/changeme';
async function generateQR() {
const text = document.getElementById('text').value;
@ -208,7 +230,7 @@ This will show you how to organize your code into reusable services and handle e
try {
// Generate QR code as base64
const qrCodeBase64 = await GenerateQRCode(text, 256);
const qrCodeBase64 = await QRService.Generate(text, 256);
// Display the QR code
const qrDiv = document.getElementById('qrcode');
@ -285,7 +307,7 @@ This will show you how to organize your code into reusable services and handle e
Type in some text and click the "Generate QR Code" button. You should see a QR code in the center of the page:
<Image src={qr1} alt="QR Code"/>
<Image src={qr1} alt="QR Code"/>
<br/>
<br/>
@ -303,13 +325,14 @@ This will show you how to organize your code into reusable services and handle e
If your service defines Go's standard http handler function `ServeHTTP(w http.ResponseWriter, r *http.Request)`,
then it can be made accessible on the frontend. Let's extend our QR code service to do this:
```go title="qrservice.go" ins={5-6,36-63}
```go title="qrservice.go" ins={4-5,37-65}
package main
import (
"github.com/skip2/go-qrcode"
"net/http"
"strconv"
"github.com/skip2/go-qrcode"
)
// QRService handles QR code generation
@ -322,8 +345,8 @@ This will show you how to organize your code into reusable services and handle e
return &QRService{}
}
// GenerateQRCode creates a QR code from the given text
func (s *QRService) GenerateQRCode(text string, size int) ([]byte, error) {
// Generate creates a QR code from the given text
func (s *QRService) Generate(text string, size int) ([]byte, error) {
// Generate the QR code
qr, err := qrcode.New(text, qrcode.Medium)
if err != nil {
@ -358,7 +381,7 @@ This will show you how to organize your code into reusable services and handle e
}
// Generate the QR code
qrCodeData, err := s.GenerateQRCode(text, size)
qrCodeData, err := s.Generate(text, size)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@ -408,11 +431,14 @@ This will show you how to organize your code into reusable services and handle e
}
```
:::note
If you do not set the `Route` option explicitly,
the HTTP handler won't be accessible from the frontend.
:::
Finally, update `main.js` to make the image source the path to the QR code service, passing the text as a query parameter:
```js title="frontend/src/main.js"
import { GenerateQRCode } from './bindings/changeme/qrservice.js';
async function generateQR() {
const text = document.getElementById('text').value;
if (!text) {
@ -422,7 +448,7 @@ This will show you how to organize your code into reusable services and handle e
const img = document.getElementById('qrcode');
// Make the image source the path to the QR code service, passing the text
img.src = `/qrservice?text=${text}`
img.src = `/qrservice?text=${encodeURIComponent(text)}`
}
export function initializeQRGenerator() {
@ -440,4 +466,153 @@ This will show you how to organize your code into reusable services and handle e
<Image src={qr1} alt="QR Code"/>
<br/>
8. ## Supporting dynamic configurations
In the example above we used a hardcoded route `/qrservice`.
If you edit `main.go` and change the `Route` option without updating `main.js`,
the application will break:
```go title="main.go" ins={3}
// ...
application.NewService(NewQRService(), application.ServiceOptions{
Route: "/services/qr",
}),
// ...
```
Hardcoded routes can be good for many applications,
but if you need more flexibility, method bindings and HTTP handlers
can work together to improve the development experience.
The `ServiceStartup` Lifecycle method provides access to service options at startup,
and a custom method can be used to announce the configured route to the frontend.
First, implement the `ServiceStartup` interface and add a new `URL` method:
```go title="qrservice.go" ins={4,6,10,15,23-27,46-55}
package main
import (
"context"
"net/http"
"net/url"
"strconv"
"github.com/skip2/go-qrcode"
"github.com/wailsapp/wails/v3/pkg/application"
)
// QRService handles QR code generation
type QRService struct {
route string
}
// NewQRService creates a new QR service
func NewQRService() *QRService {
return &QRService{}
}
// ServiceStartup runs at application startup.
func (s *QRService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
s.route = options.Route
return nil
}
// Generate creates a QR code from the given text
func (s *QRService) Generate(text string, size int) ([]byte, error) {
// Generate the QR code
qr, err := qrcode.New(text, qrcode.Medium)
if err != nil {
return nil, err
}
// Convert to PNG
png, err := qr.PNG(size)
if err != nil {
return nil, err
}
return png, nil
}
// URL returns an URL that may be used to fetch
// a QR code with the given text and size.
// It returns an error if the HTTP handler is not available.
func (s *QRService) URL(text string, size int) (string, error) {
if s.route == "" {
return "", errors.New("http handler unavailable")
}
return fmt.Sprintf("%s?text=%s&size=%d", s.route, url.QueryEscape(text), size), nil
}
func (s *QRService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Extract the text parameter from the request
text := r.URL.Query().Get("text")
if text == "" {
http.Error(w, "Missing 'text' parameter", http.StatusBadRequest)
return
}
// Extract Size parameter from the request
sizeText := r.URL.Query().Get("size")
if sizeText == "" {
sizeText = "256"
}
size, err := strconv.Atoi(sizeText)
if err != nil {
http.Error(w, "Invalid 'size' parameter", http.StatusBadRequest)
return
}
// Generate the QR code
qrCodeData, err := s.Generate(text, size)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Write the QR code data to the response
w.Header().Set("Content-Type", "image/png")
w.Write(qrCodeData)
}
```
Now update `main.js` to use the `URL` method in place of a hardcoded path:
```js title="frontend/src/main.js" ins={1,11-12}
import { QRService } from "./bindings/changeme";
async function generateQR() {
const text = document.getElementById('text').value;
if (!text) {
alert('Please enter some text');
return;
}
const img = document.getElementById('qrcode');
// Invoke the URL method to obtain an URL for the given text.
img.src = await QRService.URL(text, 256);
}
export function initializeQRGenerator() {
const button = document.getElementById('generateButton');
if (button) {
button.addEventListener('click', generateQR);
} else {
console.error('Generate button not found');
}
}
```
It should work just like the previous example,
but changing the service route in `main.go`
will not break the frontend anymore.
:::note
If a Go method returns a non-nil error,
the promise on the JS side will reject
and await statements will throw an exception.
:::
<br/>
</Steps>

View file

@ -1,4 +1,6 @@
@import './mermaid.css';
html {
scrollbar-gutter: stable;
overflow-y: scroll; /* Show vertical scrollbar */
}
}

View file

@ -0,0 +1,108 @@
/* Mermaid diagram styling for Starlight */
/* Container for the whole diagram component */
figure.expandable-diagram {
margin: 2rem 0;
padding: 1rem;
border-radius: 0.5rem;
background-color: var(--sl-color-gray-6);
box-shadow: var(--sl-shadow-sm);
}
/* Dark mode adjustments */
:root[data-theme="dark"] figure.expandable-diagram {
background-color: var(--sl-color-gray-1);
}
/* Title for the diagram */
figure.expandable-diagram figcaption {
font-weight: bold;
font-size: 1.1rem;
margin-bottom: 1rem;
color: var(--sl-color-text);
}
/* Container for the actual diagram */
.diagram-content {
display: flex;
justify-content: center;
margin: 1rem 0;
min-height: 100px;
overflow-x: auto;
}
/* The diagram itself */
.mermaid {
background-color: var(--sl-color-white);
padding: 1rem;
border-radius: 0.375rem;
max-width: 100%;
}
:root[data-theme="dark"] .mermaid {
background-color: var(--sl-color-black);
}
/* Source code details element */
figure.expandable-diagram details {
margin-top: 1rem;
border-top: 1px solid var(--sl-color-gray-5);
padding-top: 0.5rem;
}
:root[data-theme="dark"] figure.expandable-diagram details {
border-top-color: var(--sl-color-gray-3);
}
/* Source button */
figure.expandable-diagram summary {
cursor: pointer;
color: var(--sl-color-text-accent);
font-weight: 500;
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
}
figure.expandable-diagram summary:hover {
background-color: var(--sl-color-gray-5);
}
:root[data-theme="dark"] figure.expandable-diagram summary:hover {
background-color: var(--sl-color-gray-2);
}
/* Source code */
figure.expandable-diagram details pre {
margin-top: 0.5rem;
padding: 0.75rem;
border-radius: 0.375rem;
background-color: var(--sl-color-gray-7);
overflow-x: auto;
}
:root[data-theme="dark"] figure.expandable-diagram details pre {
background-color: var(--sl-color-gray-0);
}
/* Mermaid diagram specific adjustments */
.mermaid .label {
font-family: var(--sl-font);
font-size: 0.9rem;
}
/* Fix for diagram text in dark mode */
:root[data-theme="dark"] .mermaid text {
fill: var(--sl-color-white);
}
/* Ensure diagrams are responsive */
@media (max-width: 768px) {
.diagram-content {
overflow-x: auto;
}
.mermaid {
min-width: 100%;
}
}

View file

@ -0,0 +1,89 @@
# Changelog
<!--
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- `Added` for new features.
- `Changed` for changes in existing functionality.
- `Deprecated` for soon-to-be removed features.
- `Removed` for now removed features.
- `Fixed` for any bug fixes.
- `Security` in case of vulnerabilities.
-->
## [Unreleased]
### Added
- [darwin] add Event ApplicationShouldHandleReopen to be able to handle dock icon click by @5aaee9 in [#2991](https://github.com/wailsapp/wails/pull/2991)
- [darwin] add getPrimaryScreen/getScreens to impl by @tmclane in [#2618](https://github.com/wailsapp/wails/pull/2618)
- [darwin] add option for showing the toolbar in fullscreen mode on macOS by [@fbbdev](https://github.com/fbbdev) in [#3282](https://github.com/wailsapp/wails/pull/3282)
- [linux] add onKeyPress logic to convert linux keypress into an accelerator @[Atterpac](https://github.com/Atterpac) in[#3022](https://github.com/wailsapp/wails/pull/3022])
- [linux] add task `run:linux` by [@marcus-crane](https://github.com/marcus-crane) in [#3146](https://github.com/wailsapp/wails/pull/3146)
- Export `SetIcon` method by [@almas-x](https://github.com/almas-x) in [PR](https://github.com/wailsapp/wails/pull/3147)
- Improve `OnShutdown` by [@almas-x](https://github.com/almas-x) in [PR](https://github.com/wailsapp/wails/pull/3189)
- Restore `ToggleMaximise` method in `Window` interface by [@fbbdev](https://github.com/fbbdev) in [#3281](https://github.com/wailsapp/wails/pull/3281)
- Added more information to `Environment()`. By @leaanthony in [aba82cc](https://github.com/wailsapp/wails/commit/aba82cc52787c97fb99afa58b8b63a0004b7ff6c) based on [PR](https://github.com/wailsapp/wails/pull/2044) by @Mai-Lapyst
- Expose the `WebviewWindow.IsFocused` method on the `Window` interface by [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
- Support multiple space-separated trigger events in the WML system by [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
- Add ESM exports from the bundled JS runtime script by [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
- Add binding generator flag for using the bundled JS runtime script instead of the npm package by [@fbbdev](https://github.com/fbbdev) in [#3334](https://github.com/wailsapp/wails/pull/3334)
- Implement `setIcon` on linux by [@abichinger](https://github.com/abichinger) in [#3354](https://github.com/wailsapp/wails/pull/3354)
- Add flag `-port` to dev command and support environment variable `WAILS_VITE_PORT` by [@abichinger](https://github.com/abichinger) in [#3429](https://github.com/wailsapp/wails/pull/3429)
- Add tests for bound method calls by [@abichinger](https://github.com/abichinger) in [#3431](https://github.com/wailsapp/wails/pull/3431)
### Fixed
- [linux] Fixed theme handling error on NixOS by [tmclane](https://github.com/tmclane) in [#3515)(https://github.com/wailsapp/wails/pull/3515)
- Fixed cross volume project install for windows by [atterpac](https://github.com/atterac) in [#3512](https://github.com/wailsapp/wails/pull/3512)
- Fixed react template css to show footer by [atterpac](https://github.com/atterpac) in [#3477](https://github.com/wailsapp/wails/pull/3477)
- Fixed zombie processes when working in devmode by updating to latest refresh by [Atterpac](https://github.com/atterpac) in [#3320](https://github.com/wailsapp/wails/pull/3320).
- Fixed appimage webkit file sourcing by [Atterpac](https://github.com/atterpac) in [#3306](https://github.com/wailsapp/wails/pull/3306).
- Fixed Doctor apt package verify by [Atterpac](https://github.com/Atterpac) in [#2972](https://github.com/wailsapp/wails/pull/2972).
- Fixed application frozen when quit (Darwin) by @5aaee9 in [#2982](https://github.com/wailsapp/wails/pull/2982)
- Fixed background colours of examples on Windows by [mmgvh](https://github.com/mmghv) in [#2750](https://github.com/wailsapp/wails/pull/2750).
- Fixed default context menus by [mmgvh](https://github.com/mmghv) in [#2753](https://github.com/wailsapp/wails/pull/2753).
- Fixed hex values for arrow keys on Darwin by [jaybeecave](https://github.com/jaybeecave) in [#3052](https://github.com/wailsapp/wails/pull/3052).
- Set drag-n-drop for windows to working. Added by [@pylotlight](https://github.com/pylotlight) in [PR](https://github.com/wailsapp/wails/pull/3039)
- Fixed bug for linux in doctor in the event user doesn't have proper drivers installed. Added by [@pylotlight](https://github.com/pylotlight) in [PR](https://github.com/wailsapp/wails/pull/3032)
- Fix dpi scaling on start up (windows). Changed by [@almas-x](https://github.com/almas-x) in [PR](https://github.com/wailsapp/wails/pull/3145)
- Fix replace line in `go.mod` to use relative paths. Fixes Windows paths with spaces - @leaanthony.
- Fix MacOS systray click handling when no attached window by [thomas-senechal](https://github.com/thomas-senechal) in PR [#3207](https://github.com/wailsapp/wails/pull/3207)
- Fix failing Windows build due to unknown option by [thomas-senechal](https://github.com/thomas-senechal) in PR [#3208](https://github.com/wailsapp/wails/pull/3208)
- Fix crash on windows left clicking the systray icon when not having an attached window [tw1nk](https://github.com/tw1nk) in PR [#3271](https://github.com/wailsapp/wails/pull/3271)
- Fix wrong baseURL when open window twice by @5aaee9 in PR [#3273](https://github.com/wailsapp/wails/pull/3273)
- Fix ordering of if branches in `WebviewWindow.Restore` method by [@fbbdev](https://github.com/fbbdev) in [#3279](https://github.com/wailsapp/wails/pull/3279)
- Correctly compute `startURL` across multiple `GetStartURL` invocations when `FRONTEND_DEVSERVER_URL` is present. [#3299](https://github.com/wailsapp/wails/pull/3299)
- Fix the JS type of the `Screen` struct to match its Go counterpart by [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
- Fix the `WML.Reload` method to ensure proper cleanup of registered event listeners by [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
- Fix custom context menu closing immediately on linux by [@abichinger](https://github.com/abichinger) in [#3330](https://github.com/wailsapp/wails/pull/3330)
- Fix the output path and extension of model files produced by the binding generator by [@fbbdev](https://github.com/fbbdev) in [#3334](https://github.com/wailsapp/wails/pull/3334)
- Fix the import paths of model files in JS code produced by the binding generator by [@fbbdev](https://github.com/fbbdev) in [#3334](https://github.com/wailsapp/wails/pull/3334)
- Fix drag-n-drop on some linux distros by [@abichinger](https://github.com/abichinger) in [#3346](https://github.com/wailsapp/wails/pull/3346)
- Fix missing task for macOS when using `wails3 task dev` by [@hfoxy](https://github.com/hfoxy) in [#3417](https://github.com/wailsapp/wails/pull/3417)
- Fix registering events causing a nil map assignment by [@hfoxy](https://github.com/hfoxy) in [#3426](https://github.com/wailsapp/wails/pull/3426)
- Fix unmarshaling of bound method parameters by [@fbbdev](https://github.com/fbbdev) in [#3431](https://github.com/wailsapp/wails/pull/3431)
- Fix handling of multiple return values from bound methods by [@fbbdev](https://github.com/fbbdev) in [#3431](https://github.com/wailsapp/wails/pull/3431)
- Fix doctor detection of npm that is not installed with system package manager by [@pekim](https://github.com/pekim) in [#3458](https://github.com/wailsapp/wails/pull/3458)
### Changed
- Update linux webkit dependency to webkit2gtk-4.1 over webkitgtk2-4.0 to support Ubuntu 24.04 LTS by [atterpac](https://github.com/atterpac) in [#3461](https://github.com/wailsapp/wails/pull/3461)
- The bundled JS runtime script is now an ESM module: script tags importing it must have the `type="module"` attribute. By [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
- The `@wailsio/runtime` package does not publish its API on the `window.wails` object, and does not start the WML system. This has been done to improve encapsulation. The WML system can be started manually if desired by calling the new `WML.Enable` method. The bundled JS runtime script still performs both operations automatically. By [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
- The Window API module `@wailsio/runtime/src/window` now exposes the containing window object as a default export. It is not possible anymore to import individual methods through ESM named or namespace import syntax.
- The JS window API has been updated to match the current Go `WebviewWindow` API. Some methods have changed name or prototype, specifically: `Screen` becomes `GetScreen`; `GetZoomLevel`/`SetZoomLevel` become `GetZoom`/`SetZoom`; `GetZoom`, `Width` and `Height` now return values directly instead of wrapping them within objects. By [@fbbdev](https://github.com/fbbdev) in [#3295](https://github.com/wailsapp/wails/pull/3295)
- The binding generator now uses calls by ID by default. The `-id` CLI option has been removed. Use the `-names` CLI option to switch back to calls by name. By [@fbbdev](https://github.com/fbbdev) in [#3468](https://github.com/wailsapp/wails/pull/3468)
- New binding code layout: output files were previously organised in folders named after their containing package; now full Go import paths are used, including the module path. By [@fbbdev](https://github.com/fbbdev) in [#3468](https://github.com/wailsapp/wails/pull/3468)
- The struct field `application.Options.Bind` has been renamed to `application.Options.Services`. By [@fbbdev](https://github.com/fbbdev) in [#3468](https://github.com/wailsapp/wails/pull/3468)
- New syntax for binding services: service instances must now be wrapped in a call to `application.NewService`. By [@fbbdev](https://github.com/fbbdev) in [#3468](https://github.com/wailsapp/wails/pull/3468)
- Modified the `contentTypeSniffer` struct to include the `http.CloseNotifier` interface. Now compatible with Gin framework. By [@AnalogJ](https://github.com/AnalogJ) in [#3537](https://github.com/wailsapp/wails/pull/3537)
### Removed
### Deprecated
### Security

View file

@ -0,0 +1,123 @@
# Wails Issue Management Automation
This directory contains automation workflows and scripts to help manage the Wails project with minimal time investment.
## GitHub Workflow Files
### 1. Auto-Label Issues (`auto-label-issues.yml`)
- Automatically labels issues and PRs based on their content and modified files
- Labels are defined in `issue-labeler.yml` and `file-labeler.yml`
- Activates when issues are opened, edited, or reopened
### 2. Issue Triage Automation (`issue-triage-automation.yml`)
- Performs automated actions for issue triage
- Requests more info for incomplete bug reports
- Prioritizes security issues
- Adds issues to appropriate project boards
## Configuration Files
### 1. Issue Content Labeler (`issue-labeler.yml`)
- Defines patterns to match in issue title/body
- Categorizes by version (v2/v3), component, type, and priority
- Customize patterns as needed for your project
### 2. File Path Labeler (`file-labeler.yml`)
- Labels PRs based on which files they modify
- Helps identify which areas of the codebase are affected
- Customize file patterns as needed
### 3. Stale Issues Config (`stale.yml`)
- Marks issues as stale after 45 days of inactivity
- Closes stale issues after an additional 10 days
- Exempts issues with important labels
## Helper Scripts
### 1. Issue Triage Script (`scripts/issue-triage.ps1`)
- PowerShell script to quickly triage issues
- Lists recent issues needing attention
- Provides easy keyboard shortcuts for common actions
- Run during your dedicated issue triage time
### 2. PR Review Helper (`scripts/pr-review-helper.ps1`)
- PowerShell script to efficiently review PRs
- Generates review checklists
- Provides easy shortcuts for common review actions
- Run during your dedicated PR review time
## How to Use This System
### Daily Workflow (2 hours max)
**Monday (120 min):**
1. Run `scripts/issue-triage.ps1` (30 min)
2. Run `scripts/pr-review-helper.ps1` (30 min)
3. Check Discord for critical discussions (30 min)
4. Plan your week (30 min)
**Tuesday-Wednesday (120 min/day):**
1. Quick check for urgent issues (10 min)
2. v3 development (110 min)
**Thursday (120 min):**
1. v2 maintenance (90 min)
2. Documentation updates (30 min)
**Friday (120 min):**
1. Run `scripts/pr-review-helper.ps1` (60 min)
2. Discord updates/newsletter (30 min)
3. Weekly reflection (30 min)
## Installation
1. The GitHub workflow files should be placed in `.github/workflows/`
2. Configuration files should be placed in `.github/`
3. Helper scripts should be placed in `scripts/`
4. Make sure you have GitHub CLI (`gh`) installed and authenticated
## Customization
Feel free to modify the configuration files and scripts to better suit your project's needs:
1. **Adding New Label Categories**:
- Add new patterns to `issue-labeler.yml` for additional components or types
- Update `file-labeler.yml` if you add new directories or file types
2. **Adjusting Automation Thresholds**:
- Modify `stale.yml` to change how long issues remain active
- Update `issue-triage-automation.yml` to change conditions for automated actions
3. **Customizing Scripts**:
- Update the scripts with your specific GitHub username
- Add additional actions based on your workflow preferences
- Adjust time allocations based on which tasks need more attention
## Benefits
This automated issue management system will:
1. **Save Time**: Reduce manual triage of most common issues
2. **Improve Consistency**: Apply the same categorization rules every time
3. **Increase Visibility**: Clear categorization helps community members find issues
4. **Focus Development**: Clearer separation of v2 and v3 work
5. **Reduce Backlog**: Better management of stale issues
6. **Streamline Reviews**: Faster PR processing with guided workflows
## Requirements
- GitHub CLI (`gh`) installed and authenticated
- PowerShell 5.1+ for Windows scripts
- GitHub Actions enabled on your repository
- Appropriate permissions to modify workflows
## Maintenance
This system requires minimal maintenance:
- Periodically review and update label patterns as your project evolves
- Adjust time allocations based on where you need to focus
- Update scripts if GitHub CLI commands change
- Customize the workflow as you find pain points in your process
Remember that the goal is to maximize your limited time (2 hours per day) by automating repetitive tasks and streamlining essential ones.

108
scripts/issue-triage.ps1 Normal file
View file

@ -0,0 +1,108 @@
# issue-triage.ps1 - Script to help with quick issue triage
# Run this at the start of your GitHub time to quickly process issues
# Set your GitHub username
$GITHUB_USERNAME = "your-username"
# Get the latest 10 open issues that aren't assigned and aren't labeled as "awaiting feedback"
Write-Host "Fetching recent unprocessed issues..."
gh issue list --repo wailsapp/wails --limit 10 --json number,title,labels,assignees | Out-File -Encoding utf8 -FilePath "issues_temp.json"
$issues = Get-Content -Raw -Path "issues_temp.json" | ConvertFrom-Json
$newIssues = $issues | Where-Object {
$_.assignees.Count -eq 0 -and
($_.labels.Count -eq 0 -or -not ($_.labels | Where-Object { $_.name -eq "awaiting feedback" }))
}
# Process each issue
Write-Host "`n===== Issues Needing Triage =====`n"
foreach ($issue in $newIssues) {
$number = $issue.number
$title = $issue.title
$labelNames = $issue.labels | ForEach-Object { $_.name }
$labelsStr = if ($labelNames) { $labelNames -join ", " } else { "none" }
Write-Host "Issue #$number`: $title"
Write-Host "Labels: $labelsStr`n"
$continue = $true
while ($continue) {
Write-Host "Options:"
Write-Host " [v] View issue in browser"
Write-Host " [2] Add v2-only label"
Write-Host " [3] Add v3-alpha label"
Write-Host " [b] Add bug label"
Write-Host " [e] Add enhancement label"
Write-Host " [d] Add documentation label"
Write-Host " [w] Add webview2 label"
Write-Host " [f] Request more info (awaiting feedback)"
Write-Host " [c] Close issue (duplicate/invalid)"
Write-Host " [a] Assign to yourself"
Write-Host " [s] Skip to next issue"
Write-Host " [q] Quit script"
$action = Read-Host "Enter action"
switch ($action) {
"v" {
gh issue view $number --repo wailsapp/wails --web
}
"2" {
Write-Host "Adding v2-only label..."
gh issue edit $number --repo wailsapp/wails --add-label "v2-only"
}
"3" {
Write-Host "Adding v3-alpha label..."
gh issue edit $number --repo wailsapp/wails --add-label "v3-alpha"
}
"b" {
Write-Host "Adding bug label..."
gh issue edit $number --repo wailsapp/wails --add-label "Bug"
}
"e" {
Write-Host "Adding enhancement label..."
gh issue edit $number --repo wailsapp/wails --add-label "Enhancement"
}
"d" {
Write-Host "Adding documentation label..."
gh issue edit $number --repo wailsapp/wails --add-label "Documentation"
}
"w" {
Write-Host "Adding webview2 label..."
gh issue edit $number --repo wailsapp/wails --add-label "webview2"
}
"f" {
Write-Host "Requesting more info..."
gh issue comment $number --repo wailsapp/wails --body "Thank you for reporting this issue. Could you please provide additional information to help us investigate?`n`n- [Specific details needed]`n`nThis will help us address your issue more effectively."
gh issue edit $number --repo wailsapp/wails --add-label "awaiting feedback"
}
"c" {
$reason = Read-Host "Reason for closing (duplicate/invalid/etc)"
gh issue comment $number --repo wailsapp/wails --body "Closing this issue: $reason"
gh issue close $number --repo wailsapp/wails
}
"a" {
Write-Host "Assigning to yourself..."
gh issue edit $number --repo wailsapp/wails --add-assignee "$GITHUB_USERNAME"
}
"s" {
Write-Host "Skipping to next issue..."
$continue = $false
}
"q" {
Write-Host "Exiting script."
exit
}
default {
Write-Host "Invalid option. Please try again."
}
}
Write-Host ""
}
Write-Host "--------------------------------`n"
}
Write-Host "No more issues to triage!"
# Clean up temp file
Remove-Item -Path "issues_temp.json"

103
scripts/issue-triage.sh Normal file
View file

@ -0,0 +1,103 @@
#!/bin/bash
# issue-triage.sh - Script to help with quick issue triage
# Run this at the start of your GitHub time to quickly process issues
# Set your GitHub username
GITHUB_USERNAME="your-username"
# Get the latest 10 open issues that aren't assigned and aren't labeled as "awaiting feedback"
echo "Fetching recent unprocessed issues..."
gh issue list --repo wailsapp/wails --limit 10 --json number,title,labels,assignees --jq '.[] | select(.assignees | length == 0) | select(any(.labels[]; .name != "awaiting feedback"))' > new_issues.json
# Process each issue
echo -e "\n===== Issues Needing Triage =====\n"
cat new_issues.json | jq -c '.[]' | while read -r issue; do
number=$(echo $issue | jq -r '.number')
title=$(echo $issue | jq -r '.title')
labels=$(echo $issue | jq -r '.labels[] | .name' 2>/dev/null | tr '\n' ', ' | sed 's/,$//')
if [ -z "$labels" ]; then
labels="none"
fi
echo -e "Issue #$number: $title"
echo -e "Labels: $labels\n"
while true; do
echo "Options:"
echo " [v] View issue in browser"
echo " [2] Add v2-only label"
echo " [3] Add v3-alpha label"
echo " [b] Add bug label"
echo " [e] Add enhancement label"
echo " [d] Add documentation label"
echo " [w] Add webview2 label"
echo " [f] Request more info (awaiting feedback)"
echo " [c] Close issue (duplicate/invalid)"
echo " [a] Assign to yourself"
echo " [s] Skip to next issue"
echo " [q] Quit script"
read -p "Enter action: " action
case $action in
v)
gh issue view $number --repo wailsapp/wails --web
;;
2)
echo "Adding v2-only label..."
gh issue edit $number --repo wailsapp/wails --add-label "v2-only"
;;
3)
echo "Adding v3-alpha label..."
gh issue edit $number --repo wailsapp/wails --add-label "v3-alpha"
;;
b)
echo "Adding bug label..."
gh issue edit $number --repo wailsapp/wails --add-label "Bug"
;;
e)
echo "Adding enhancement label..."
gh issue edit $number --repo wailsapp/wails --add-label "Enhancement"
;;
d)
echo "Adding documentation label..."
gh issue edit $number --repo wailsapp/wails --add-label "Documentation"
;;
w)
echo "Adding webview2 label..."
gh issue edit $number --repo wailsapp/wails --add-label "webview2"
;;
f)
echo "Requesting more info..."
gh issue comment $number --repo wailsapp/wails --body "Thank you for reporting this issue. Could you please provide additional information to help us investigate?\n\n- [Specific details needed]\n\nThis will help us address your issue more effectively."
gh issue edit $number --repo wailsapp/wails --add-label "awaiting feedback"
;;
c)
read -p "Reason for closing (duplicate/invalid/etc): " reason
gh issue comment $number --repo wailsapp/wails --body "Closing this issue: $reason"
gh issue close $number --repo wailsapp/wails
;;
a)
echo "Assigning to yourself..."
gh issue edit $number --repo wailsapp/wails --add-assignee "$GITHUB_USERNAME"
;;
s)
echo "Skipping to next issue..."
break
;;
q)
echo "Exiting script."
exit 0
;;
*)
echo "Invalid option. Please try again."
;;
esac
echo ""
done
echo -e "--------------------------------\n"
done
echo "No more issues to triage!"

View file

@ -0,0 +1,152 @@
# pr-review-helper.ps1 - Script to help with efficient PR reviews
# Run this during your PR review time
# Set your GitHub username
$GITHUB_USERNAME = "your-username"
# Get open PRs that are ready for review
Write-Host "Fetching PRs ready for review..."
gh pr list --repo wailsapp/wails --json number,title,author,labels,reviewDecision,additions,deletions,baseRefName,headRefName --limit 10 | Out-File -Encoding utf8 -FilePath "prs_temp.json"
$prs = Get-Content -Raw -Path "prs_temp.json" | ConvertFrom-Json
# Process each PR
Write-Host "`n===== PRs Needing Review =====`n"
foreach ($pr in $prs) {
$number = $pr.number
$title = $pr.title
$author = $pr.author.login
$labels = if ($pr.labels) { $pr.labels | ForEach-Object { $_.name } | Join-String -Separator ", " } else { "none" }
$reviewState = if ($pr.reviewDecision) { $pr.reviewDecision } else { "PENDING" }
$baseRef = $pr.baseRefName
$headRef = $pr.headRefName
$changes = $pr.additions + $pr.deletions
Write-Host "PR #$number`: $title"
Write-Host "Author: $author"
Write-Host "Labels: $labels"
Write-Host "Branch: $headRef -> $baseRef"
Write-Host "Changes: +$($pr.additions)/-$($pr.deletions) lines"
Write-Host "Review state: $reviewState`n"
# Determine complexity based on size
$complexity = if ($changes -lt 50) {
"Quick review"
} elseif ($changes -lt 300) {
"Moderate review"
} else {
"Extensive review"
}
Write-Host "Complexity: $complexity"
$continue = $true
while ($continue) {
Write-Host "`nOptions:"
Write-Host " [v] View PR in browser"
Write-Host " [d] View diff in browser"
Write-Host " [c] Generate review checklist"
Write-Host " [a] Approve PR"
Write-Host " [r] Request changes"
Write-Host " [m] Add comment"
Write-Host " [l] Add labels"
Write-Host " [s] Skip to next PR"
Write-Host " [q] Quit script"
$action = Read-Host "Enter action"
switch ($action) {
"v" {
gh pr view $number --repo wailsapp/wails --web
}
"d" {
gh pr diff $number --repo wailsapp/wails --web
}
"c" {
# Generate review checklist
$checklist = @"
## PR Review: $title
### Basic Checks:
- [ ] PR title is descriptive
- [ ] PR description explains the changes
- [ ] Related issues are linked
### Technical Checks:
- [ ] Code follows project style
- [ ] No unnecessary commented code
- [ ] Error handling is appropriate
- [ ] Documentation updated (if needed)
- [ ] Tests included (if needed)
### Impact Assessment:
- [ ] Changes are backward compatible (if applicable)
- [ ] No breaking changes to public APIs
- [ ] Performance impact considered
### Version Specific:
"@
if ($baseRef -eq "master") {
$checklist += @"
- [ ] Appropriate for v2 maintenance
- [ ] No features that should be v3-only
"@
} elseif ($baseRef -eq "v3-alpha") {
$checklist += @"
- [ ] Appropriate for v3 development
- [ ] Aligns with v3 roadmap
"@
}
# Write to clipboard
$checklist | Set-Clipboard
Write-Host "`nReview checklist copied to clipboard!`n"
}
"a" {
$comment = Read-Host "Approval comment (blank for none)"
if ($comment) {
gh pr review $number --repo wailsapp/wails --approve --body $comment
} else {
gh pr review $number --repo wailsapp/wails --approve
}
}
"r" {
$comment = Read-Host "Feedback for changes requested"
gh pr review $number --repo wailsapp/wails --request-changes --body $comment
}
"m" {
$comment = Read-Host "Comment text"
gh pr comment $number --repo wailsapp/wails --body $comment
}
"l" {
$labels = Read-Host "Labels to add (comma-separated)"
$labelArray = $labels -split ","
foreach ($label in $labelArray) {
$labelTrimmed = $label.Trim()
if ($labelTrimmed) {
gh pr edit $number --repo wailsapp/wails --add-label $labelTrimmed
}
}
}
"s" {
Write-Host "Skipping to next PR..."
$continue = $false
}
"q" {
Write-Host "Exiting script."
exit
}
default {
Write-Host "Invalid option. Please try again."
}
}
}
Write-Host "--------------------------------`n"
}
Write-Host "No more PRs to review!"
# Clean up temp file
Remove-Item -Path "prs_temp.json"

70
test-changelog-extraction.sh Executable file
View file

@ -0,0 +1,70 @@
#!/bin/bash
# Test script for changelog extraction logic
set -e
echo "🧪 Testing Changelog Extraction Logic"
echo "======================================"
# Test v2 changelog extraction
echo "📋 Testing v2 changelog extraction..."
CHANGELOG_FILE="website/src/pages/changelog.mdx"
if [ ! -f "$CHANGELOG_FILE" ]; then
echo "❌ v2 changelog file not found: $CHANGELOG_FILE"
exit 1
fi
echo "✅ v2 changelog file found"
# Extract unreleased section
awk '
/^## \[Unreleased\]/ { found=1; next }
found && /^## / { exit }
found && !/^$/ { print }
' $CHANGELOG_FILE > test_v2_notes.md
echo "📝 v2 extracted content:"
echo "------------------------"
if [ -s test_v2_notes.md ]; then
head -10 test_v2_notes.md
echo "..."
echo "✅ v2 changelog extraction successful ($(wc -l < test_v2_notes.md) lines)"
else
echo "⚠️ v2 unreleased section is empty"
fi
echo ""
# Test v3 changelog extraction (when on v3-alpha branch)
echo "📋 Testing v3 changelog extraction..."
# Check if we can access v3 changelog
if git show v3-alpha:docs/src/content/docs/changelog.mdx > /dev/null 2>&1; then
echo "✅ v3 changelog accessible from v3-alpha branch"
# Extract from v3-alpha branch
git show v3-alpha:docs/src/content/docs/changelog.mdx | awk '
/^## \[Unreleased\]/ { found=1; next }
found && /^## / { exit }
found && !/^$/ { print }
' > test_v3_notes.md
echo "📝 v3 extracted content:"
echo "------------------------"
if [ -s test_v3_notes.md ]; then
head -10 test_v3_notes.md
echo "..."
echo "✅ v3 changelog extraction successful ($(wc -l < test_v3_notes.md) lines)"
else
echo "⚠️ v3 unreleased section is empty"
fi
else
echo "⚠️ v3 changelog not accessible (expected if not on v3-alpha branch)"
fi
echo ""
echo "🧹 Cleaning up test files..."
rm -f test_v2_notes.md test_v3_notes.md
echo "✅ Changelog extraction test completed!"

85
test-version-logic.sh Executable file
View file

@ -0,0 +1,85 @@
#!/bin/bash
# Test script for version increment logic
set -e
echo "🧪 Testing Version Increment Logic"
echo "=================================="
# Test v2 version increment
echo "📈 Testing v2 version increment..."
# Get current v2 version
CURRENT_V2=$(cat v2/cmd/wails/internal/version.txt | sed 's/^v//')
echo "Current v2 version: v$CURRENT_V2"
# Parse version parts
IFS='.' read -ra VERSION_PARTS <<< "$CURRENT_V2"
MAJOR=${VERSION_PARTS[0]}
MINOR=${VERSION_PARTS[1]}
PATCH=${VERSION_PARTS[2]}
echo "Parsed: MAJOR=$MAJOR, MINOR=$MINOR, PATCH=$PATCH"
# Test patch increment
PATCH_VERSION="$MAJOR.$MINOR.$((PATCH + 1))"
echo "✅ Patch increment: v$CURRENT_V2 → v$PATCH_VERSION"
# Test minor increment
MINOR_VERSION="$MAJOR.$((MINOR + 1)).0"
echo "✅ Minor increment: v$CURRENT_V2 → v$MINOR_VERSION"
# Test major increment
MAJOR_VERSION="$((MAJOR + 1)).0.0"
echo "✅ Major increment: v$CURRENT_V2 → v$MAJOR_VERSION"
echo ""
# Test v3 version increment (simulate)
echo "📈 Testing v3 version increment..."
# Simulate current v3 version
CURRENT_V3="v3.0.0-alpha.9"
echo "Simulated current v3 version: $CURRENT_V3"
if [[ $CURRENT_V3 =~ v3\.0\.0-alpha\.([0-9]+) ]]; then
ALPHA_NUM=${BASH_REMATCH[1]}
NEW_ALPHA_NUM=$((ALPHA_NUM + 1))
NEW_V3_VERSION="v3.0.0-alpha.$NEW_ALPHA_NUM"
echo "✅ Alpha increment: $CURRENT_V3$NEW_V3_VERSION"
else
echo "❌ Failed to parse v3 version format"
exit 1
fi
echo ""
# Test conventional commit detection
echo "🔍 Testing Conventional Commit Detection..."
# Simulate commit messages
COMMITS="
feat: add new dialog API
fix: resolve memory leak
chore: update dependencies
feat!: remove deprecated API
docs: update README
BREAKING CHANGE: remove v1 compatibility
"
echo "Test commits:"
echo "$COMMITS"
# Test release type detection
if echo "$COMMITS" | grep -q "feat!\|fix!\|BREAKING CHANGE:"; then
RELEASE_TYPE="major"
elif echo "$COMMITS" | grep -q "feat\|BREAKING CHANGE"; then
RELEASE_TYPE="minor"
else
RELEASE_TYPE="patch"
fi
echo "✅ Detected release type: $RELEASE_TYPE"
echo ""
echo "✅ Version logic test completed!"

61
test-workflow.md Normal file
View file

@ -0,0 +1,61 @@
# Testing the Nightly Release Workflow
## Method 1: Fork Testing (Recommended)
1. **Create a fork** of the Wails repository
2. **Push the workflow** to your fork
3. **Test manually** using `workflow_dispatch`
4. **Verify behavior** without affecting main repo
```bash
# In your fork
git remote add upstream https://github.com/wailsapp/wails.git
git push origin master # Push workflow to your fork
```
## Method 2: Local Script Testing
Create local test scripts to validate the logic:
```bash
# Test changelog parsing
./test-changelog-extraction.sh
# Test version increment logic
./test-version-logic.sh
# Test commit analysis
./test-commit-detection.sh
```
## Method 3: Dry Run Workflow
Add a `dry_run` input parameter to test without creating releases:
```yaml
workflow_dispatch:
inputs:
dry_run:
description: 'Run in dry-run mode (no releases created)'
default: true
type: boolean
```
## Method 4: Act (GitHub Actions Local Runner)
Use `act` to run GitHub Actions locally:
```bash
brew install act
act workflow_dispatch -W .github/workflows/nightly-releases.yml
```
## Testing Checklist
- [ ] Changelog parsing works correctly
- [ ] Version increment logic is accurate
- [ ] Conventional commit detection works
- [ ] Release notes format properly
- [ ] Authorization checks function
- [ ] Branch handling (master vs v3-alpha)
- [ ] Error handling and fallbacks

View file

@ -29,11 +29,11 @@ require (
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/wailsapp/go-webview2 v1.0.10 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
)
replace github.com/wailsapp/wails/v2 v2.1.0 => ../..

View file

@ -63,11 +63,13 @@ github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhw
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -80,10 +82,12 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -10,25 +10,25 @@ require (
github.com/google/uuid v1.3.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/labstack/echo/v4 v4.9.1 // indirect
github.com/labstack/echo/v4 v4.10.2 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/leaanthony/go-ansi-parser v1.6.0 // indirect
github.com/leaanthony/gosod v1.0.3 // indirect
github.com/leaanthony/slicer v1.6.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.2 // indirect
github.com/samber/lo v1.27.1 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/samber/lo v1.38.1 // indirect
github.com/tkrajina/go-reflector v0.5.6 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect
)

View file

@ -13,6 +13,7 @@ github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4P
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y=
github.com/labstack/echo/v4 v4.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo=
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
@ -32,6 +33,7 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@ -41,8 +43,10 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/samber/lo v1.27.1 h1:sTXwkRiIFIQG+G0HeAvOEnGjqWeWtI9cg5/n51KrxPg=
github.com/samber/lo v1.27.1/go.mod h1:it33p9UtPMS7z72fP4gw/EIfQB2eI8ke7GR2wc6+Rhg=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
@ -53,17 +57,22 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/wailsapp/wails/v2 v2.3.1 h1:ZJz+pyIBKyASkgO8JO31NuHO1gTTHmvwiHYHwei1CqM=
github.com/wailsapp/wails/v2 v2.3.1/go.mod h1:zlNLI0E2c2qA6miiuAHtp0Bac8FaGH0tlhA19OssR/8=
github.com/wailsapp/wails/v2 v2.8.0/go.mod h1:EFUGWkUX3KofO4fmKR/GmsLy3HhPH7NbyOEaMt8lBF0=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -73,8 +82,10 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=

1
v3/.gitignore vendored
View file

@ -8,3 +8,4 @@ cmd/wails3/wails
/examples/plain/plain
/cmd/wails3/ui/.task/
!internal/commands/webview2/MicrosoftEdgeWebview2Setup.exe
internal/commands/appimage_testfiles/appimage_testfiles

452
v3/TESTING.md Normal file
View file

@ -0,0 +1,452 @@
# Cross-Platform Testing Guide for Wails v3
This document describes the comprehensive cross-platform testing system for Wails v3 examples, supporting Mac, Linux, and Windows compilation.
## Overview
The testing system ensures all Wails v3 examples build successfully across all supported platforms:
- **macOS (Darwin)** - Native compilation
- **Windows** - Cross-compilation from any platform
- **Linux** - Multi-architecture Docker compilation (ARM64 + x86_64)
## Test Directory Structure
The testing infrastructure is organized in a dedicated test directory:
```bash
v3/
├── test/
│ └── docker/
│ ├── Dockerfile.linux-arm64 # ARM64 native compilation
│ └── Dockerfile.linux-x86_64 # x86_64 native compilation
├── Taskfile.yaml # Build task definitions
└── TESTING.md # This documentation
```
**Benefits of the organized structure:**
- **Separation of Concerns**: Testing files are isolated from application code
- **Clear Organization**: All Docker-related files in one location
- **Easier Maintenance**: Centralized testing infrastructure
- **Better Git Management**: Clean separation for .gitignore patterns
## Available Commands
### 🚀 Complete Cross-Platform Testing
```bash
# Build all examples for ALL platforms (macOS + Windows + Linux)
task test:examples:all
```
**Total: 129 builds** (43 examples × 3 platforms) + CLI code testing
### All Examples (No DIR Parameter Needed)
```bash
# Current platform only (all 43 examples + CLI code)
task test:examples
# All examples for specific Linux architectures
task test:examples:linux:docker # Auto-detect architecture
task test:examples:linux:docker:arm64 # ARM64 native
task test:examples:linux:docker:x86_64 # x86_64 native
# CLI code testing only
task test:cli
```
### Single Example Builds (Requires DIR=example)
```bash
# macOS/Darwin single example
task test:example:darwin DIR=badge
# Windows cross-compilation single example
task test:example:windows DIR=badge
# Linux native builds (on Linux systems)
task test:example:linux DIR=badge
# Linux Docker builds (multi-architecture)
task test:example:linux:docker DIR=badge # Auto-detect architecture
task test:example:linux:docker:arm64 DIR=badge # ARM64 native
task test:example:linux:docker:x86_64 DIR=badge # x86_64 native
```
## Build Artifacts
All builds generate platform-specific binaries with clear naming:
- **macOS**: `testbuild-{example}-darwin`
- **Windows**: `testbuild-{example}-windows.exe`
- **Linux**: `testbuild-{example}-linux`
- **Linux ARM64**: `testbuild-{example}-linux-arm64` (Docker)
- **Linux x86_64**: `testbuild-{example}-linux-x86_64` (Docker)
Example outputs:
```text
examples/badge/testbuild-badge-darwin
examples/badge/testbuild-badge-windows.exe
examples/badge/testbuild-badge-linux-arm64
examples/badge/testbuild-badge-linux-x86_64
```
## Validation Status
### ✅ **Production Ready (v3.0.0-alpha)**
- **Total Examples**: 43 examples fully tested
- **macOS**: ✅ All examples compile successfully (100%)
- **Windows**: ✅ All examples cross-compile successfully (100%)
- **Linux**: ✅ Multi-architecture Docker compilation (ARM64 + x86_64)
- **Build System**: Comprehensive Taskfile.yaml integration
- **Git Integration**: Complete .gitignore patterns for build artifacts
- **Total Build Capacity**: 129 cross-platform builds per test cycle
## Supported Examples
The system builds all 43 Wails v3 examples:
- badge, badge-custom, binding, build
- cancel-async, cancel-chaining, clipboard, contextmenus
- dev, dialogs, dialogs-basic, drag-n-drop
- environment, events, events-bug, file-association
- frameless, gin-example, gin-routing, gin-service
- hide-window, html-dnd-api, ignore-mouse, keybindings
- menu, notifications, panic-handling, plain
- raw-message, screen, services, show-macos-toolbar
- single-instance, systray-basic, systray-custom, systray-menu
- video, window, window-api, window-call
- window-menu, wml
**Recently Added (v3.0.0-alpha):**
- dev, events-bug, gin-example, gin-routing, gin-service
- html-dnd-api, notifications
## Platform Requirements
### macOS (Darwin)
- Go 1.23+
- Xcode Command Line Tools
- No additional dependencies required
**Environment Variables:**
```bash
CGO_LDFLAGS="-framework UniformTypeIdentifiers -mmacosx-version-min=10.13"
CGO_CFLAGS="-mmacosx-version-min=10.13"
```
### Windows (Cross-compilation)
- Go 1.23+
- No additional dependencies for cross-compilation
**Environment Variables:**
```bash
GOOS=windows
GOARCH=amd64
```
### Linux (Docker) - ✅ Multi-Architecture Support
Uses Ubuntu 24.04 base image with full GTK development environment:
**Current Status:** Complete multi-architecture Docker compilation system
- ✅ ARM64 native compilation (Ubuntu 24.04)
- ✅ x86_64 native compilation (Ubuntu 24.04)
- ✅ Automatic architecture detection
- ✅ All dependencies install correctly (GTK + WebKit)
- ✅ Go 1.24 environment configured for each architecture
- ✅ Native compilation eliminates cross-compilation CGO issues
**Architecture Support:**
- **ARM64**: Native compilation using `Dockerfile.linux-arm64`
- **x86_64**: Native compilation using `Dockerfile.linux-x86_64` with `--platform=linux/amd64`
- **Auto-detect**: Taskfile automatically selects appropriate architecture
**Core Dependencies:**
- `build-essential` - GCC compiler toolchain (architecture-specific)
- `pkg-config` - Package configuration tool
- `libgtk-3-dev` - GTK+ 3.x development files
- `libwebkit2gtk-4.1-dev` - WebKit2GTK development files
- `git` - Version control (for go mod operations)
- `ca-certificates` - HTTPS support
**Docker Images:**
- `wails-v3-linux-arm64` - Ubuntu 24.04 ARM64 native compilation (built from `test/docker/Dockerfile.linux-arm64`)
- `wails-v3-linux-x86_64` - Ubuntu 24.04 x86_64 native compilation (built from `test/docker/Dockerfile.linux-x86_64`)
- `wails-v3-linux-fixed` - Legacy unified image (deprecated)
## Docker Configuration
### Multi-Architecture Build System
#### ARM64 Native Build Environment (`test/docker/Dockerfile.linux-arm64`)
```dockerfile
FROM ubuntu:24.04
# ARM64 native compilation environment
# Go 1.24.0 ARM64 binary (go1.24.0.linux-arm64.tar.gz)
# Native GCC toolchain for ARM64
# All GTK/WebKit dependencies for ARM64
# Build script: /build/build-linux-arm64.sh
# Output: testbuild-{example}-linux-arm64
```
#### x86_64 Native Build Environment (`test/docker/Dockerfile.linux-x86_64`)
```dockerfile
FROM --platform=linux/amd64 ubuntu:24.04
# x86_64 native compilation environment
# Go 1.24.0 x86_64 binary (go1.24.0.linux-amd64.tar.gz)
# Native GCC toolchain for x86_64
# All GTK/WebKit dependencies for x86_64
# Build script: /build/build-linux-x86_64.sh
# Output: testbuild-{example}-linux-x86_64
```
### Available Docker Tasks
#### Architecture-Specific Tasks
```bash
# ARM64 builds
task test:example:linux:docker:arm64 DIR=badge
task test:examples:linux:docker:arm64
# x86_64 builds
task test:example:linux:docker:x86_64 DIR=badge
task test:examples:linux:docker:x86_64
```
#### Auto-Detection Tasks (Recommended)
```bash
# Single example (auto-detects host architecture)
task test:example:linux:docker DIR=badge
# All examples (auto-detects host architecture)
task test:examples:linux:docker
```
## Implementation Details
### Key Fixes Applied in v3.0.0-alpha
#### 1. **Complete Example Coverage**
- **Before**: 35 examples tested
- **After**: 43 examples tested (100% coverage)
- **Added**: dev, events-bug, gin-example, gin-routing, gin-service, html-dnd-api, notifications
#### 2. **Go Module Resolution**
- **Issue**: Inconsistent replace directives across examples
- **Fix**: Standardized all examples to use `replace github.com/wailsapp/wails/v3 => ../..`
- **Examples Fixed**: gin-example, gin-routing, notifications
#### 3. **Frontend Asset Embedding**
- **Issue**: Some examples referenced missing `frontend/dist` directories
- **Fix**: Updated embed paths from `//go:embed all:frontend/dist` to `//go:embed all:frontend`
- **Examples Fixed**: file-association, notifications
#### 4. **Manager API Migration**
- **Issue**: Windows badge service using deprecated API
- **Fix**: Updated `app.CurrentWindow()``app.Windows.Current()`
- **Files Fixed**: pkg/services/badge/badge_windows.go
#### 5. **File Association Example**
- **Issue**: Undefined window variable
- **Fix**: Added proper window assignment from `app.Windows.NewWithOptions()`
- **Files Fixed**: examples/file-association/main.go
### Build Performance
- **macOS**: ~2-3 minutes for all 43 examples
- **Windows Cross-Compile**: ~2-3 minutes for all 43 examples
- **Linux Docker**: ~5-10 minutes for all 43 examples (includes image build)
- **Total Build Time**: ~10-15 minutes for complete cross-platform validation (129 builds)
## Usage Examples
### Single Example Testing (Requires DIR Parameter)
```bash
# Test the badge example on all platforms
task test:example:darwin DIR=badge # macOS native
task test:example:windows DIR=badge # Windows cross-compile
task test:example:linux:docker DIR=badge # Linux Docker (auto-detect arch)
```
### All Examples Testing (No DIR Parameter)
```bash
# Test everything - all 43 examples, all platforms
task test:examples:all
# This runs:
# 1. All Darwin builds (43 examples)
# 2. All Windows cross-compilation (43 examples)
# 3. All Linux Docker builds (43 examples, auto-architecture)
# Platform-specific all examples
task test:examples # Current platform (43 examples)
task test:examples:linux:docker:arm64 # ARM64 builds (43 examples)
task test:examples:linux:docker:x86_64 # x86_64 builds (43 examples)
```
### Continuous Integration
```bash
# For CI/CD pipelines
task test:examples:all # Complete cross-platform (129 builds)
task test:examples # Current platform only (43 builds)
```
## Build Process Details
### macOS Builds
1. Sets macOS-specific CGO flags for compatibility
2. Runs `go mod tidy` in each example directory
3. Compiles with `go build -o testbuild-{example}-darwin`
4. Links against UniformTypeIdentifiers framework
### Windows Cross-Compilation
1. Sets `GOOS=windows GOARCH=amd64` environment
2. Runs `go mod tidy` in each example directory
3. Cross-compiles with `go build -o testbuild-{example}-windows.exe`
4. No CGO dependencies required (uses Windows APIs)
### Linux Docker Builds
1. **Auto-Detection**: Detects host architecture (ARM64 or x86_64)
2. **Image Selection**: Uses appropriate Ubuntu 24.04 image for target architecture
3. **Native Compilation**: Eliminates cross-compilation CGO issues
4. **Environment Setup**: Full GTK/WebKit development environment
5. **Build Process**: Runs `go mod tidy && go build` with native toolchain
6. **Output**: Architecture-specific binaries (`-linux-arm64` or `-linux-x86_64`)
## Troubleshooting
### Common Issues (All Resolved in v3.0.0-alpha)
#### **Go Module Resolution Errors**
```bash
Error: replacement directory ../wails/v3 does not exist
```
**Solution**: All examples now use standardized `replace github.com/wailsapp/wails/v3 => ../..`
#### **Frontend Asset Embedding Errors**
```bash
Error: pattern frontend/dist: no matching files found
```
**Solution**: Updated to `//go:embed all:frontend` for examples without dist directories
#### **Manager API Errors**
```bash
Error: app.CurrentWindow undefined
```
**Solution**: Updated to use new manager pattern `app.Windows.Current()`
#### **Build Warnings**
Some examples may show compatibility warnings (e.g., notifications using macOS 10.14+ APIs with 10.13 target). These are non-blocking warnings that can be addressed separately.
### Performance Optimization
#### **Parallel Builds**
```bash
# The task system automatically runs builds in parallel where possible
task v3:test:examples:all # Optimized for maximum throughput
```
#### **Selective Testing**
```bash
# Test specific examples to debug issues
task v3:test:example:darwin DIR=badge
task v3:test:example:windows DIR=contextmenus
```
### Performance Tips
**Parallel Builds:**
```bash
# Build multiple examples simultaneously
task v3:test:example:darwin DIR=badge &
task v3:test:example:darwin DIR=binding &
task v3:test:example:darwin DIR=build &
wait
```
**Docker Image Caching:**
```bash
# Pre-build Docker images
docker build -f Dockerfile.linux -t wails-v3-linux-builder .
docker build -f Dockerfile.linux-simple -t wails-v3-linux-simple .
```
## Integration with Git
### Ignored Files
All build artifacts are automatically ignored via `.gitignore`:
```gitignore
/v3/examples/*/testbuild-*
```
### Clean Build Environment
```bash
# Remove all test build artifacts
find v3/examples -name "testbuild-*" -delete
```
## Validation Results
### Current Status (as of implementation):
- ✅ **macOS**: All 43 examples compile successfully
- ✅ **Windows**: All 43 examples cross-compile successfully
- ✅ **Linux**: Multi-architecture Docker system fully functional
### Build Time Estimates:
- **macOS**: ~2-3 minutes for all examples
- **Windows**: ~2-3 minutes for all examples (cross-compile)
- **Linux Docker**: ~5-10 minutes for all examples (includes image build and compilation)
- **Complete Cross-Platform**: ~10-15 minutes for 129 total builds
## Future Enhancements
### Planned Improvements:
1. **Automated Testing**: Add runtime testing in addition to compilation
2. **Multi-Architecture**: Support ARM64 builds for Apple Silicon and Windows ARM
3. **Build Caching**: Implement Go build cache for faster repeated builds
4. **Parallel Docker**: Multi-stage Docker builds for faster Linux compilation
5. **Platform Matrix**: GitHub Actions integration for automated CI/CD
### Platform Extensions:
- **FreeBSD**: Add BSD build support
- **Android/iOS**: Mobile platform compilation (when supported)
- **WebAssembly**: WASM target compilation
## Changelog
### v3.0.0-alpha (2025-06-20)
#### 🎯 Complete Cross-Platform Testing System
#### **✨ New Features**
- **Complete Example Coverage**: All 43 examples now tested (was 35)
- **Cross-Platform Validation**: Mac + Windows builds for all examples
- **Standardized Build Artifacts**: Consistent platform-specific naming
- **Enhanced Git Integration**: Complete .gitignore patterns for build artifacts
#### **🐛 Major Fixes**
- **Go Module Resolution**: Standardized replace directives across all examples
- **Frontend Asset Embedding**: Fixed missing frontend/dist directory references
- **Manager API Migration**: Updated deprecated Windows badge service calls
- **File Association**: Fixed undefined window variable
- **Build Completeness**: Added 8 missing examples to test suite
#### **🔧 Infrastructure Improvements**
- **Taskfile Integration**: Comprehensive cross-platform build tasks
- **Performance Optimization**: Parallel builds where possible
- **Error Handling**: Clear build failure reporting and debugging
- **Documentation**: Complete testing guide with troubleshooting
#### **📊 Validation Results**
- **macOS**: ✅ 43/43 examples compile successfully
- **Windows**: ✅ 43/43 examples cross-compile successfully
- **Build Time**: ~5-6 minutes for complete cross-platform validation
- **Reliability**: 100% success rate with proper error handling
## Support
For issues with cross-platform builds:
1. Check platform-specific requirements above
2. Review the troubleshooting section for resolved issues
3. Verify Go 1.24+ is installed
4. Check build logs for specific error messages
5. Use selective testing to isolate problems
## References
- [Wails v3 Documentation](https://wails.io/docs/)
- [Go Cross Compilation](https://golang.org/doc/install/cross)
- [GTK Development Libraries](https://www.gtk.org/docs/installations/linux)
- [Task Runner Documentation](https://taskfile.dev/)

View file

@ -71,61 +71,298 @@ tasks:
platforms:
- darwin
cmds:
- echo "Building example {{.DIR}}"
- echo "Building example {{.DIR}} for Darwin"
- go mod tidy
- go build -o "testbuild-{{.DIR}}{{exeExt}}"
- go build -o "testbuild-{{.DIR}}-darwin{{exeExt}}"
env:
CGO_LDFLAGS: -framework UniformTypeIdentifiers -mmacosx-version-min=10.13
CGO_CFLAGS: -mmacosx-version-min=10.13
test:example:notdarwin:
test:example:windows:
dir: 'examples/{{.DIR}}'
platforms:
- windows
cmds:
- echo "Building example {{.DIR}} for Windows"
- go mod tidy
- go build -o "testbuild-{{.DIR}}-windows.exe"
env:
GOOS: windows
GOARCH: amd64
test:example:linux:
dir: 'examples/{{.DIR}}'
platforms:
- linux
- windows
cmds:
- echo "Building example {{.DIR}}"
- echo "Building example {{.DIR}} for Linux"
- go mod tidy
- go build -o "testbuild-{{.DIR}}{{exeExt}}"
- go build -o "testbuild-{{.DIR}}-linux"
test:example:linux:docker:arm64:
summary: Build a single example for Linux ARM64 using Docker (Ubuntu 24.04)
cmds:
- echo "Building example {{.DIR}} for Linux ARM64 using Docker"
- docker build --pull -f test/docker/Dockerfile.linux-arm64 -t wails-v3-linux-arm64 .
- docker run --rm wails-v3-linux-arm64 /build/build-linux-arm64.sh {{.DIR}}
test:examples:
summary: Builds the examples
dir: examples
test:example:linux:docker:x86_64:
summary: Build a single example for Linux x86_64 using Docker (Ubuntu 24.04)
cmds:
- echo "Building example {{.DIR}} for Linux x86_64 using Docker"
- docker build --pull -f test/docker/Dockerfile.linux-x86_64 -t wails-v3-linux-x86_64 .
- docker run --rm wails-v3-linux-x86_64 /build/build-linux-x86_64.sh {{.DIR}}
test:examples:linux:docker:arm64:
summary: Build all examples for Linux ARM64 using Docker (Ubuntu 24.04)
cmds:
- echo "Building Docker image for Linux ARM64 compilation..."
- docker build --pull -f test/docker/Dockerfile.linux-arm64 -t wails-v3-linux-arm64 .
- echo "Running Linux ARM64 compilation in Docker container..."
- docker run --rm wails-v3-linux-arm64
test:examples:linux:docker:x86_64:
summary: Build all examples for Linux x86_64 using Docker (Ubuntu 24.04)
cmds:
- echo "Building Docker image for Linux x86_64 compilation..."
- docker build --pull -f test/docker/Dockerfile.linux-x86_64 -t wails-v3-linux-x86_64 .
- echo "Running Linux x86_64 compilation in Docker container..."
- docker run --rm wails-v3-linux-x86_64
test:example:linux:docker:
summary: Build a single example for Linux using Docker (auto-detect architecture)
cmds:
- echo "Auto-detecting architecture for Linux Docker build..."
- |
if [ "$(uname -m)" = "arm64" ] || [ "$(uname -m)" = "aarch64" ]; then
echo "Detected ARM64, using ARM64 Docker image"
task test:example:linux:docker:arm64 DIR={{.DIR}}
else
echo "Detected x86_64, using x86_64 Docker image"
task test:example:linux:docker:x86_64 DIR={{.DIR}}
fi
test:examples:linux:docker:
summary: Build all examples for Linux using Docker (auto-detect architecture)
cmds:
- echo "Auto-detecting architecture for Linux Docker build..."
- |
if [ "$(uname -m)" = "arm64" ] || [ "$(uname -m)" = "aarch64" ]; then
echo "Detected ARM64, using ARM64 Docker image"
task test:examples:linux:docker:arm64
else
echo "Detected x86_64, using x86_64 Docker image"
task test:examples:linux:docker:x86_64
fi
test:examples:all:
summary: Builds all examples for all platforms (Mac + Windows + Linux via Docker)
vars:
EXAMPLEDIRS: |
badge
badge-custom
binding
build
cancel-async
cancel-chaining
clipboard
contextmenus
dev
dialogs
dialogs-basic
drag-n-drop
environment
events
events-bug
file-association
frameless
gin-example
gin-routing
gin-service
hide-window
html-dnd-api
ignore-mouse
keybindings
menu
notifications
panic-handling
plain
raw-message
screen
services
show-macos-toolbar
single-instance
systray-basic
systray-menu
systray-custom
systray-menu
video
window
window-api
window-call
window-menu
wml
cmds:
- echo "Building all examples for all platforms..."
- echo "=== Building for Darwin ==="
- for: { var: EXAMPLEDIRS }
task: test:example:darwin
vars:
DIR: "{{.ITEM}}"
- echo "=== Building for Windows (cross-compile) ==="
- for: { var: EXAMPLEDIRS }
task: test:example:notdarwin
task: test:example:windows
vars:
DIR: "{{.ITEM}}"
- echo "=== Building for Linux (Docker) ==="
- task: test:examples:linux:docker
- echo "=== Testing CLI Code ==="
- task: test:cli
- echo "=== Cleaning Up Test Binaries ==="
- task: clean:test:binaries
test:cli:
summary: Test CLI-related code compilation
cmds:
- echo "Testing CLI appimage testfiles compilation..."
- cd internal/commands/appimage_testfiles && go mod tidy && go build
- echo "✅ CLI appimage testfiles compile successfully"
test:cli:all:
summary: Test all CLI components and critical test files
cmds:
- echo "Testing CLI appimage testfiles..."
- cd internal/commands/appimage_testfiles && go mod tidy && go build
- echo "Testing window visibility test..."
- cd tests/window-visibility-test && go mod tidy && go build
- echo "Testing service implementations..."
- cd pkg/services/badge && go build
- echo "✅ All CLI components compile successfully"
test:generator:
summary: Test code generator test cases compilation
cmds:
- echo "Testing generator test cases (sample)..."
- cd internal/generator/testcases/function_single && go mod tidy && go build
- cd internal/generator/testcases/complex_method && go mod tidy && go build
- cd internal/generator/testcases/struct_literal_single && go mod tidy && go build
- echo "✅ Generator test cases compile successfully"
test:templates:
summary: Test template generation for core templates
cmds:
- echo "Testing template generation (core templates)..."
- task: install
- echo "Testing lit template generation..."
- rm -rf ./test-template-lit && wails3 init -n test-template-lit -t lit
- mkdir -p ./test-template-lit/frontend/dist && touch ./test-template-lit/frontend/dist/.keep
- cd ./test-template-lit && go mod tidy && go build
- rm -rf ./test-template-lit
- echo "Testing react template generation..."
- rm -rf ./test-template-react && wails3 init -n test-template-react -t react
- mkdir -p ./test-template-react/frontend/dist && touch ./test-template-react/frontend/dist/.keep
- cd ./test-template-react && go mod tidy && go build
- rm -rf ./test-template-react
- echo "✅ Template generation tests completed successfully"
test:infrastructure:
summary: Test critical infrastructure components
cmds:
- echo "=== Testing CLI Components ==="
- task: test:cli:all
- echo "=== Testing Generator ==="
- task: test:generator
- echo "=== Testing Templates ==="
- task: test:templates
- echo "=== Testing pkg/application ==="
- cd pkg/application && go test -c -o /dev/null ./...
- echo "=== Cleaning Up Test Binaries ==="
- task: clean:test:binaries
- echo "✅ All infrastructure components test successfully"
test:examples:
summary: Builds the examples for current platform only
vars:
EXAMPLEDIRS: |
badge
badge-custom
binding
build
cancel-async
cancel-chaining
clipboard
contextmenus
dev
dialogs
dialogs-basic
drag-n-drop
environment
events
events-bug
file-association
frameless
gin-example
gin-routing
gin-service
hide-window
html-dnd-api
ignore-mouse
keybindings
menu
notifications
panic-handling
plain
raw-message
screen
services
show-macos-toolbar
single-instance
systray-basic
systray-custom
systray-menu
video
window
window-api
window-call
window-menu
wml
cmds:
- echo "Testing examples compilation..."
- for: { var: EXAMPLEDIRS }
task: test:example:darwin
vars:
DIR: "{{.ITEM}}"
platforms: [darwin]
- for: { var: EXAMPLEDIRS }
task: test:example:linux
vars:
DIR: "{{.ITEM}}"
platforms: [linux]
- for: { var: EXAMPLEDIRS }
task: test:example:windows
vars:
DIR: "{{.ITEM}}"
platforms: [windows]
- echo "Testing CLI code..."
- task: test:cli
- echo "=== Cleaning Up Test Binaries ==="
- task: clean:test:binaries
clean:test:binaries:
summary: Clean up all test-generated binary files and directories (cross-platform)
cmds:
- echo "🧹 Cleaning up test binaries..."
- go run tasks/cleanup/cleanup.go
- echo "✅ Test binaries cleaned up"
test:all:
summary: Run all tests including examples, infrastructure, and Go unit tests
cmds:
- echo "=== Running Go Unit Tests ==="
- go test ./...
- echo "=== Testing Examples (Current Platform) ==="
- task: test:examples
- echo "=== Testing Infrastructure Components ==="
- task: test:infrastructure
- echo "=== Cleaning Up Test Binaries ==="
- task: clean:test:binaries
- echo "✅ All tests completed successfully"

View file

@ -0,0 +1,53 @@
# Unreleased Changes
<!--
This file is used to collect changelog entries for the next v3-alpha release.
Add your changes under the appropriate sections below.
Guidelines:
- Follow the "Keep a Changelog" format (https://keepachangelog.com/)
- Write clear, concise descriptions of changes
- Include the impact on users when relevant
- Use present tense ("Add feature" not "Added feature")
- Reference issue/PR numbers when applicable
This file is automatically processed by the nightly release workflow.
After processing, the content will be moved to the main changelog and this file will be reset.
-->
## Added
<!-- New features, capabilities, or enhancements -->
## Changed
<!-- Changes in existing functionality -->
## Fixed
<!-- Bug fixes -->
## Deprecated
<!-- Soon-to-be removed features -->
## Removed
<!-- Features removed in this release -->
## Security
<!-- Security-related changes -->
---
### Example Entries:
**Added:**
- Add support for custom window icons in application options
- Add new `SetWindowIcon()` method to runtime API (#1234)
**Changed:**
- Update minimum Go version requirement to 1.21
- Improve error messages for invalid configuration files
**Fixed:**
- Fix memory leak in event system during window close operations (#5678)
- Fix crash when using context menus on Linux with Wayland
**Security:**
- Update dependencies to address CVE-2024-12345 in third-party library

View file

@ -21,13 +21,13 @@ It can be used to generate many things including:
The `icon` command generates icons for your project.
| Flag | Type | Description | Default |
|--------------------|--------|------------------------------------------------------|-----------------------|
| `-example` | bool | Generates example icon file (appicon.png) | |
| `-input` | string | The input image file | |
| Flag | Type | Description | Default |
|--------------------|--------|------------------------------------------------------|----------------------|
| `-example` | bool | Generates example icon file (appicon.png) | |
| `-input` | string | The input image file | |
| `-sizes` | string | The sizes to generate in .ico file (comma separated) | "256,128,64,48,32,16" |
| `-windowsFilename` | string | The output filename for the Windows icon | icons.ico |
| `-macFilename` | string | The output filename for the Mac icon bundle | icons.icns |
| `-windowsFilename` | string | The output filename for the Windows icon | icon.ico |
| `-macFilename` | string | The output filename for the Mac icon bundle | icons.icns |
```bash
wails3 generate icon -input myicon.png -sizes "32,64,128" -windowsFilename myicon.ico -macFilename myicon.icns

View file

@ -79,6 +79,7 @@ func main() {
tool.NewSubCommandFunction("cp", "Copy files", commands.Cp)
tool.NewSubCommandFunction("buildinfo", "Show Build Info", commands.BuildInfo)
tool.NewSubCommandFunction("package", "Generate Linux packages (deb, rpm, archlinux)", commands.ToolPackage)
tool.NewSubCommandFunction("version", "Bump semantic version", commands.ToolVersion)
app.NewSubCommandFunction("version", "Print the version", commands.Version)
app.NewSubCommand("sponsor", "Sponsor the project").Action(openSponsor)

View file

@ -0,0 +1,128 @@
# Welcome to Your New Wails3 Project!
Now that you have your project set up, it's time to explore the custom badge features that Wails3 offers on **Windows**.
## Exploring Custom Badge Features
### Creating the Service with Custom Options (Windows Only)
On Windows, you can customize the badge appearance with various options:
```go
import "github.com/wailsapp/wails/v3/pkg/application"
import "github.com/wailsapp/wails/v3/pkg/services/badge"
import "image/color"
// Create a badge service with custom options
options := badge.Options{
TextColour: color.RGBA{255, 255, 255, 255}, // White text
BackgroundColour: color.RGBA{0, 0, 255, 255}, // Green background
FontName: "consolab.ttf", // Bold Consolas font
FontSize: 20, // Font size for single character
SmallFontSize: 14, // Font size for multiple characters
}
badgeService := badge.NewWithOptions(options)
// Register the service with the application
app := application.New(application.Options{
Services: []application.Service{
application.NewService(badgeService),
},
})
```
## Badge Operations
### Setting a Badge
Set a badge on the application tile/dock icon with the global options applied:
#### Go
```go
// Set a default badge
badgeService.SetBadge("")
// Set a numeric badge
badgeService.SetBadge("3")
// Set a text badge
badgeService.SetBadge("New")
```
#### JS
```js
import {SetBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/badge/service";
// Set a default badge
SetBadge("")
// Set a numeric badge
SetBadge("3")
// Set a text badge
SetBadge("New")
```
### Setting a Custom Badge
Set a badge on the application tile/dock icon with one-off options applied:
#### Go
```go
// Set a default badge
badgeService.SetCustomBadge("")
// Set a numeric badge
badgeService.SetCustomBadge("3")
// Set a text badge
badgeService.SetCustomBadge("New")
```
#### JS
```js
import {SetCustomBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/badge/service";
const options = {
BackgroundColour: RGBA.createFrom({
R: 0,
G: 255,
B: 255,
A: 255,
}),
FontName: "arialb.ttf", // System font
FontSize: 16,
SmallFontSize: 10,
TextColour: RGBA.createFrom({
R: 0,
G: 0,
B: 0,
A: 255,
}),
}
// Set a default badge
SetCustomBadge("", options)
// Set a numeric badge
SetCustomBadge("3", options)
// Set a text badge
SetCustomBadge("New", options)
```
### Removing a Badge
Remove the badge from the application icon:
#### Go
```go
badgeService.RemoveBadge()
```
#### JS
```js
import {RemoveBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/badge/service";
RemoveBadge()
```

View file

@ -0,0 +1,34 @@
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: "badge"
BIN_DIR: "bin"
VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}'
tasks:
build:
summary: Builds the application
cmds:
- task: "{{OS}}:build"
package:
summary: Packages a production build of the application
cmds:
- task: "{{OS}}:package"
run:
summary: Runs the application
cmds:
- task: "{{OS}}:run"
dev:
summary: Runs the application in development mode
cmds:
- wails3 dev -config ./build/config.yml -port {{.VITE_PORT}}

View file

@ -0,0 +1,86 @@
version: '3'
tasks:
go:mod:tidy:
summary: Runs `go mod tidy`
internal: true
cmds:
- go mod tidy
install:frontend:deps:
summary: Install frontend dependencies
dir: frontend
sources:
- package.json
- package-lock.json
generates:
- node_modules/*
preconditions:
- sh: npm version
msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/"
cmds:
- npm install
build:frontend:
label: build:frontend (PRODUCTION={{.PRODUCTION}})
summary: Build the frontend project
dir: frontend
sources:
- "**/*"
generates:
- dist/**/*
deps:
- task: install:frontend:deps
- task: generate:bindings
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
cmds:
- npm run {{.BUILD_COMMAND}} -q
env:
PRODUCTION: '{{.PRODUCTION | default "false"}}'
vars:
BUILD_COMMAND: '{{if eq .PRODUCTION "true"}}build{{else}}build:dev{{end}}'
generate:bindings:
label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}})
summary: Generates bindings for the frontend
deps:
- task: go:mod:tidy
sources:
- "**/*.[jt]s"
- exclude: frontend/**/*
- frontend/bindings/**/* # Rerun when switching between dev/production mode causes changes in output
- "**/*.go"
- go.mod
- go.sum
generates:
- frontend/bindings/**/*
cmds:
- wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=true -ts
generate:icons:
summary: Generates Windows `.ico` and Mac `.icns` files from an image
dir: build
sources:
- "appicon.png"
generates:
- "darwin/icons.icns"
- "windows/icon.ico"
cmds:
- wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico
dev:frontend:
summary: Runs the frontend in development mode
dir: frontend
deps:
- task: install:frontend:deps
cmds:
- npm run dev -- --port {{.VITE_PORT}} --strictPort
update:build-assets:
summary: Updates the build assets
dir: build
cmds:
- wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir .

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View file

@ -0,0 +1,63 @@
# This file contains the configuration for this project.
# When you update `info` or `fileAssociations`, run `wails3 task common:update:build-assets` to update the assets.
# Note that this will overwrite any changes you have made to the assets.
version: '3'
# This information is used to generate the build assets.
info:
companyName: "My Company" # The name of the company
productName: "My Product" # The name of the application
productIdentifier: "com.mycompany.myproduct" # The unique product identifier
description: "A program that does X" # The application description
copyright: "(c) 2025, My Company" # Copyright text
comments: "Some Product Comments" # Comments
version: "0.0.1" # The application version
# Dev mode configuration
dev_mode:
root_path: .
log_level: warn
debounce: 1000
ignore:
dir:
- .git
- node_modules
- frontend
- bin
file:
- .DS_Store
- .gitignore
- .gitkeep
watched_extension:
- "*.go"
git_ignore: true
executes:
- cmd: wails3 task common:install:frontend:deps
type: once
- cmd: wails3 task common:dev:frontend
type: background
- cmd: go mod tidy
type: blocking
- cmd: wails3 task build
type: blocking
- cmd: wails3 task run
type: primary
# File Associations
# More information at: https://v3.wails.io/noit/done/yet
fileAssociations:
# - ext: wails
# name: Wails
# description: Wails Application File
# iconName: wailsFileIcon
# role: Editor
# - ext: jpg
# name: JPEG
# description: Image File
# iconName: jpegFileIcon
# role: Editor
# mimeType: image/jpeg # (optional)
# Other data
other:
- name: My Other Data

View file

@ -0,0 +1,32 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleName</key>
<string>My Product</string>
<key>CFBundleExecutable</key>
<string>badge</string>
<key>CFBundleIdentifier</key>
<string>com.wails.badge</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>CFBundleGetInfoString</key>
<string>This is a comment</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<key>CFBundleIconFile</key>
<string>icons</string>
<key>LSMinimumSystemVersion</key>
<string>10.15.0</string>
<key>NSHighResolutionCapable</key>
<string>true</string>
<key>NSHumanReadableCopyright</key>
<string>© now, My Company</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
</dict>
</plist>

View file

@ -0,0 +1,27 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleName</key>
<string>My Product</string>
<key>CFBundleExecutable</key>
<string>badge</string>
<key>CFBundleIdentifier</key>
<string>com.wails.badge</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>CFBundleGetInfoString</key>
<string>This is a comment</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<key>CFBundleIconFile</key>
<string>icons</string>
<key>LSMinimumSystemVersion</key>
<string>10.15.0</string>
<key>NSHighResolutionCapable</key>
<string>true</string>
<key>NSHumanReadableCopyright</key>
<string>© now, My Company</string>
</dict>
</plist>

View file

@ -0,0 +1,81 @@
version: '3'
includes:
common: ../Taskfile.yml
tasks:
build:
summary: Creates a production build of the application
deps:
- task: common:go:mod:tidy
- task: common:build:frontend
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
PRODUCTION:
ref: .PRODUCTION
- task: common:generate:icons
cmds:
- go build {{.BUILD_FLAGS}} -o {{.OUTPUT}}
vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}'
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
env:
GOOS: darwin
CGO_ENABLED: 1
GOARCH: '{{.ARCH | default ARCH}}'
CGO_CFLAGS: "-mmacosx-version-min=10.15"
CGO_LDFLAGS: "-mmacosx-version-min=10.15"
MACOSX_DEPLOYMENT_TARGET: "10.15"
PRODUCTION: '{{.PRODUCTION | default "false"}}'
build:universal:
summary: Builds darwin universal binary (arm64 + amd64)
deps:
- task: build
vars:
ARCH: amd64
OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-amd64"
- task: build
vars:
ARCH: arm64
OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
cmds:
- lipo -create -output "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
- rm "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
package:
summary: Packages a production build of the application into a `.app` bundle
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- task: create:app:bundle
package:universal:
summary: Packages darwin universal binary (arm64 + amd64)
deps:
- task: build:universal
cmds:
- task: create:app:bundle
create:app:bundle:
summary: Creates an `.app` bundle
cmds:
- mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/{MacOS,Resources}
- cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources
- cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS
- cp build/darwin/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents
- codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.app
run:
cmds:
- mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/{MacOS,Resources}
- cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources
- cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS
- cp build/darwin/Info.dev.plist {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Info.plist
- codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.dev.app
- '{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS/{{.APP_NAME}}'

Binary file not shown.

View file

@ -0,0 +1,119 @@
version: '3'
includes:
common: ../Taskfile.yml
tasks:
build:
summary: Builds the application for Linux
deps:
- task: common:go:mod:tidy
- task: common:build:frontend
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
PRODUCTION:
ref: .PRODUCTION
- task: common:generate:icons
cmds:
- go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}
vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}'
env:
GOOS: linux
CGO_ENABLED: 1
GOARCH: '{{.ARCH | default ARCH}}'
PRODUCTION: '{{.PRODUCTION | default "false"}}'
package:
summary: Packages a production build of the application for Linux
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- task: create:appimage
- task: create:deb
- task: create:rpm
- task: create:aur
create:appimage:
summary: Creates an AppImage
dir: build/linux/appimage
deps:
- task: build
vars:
PRODUCTION: "true"
- task: generate:dotdesktop
cmds:
- cp {{.APP_BINARY}} {{.APP_NAME}}
- cp ../../appicon.png appicon.png
- wails3 generate appimage -binary {{.APP_NAME}} -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build
vars:
APP_NAME: '{{.APP_NAME}}'
APP_BINARY: '../../../bin/{{.APP_NAME}}'
ICON: '../../appicon.png'
DESKTOP_FILE: '../{{.APP_NAME}}.desktop'
OUTPUT_DIR: '../../../bin'
create:deb:
summary: Creates a deb package
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- task: generate:dotdesktop
- task: generate:deb
create:rpm:
summary: Creates a rpm package
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- task: generate:dotdesktop
- task: generate:rpm
create:aur:
summary: Creates a arch linux packager package
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- task: generate:dotdesktop
- task: generate:aur
generate:deb:
summary: Creates a deb package
cmds:
- wails3 tool package -name {{.APP_NAME}} -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
generate:rpm:
summary: Creates a rpm package
cmds:
- wails3 tool package -name {{.APP_NAME}} -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
generate:aur:
summary: Creates a arch linux packager package
cmds:
- wails3 tool package -name {{.APP_NAME}} -format archlinux -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
generate:dotdesktop:
summary: Generates a `.desktop` file
dir: build
cmds:
- mkdir -p {{.ROOT_DIR}}/build/linux/appimage
- wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile {{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop -categories "{{.CATEGORIES}}"
vars:
APP_NAME: '{{.APP_NAME}}'
EXEC: '{{.APP_NAME}}'
ICON: 'appicon'
CATEGORIES: 'Development;'
OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop'
run:
cmds:
- '{{.BIN_DIR}}/{{.APP_NAME}}'

View file

@ -0,0 +1,35 @@
#!/usr/bin/env bash
# Copyright (c) 2018-Present Lea Anthony
# SPDX-License-Identifier: MIT
# Fail script on any error
set -euxo pipefail
# Define variables
APP_DIR="${APP_NAME}.AppDir"
# Create AppDir structure
mkdir -p "${APP_DIR}/usr/bin"
cp -r "${APP_BINARY}" "${APP_DIR}/usr/bin/"
cp "${ICON_PATH}" "${APP_DIR}/"
cp "${DESKTOP_FILE}" "${APP_DIR}/"
if [[ $(uname -m) == *x86_64* ]]; then
# Download linuxdeploy and make it executable
wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
chmod +x linuxdeploy-x86_64.AppImage
# Run linuxdeploy to bundle the application
./linuxdeploy-x86_64.AppImage --appdir "${APP_DIR}" --output appimage
else
# Download linuxdeploy and make it executable (arm64)
wget -q -4 -N https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-aarch64.AppImage
chmod +x linuxdeploy-aarch64.AppImage
# Run linuxdeploy to bundle the application (arm64)
./linuxdeploy-aarch64.AppImage --appdir "${APP_DIR}" --output appimage
fi
# Rename the generated AppImage
mv "${APP_NAME}*.AppImage" "${APP_NAME}.AppImage"

View file

@ -0,0 +1,50 @@
# Feel free to remove those if you don't want/need to use them.
# Make sure to check the documentation at https://nfpm.goreleaser.com
#
# The lines below are called `modelines`. See `:help modeline`
name: "badge"
arch: ${GOARCH}
platform: "linux"
version: "0.1.0"
section: "default"
priority: "extra"
maintainer: ${GIT_COMMITTER_NAME} <${GIT_COMMITTER_EMAIL}>
description: "My Product Description"
vendor: "My Company"
homepage: "https://wails.io"
license: "MIT"
release: "1"
contents:
- src: "./bin/badge"
dst: "/usr/local/bin/badge"
- src: "./build/appicon.png"
dst: "/usr/share/icons/hicolor/128x128/apps/badge.png"
- src: "./build/linux/badge.desktop"
dst: "/usr/share/applications/badge.desktop"
depends:
- gtk3
- libwebkit2gtk
# replaces:
# - foobar
# provides:
# - bar
# depends:
# - gtk3
# - libwebkit2gtk
# recommends:
# - whatever
# suggests:
# - something-else
# conflicts:
# - not-foo
# - not-bar
# changelog: "changelog.yaml"
# scripts:
# preinstall: ./build/linux/nfpm/scripts/preinstall.sh
# postinstall: ./build/linux/nfpm/scripts/postinstall.sh
# preremove: ./build/linux/nfpm/scripts/preremove.sh
# postremove: ./build/linux/nfpm/scripts/postremove.sh

Some files were not shown because too many files have changed in this diff Show more