mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
Merge branch 'v3-alpha-feature/mac-window-tabbing' of github.com:Eriyc/wails-fork into v3-alpha-feature/mac-window-tabbing
This commit is contained in:
commit
2a5579ffe8
34 changed files with 1066 additions and 124 deletions
26
.github/workflows/build-and-test-v3.yml
vendored
26
.github/workflows/build-and-test-v3.yml
vendored
|
|
@ -115,11 +115,11 @@ jobs:
|
|||
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
|
||||
packages: libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev build-essential pkg-config xvfb x11-xserver-utils at-spi2-core xdg-desktop-portal-gtk
|
||||
version: 1.0
|
||||
|
||||
- name: Install linux dependencies (GTK4) - webkit-gtk6-support branch only
|
||||
if: matrix.os == 'ubuntu-latest' && github.head_ref == 'feature/webkit-gtk6-support'
|
||||
- name: Install linux dependencies (GTK4)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-4-dev libwebkitgtk-6.0-dev
|
||||
|
|
@ -150,8 +150,8 @@ jobs:
|
|||
task test:examples
|
||||
echo "Example compilation tests (GTK3) completed successfully"
|
||||
|
||||
- name: Build Examples (GTK4 experimental)
|
||||
if: matrix.os == 'ubuntu-latest' && github.head_ref == 'feature/webkit-gtk6-support'
|
||||
- name: Build Examples (GTK4)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
working-directory: v3
|
||||
run: |
|
||||
echo "Starting example compilation tests (GTK4)..."
|
||||
|
|
@ -180,11 +180,10 @@ jobs:
|
|||
go test -v ./...
|
||||
'
|
||||
|
||||
- name: Run tests (ubuntu) - GTK4 experimental
|
||||
if: matrix.os == 'ubuntu-latest' && github.head_ref == 'feature/webkit-gtk6-support'
|
||||
- name: Run tests (ubuntu) - GTK4
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
working-directory: v3
|
||||
# Skip all service tests that hang in CI due to GTK4 display requirements
|
||||
# The services tests require a fully functional GTK4 display which xvfb cannot provide
|
||||
# Skip service tests that hang in CI due to GTK4 display requirements
|
||||
run: >
|
||||
xvfb-run --auto-servernum
|
||||
sh -c '
|
||||
|
|
@ -240,11 +239,11 @@ jobs:
|
|||
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
|
||||
packages: libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev build-essential pkg-config
|
||||
version: 1.0
|
||||
|
||||
- name: Install linux dependencies (GTK4) - webkit-gtk6-support branch only
|
||||
if: matrix.os == 'ubuntu-latest' && github.head_ref == 'feature/webkit-gtk6-support'
|
||||
- name: Install linux dependencies (GTK4)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-4-dev libwebkitgtk-6.0-dev
|
||||
|
|
@ -292,8 +291,7 @@ jobs:
|
|||
cd ..
|
||||
wails3 build
|
||||
|
||||
# Note: GTK4 template builds are not tested here as wails build doesn't
|
||||
# support -tags flag yet. GTK4 compilation is verified by Go tests.
|
||||
# GTK4 template builds are covered by the Go example compilation tests above.
|
||||
|
||||
build_results:
|
||||
if: ${{ always() }}
|
||||
|
|
|
|||
4
.github/workflows/build-and-test.yml
vendored
4
.github/workflows/build-and-test.yml
vendored
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
- uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
if: matrix.os == 'ubuntu-24.04'
|
||||
with:
|
||||
packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config
|
||||
packages: libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev build-essential pkg-config
|
||||
version: 1.0
|
||||
|
||||
- name: Setup Go
|
||||
|
|
@ -126,7 +126,7 @@ jobs:
|
|||
|
||||
- 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
|
||||
run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev build-essential pkg-config
|
||||
|
||||
- name: Generate & Build template '${{ matrix.template }}'
|
||||
if: matrix.os != 'ubuntu-24.04'
|
||||
|
|
|
|||
2
.github/workflows/build-cross-image.yml
vendored
2
.github/workflows/build-cross-image.yml
vendored
|
|
@ -153,7 +153,7 @@ jobs:
|
|||
- name: Install Linux dev dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev
|
||||
|
||||
- name: Install wails3 CLI
|
||||
run: |
|
||||
|
|
|
|||
6
.github/workflows/cross-compile-test-v3.yml
vendored
6
.github/workflows/cross-compile-test-v3.yml
vendored
|
|
@ -83,7 +83,7 @@ jobs:
|
|||
- name: Install Linux dependencies
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
packages: libgtk-3-dev libwebkit2gtk-4.1-dev build-essential pkg-config
|
||||
packages: libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev build-essential pkg-config
|
||||
version: 1.0
|
||||
|
||||
- name: Install Wails3 CLI
|
||||
|
|
@ -126,8 +126,8 @@ jobs:
|
|||
- run: |
|
||||
result="${{ needs.cross_compile.result }}"
|
||||
echo "Cross-compile result: $result"
|
||||
if [[ $result == "success" ]]; then
|
||||
echo "All cross-compile tests passed!"
|
||||
if [[ $result == "success" || $result == "skipped" ]]; then
|
||||
echo "Cross-compile tests passed (or were skipped)!"
|
||||
exit 0
|
||||
else
|
||||
echo "One or more cross-compile tests failed"
|
||||
|
|
|
|||
2
.github/workflows/pr-master.yml
vendored
2
.github/workflows/pr-master.yml
vendored
|
|
@ -52,7 +52,7 @@ jobs:
|
|||
|
||||
- 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
|
||||
run: sudo apt-get update -y && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libwayland-dev build-essential pkg-config
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
|
|
|
|||
|
|
@ -138,6 +138,8 @@ export default defineConfig({
|
|||
items: [
|
||||
{ label: "Method Binding", link: "/features/bindings/methods" },
|
||||
{ label: "Services", link: "/features/bindings/services" },
|
||||
{ label: "Data Models", link: "/features/bindings/models" },
|
||||
{ label: "Enums", link: "/features/bindings/enums" },
|
||||
{ label: "Advanced Binding", link: "/features/bindings/advanced" },
|
||||
{ label: "Best Practices", link: "/features/bindings/best-practices" },
|
||||
],
|
||||
|
|
|
|||
|
|
@ -30,6 +30,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
*/
|
||||
## [Unreleased]
|
||||
|
||||
## v3.0.0-alpha.71 - 2026-02-10
|
||||
|
||||
## Added
|
||||
- Bumped ghw version for better Apple device support by @leaanthony (#4977)
|
||||
- Add `GetBadge` method to the dock service
|
||||
|
||||
## Fixed
|
||||
- Fix GTK4 build failure caused by C `Screen` typedef colliding with X11 Xlib.h (#4957)
|
||||
- Fix dock badge methods consistency on macOS
|
||||
|
||||
## v3.0.0-alpha.70 - 2026-02-09
|
||||
|
||||
## Added
|
||||
- Add `-tags` flag to `wails3 build` command for passing custom Go build tags (e.g., `wails3 build -tags gtk4`) (#4957)
|
||||
- Add documentation for automatic enum generation in binding generator, including dedicated Enums page and sidebar navigation (#4972)
|
||||
|
||||
## Fixed
|
||||
- Fix `InvisibleTitleBarHeight` being applied to all macOS windows instead of only frameless or transparent title bar windows (#4960)
|
||||
- Fix window shaking/jitter when resizing from top corners with `InvisibleTitleBarHeight` enabled, by skipping drag initiation near window edges (#4960)
|
||||
- Fix generation of mapped types with enum keys in JS/TS bindings (#4437) by @fbbdev
|
||||
|
||||
## v3.0.0-alpha.69 - 2026-02-08
|
||||
|
||||
## Added
|
||||
- Add `-tags` flag to `wails3 build` command for passing custom Go build tags (e.g., `wails3 build -tags gtk4`) (#4957)
|
||||
|
||||
## Fixed
|
||||
- Fix file drag-and-drop on Windows not working at non-100% display scaling
|
||||
- Fix HTML5 internal drag-and-drop being broken when file drop was enabled on Windows
|
||||
- Fix file drop coordinates being in wrong pixel space on Windows (physical vs CSS pixels)
|
||||
- Fix file drag-and-drop on Linux not working reliably with hover effects
|
||||
- Fix HTML5 internal drag-and-drop being broken when file drop was enabled on Linux
|
||||
- Fix window show/hide on Linux/GTK4 sometimes restoring to minimized state by using `gtk_window_present()` (#4957)
|
||||
- Fix window position get/set on Linux/GTK4 always returning 0,0 by adding X11-conditional support via `XTranslateCoordinates`/`XMoveWindow` (#4957)
|
||||
- Fix max window size not being enforced on Linux/GTK4 by adding signal-based size clamping to replace removed `gtk_window_set_geometry_hints` (#4957)
|
||||
- Fix DPI scaling on Linux/GTK4 by implementing proper PhysicalBounds calculation and fractional scaling support via `gdk_monitor_get_scale` (GTK 4.14+)
|
||||
- Fix menu items duplicating when creating new windows on Linux/GTK4
|
||||
- Fix generation of mapped types with enum keys in JS/TS bindings (#4437) by @fbbdev
|
||||
|
||||
## v3.0.0-alpha.68 - 2026-02-07
|
||||
|
||||
## Added
|
||||
- Add Web API examples in `v3/examples/web-apis/` demonstrating 41 browser APIs including Storage (localStorage, sessionStorage, IndexedDB, Cache API), Network (Fetch, WebSocket, XMLHttpRequest, EventSource, Beacon), Media (Canvas, WebGL, Web Audio, MediaDevices, MediaRecorder, Speech Synthesis), Device (Geolocation, Clipboard, Fullscreen, Device Orientation, Vibration, Gamepad), Performance (Performance API, Mutation Observer, Intersection/Resize Observer), UI (Web Components, Pointer Events, Selection, Dialog, Drag and Drop), and more
|
||||
- Add WebView API compatibility checker example (`v3/examples/webview-api-check/`) that tests 200+ browser APIs across platforms
|
||||
- Add `internal/libpath` package for finding native library paths on Linux with parallel search, caching, and support for Flatpak/Snap/Nix
|
||||
- **WIP:** Add experimental WebKitGTK 6.0 / GTK4 support for Linux, available via `-tags gtk4` (GTK3/WebKit2GTK 4.1 remains the default)
|
||||
- Note: On tiling window managers (e.g., Hyprland, Sway), Minimize/Maximize operations may not work as expected since the WM controls window geometry
|
||||
|
||||
## Changed
|
||||
- **BREAKING**: Map keys in generated JS/TS bindings are now marked optional to accurately reflect Go map semantics. Map value access in Typescript now returns `T | undefined` instead of `T`, requiring null checks or assertions (#4943) by `@fbbdev`
|
||||
|
||||
## Fixed
|
||||
- Fix file drag-and-drop on Windows not working at non-100% display scaling
|
||||
- Fix HTML5 internal drag-and-drop being broken when file drop was enabled on Windows
|
||||
- Fix file drop coordinates being in wrong pixel space on Windows (physical vs CSS pixels)
|
||||
- Fix file drag-and-drop on Linux not working reliably with hover effects
|
||||
- Fix HTML5 internal drag-and-drop being broken when file drop was enabled on Linux
|
||||
- Fix DPI scaling on Linux/GTK4 by implementing proper PhysicalBounds calculation and fractional scaling support via `gdk_monitor_get_scale` (GTK 4.14+)
|
||||
- Fix menu items duplicating when creating new windows on Linux/GTK4
|
||||
- Fix generation of mapped types with enum keys in JS/TS bindings (#4437) by @fbbdev
|
||||
|
||||
## v3.0.0-alpha.67 - 2026-02-04
|
||||
|
||||
## Added
|
||||
|
|
|
|||
529
docs/src/content/docs/features/bindings/enums.mdx
Normal file
529
docs/src/content/docs/features/bindings/enums.mdx
Normal file
|
|
@ -0,0 +1,529 @@
|
|||
---
|
||||
title: Enums
|
||||
description: Automatic enum generation from Go constants
|
||||
sidebar:
|
||||
order: 4
|
||||
---
|
||||
|
||||
import { Card, CardGrid } from "@astrojs/starlight/components";
|
||||
|
||||
## Enum Bindings
|
||||
|
||||
The Wails v3 binding generator **automatically detects Go constant types and generates TypeScript enums or JavaScript const objects**. No registration, no configuration — just define your types and constants in Go, and the generator handles the rest.
|
||||
|
||||
:::note
|
||||
Unlike Wails v2, there is **no need to call `EnumBind`** or register enums manually. The generator discovers them automatically from your source code.
|
||||
:::
|
||||
|
||||
## Quick Start
|
||||
|
||||
**Define a named type with constants in Go:**
|
||||
|
||||
```go
|
||||
type Status string
|
||||
|
||||
const (
|
||||
StatusActive Status = "active"
|
||||
StatusPending Status = "pending"
|
||||
StatusClosed Status = "closed"
|
||||
)
|
||||
```
|
||||
|
||||
**Use the type in a struct or service method:**
|
||||
|
||||
```go
|
||||
type Ticket struct {
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
```
|
||||
|
||||
**Generate bindings:**
|
||||
|
||||
```bash
|
||||
wails3 generate bindings
|
||||
```
|
||||
|
||||
The generator output will report enum counts alongside models:
|
||||
|
||||
```
|
||||
3 Enums, 5 Models
|
||||
```
|
||||
|
||||
**Use in your frontend:**
|
||||
|
||||
```javascript
|
||||
import { Ticket, Status } from './bindings/myapp/models'
|
||||
|
||||
const ticket = new Ticket({
|
||||
id: 1,
|
||||
title: "Bug report",
|
||||
status: Status.StatusActive
|
||||
})
|
||||
```
|
||||
|
||||
**That's it!** The enum type is enforced in both Go and JavaScript/TypeScript.
|
||||
|
||||
## Defining Enums
|
||||
|
||||
An enum in Wails is a **named type** with an underlying basic type, combined with **const declarations** of that type.
|
||||
|
||||
### String Enums
|
||||
|
||||
```go
|
||||
// Title is a title
|
||||
type Title string
|
||||
|
||||
const (
|
||||
// Mister is a title
|
||||
Mister Title = "Mr"
|
||||
Miss Title = "Miss"
|
||||
Ms Title = "Ms"
|
||||
Mrs Title = "Mrs"
|
||||
Dr Title = "Dr"
|
||||
)
|
||||
```
|
||||
|
||||
**Generated TypeScript:**
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Title is a title
|
||||
*/
|
||||
export enum Title {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = "",
|
||||
|
||||
/**
|
||||
* Mister is a title
|
||||
*/
|
||||
Mister = "Mr",
|
||||
Miss = "Miss",
|
||||
Ms = "Ms",
|
||||
Mrs = "Mrs",
|
||||
Dr = "Dr",
|
||||
}
|
||||
```
|
||||
|
||||
**Generated JavaScript:**
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Title is a title
|
||||
* @readonly
|
||||
* @enum {string}
|
||||
*/
|
||||
export const Title = {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero: "",
|
||||
|
||||
/**
|
||||
* Mister is a title
|
||||
*/
|
||||
Mister: "Mr",
|
||||
Miss: "Miss",
|
||||
Ms: "Ms",
|
||||
Mrs: "Mrs",
|
||||
Dr: "Dr",
|
||||
};
|
||||
```
|
||||
|
||||
### Integer Enums
|
||||
|
||||
```go
|
||||
type Priority int
|
||||
|
||||
const (
|
||||
PriorityLow Priority = 0
|
||||
PriorityMedium Priority = 1
|
||||
PriorityHigh Priority = 2
|
||||
)
|
||||
```
|
||||
|
||||
**Generated TypeScript:**
|
||||
|
||||
```typescript
|
||||
export enum Priority {
|
||||
/**
|
||||
* The Go zero value for the underlying type of the enum.
|
||||
*/
|
||||
$zero = 0,
|
||||
|
||||
PriorityLow = 0,
|
||||
PriorityMedium = 1,
|
||||
PriorityHigh = 2,
|
||||
}
|
||||
```
|
||||
|
||||
### Type Alias Enums
|
||||
|
||||
Go type aliases (`=`) also work, but generate a slightly different output — a type definition plus a const object, rather than a native TypeScript `enum`:
|
||||
|
||||
```go
|
||||
// Age is an integer with some predefined values
|
||||
type Age = int
|
||||
|
||||
const (
|
||||
NewBorn Age = 0
|
||||
Teenager Age = 12
|
||||
YoungAdult Age = 18
|
||||
|
||||
// Oh no, some grey hair!
|
||||
MiddleAged Age = 50
|
||||
Mathusalem Age = 1000 // Unbelievable!
|
||||
)
|
||||
```
|
||||
|
||||
**Generated TypeScript:**
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Age is an integer with some predefined values
|
||||
*/
|
||||
export type Age = number;
|
||||
|
||||
/**
|
||||
* Predefined constants for type Age.
|
||||
* @namespace
|
||||
*/
|
||||
export const Age = {
|
||||
NewBorn: 0,
|
||||
Teenager: 12,
|
||||
YoungAdult: 18,
|
||||
|
||||
/**
|
||||
* Oh no, some grey hair!
|
||||
*/
|
||||
MiddleAged: 50,
|
||||
|
||||
/**
|
||||
* Unbelievable!
|
||||
*/
|
||||
Mathusalem: 1000,
|
||||
};
|
||||
```
|
||||
|
||||
**Generated JavaScript:**
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Age is an integer with some predefined values
|
||||
* @typedef {number} Age
|
||||
*/
|
||||
|
||||
/**
|
||||
* Predefined constants for type Age.
|
||||
* @namespace
|
||||
*/
|
||||
export const Age = {
|
||||
NewBorn: 0,
|
||||
Teenager: 12,
|
||||
YoungAdult: 18,
|
||||
|
||||
/**
|
||||
* Oh no, some grey hair!
|
||||
*/
|
||||
MiddleAged: 50,
|
||||
|
||||
/**
|
||||
* Unbelievable!
|
||||
*/
|
||||
Mathusalem: 1000,
|
||||
};
|
||||
```
|
||||
|
||||
:::tip
|
||||
**Named types** (`type Title string`) generate native TypeScript `enum` declarations with a `$zero` member.
|
||||
**Type aliases** (`type Age = int`) generate a `type` + `const` namespace pair without `$zero`.
|
||||
:::
|
||||
|
||||
## The `$zero` Value
|
||||
|
||||
Every named-type enum includes a special `$zero` member representing the **Go zero value** for the underlying type:
|
||||
|
||||
| Underlying Type | `$zero` Value |
|
||||
|----------------|---------------|
|
||||
| `string` | `""` |
|
||||
| `int`, `int8`, `int16`, `int32`, `int64` | `0` |
|
||||
| `uint`, `uint8`, `uint16`, `uint32`, `uint64` | `0` |
|
||||
| `float32`, `float64` | `0` |
|
||||
| `bool` | `false` |
|
||||
|
||||
When a struct field uses an enum type and no value is provided, the constructor defaults to `$zero`:
|
||||
|
||||
```typescript
|
||||
export class Person {
|
||||
"Title": Title;
|
||||
|
||||
constructor($$source: Partial<Person> = {}) {
|
||||
if (!("Title" in $$source)) {
|
||||
this["Title"] = Title.$zero; // defaults to ""
|
||||
}
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This ensures type-safe initialisation when generating classes — enum fields are never `undefined`. When generating TypeScript interfaces (using `-i`), there is no constructor and fields may be absent as usual.
|
||||
|
||||
## Using Enums in Structs
|
||||
|
||||
When a struct field has an enum type, the generated code **preserves that type** rather than falling back to the primitive:
|
||||
|
||||
```go
|
||||
type Person struct {
|
||||
Title Title
|
||||
Name string
|
||||
Age Age
|
||||
}
|
||||
```
|
||||
|
||||
**Generated TypeScript:**
|
||||
|
||||
```typescript
|
||||
export class Person {
|
||||
"Title": Title;
|
||||
"Name": string;
|
||||
"Age": Age;
|
||||
|
||||
constructor($$source: Partial<Person> = {}) {
|
||||
if (!("Title" in $$source)) {
|
||||
this["Title"] = Title.$zero;
|
||||
}
|
||||
if (!("Name" in $$source)) {
|
||||
this["Name"] = "";
|
||||
}
|
||||
if (!("Age" in $$source)) {
|
||||
this["Age"] = 0;
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `Title` field is typed as `Title`, not `string`. This gives your IDE full autocompletion and type checking on enum values.
|
||||
|
||||
## Enums from Imported Packages
|
||||
|
||||
Enums defined in separate packages are fully supported. They are generated into the corresponding package directory:
|
||||
|
||||
```go
|
||||
// services/types.go
|
||||
package services
|
||||
|
||||
type Title string
|
||||
|
||||
const (
|
||||
Mister Title = "Mr"
|
||||
Miss Title = "Miss"
|
||||
Ms Title = "Ms"
|
||||
)
|
||||
```
|
||||
|
||||
```go
|
||||
// main.go
|
||||
package main
|
||||
|
||||
import "myapp/services"
|
||||
|
||||
func (*GreetService) Greet(name string, title services.Title) string {
|
||||
return "Hello " + string(title) + " " + name
|
||||
}
|
||||
```
|
||||
|
||||
The `Title` enum is generated in the `services` models file, and import paths are resolved automatically:
|
||||
|
||||
```typescript
|
||||
// bindings/myapp/services/models.ts
|
||||
export enum Title {
|
||||
$zero = "",
|
||||
Mister = "Mr",
|
||||
Miss = "Miss",
|
||||
Ms = "Ms",
|
||||
}
|
||||
```
|
||||
|
||||
## Enum Methods
|
||||
|
||||
You can add methods to your enum types in Go. These don't affect binding generation but provide useful server-side functionality:
|
||||
|
||||
```go
|
||||
type Title string
|
||||
|
||||
func (t Title) String() string {
|
||||
return string(t)
|
||||
}
|
||||
|
||||
const (
|
||||
Mister Title = "Mr"
|
||||
Miss Title = "Miss"
|
||||
)
|
||||
```
|
||||
|
||||
The generated enum is identical whether or not Go methods exist on the type.
|
||||
|
||||
## Comments and Documentation
|
||||
|
||||
The generator preserves Go comments as JSDoc in the generated output:
|
||||
|
||||
- **Type comments** become the enum's doc comment
|
||||
- **Const group comments** become section separators
|
||||
- **Individual const comments** become member doc comments
|
||||
- **Inline comments** are preserved where possible
|
||||
|
||||
This means your IDE will show documentation for enum values on hover.
|
||||
|
||||
## Supported Underlying Types
|
||||
|
||||
The binding generator supports enums with the following Go underlying types:
|
||||
|
||||
| Go Type | Works as Enum |
|
||||
|---------|:---:|
|
||||
| `string` | Yes |
|
||||
| `int`, `int8`, `int16`, `int32`, `int64` | Yes |
|
||||
| `uint`, `uint8`, `uint16`, `uint32`, `uint64` | Yes |
|
||||
| `float32`, `float64` | Yes |
|
||||
| `byte` (`uint8`) | Yes |
|
||||
| `rune` (`int32`) | Yes |
|
||||
| `bool` | Yes |
|
||||
| `complex64`, `complex128` | No |
|
||||
|
||||
## Limitations
|
||||
|
||||
The following are **not** supported for enum generation:
|
||||
|
||||
- **Generic types** — Type parameters prevent constant detection
|
||||
- **Types with custom `json.Marshaler` or `encoding.TextMarshaler`** — Custom serialisation means the generated values may not match runtime behaviour, so the generator skips these
|
||||
- **Constants whose values cannot be statically evaluated or represented** — Constants must have known, representable values in their underlying type. Standard `iota` patterns work fine since the compiler resolves them to concrete values
|
||||
- **Complex number types** — `complex64` and `complex128` cannot be enum underlying types
|
||||
|
||||
## Complete Example
|
||||
|
||||
**Go:**
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
// BackgroundType defines the type of background
|
||||
type BackgroundType string
|
||||
|
||||
const (
|
||||
BackgroundSolid BackgroundType = "solid"
|
||||
BackgroundGradient BackgroundType = "gradient"
|
||||
BackgroundImage BackgroundType = "image"
|
||||
)
|
||||
|
||||
type BackgroundConfig struct {
|
||||
Type BackgroundType `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type ThemeService struct{}
|
||||
|
||||
func (*ThemeService) GetBackground() BackgroundConfig {
|
||||
return BackgroundConfig{
|
||||
Type: BackgroundSolid,
|
||||
Value: "#ffffff",
|
||||
}
|
||||
}
|
||||
|
||||
func (*ThemeService) SetBackground(config BackgroundConfig) error {
|
||||
// Apply background
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Services: []application.Service{
|
||||
application.NewService(&ThemeService{}),
|
||||
},
|
||||
})
|
||||
app.Window.New()
|
||||
app.Run()
|
||||
}
|
||||
```
|
||||
|
||||
**Frontend (TypeScript):**
|
||||
|
||||
```typescript
|
||||
import { GetBackground, SetBackground } from './bindings/myapp/themeservice'
|
||||
import { BackgroundConfig, BackgroundType } from './bindings/myapp/models'
|
||||
|
||||
// Get current background
|
||||
const bg = await GetBackground()
|
||||
|
||||
// Check the type using enum values
|
||||
if (bg.type === BackgroundType.BackgroundSolid) {
|
||||
console.log("Solid background:", bg.value)
|
||||
}
|
||||
|
||||
// Set a new background
|
||||
await SetBackground(new BackgroundConfig({
|
||||
type: BackgroundType.BackgroundGradient,
|
||||
value: "linear-gradient(to right, #000, #fff)"
|
||||
}))
|
||||
```
|
||||
|
||||
**Frontend (JavaScript):**
|
||||
|
||||
```javascript
|
||||
import { GetBackground, SetBackground } from './bindings/myapp/themeservice'
|
||||
import { BackgroundConfig, BackgroundType } from './bindings/myapp/models'
|
||||
|
||||
// Use enum values for type-safe comparisons
|
||||
const bg = await GetBackground()
|
||||
|
||||
switch (bg.type) {
|
||||
case BackgroundType.BackgroundSolid:
|
||||
applySolid(bg.value)
|
||||
break
|
||||
case BackgroundType.BackgroundGradient:
|
||||
applyGradient(bg.value)
|
||||
break
|
||||
case BackgroundType.BackgroundImage:
|
||||
applyImage(bg.value)
|
||||
break
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGrid>
|
||||
<Card title="Data Models" icon="document">
|
||||
Structs, type mapping, and model generation.
|
||||
|
||||
[Learn More →](/features/bindings/models)
|
||||
</Card>
|
||||
|
||||
<Card title="Method Binding" icon="rocket">
|
||||
Bind Go methods to the frontend.
|
||||
|
||||
[Learn More →](/features/bindings/methods)
|
||||
</Card>
|
||||
|
||||
<Card title="Advanced Binding" icon="setting">
|
||||
Directives, code injection, and custom IDs.
|
||||
|
||||
[Learn More →](/features/bindings/advanced)
|
||||
</Card>
|
||||
|
||||
<Card title="Best Practices" icon="approve-check">
|
||||
Binding design patterns.
|
||||
|
||||
[Learn More →](/features/bindings/best-practices)
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
||||
---
|
||||
|
||||
**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [binding examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/binding).
|
||||
|
|
@ -480,6 +480,8 @@ if (user.nickname) {
|
|||
|
||||
### Enums
|
||||
|
||||
The binding generator automatically detects Go named types with constants and generates TypeScript enums or JavaScript const objects — including a `$zero` member for Go's zero value and full JSDoc preservation.
|
||||
|
||||
```go
|
||||
type UserRole string
|
||||
|
||||
|
|
@ -488,26 +490,16 @@ const (
|
|||
RoleUser UserRole = "user"
|
||||
RoleGuest UserRole = "guest"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Role UserRole `json:"role"`
|
||||
}
|
||||
```
|
||||
|
||||
**Generated:**
|
||||
**Generated TypeScript:**
|
||||
|
||||
```javascript
|
||||
export const UserRole = {
|
||||
Admin: "admin",
|
||||
User: "user",
|
||||
Guest: "guest"
|
||||
}
|
||||
|
||||
export class User {
|
||||
/** @type {string} */
|
||||
role = UserRole.User
|
||||
```typescript
|
||||
export enum UserRole {
|
||||
$zero = "",
|
||||
RoleAdmin = "admin",
|
||||
RoleUser = "user",
|
||||
RoleGuest = "guest",
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -518,10 +510,12 @@ import { User, UserRole } from './bindings/myapp/models'
|
|||
|
||||
const admin = new User({
|
||||
name: "Admin",
|
||||
role: UserRole.Admin
|
||||
role: UserRole.RoleAdmin
|
||||
})
|
||||
```
|
||||
|
||||
For comprehensive coverage of string enums, integer enums, type aliases, imported package enums, and limitations, see the dedicated **[Enums](/features/bindings/enums)** page.
|
||||
|
||||
### Validation
|
||||
|
||||
```javascript
|
||||
|
|
|
|||
|
|
@ -131,6 +131,11 @@ Remove the badge from the application icon:
|
|||
dockService.RemoveBadge()
|
||||
```
|
||||
|
||||
### Getting the set badge
|
||||
```go
|
||||
dockService.GetBadge()
|
||||
```
|
||||
|
||||
## Platform Considerations
|
||||
|
||||
<Tabs>
|
||||
|
|
@ -220,6 +225,7 @@ dockService.RemoveBadge()
|
|||
| `SetBadge(label string) error` | Sets a badge with the specified label |
|
||||
| `SetCustomBadge(label string, options BadgeOptions) error` | Sets a badge with the specified label and custom styling options (Windows only) |
|
||||
| `RemoveBadge() error` | Removes the badge from the application icon |
|
||||
| `GetBadge() *string` | Gets the current badge |
|
||||
|
||||
### Structs and Types
|
||||
|
||||
|
|
|
|||
|
|
@ -394,7 +394,7 @@ body {
|
|||
```
|
||||
|
||||
**Invisible title bar:**
|
||||
Allows dragging whilst hiding the title bar:
|
||||
Allows dragging whilst hiding the title bar. This only takes effect when the window is frameless or uses `AppearsTransparent`:
|
||||
```go
|
||||
Mac: application.MacOptions{
|
||||
InvisibleTitleBarHeight: 40,
|
||||
|
|
|
|||
|
|
@ -793,7 +793,8 @@ Mac: application.MacWindow{
|
|||
|
||||
**InvisibleTitleBarHeight** (`int`)
|
||||
- Height of invisible title bar area (for dragging)
|
||||
- Useful when title bar is hidden
|
||||
- Only takes effect when the native title bar drag area is hidden — i.e. when the window is frameless (`Frameless: true`) or uses a transparent title bar (`AppearsTransparent: true`)
|
||||
- Has no effect on standard windows with a visible title bar
|
||||
|
||||
**WindowLevel** (`MacWindowLevel`)
|
||||
- `MacWindowLevelNormal` - Standard window level (default)
|
||||
|
|
|
|||
|
|
@ -92,14 +92,14 @@ tasks:
|
|||
GOARCH: amd64
|
||||
|
||||
test:example:linux:
|
||||
summary: Build example for Linux with GTK3 (default)
|
||||
summary: Build example for Linux (pass BUILD_TAGS env var to add -tags, e.g. BUILD_TAGS=gtk4)
|
||||
dir: 'examples/{{.DIR}}'
|
||||
platforms:
|
||||
- linux
|
||||
cmds:
|
||||
- echo "Building example {{.DIR}} for Linux (GTK3)"
|
||||
- echo "Building example {{.DIR}} for Linux (${BUILD_TAGS:-GTK3})"
|
||||
- go mod tidy
|
||||
- go build -o "testbuild-{{.DIR}}-linux"
|
||||
- go build ${BUILD_TAGS:+-tags $BUILD_TAGS} -o "testbuild-{{.DIR}}-linux"
|
||||
|
||||
test:example:linux:gtk4:
|
||||
summary: Build example for Linux with GTK4 (experimental, opt-in via -tags gtk4)
|
||||
|
|
@ -365,12 +365,7 @@ tasks:
|
|||
vars:
|
||||
DIR: "{{.ITEM}}"
|
||||
platforms: [linux]
|
||||
# GTK4 tests are run separately via Docker tasks since CI doesn't have GTK4 deps
|
||||
# - for: { var: EXAMPLEDIRS }
|
||||
# task: test:example:linux:gtk4
|
||||
# vars:
|
||||
# DIR: "{{.ITEM}}"
|
||||
# platforms: [linux]
|
||||
# GTK4 example builds are handled in CI via: BUILD_TAGS=gtk4 task test:examples
|
||||
- for: { var: EXAMPLEDIRS }
|
||||
task: test:example:windows
|
||||
vars:
|
||||
|
|
|
|||
|
|
@ -17,26 +17,12 @@ After processing, the content will be moved to the main changelog and this file
|
|||
|
||||
## Added
|
||||
<!-- New features, capabilities, or enhancements -->
|
||||
- Add Web API examples in `v3/examples/web-apis/` demonstrating 41 browser APIs including Storage (localStorage, sessionStorage, IndexedDB, Cache API), Network (Fetch, WebSocket, XMLHttpRequest, EventSource, Beacon), Media (Canvas, WebGL, Web Audio, MediaDevices, MediaRecorder, Speech Synthesis), Device (Geolocation, Clipboard, Fullscreen, Device Orientation, Vibration, Gamepad), Performance (Performance API, Mutation Observer, Intersection/Resize Observer), UI (Web Components, Pointer Events, Selection, Dialog, Drag and Drop), and more
|
||||
- Add WebView API compatibility checker example (`v3/examples/webview-api-check/`) that tests 200+ browser APIs across platforms
|
||||
- Add `internal/libpath` package for finding native library paths on Linux with parallel search, caching, and support for Flatpak/Snap/Nix
|
||||
- **WIP:** Add experimental WebKitGTK 6.0 / GTK4 support for Linux, available via `-tags gtk4` (GTK3/WebKit2GTK 4.1 remains the default)
|
||||
- Note: On tiling window managers (e.g., Hyprland, Sway), Minimize/Maximize operations may not work as expected since the WM controls window geometry
|
||||
- Add macOS window option to configure NSWindow tabbing behavior
|
||||
- Add macOS window option to configure NSWindow tabbing behavior (#4966)
|
||||
## Changed
|
||||
<!-- Changes in existing functionality -->
|
||||
- **BREAKING**: Map keys in generated JS/TS bindings are now marked optional to accurately reflect Go map semantics. Map value access in Typescript now returns `T | undefined` instead of `T`, requiring null checks or assertions (#4943) by `@fbbdev`
|
||||
|
||||
## Fixed
|
||||
<!-- Bug fixes -->
|
||||
- Fix file drag-and-drop on Windows not working at non-100% display scaling
|
||||
- Fix HTML5 internal drag-and-drop being broken when file drop was enabled on Windows
|
||||
- Fix file drop coordinates being in wrong pixel space on Windows (physical vs CSS pixels)
|
||||
- Fix file drag-and-drop on Linux not working reliably with hover effects
|
||||
- Fix HTML5 internal drag-and-drop being broken when file drop was enabled on Linux
|
||||
- Fix DPI scaling on Linux/GTK4 by implementing proper PhysicalBounds calculation and fractional scaling support via `gdk_monitor_get_scale` (GTK 4.14+)
|
||||
- Fix menu items duplicating when creating new windows on Linux/GTK4
|
||||
- Fix generation of mapped types with enum keys in JS/TS bindings (#4437) by @fbbdev
|
||||
|
||||
## Deprecated
|
||||
<!-- Soon-to-be removed features -->
|
||||
|
|
|
|||
10
v3/go.mod
10
v3/go.mod
|
|
@ -6,8 +6,11 @@ require (
|
|||
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/adrg/xdg v0.5.3
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/atterpac/refresh v0.8.6
|
||||
github.com/bep/debounce v1.2.1
|
||||
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/glamour v0.10.0
|
||||
github.com/charmbracelet/huh v0.8.0
|
||||
github.com/coder/websocket v1.8.14
|
||||
|
|
@ -22,7 +25,7 @@ require (
|
|||
github.com/goreleaser/nfpm/v2 v2.44.1
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/jackmordaunt/icns/v2 v2.2.7
|
||||
github.com/jaypipes/ghw v0.21.2
|
||||
github.com/jaypipes/ghw v0.21.3
|
||||
github.com/konoui/lipo v0.10.0
|
||||
github.com/leaanthony/clir v1.7.0
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1
|
||||
|
|
@ -52,10 +55,7 @@ require (
|
|||
require (
|
||||
al.essio.dev/pkg/shellescape v1.6.0 // indirect
|
||||
atomicgo.dev/schedule v0.1.0 // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/catppuccin/go v0.3.0 // indirect
|
||||
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
|
||||
github.com/charmbracelet/bubbletea v1.3.10 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.1 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.14 // indirect
|
||||
github.com/charmbracelet/x/exp/slice v0.0.0-20260122224438-b01af16209d9 // indirect
|
||||
|
|
@ -93,7 +93,7 @@ require (
|
|||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
|
||||
github.com/cavaliergopher/cpio v1.0.1 // indirect
|
||||
github.com/chainguard-dev/git-urls v1.0.2 // indirect
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
|
||||
github.com/charmbracelet/x/ansi v0.11.4 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/containerd/console v1.0.5 // indirect
|
||||
|
|
|
|||
|
|
@ -218,8 +218,8 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI
|
|||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/jackmordaunt/icns/v2 v2.2.7 h1:K/RbfvuzjmjVY5y4g+XENRs8ZZatwz4YnLHypa2KwQg=
|
||||
github.com/jackmordaunt/icns/v2 v2.2.7/go.mod h1:ovoTxGguSuoUGKMk5Nn3R7L7BgMQkylsO+bblBuI22A=
|
||||
github.com/jaypipes/ghw v0.21.2 h1:woW0lqNMPbYk59sur6thOVM8YFP9Hxxr8PM+JtpUrNU=
|
||||
github.com/jaypipes/ghw v0.21.2/go.mod h1:GPrvwbtPoxYUenr74+nAnWbardIZq600vJDD5HnPsPE=
|
||||
github.com/jaypipes/ghw v0.21.3 h1:v5mUHM+RN854Vqmk49Uh213jyUA4+8uqaRajlYESsh8=
|
||||
github.com/jaypipes/ghw v0.21.3/go.mod h1:GPrvwbtPoxYUenr74+nAnWbardIZq600vJDD5HnPsPE=
|
||||
github.com/jaypipes/pcidb v1.1.1 h1:QmPhpsbmmnCwZmHeYAATxEaoRuiMAJusKYkUncMC0ro=
|
||||
github.com/jaypipes/pcidb v1.1.1/go.mod h1:x27LT2krrUgjf875KxQXKB0Ha/YXLdZRVmw6hH0G7g8=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ tasks:
|
|||
ARCH: '{{.ARCH}}'
|
||||
DEV: '{{.DEV}}'
|
||||
OUTPUT: '{{.OUTPUT}}'
|
||||
EXTRA_TAGS: '{{.EXTRA_TAGS}}'
|
||||
vars:
|
||||
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
|
||||
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
|
||||
|
|
@ -40,7 +41,7 @@ tasks:
|
|||
cmds:
|
||||
- go build {{.BUILD_FLAGS}} -o {{.OUTPUT}}
|
||||
vars:
|
||||
BUILD_FLAGS: '{{if eq .DEV "true"}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{end}}'
|
||||
BUILD_FLAGS: '{{if eq .DEV "true"}}{{if .EXTRA_TAGS}}-tags {{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s"{{end}}'
|
||||
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
|
||||
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
|
||||
env:
|
||||
|
|
@ -65,7 +66,7 @@ tasks:
|
|||
Docker image '{{.CROSS_IMAGE}}' not found.
|
||||
Build it first: wails3 task setup:docker
|
||||
cmds:
|
||||
- docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{.CROSS_IMAGE}} darwin {{.DOCKER_ARCH}}
|
||||
- docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{if .EXTRA_TAGS}}-e EXTRA_TAGS="{{.EXTRA_TAGS}}"{{end}} {{.CROSS_IMAGE}} darwin {{.DOCKER_ARCH}}
|
||||
- docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin
|
||||
- mkdir -p {{.BIN_DIR}}
|
||||
- mv "bin/{{.APP_NAME}}-darwin-{{.DOCKER_ARCH}}" "{{.OUTPUT}}"
|
||||
|
|
|
|||
|
|
@ -188,7 +188,12 @@ if [ "$GOOS" = "windows" ]; then
|
|||
LDFLAGS="-s -w -H windowsgui"
|
||||
fi
|
||||
|
||||
go build -ldflags="$LDFLAGS" -o bin/${APP}-${GOOS}-${GOARCH}${EXT} .
|
||||
TAGS="production"
|
||||
if [ -n "$EXTRA_TAGS" ]; then
|
||||
TAGS="${TAGS},${EXTRA_TAGS}"
|
||||
fi
|
||||
|
||||
go build -tags "$TAGS" -trimpath -buildvcs=false -ldflags="$LDFLAGS" -o bin/${APP}-${GOOS}-${GOARCH}${EXT} .
|
||||
echo "Built: bin/${APP}-${GOOS}-${GOARCH}${EXT}"
|
||||
SCRIPT
|
||||
RUN chmod +x /usr/local/bin/build.sh
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ tasks:
|
|||
ARCH: '{{.ARCH}}'
|
||||
DEV: '{{.DEV}}'
|
||||
OUTPUT: '{{.OUTPUT}}'
|
||||
EXTRA_TAGS: '{{.EXTRA_TAGS}}'
|
||||
vars:
|
||||
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
|
||||
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
|
||||
|
|
@ -51,7 +52,7 @@ tasks:
|
|||
cmds:
|
||||
- go build {{.BUILD_FLAGS}} -o {{.OUTPUT}}
|
||||
vars:
|
||||
BUILD_FLAGS: '{{if eq .DEV "true"}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{end}}'
|
||||
BUILD_FLAGS: '{{if eq .DEV "true"}}{{if .EXTRA_TAGS}}-tags {{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s"{{end}}'
|
||||
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
|
||||
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
|
||||
env:
|
||||
|
|
@ -74,7 +75,7 @@ tasks:
|
|||
Docker image '{{.CROSS_IMAGE}}' not found.
|
||||
Build it first: wails3 task setup:docker
|
||||
cmds:
|
||||
- docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" "{{.CROSS_IMAGE}}" linux {{.DOCKER_ARCH}}
|
||||
- docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{if .EXTRA_TAGS}}-e EXTRA_TAGS="{{.EXTRA_TAGS}}"{{end}} "{{.CROSS_IMAGE}}" linux {{.DOCKER_ARCH}}
|
||||
- docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin
|
||||
- mkdir -p {{.BIN_DIR}}
|
||||
- mv "bin/{{.APP_NAME}}-linux-{{.DOCKER_ARCH}}" "{{.OUTPUT}}"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ tasks:
|
|||
vars:
|
||||
ARCH: '{{.ARCH}}'
|
||||
DEV: '{{.DEV}}'
|
||||
EXTRA_TAGS: '{{.EXTRA_TAGS}}'
|
||||
vars:
|
||||
# Default to CGO_ENABLED=0 if not explicitly set
|
||||
CGO_ENABLED: '{{.CGO_ENABLED | default "0"}}'
|
||||
|
|
@ -47,7 +48,7 @@ tasks:
|
|||
- cmd: rm -f *.syso
|
||||
platforms: [linux, darwin]
|
||||
vars:
|
||||
BUILD_FLAGS: '{{if eq .DEV "true"}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{end}}'
|
||||
BUILD_FLAGS: '{{if eq .DEV "true"}}{{if .EXTRA_TAGS}}-tags {{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l"{{else}}-tags production{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{end}}'
|
||||
env:
|
||||
GOOS: windows
|
||||
CGO_ENABLED: '{{.CGO_ENABLED | default "0"}}'
|
||||
|
|
@ -68,7 +69,7 @@ tasks:
|
|||
Build it first: wails3 task setup:docker
|
||||
cmds:
|
||||
- task: generate:syso
|
||||
- docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{.CROSS_IMAGE}} windows {{.DOCKER_ARCH}}
|
||||
- docker run --rm -v "{{.ROOT_DIR}}:/app" {{.GO_CACHE_MOUNT}} {{.REPLACE_MOUNTS}} -e APP_NAME="{{.APP_NAME}}" {{if .EXTRA_TAGS}}-e EXTRA_TAGS="{{.EXTRA_TAGS}}"{{end}} {{.CROSS_IMAGE}} windows {{.DOCKER_ARCH}}
|
||||
- docker run --rm -v "{{.ROOT_DIR}}:/app" alpine chown -R $(id -u):$(id -g) /app/bin
|
||||
- rm -f *.syso
|
||||
vars:
|
||||
|
|
|
|||
|
|
@ -18,7 +18,10 @@ var validPlatforms = map[string]bool{
|
|||
"linux": true,
|
||||
}
|
||||
|
||||
func Build(_ *flags.Build, otherArgs []string) error {
|
||||
func Build(buildFlags *flags.Build, otherArgs []string) error {
|
||||
if buildFlags.Tags != "" {
|
||||
otherArgs = append(otherArgs, "EXTRA_TAGS="+buildFlags.Tags)
|
||||
}
|
||||
return wrapTask("build", otherArgs)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -199,6 +199,150 @@ func TestBuildCommand(t *testing.T) {
|
|||
assert.Equal(t, []string{"CONFIG=release", "ARCH=" + currentArch}, capturedOtherArgs)
|
||||
}
|
||||
|
||||
func TestBuildCommandWithTags(t *testing.T) {
|
||||
currentOS := runtime.GOOS
|
||||
currentArch := runtime.GOARCH
|
||||
|
||||
// Save original RunTask
|
||||
originalRunTask := runTaskFunc
|
||||
defer func() { runTaskFunc = originalRunTask }()
|
||||
|
||||
// Mock RunTask to capture the arguments
|
||||
var capturedOptions *RunTaskOptions
|
||||
var capturedOtherArgs []string
|
||||
runTaskFunc = func(options *RunTaskOptions, otherArgs []string) error {
|
||||
capturedOptions = options
|
||||
capturedOtherArgs = otherArgs
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save original os.Args and environment
|
||||
originalArgs := os.Args
|
||||
defer func() { os.Args = originalArgs }()
|
||||
|
||||
originalGOOS := os.Getenv("GOOS")
|
||||
originalGOARCH := os.Getenv("GOARCH")
|
||||
defer func() {
|
||||
if originalGOOS == "" {
|
||||
os.Unsetenv("GOOS")
|
||||
} else {
|
||||
os.Setenv("GOOS", originalGOOS)
|
||||
}
|
||||
if originalGOARCH == "" {
|
||||
os.Unsetenv("GOARCH")
|
||||
} else {
|
||||
os.Setenv("GOARCH", originalGOARCH)
|
||||
}
|
||||
}()
|
||||
os.Unsetenv("GOOS")
|
||||
os.Unsetenv("GOARCH")
|
||||
|
||||
// Test Build command with tags
|
||||
buildFlags := &flags.Build{}
|
||||
buildFlags.Tags = "gtk4"
|
||||
otherArgs := []string{"CONFIG=release"}
|
||||
|
||||
err := Build(buildFlags, otherArgs)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, currentOS+":build", capturedOptions.Name)
|
||||
assert.Equal(t, []string{"CONFIG=release", "EXTRA_TAGS=gtk4", "ARCH=" + currentArch}, capturedOtherArgs)
|
||||
}
|
||||
|
||||
func TestBuildCommandWithMultipleTags(t *testing.T) {
|
||||
currentOS := runtime.GOOS
|
||||
currentArch := runtime.GOARCH
|
||||
|
||||
// Save original RunTask
|
||||
originalRunTask := runTaskFunc
|
||||
defer func() { runTaskFunc = originalRunTask }()
|
||||
|
||||
// Mock RunTask to capture the arguments
|
||||
var capturedOptions *RunTaskOptions
|
||||
var capturedOtherArgs []string
|
||||
runTaskFunc = func(options *RunTaskOptions, otherArgs []string) error {
|
||||
capturedOptions = options
|
||||
capturedOtherArgs = otherArgs
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save original os.Args and environment
|
||||
originalArgs := os.Args
|
||||
defer func() { os.Args = originalArgs }()
|
||||
|
||||
originalGOOS := os.Getenv("GOOS")
|
||||
originalGOARCH := os.Getenv("GOARCH")
|
||||
defer func() {
|
||||
if originalGOOS == "" {
|
||||
os.Unsetenv("GOOS")
|
||||
} else {
|
||||
os.Setenv("GOOS", originalGOOS)
|
||||
}
|
||||
if originalGOARCH == "" {
|
||||
os.Unsetenv("GOARCH")
|
||||
} else {
|
||||
os.Setenv("GOARCH", originalGOARCH)
|
||||
}
|
||||
}()
|
||||
os.Unsetenv("GOOS")
|
||||
os.Unsetenv("GOARCH")
|
||||
|
||||
// Test Build command with multiple comma-separated tags
|
||||
buildFlags := &flags.Build{}
|
||||
buildFlags.Tags = "gtk4,server"
|
||||
|
||||
err := Build(buildFlags, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, currentOS+":build", capturedOptions.Name)
|
||||
assert.Equal(t, []string{"EXTRA_TAGS=gtk4,server", "ARCH=" + currentArch}, capturedOtherArgs)
|
||||
}
|
||||
|
||||
func TestBuildCommandWithoutTags(t *testing.T) {
|
||||
currentOS := runtime.GOOS
|
||||
currentArch := runtime.GOARCH
|
||||
|
||||
// Save original RunTask
|
||||
originalRunTask := runTaskFunc
|
||||
defer func() { runTaskFunc = originalRunTask }()
|
||||
|
||||
// Mock RunTask to capture the arguments
|
||||
var capturedOptions *RunTaskOptions
|
||||
var capturedOtherArgs []string
|
||||
runTaskFunc = func(options *RunTaskOptions, otherArgs []string) error {
|
||||
capturedOptions = options
|
||||
capturedOtherArgs = otherArgs
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save original os.Args and environment
|
||||
originalArgs := os.Args
|
||||
defer func() { os.Args = originalArgs }()
|
||||
|
||||
originalGOOS := os.Getenv("GOOS")
|
||||
originalGOARCH := os.Getenv("GOARCH")
|
||||
defer func() {
|
||||
if originalGOOS == "" {
|
||||
os.Unsetenv("GOOS")
|
||||
} else {
|
||||
os.Setenv("GOOS", originalGOOS)
|
||||
}
|
||||
if originalGOARCH == "" {
|
||||
os.Unsetenv("GOARCH")
|
||||
} else {
|
||||
os.Setenv("GOARCH", originalGOARCH)
|
||||
}
|
||||
}()
|
||||
os.Unsetenv("GOOS")
|
||||
os.Unsetenv("GOARCH")
|
||||
|
||||
// Test Build command without tags - no EXTRA_TAGS should be present
|
||||
buildFlags := &flags.Build{}
|
||||
|
||||
err := Build(buildFlags, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, currentOS+":build", capturedOptions.Name)
|
||||
assert.Equal(t, []string{"ARCH=" + currentArch}, capturedOtherArgs)
|
||||
}
|
||||
|
||||
func TestPackageCommand(t *testing.T) {
|
||||
currentOS := runtime.GOOS
|
||||
currentArch := runtime.GOARCH
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package flags
|
|||
|
||||
type Build struct {
|
||||
Common
|
||||
Tags string `name:"tags" description:"Additional build tags to pass to the Go compiler (comma-separated)"`
|
||||
}
|
||||
|
||||
type Dev struct {
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
v3.0.0-alpha.67
|
||||
v3.0.0-alpha.71
|
||||
|
|
@ -2,6 +2,31 @@
|
|||
|
||||
#include "linux_cgo_gtk4.h"
|
||||
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
#include <gdk/x11/gdkx.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
// Function pointer types for Xlib functions loaded at runtime via dlsym.
|
||||
// This avoids a direct link dependency on libX11 - the symbols are resolved
|
||||
// from GTK4's already-loaded X11 backend.
|
||||
typedef int (*WailsXMoveWindowFunc)(Display*, Window, int, int);
|
||||
typedef int (*WailsXFlushFunc)(Display*);
|
||||
typedef Bool (*WailsXTranslateCoordinatesFunc)(Display*, Window, Window, int, int, int*, int*, Window*);
|
||||
|
||||
static WailsXMoveWindowFunc wails_XMoveWindow = NULL;
|
||||
static WailsXFlushFunc wails_XFlush = NULL;
|
||||
static WailsXTranslateCoordinatesFunc wails_XTranslateCoordinates = NULL;
|
||||
static gboolean x11_funcs_resolved = FALSE;
|
||||
|
||||
static void resolve_x11_funcs(void) {
|
||||
if (x11_funcs_resolved) return;
|
||||
x11_funcs_resolved = TRUE;
|
||||
wails_XMoveWindow = (WailsXMoveWindowFunc)dlsym(RTLD_DEFAULT, "XMoveWindow");
|
||||
wails_XFlush = (WailsXFlushFunc)dlsym(RTLD_DEFAULT, "XFlush");
|
||||
wails_XTranslateCoordinates = (WailsXTranslateCoordinatesFunc)dlsym(RTLD_DEFAULT, "XTranslateCoordinates");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WAILS_GTK_DEBUG
|
||||
#define DEBUG_LOG(fmt, ...) fprintf(stderr, "[GTK4] " fmt "\n", ##__VA_ARGS__)
|
||||
#else
|
||||
|
|
@ -867,6 +892,102 @@ void clipboard_free_text(char *text) {
|
|||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Window position (X11 only)
|
||||
// ============================================================================
|
||||
|
||||
void window_move_x11(GtkWindow *window, int x, int y) {
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
GtkNative *native = gtk_widget_get_native(GTK_WIDGET(window));
|
||||
if (native == NULL) return;
|
||||
|
||||
GdkSurface *surface = gtk_native_get_surface(native);
|
||||
if (surface == NULL) return;
|
||||
|
||||
GdkDisplay *display = gdk_surface_get_display(surface);
|
||||
if (!GDK_IS_X11_DISPLAY(display)) return;
|
||||
|
||||
resolve_x11_funcs();
|
||||
if (wails_XMoveWindow == NULL) return;
|
||||
|
||||
Display *xdisplay = gdk_x11_display_get_xdisplay(display);
|
||||
Window xwindow = gdk_x11_surface_get_xid(GDK_X11_SURFACE(surface));
|
||||
wails_XMoveWindow(xdisplay, xwindow, x, y);
|
||||
if (wails_XFlush != NULL) wails_XFlush(xdisplay);
|
||||
#endif
|
||||
}
|
||||
|
||||
void window_get_position_x11(GtkWindow *window, int *x, int *y) {
|
||||
*x = 0;
|
||||
*y = 0;
|
||||
#ifdef GDK_WINDOWING_X11
|
||||
GtkNative *native = gtk_widget_get_native(GTK_WIDGET(window));
|
||||
if (native == NULL) return;
|
||||
|
||||
GdkSurface *surface = gtk_native_get_surface(native);
|
||||
if (surface == NULL) return;
|
||||
|
||||
GdkDisplay *display = gdk_surface_get_display(surface);
|
||||
if (!GDK_IS_X11_DISPLAY(display)) return;
|
||||
|
||||
resolve_x11_funcs();
|
||||
if (wails_XTranslateCoordinates == NULL) return;
|
||||
|
||||
Display *xdisplay = gdk_x11_display_get_xdisplay(display);
|
||||
Window xwindow = gdk_x11_surface_get_xid(GDK_X11_SURFACE(surface));
|
||||
|
||||
Window child;
|
||||
Window root = DefaultRootWindow(xdisplay);
|
||||
int abs_x, abs_y;
|
||||
if (wails_XTranslateCoordinates(xdisplay, xwindow, root, 0, 0, &abs_x, &abs_y, &child)) {
|
||||
*x = abs_x;
|
||||
*y = abs_y;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Window size constraints (max size enforcement for GTK4)
|
||||
// ============================================================================
|
||||
|
||||
static void on_window_size_changed(GObject *object, GParamSpec *pspec, gpointer data) {
|
||||
GtkWindow *window = GTK_WINDOW(object);
|
||||
|
||||
// Don't clamp during fullscreen or maximize - these should bypass max size
|
||||
// constraints, matching V2 behaviour where geometry hints are suspended.
|
||||
if (gtk_window_is_fullscreen(window) || gtk_window_is_maximized(window)) return;
|
||||
|
||||
int maxW = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(window), "wails-max-width"));
|
||||
int maxH = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(window), "wails-max-height"));
|
||||
|
||||
if (maxW <= 0 && maxH <= 0) return;
|
||||
|
||||
int w = gtk_widget_get_width(GTK_WIDGET(window));
|
||||
int h = gtk_widget_get_height(GTK_WIDGET(window));
|
||||
|
||||
gboolean needs_clamp = FALSE;
|
||||
if (maxW > 0 && w > maxW) { w = maxW; needs_clamp = TRUE; }
|
||||
if (maxH > 0 && h > maxH) { h = maxH; needs_clamp = TRUE; }
|
||||
|
||||
if (needs_clamp) {
|
||||
gtk_window_set_default_size(window, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
void window_set_max_size(GtkWindow *window, int maxWidth, int maxHeight) {
|
||||
g_object_set_data(G_OBJECT(window), "wails-max-width", GINT_TO_POINTER(maxWidth));
|
||||
g_object_set_data(G_OBJECT(window), "wails-max-height", GINT_TO_POINTER(maxHeight));
|
||||
|
||||
// Check if we already connected the signal
|
||||
gpointer connected = g_object_get_data(G_OBJECT(window), "wails-max-size-connected");
|
||||
if (connected == NULL) {
|
||||
g_signal_connect(window, "notify::default-width", G_CALLBACK(on_window_size_changed), NULL);
|
||||
g_signal_connect(window, "notify::default-height", G_CALLBACK(on_window_size_changed), NULL);
|
||||
g_object_set_data(G_OBJECT(window), "wails-max-size-connected", GINT_TO_POINTER(1));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Misc
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -1063,9 +1063,14 @@ func (w *linuxWebviewWindow) size() (int, int) {
|
|||
}
|
||||
|
||||
func (w *linuxWebviewWindow) relativePosition() (int, int) {
|
||||
// GTK4/Wayland: Window positioning is not reliable
|
||||
// This is a documented limitation
|
||||
return 0, 0
|
||||
x, y := w.position()
|
||||
monitor := w.getCurrentMonitor()
|
||||
if monitor == nil {
|
||||
return x, y
|
||||
}
|
||||
var geometry C.GdkRectangle
|
||||
C.gdk_monitor_get_geometry(monitor, &geometry)
|
||||
return x - int(geometry.x), y - int(geometry.y)
|
||||
}
|
||||
|
||||
func (w *linuxWebviewWindow) gtkWidget() *C.GtkWidget {
|
||||
|
|
@ -1228,6 +1233,9 @@ func windowSetGeometryHints(window pointer, minWidth, minHeight, maxWidth, maxHe
|
|||
if minWidth > 0 && minHeight > 0 {
|
||||
C.gtk_widget_set_size_request(w, C.int(minWidth), C.int(minHeight))
|
||||
}
|
||||
if maxWidth > 0 || maxHeight > 0 {
|
||||
C.window_set_max_size((*C.GtkWindow)(window), C.int(maxWidth), C.int(maxHeight))
|
||||
}
|
||||
}
|
||||
|
||||
func (w *linuxWebviewWindow) setResizable(resizable bool) {
|
||||
|
|
@ -1235,11 +1243,17 @@ func (w *linuxWebviewWindow) setResizable(resizable bool) {
|
|||
}
|
||||
|
||||
func (w *linuxWebviewWindow) move(x, y int) {
|
||||
// C-side GDK_IS_X11_DISPLAY check handles X11 vs Wayland correctly,
|
||||
// including XWayland and GDK_BACKEND=x11 scenarios.
|
||||
C.window_move_x11(w.gtkWindow(), C.int(x), C.int(y))
|
||||
}
|
||||
|
||||
func (w *linuxWebviewWindow) position() (int, int) {
|
||||
// GTK4/Wayland: Cannot reliably get window position
|
||||
return 0, 0
|
||||
// C-side GDK_IS_X11_DISPLAY check handles X11 vs Wayland correctly,
|
||||
// returning 0,0 on non-X11 displays.
|
||||
var x, y C.int
|
||||
C.window_get_position_x11(w.gtkWindow(), &x, &y)
|
||||
return int(x), int(y)
|
||||
}
|
||||
|
||||
func (w *linuxWebviewWindow) unfullscreen() {
|
||||
|
|
@ -1255,7 +1269,7 @@ func (w *linuxWebviewWindow) windowShow() {
|
|||
if w.gtkWidget() == nil {
|
||||
return
|
||||
}
|
||||
C.gtk_widget_set_visible(w.gtkWidget(), gtkBool(true))
|
||||
C.gtk_window_present(w.gtkWindow())
|
||||
}
|
||||
|
||||
func (w *linuxWebviewWindow) setAlwaysOnTop(alwaysOnTop bool) {
|
||||
|
|
|
|||
|
|
@ -29,22 +29,6 @@ typedef struct WindowEvent {
|
|||
uint event;
|
||||
} WindowEvent;
|
||||
|
||||
typedef struct Screen {
|
||||
const char* id;
|
||||
const char* name;
|
||||
int p_width;
|
||||
int p_height;
|
||||
int x;
|
||||
int y;
|
||||
int w_width;
|
||||
int w_height;
|
||||
int w_x;
|
||||
int w_y;
|
||||
float scaleFactor;
|
||||
double rotation;
|
||||
bool isPrimary;
|
||||
} Screen;
|
||||
|
||||
typedef struct MenuItemData {
|
||||
guint id;
|
||||
GSimpleAction *action;
|
||||
|
|
@ -155,6 +139,13 @@ void setupWindowEventControllers(GtkWindow *window, GtkWidget *webview, uintptr_
|
|||
void beginWindowDrag(GtkWindow *window, int button, double x, double y, guint32 timestamp);
|
||||
void beginWindowResize(GtkWindow *window, GdkSurfaceEdge edge, int button, double x, double y, guint32 timestamp);
|
||||
|
||||
// ============================================================================
|
||||
// Window position (X11 only)
|
||||
// ============================================================================
|
||||
|
||||
void window_move_x11(GtkWindow *window, int x, int y);
|
||||
void window_get_position_x11(GtkWindow *window, int *x, int *y);
|
||||
|
||||
// ============================================================================
|
||||
// Drag and drop (GtkDropTarget for GTK4)
|
||||
// ============================================================================
|
||||
|
|
@ -189,6 +180,12 @@ void show_message_dialog(GtkWindow *parent, const char *heading, const char *bod
|
|||
char* clipboard_get_text_sync(void);
|
||||
void clipboard_free_text(char *text);
|
||||
|
||||
// ============================================================================
|
||||
// Window size constraints
|
||||
// ============================================================================
|
||||
|
||||
void window_set_max_size(GtkWindow *window, int maxWidth, int maxHeight);
|
||||
|
||||
// ============================================================================
|
||||
// Misc
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -1358,7 +1358,10 @@ func (w *macosWebviewWindow) run() {
|
|||
C.windowSetAppearanceTypeByName(w.nsWindow, C.CString(string(macOptions.Appearance)))
|
||||
}
|
||||
|
||||
if macOptions.InvisibleTitleBarHeight != 0 {
|
||||
// Only apply invisible title bar when the native drag area is hidden
|
||||
// (frameless window or transparent/hidden title bar presets like HiddenInset)
|
||||
if macOptions.InvisibleTitleBarHeight != 0 &&
|
||||
(w.parent.options.Frameless || titleBarOptions.AppearsTransparent) {
|
||||
C.windowSetInvisibleTitleBar(w.nsWindow, C.uint(macOptions.InvisibleTitleBarHeight))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -345,6 +345,15 @@ typedef NS_ENUM(NSInteger, MacLiquidGlassStyle) {
|
|||
NSPoint location = [event locationInWindow];
|
||||
NSRect frame = [window frame];
|
||||
if( location.y > frame.size.height - self.invisibleTitleBarHeight ) {
|
||||
// Skip drag if the click is near a window edge (resize zone).
|
||||
// This prevents conflict between dragging and native top-corner resizing,
|
||||
// which causes window content to shake/jitter (#4960).
|
||||
CGFloat resizeThreshold = 5.0;
|
||||
BOOL nearLeftEdge = location.x < resizeThreshold;
|
||||
BOOL nearRightEdge = location.x > frame.size.width - resizeThreshold;
|
||||
if( nearLeftEdge || nearRightEdge ) {
|
||||
return;
|
||||
}
|
||||
[window performWindowDragWithEvent:event];
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ import (
|
|||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
type iosDock struct{}
|
||||
type iosDock struct {
|
||||
}
|
||||
|
||||
// New creates a new Dock Service.
|
||||
// On iOS, this returns a stub implementation.
|
||||
|
|
@ -61,4 +62,10 @@ func (d *iosDock) SetCustomBadge(label string, options BadgeOptions) error {
|
|||
func (d *iosDock) RemoveBadge() error {
|
||||
// iOS badge removal would go here via native bridge
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetBadge retrieves the badge from the iOS app icon.
|
||||
func (d *iosDock) GetBadge() *string {
|
||||
// iOS badge retrieval would go here via native bridge
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ type platformDock interface {
|
|||
SetBadge(label string) error
|
||||
SetCustomBadge(label string, options BadgeOptions) error
|
||||
RemoveBadge() error
|
||||
GetBadge() *string
|
||||
}
|
||||
|
||||
// Service represents the dock service
|
||||
|
|
@ -75,3 +76,8 @@ func (d *DockService) SetCustomBadge(label string, options BadgeOptions) error {
|
|||
func (d *DockService) RemoveBadge() error {
|
||||
return d.impl.RemoveBadge()
|
||||
}
|
||||
|
||||
// GetBadge returns the badge label on the application icon.
|
||||
func (d *DockService) GetBadge() *string {
|
||||
return d.impl.GetBadge()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,42 +8,59 @@ package dock
|
|||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
void hideDockIcon() {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
|
||||
});
|
||||
}
|
||||
|
||||
void showDockIcon() {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
});
|
||||
}
|
||||
|
||||
static void setBadge(const char *label) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
bool setBadge(const char *label) {
|
||||
__block bool success = false;
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
// Ensure the app is in Regular activation policy (dock icon visible)
|
||||
NSApplicationActivationPolicy currentPolicy = [NSApp activationPolicy];
|
||||
if (currentPolicy != NSApplicationActivationPolicyRegular) {
|
||||
success = false;
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *nsLabel = nil;
|
||||
if (label != NULL) {
|
||||
nsLabel = [NSString stringWithUTF8String:label];
|
||||
}
|
||||
[[NSApp dockTile] setBadgeLabel:nsLabel];
|
||||
[[NSApp dockTile] display];
|
||||
success = true;
|
||||
});
|
||||
return success;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
type darwinDock struct{}
|
||||
type darwinDock struct {
|
||||
mu sync.RWMutex
|
||||
Badge *string
|
||||
}
|
||||
|
||||
// Creates a new Dock Service.
|
||||
func New() *DockService {
|
||||
return &DockService{
|
||||
impl: &darwinDock{},
|
||||
impl: &darwinDock{
|
||||
Badge: nil,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -67,22 +84,42 @@ func (d *darwinDock) HideAppIcon() {
|
|||
}
|
||||
|
||||
// ShowAppIcon shows the app icon in the macOS Dock.
|
||||
// Note: After showing the dock icon, you may need to call SetBadge again
|
||||
// to reapply any previously set badge, as changing activation policies clears the badge.
|
||||
func (d *darwinDock) ShowAppIcon() {
|
||||
C.showDockIcon()
|
||||
}
|
||||
|
||||
// SetBadge sets the badge label on the application icon.
|
||||
func (d *darwinDock) SetBadge(label string) error {
|
||||
// Always pick a label (use “●” if empty), then allocate + free exactly once.
|
||||
value := label
|
||||
if value == "" {
|
||||
value = "●" // Default badge character
|
||||
}
|
||||
cLabel := C.CString(value)
|
||||
defer C.free(unsafe.Pointer(cLabel))
|
||||
// setBadge handles the C call and updates the internal badge state with locking.
|
||||
func (d *darwinDock) setBadge(label *string) error {
|
||||
var cLabel *C.char
|
||||
if label != nil {
|
||||
cLabel = C.CString(*label)
|
||||
defer C.free(unsafe.Pointer(cLabel))
|
||||
}
|
||||
|
||||
C.setBadge(cLabel)
|
||||
return nil
|
||||
success := C.setBadge(cLabel)
|
||||
if !success {
|
||||
return fmt.Errorf("failed to set badge")
|
||||
}
|
||||
|
||||
d.mu.Lock()
|
||||
d.Badge = label
|
||||
d.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetBadge sets the badge label on the application icon.
|
||||
// Available default badge labels:
|
||||
// Single space " " empty badge
|
||||
// Empty string "" dot "●" indeterminate badge
|
||||
func (d *darwinDock) SetBadge(label string) error {
|
||||
// Always pick a label (use "●" if empty), then allocate + free exactly once.
|
||||
if label == "" {
|
||||
label = "●" // Default badge character
|
||||
}
|
||||
return d.setBadge(&label)
|
||||
}
|
||||
|
||||
// SetCustomBadge is not supported on macOS, SetBadge is called instead.
|
||||
|
|
@ -92,6 +129,12 @@ func (d *darwinDock) SetCustomBadge(label string, options BadgeOptions) error {
|
|||
|
||||
// RemoveBadge removes the badge label from the application icon.
|
||||
func (d *darwinDock) RemoveBadge() error {
|
||||
C.setBadge(nil)
|
||||
return nil
|
||||
return d.setBadge(nil)
|
||||
}
|
||||
|
||||
// GetBadge returns the badge label on the application icon.
|
||||
func (d *darwinDock) GetBadge() *string {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
return d.Badge
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,3 +68,8 @@ func (l *linuxDock) RemoveBadge() error {
|
|||
// No-op: Linux doesn't have standardized badge support
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *linuxDock) GetBadge() *string {
|
||||
// No-op: Linux doesn't have standardized badge support
|
||||
return nil
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ type windowsDock struct {
|
|||
badgeSize int
|
||||
fontManager *FontManager
|
||||
badgeOptions BadgeOptions
|
||||
badge *string
|
||||
}
|
||||
|
||||
var defaultOptions = BadgeOptions{
|
||||
|
|
@ -48,6 +49,7 @@ func NewWithOptions(options BadgeOptions) *DockService {
|
|||
return &DockService{
|
||||
impl: &windowsDock{
|
||||
badgeOptions: options,
|
||||
badge: nil,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -121,6 +123,7 @@ func (w *windowsDock) SetBadge(label string) error {
|
|||
}
|
||||
defer w32.DestroyIcon(hicon)
|
||||
|
||||
w.badge = &label
|
||||
return w.taskbar.SetOverlayIcon(hwnd, hicon, nil)
|
||||
})
|
||||
}
|
||||
|
|
@ -182,6 +185,7 @@ func (w *windowsDock) SetCustomBadge(label string, options BadgeOptions) error {
|
|||
}
|
||||
defer w32.DestroyIcon(hicon)
|
||||
|
||||
w.badge = &label
|
||||
return w.taskbar.SetOverlayIcon(hwnd, hicon, nil)
|
||||
})
|
||||
}
|
||||
|
|
@ -209,6 +213,7 @@ func (w *windowsDock) RemoveBadge() error {
|
|||
}
|
||||
hwnd := uintptr(nativeWindow)
|
||||
|
||||
w.badge = nil
|
||||
return w.taskbar.SetOverlayIcon(hwnd, 0, nil)
|
||||
})
|
||||
}
|
||||
|
|
@ -394,3 +399,7 @@ func (w *windowsDock) createBadge() {
|
|||
|
||||
w.badgeImg = img
|
||||
}
|
||||
|
||||
func (w *windowsDock) GetBadge() *string {
|
||||
return w.badge
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue