feat(macos): add CollectionBehavior option to MacWindow (#4799)

* feat(macos): add CollectionBehavior option to MacWindow (#4756)

Add configurable NSWindowCollectionBehavior support for macOS windows,
allowing control over window behavior across Spaces and fullscreen.

New options include:
- MacWindowCollectionBehaviorCanJoinAllSpaces
- MacWindowCollectionBehaviorFullScreenAuxiliary
- MacWindowCollectionBehaviorMoveToActiveSpace
- And more...

This enables building Spotlight-like apps that appear on all Spaces
or overlay fullscreen applications.

Closes #4756

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Potential fix for code scanning alert no. 140: Workflow does not contain permissions

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* Potential fix for code scanning alert no. 139: Workflow does not contain permissions

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* feat(examples): add spotlight example for CollectionBehavior

Demonstrates creating a Spotlight-like launcher window that:
- Appears on all macOS Spaces
- Floats above other windows
- Uses accessory activation policy (no Dock icon)
- Has frameless translucent design

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(macos): support bitwise OR for CollectionBehavior options

Update CollectionBehavior to use actual NSWindowCollectionBehavior
bitmask values, allowing multiple behaviors to be combined:

```go
CollectionBehavior: application.MacWindowCollectionBehaviorCanJoinAllSpaces |
                    application.MacWindowCollectionBehaviorFullScreenAuxiliary,
```

Changes:
- Update Go constants to use actual bitmask values (1<<0, 1<<1, etc.)
- Simplify C function to pass through combined bitmask directly
- Add ParticipatesInCycle, IgnoresCycle, FullScreenDisallowsTiling options
- Update documentation with combined behavior examples
- Update spotlight example to demonstrate combining behaviors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
This commit is contained in:
Lea Anthony 2025-12-29 11:07:37 +11:00 committed by GitHub
commit 0de429b8fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 353 additions and 31 deletions

View file

@ -23,6 +23,7 @@ env:
jobs:
check-permissions:
name: Check Release Permissions
permissions: {}
runs-on: ubuntu-latest
outputs:
authorized: ${{ steps.check.outputs.authorized }}

View file

@ -1,4 +1,6 @@
name: Test Nightly Releases (Dry Run)
permissions:
contents: read
on:
workflow_dispatch:

View file

@ -410,22 +410,29 @@ childWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
<TabItem label="macOS" icon="apple">
**macOS-specific features:**
```go
// Transparent title bar
window := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Mac: application.MacOptions{
TitleBarAppearsTransparent: true,
Backdrop: application.MacBackdropTranslucent,
Mac: application.MacWindow{
TitleBar: application.MacTitleBar{
AppearsTransparent: true,
},
Backdrop: application.MacBackdropTranslucent,
},
})
```
**Backdrop types:**
- `MacBackdropNormal` - Standard window
- `MacBackdropTranslucent` - Translucent background
- `MacBackdropTransparent` - Fully transparent
**Collection behavior:**
Control how windows behave across Spaces:
- `MacWindowCollectionBehaviorCanJoinAllSpaces` - Visible on all Spaces
- `MacWindowCollectionBehaviorFullScreenAuxiliary` - Can overlay fullscreen apps
**Native fullscreen:**
macOS fullscreen creates a new Space (virtual desktop).
</TabItem>

View file

@ -665,39 +665,85 @@ OnDestroy: func() {
### Mac Options
```go
Mac: application.MacOptions{
TitleBarAppearsTransparent: true,
Backdrop: application.MacBackdropTranslucent,
InvisibleTitleBarHeight: 50,
TitleBarStyle: application.MacTitleBarStyleHidden,
Mac: application.MacWindow{
TitleBar: application.MacTitleBar{
AppearsTransparent: true,
Hide: false,
HideTitle: true,
FullSizeContent: true,
},
Backdrop: application.MacBackdropTranslucent,
InvisibleTitleBarHeight: 50,
WindowLevel: application.MacWindowLevelNormal,
CollectionBehavior: application.MacWindowCollectionBehaviorDefault,
},
```
**TitleBarAppearsTransparent** (`bool`)
- Makes title bar transparent
- Content extends into title bar area
**TitleBar** (`MacTitleBar`)
- `AppearsTransparent` - Makes title bar transparent, content extends into title bar area
- `Hide` - Hides the title bar completely
- `HideTitle` - Hides only the title text
- `FullSizeContent` - Extends content to full window size
**Backdrop** (`MacBackdrop`)
- `MacBackdropNormal` - Standard
- `MacBackdropTranslucent` - Blurred translucent
- `MacBackdropTransparent` - Fully transparent
- `MacBackdropNormal` - Standard opaque background
- `MacBackdropTranslucent` - Blurred translucent background
- `MacBackdropTransparent` - Fully transparent background
**InvisibleTitleBarHeight** (`int`)
- Height of invisible title bar (for dragging)
- Only when `TitleBarStyle` is `MacTitleBarStyleHidden`
- Height of invisible title bar area (for dragging)
- Useful when title bar is hidden
**TitleBarStyle** (`MacTitleBarStyle`)
- `MacTitleBarStyleDefault` - Standard title bar
- `MacTitleBarStyleHidden` - Hidden title bar
- `MacTitleBarStyleHiddenInset` - Hidden with inset
**WindowLevel** (`MacWindowLevel`)
- `MacWindowLevelNormal` - Standard window level (default)
- `MacWindowLevelFloating` - Floats above normal windows
- `MacWindowLevelTornOffMenu` - Torn-off menu level
- `MacWindowLevelModalPanel` - Modal panel level
- `MacWindowLevelMainMenu` - Main menu level
- `MacWindowLevelStatus` - Status window level
- `MacWindowLevelPopUpMenu` - Pop-up menu level
- `MacWindowLevelScreenSaver` - Screen saver level
**Example:**
**CollectionBehavior** (`MacWindowCollectionBehavior`)
Controls how the window behaves across macOS Spaces and fullscreen. These are bitmask values that can be combined using bitwise OR (`|`).
**Space behavior:**
- `MacWindowCollectionBehaviorDefault` - Uses FullScreenPrimary (default, backwards compatible)
- `MacWindowCollectionBehaviorCanJoinAllSpaces` - Window appears on all Spaces
- `MacWindowCollectionBehaviorMoveToActiveSpace` - Moves to active Space when shown
- `MacWindowCollectionBehaviorManaged` - Default managed window behavior
- `MacWindowCollectionBehaviorTransient` - Temporary/transient window
- `MacWindowCollectionBehaviorStationary` - Stays stationary during Space switches
**Window cycling:**
- `MacWindowCollectionBehaviorParticipatesInCycle` - Included in Cmd+` cycling
- `MacWindowCollectionBehaviorIgnoresCycle` - Excluded from Cmd+` cycling
**Fullscreen behavior:**
- `MacWindowCollectionBehaviorFullScreenPrimary` - Can enter fullscreen mode
- `MacWindowCollectionBehaviorFullScreenAuxiliary` - Can overlay fullscreen apps
- `MacWindowCollectionBehaviorFullScreenNone` - Disables fullscreen capability
- `MacWindowCollectionBehaviorFullScreenAllowsTiling` - Allows side-by-side tiling (macOS 10.11+)
- `MacWindowCollectionBehaviorFullScreenDisallowsTiling` - Prevents tiling (macOS 10.11+)
**Example - Spotlight-like window:**
```go
Mac: application.MacOptions{
TitleBarAppearsTransparent: true,
Backdrop: application.MacBackdropTranslucent,
InvisibleTitleBarHeight: 50,
// Window that appears on all Spaces AND can overlay fullscreen apps
Mac: application.MacWindow{
CollectionBehavior: application.MacWindowCollectionBehaviorCanJoinAllSpaces |
application.MacWindowCollectionBehaviorFullScreenAuxiliary,
WindowLevel: application.MacWindowLevelFloating,
},
```
**Example - Single behavior:**
```go
// Window that can appear over fullscreen applications
Mac: application.MacWindow{
CollectionBehavior: application.MacWindowCollectionBehaviorFullScreenAuxiliary,
},
```

View file

@ -17,6 +17,7 @@ After processing, the content will be moved to the main changelog and this file
## Added
<!-- New features, capabilities, or enhancements -->
- Add `CollectionBehavior` option to `MacWindow` for controlling window behavior across macOS Spaces and fullscreen (#4756) by @leaanthony
## Changed
<!-- Changes in existing functionality -->

View file

@ -0,0 +1,66 @@
# Spotlight Example
This example demonstrates how to create a Spotlight-like launcher window using the `CollectionBehavior` option on macOS.
## Features
- **Appears on all Spaces**: Using `MacWindowCollectionBehaviorCanJoinAllSpaces`, the window is visible across all virtual desktops
- **Overlays fullscreen apps**: Using `MacWindowCollectionBehaviorFullScreenAuxiliary`, the window can appear over fullscreen applications
- **Combined behaviors**: Demonstrates combining multiple behaviors with bitwise OR
- **Floating window**: `MacWindowLevelFloating` keeps the window above other windows
- **Accessory app**: Doesn't appear in the Dock (uses `ActivationPolicyAccessory`)
- **Frameless design**: Clean, borderless appearance with translucent backdrop
## Running the example
```bash
go run .
```
**Note**: This example is macOS-specific due to the use of `CollectionBehavior`.
## Combining CollectionBehaviors
Behaviors can be combined using bitwise OR (`|`):
```go
CollectionBehavior: application.MacWindowCollectionBehaviorCanJoinAllSpaces |
application.MacWindowCollectionBehaviorFullScreenAuxiliary,
```
## CollectionBehavior Options
These are bitmask values that can be combined:
**Space behavior:**
| Option | Description |
|--------|-------------|
| `MacWindowCollectionBehaviorDefault` | Uses FullScreenPrimary (default) |
| `MacWindowCollectionBehaviorCanJoinAllSpaces` | Window appears on all Spaces |
| `MacWindowCollectionBehaviorMoveToActiveSpace` | Moves to active Space when shown |
| `MacWindowCollectionBehaviorManaged` | Default managed window behavior |
| `MacWindowCollectionBehaviorTransient` | Temporary/transient window |
| `MacWindowCollectionBehaviorStationary` | Stays stationary during Space switches |
**Fullscreen behavior:**
| Option | Description |
|--------|-------------|
| `MacWindowCollectionBehaviorFullScreenPrimary` | Can enter fullscreen mode |
| `MacWindowCollectionBehaviorFullScreenAuxiliary` | Can overlay fullscreen apps |
| `MacWindowCollectionBehaviorFullScreenNone` | Disables fullscreen |
| `MacWindowCollectionBehaviorFullScreenAllowsTiling` | Allows side-by-side tiling |
## Use Cases
- **Launcher apps** (like Spotlight, Alfred, Raycast)
- **Quick capture tools** (notes, screenshots)
- **System utilities** that need to be accessible anywhere
- **Overlay widgets** that should appear over fullscreen apps
## Status
| Platform | Status |
|----------|--------|
| Mac | Working |
| Windows | N/A (macOS-specific feature) |
| Linux | N/A (macOS-specific feature) |

View file

@ -0,0 +1,142 @@
package main
import (
"log"
"net/http"
"github.com/wailsapp/wails/v3/pkg/application"
)
// This example demonstrates how to create a Spotlight-like launcher window
// that appears on all macOS Spaces and can overlay fullscreen applications.
//
// Key features:
// - Window appears on all Spaces (virtual desktops)
// - Can overlay fullscreen applications
// - Floating window level keeps it above other windows
// - Accessory activation policy hides from Dock
// - Frameless design with translucent backdrop
func main() {
app := application.New(application.Options{
Name: "Spotlight Example",
Description: "A Spotlight-like launcher demonstrating CollectionBehavior",
Mac: application.MacOptions{
// Accessory apps don't appear in the Dock
ActivationPolicy: application.ActivationPolicyAccessory,
ApplicationShouldTerminateAfterLastWindowClosed: true,
},
Assets: application.AssetOptions{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(spotlightHTML))
}),
},
})
// Create a Spotlight-like window
app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Spotlight",
Width: 680,
Height: 80,
Frameless: true,
// Center the window
InitialPosition: application.WindowCentered,
// Prevent resizing
DisableResize: true,
Mac: application.MacWindow{
// Combine multiple behaviors using bitwise OR:
// - CanJoinAllSpaces: window appears on ALL Spaces (virtual desktops)
// - FullScreenAuxiliary: window can overlay fullscreen applications
CollectionBehavior: application.MacWindowCollectionBehaviorCanJoinAllSpaces |
application.MacWindowCollectionBehaviorFullScreenAuxiliary,
// Float above other windows
WindowLevel: application.MacWindowLevelFloating,
// Translucent vibrancy effect
Backdrop: application.MacBackdropTranslucent,
// Hidden title bar for clean look
TitleBar: application.MacTitleBar{
AppearsTransparent: true,
Hide: true,
},
},
URL: "/",
})
err := app.Run()
if err != nil {
log.Fatal(err)
}
}
const spotlightHTML = `<!DOCTYPE html>
<html>
<head>
<title>Spotlight</title>
<script type="module" src="/wails/runtime.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif;
background: transparent;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
padding: 16px;
user-select: none;
-webkit-user-select: none;
}
.search-container {
width: 100%;
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: rgba(255, 255, 255, 0.1);
border-radius: 12px;
}
.search-icon {
width: 24px;
height: 24px;
opacity: 0.6;
}
.search-input {
flex: 1;
background: transparent;
border: none;
outline: none;
font-size: 24px;
color: white;
font-weight: 300;
}
.search-input::placeholder {
color: rgba(255, 255, 255, 0.5);
}
@media (prefers-color-scheme: light) {
.search-container {
background: rgba(0, 0, 0, 0.05);
}
.search-input {
color: #333;
}
.search-input::placeholder {
color: rgba(0, 0, 0, 0.4);
}
}
</style>
</head>
<body>
<div class="search-container">
<svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/>
<path d="M21 21l-4.35-4.35"/>
</svg>
<input type="text" class="search-input" placeholder="Spotlight Search" autofocus>
</div>
</body>
</html>`

View file

@ -34,8 +34,8 @@ void* windowNew(unsigned int id, int width, int height, bool fraudulentWebsiteWa
backing:NSBackingStoreBuffered
defer:NO];
// Allow fullscreen. Needed for frameless windows
window.collectionBehavior = NSWindowCollectionBehaviorFullScreenPrimary;
// Note: collectionBehavior is set later via windowSetCollectionBehavior()
// to allow user configuration of Space and fullscreen behavior
// Create delegate
WebviewWindowDelegate* delegate = [[WebviewWindowDelegate alloc] init];
@ -234,6 +234,19 @@ void setModalPanelWindowLevel(void* nsWindow) { [(WebviewWindow*)nsWindow setLev
void setScreenSaverWindowLevel(void* nsWindow) { [(WebviewWindow*)nsWindow setLevel:NSScreenSaverWindowLevel]; }
void setTornOffMenuWindowLevel(void* nsWindow) { [(WebviewWindow*)nsWindow setLevel:NSTornOffMenuWindowLevel]; }
// Set NSWindow collection behavior for Spaces and fullscreen
// The behavior parameter is a bitmask that can combine multiple NSWindowCollectionBehavior values
void windowSetCollectionBehavior(void* nsWindow, int behavior) {
WebviewWindow* window = (WebviewWindow*)nsWindow;
if (behavior == 0) {
// Default: use FullScreenPrimary for backwards compatibility
window.collectionBehavior = NSWindowCollectionBehaviorFullScreenPrimary;
} else {
// Pass through the combined bitmask directly
window.collectionBehavior = (NSWindowCollectionBehavior)behavior;
}
}
// Load URL in NSWindow
void navigationLoadURL(void* nsWindow, char* url) {
// Load URL on main thread
@ -1162,6 +1175,10 @@ func (w *macosWebviewWindow) setWindowLevel(level MacWindowLevel) {
}
}
func (w *macosWebviewWindow) setCollectionBehavior(behavior MacWindowCollectionBehavior) {
C.windowSetCollectionBehavior(w.nsWindow, C.int(behavior))
}
func (w *macosWebviewWindow) width() int {
var width C.int
var wg sync.WaitGroup
@ -1260,6 +1277,9 @@ func (w *macosWebviewWindow) run() {
}
w.setWindowLevel(macOptions.WindowLevel)
// Set collection behavior (defaults to FullScreenPrimary for backwards compatibility)
w.setCollectionBehavior(macOptions.CollectionBehavior)
// Initialise the window buttons
w.setMinimiseButtonState(options.MinimiseButtonState)
w.setMaximiseButtonState(options.MaximiseButtonState)

View file

@ -489,6 +489,9 @@ type MacWindow struct {
// WindowLevel sets the window level to control the order of windows in the screen
WindowLevel MacWindowLevel
// CollectionBehavior controls how the window behaves across macOS Spaces and fullscreen
CollectionBehavior MacWindowCollectionBehavior
// LiquidGlass contains configuration for the Liquid Glass effect
LiquidGlass MacLiquidGlass
}
@ -506,6 +509,40 @@ const (
MacWindowLevelScreenSaver MacWindowLevel = "screenSaver"
)
// MacWindowCollectionBehavior controls window behavior across macOS Spaces and fullscreen.
// These correspond to NSWindowCollectionBehavior bitmask values and can be combined using bitwise OR.
// For example: MacWindowCollectionBehaviorCanJoinAllSpaces | MacWindowCollectionBehaviorFullScreenAuxiliary
type MacWindowCollectionBehavior int
const (
// MacWindowCollectionBehaviorDefault is zero value - when set, FullScreenPrimary is used for backwards compatibility
MacWindowCollectionBehaviorDefault MacWindowCollectionBehavior = 0
// MacWindowCollectionBehaviorCanJoinAllSpaces allows window to appear on all Spaces
MacWindowCollectionBehaviorCanJoinAllSpaces MacWindowCollectionBehavior = 1 << 0 // 1
// MacWindowCollectionBehaviorMoveToActiveSpace moves window to active Space when shown
MacWindowCollectionBehaviorMoveToActiveSpace MacWindowCollectionBehavior = 1 << 1 // 2
// MacWindowCollectionBehaviorManaged is the default managed window behavior
MacWindowCollectionBehaviorManaged MacWindowCollectionBehavior = 1 << 2 // 4
// MacWindowCollectionBehaviorTransient marks window as temporary/transient
MacWindowCollectionBehaviorTransient MacWindowCollectionBehavior = 1 << 3 // 8
// MacWindowCollectionBehaviorStationary keeps window stationary during Space switches
MacWindowCollectionBehaviorStationary MacWindowCollectionBehavior = 1 << 4 // 16
// MacWindowCollectionBehaviorParticipatesInCycle includes window in Cmd+` cycling (default for normal windows)
MacWindowCollectionBehaviorParticipatesInCycle MacWindowCollectionBehavior = 1 << 5 // 32
// MacWindowCollectionBehaviorIgnoresCycle excludes window from Cmd+` cycling
MacWindowCollectionBehaviorIgnoresCycle MacWindowCollectionBehavior = 1 << 6 // 64
// MacWindowCollectionBehaviorFullScreenPrimary allows the window to enter fullscreen
MacWindowCollectionBehaviorFullScreenPrimary MacWindowCollectionBehavior = 1 << 7 // 128
// MacWindowCollectionBehaviorFullScreenAuxiliary allows window to overlay fullscreen apps
MacWindowCollectionBehaviorFullScreenAuxiliary MacWindowCollectionBehavior = 1 << 8 // 256
// MacWindowCollectionBehaviorFullScreenNone prevents window from entering fullscreen (macOS 10.7+)
MacWindowCollectionBehaviorFullScreenNone MacWindowCollectionBehavior = 1 << 9 // 512
// MacWindowCollectionBehaviorFullScreenAllowsTiling allows side-by-side tiling in fullscreen (macOS 10.11+)
MacWindowCollectionBehaviorFullScreenAllowsTiling MacWindowCollectionBehavior = 1 << 11 // 2048
// MacWindowCollectionBehaviorFullScreenDisallowsTiling prevents tiling in fullscreen (macOS 10.11+)
MacWindowCollectionBehaviorFullScreenDisallowsTiling MacWindowCollectionBehavior = 1 << 12 // 4096
)
// MacWebviewPreferences contains preferences for the Mac webview
type MacWebviewPreferences struct {
// TabFocusesLinks will enable tabbing to links