From 0de429b8fc8332ea4ab278b52ae95f6ebbcb88b6 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Mon, 29 Dec 2025 11:07:37 +1100 Subject: [PATCH] feat(macos): add CollectionBehavior option to MacWindow (#4799) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 * 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 * 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 --------- Co-authored-by: Claude Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- .github/workflows/automated-releases.yml | 1 + .github/workflows/test-nightly-releases.yml | 2 + .../content/docs/features/windows/basics.mdx | 19 ++- .../content/docs/features/windows/options.mdx | 90 ++++++++--- v3/UNRELEASED_CHANGELOG.md | 1 + v3/examples/spotlight/README.md | 66 ++++++++ v3/examples/spotlight/main.go | 142 ++++++++++++++++++ v3/pkg/application/webview_window_darwin.go | 24 ++- v3/pkg/application/webview_window_options.go | 37 +++++ 9 files changed, 352 insertions(+), 30 deletions(-) create mode 100644 v3/examples/spotlight/README.md create mode 100644 v3/examples/spotlight/main.go diff --git a/.github/workflows/automated-releases.yml b/.github/workflows/automated-releases.yml index e88f54eb9..83445d869 100644 --- a/.github/workflows/automated-releases.yml +++ b/.github/workflows/automated-releases.yml @@ -23,6 +23,7 @@ env: jobs: check-permissions: name: Check Release Permissions + permissions: {} runs-on: ubuntu-latest outputs: authorized: ${{ steps.check.outputs.authorized }} diff --git a/.github/workflows/test-nightly-releases.yml b/.github/workflows/test-nightly-releases.yml index 63df09935..1e6f12a69 100644 --- a/.github/workflows/test-nightly-releases.yml +++ b/.github/workflows/test-nightly-releases.yml @@ -1,4 +1,6 @@ name: Test Nightly Releases (Dry Run) +permissions: + contents: read on: workflow_dispatch: diff --git a/docs/src/content/docs/features/windows/basics.mdx b/docs/src/content/docs/features/windows/basics.mdx index 007c62c52..3137111ac 100644 --- a/docs/src/content/docs/features/windows/basics.mdx +++ b/docs/src/content/docs/features/windows/basics.mdx @@ -410,22 +410,29 @@ childWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ **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). diff --git a/docs/src/content/docs/features/windows/options.mdx b/docs/src/content/docs/features/windows/options.mdx index 521d50b01..93a468120 100644 --- a/docs/src/content/docs/features/windows/options.mdx +++ b/docs/src/content/docs/features/windows/options.mdx @@ -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, }, ``` diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 21cde115e..a3289242e 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -17,6 +17,7 @@ After processing, the content will be moved to the main changelog and this file ## Added +- Add `CollectionBehavior` option to `MacWindow` for controlling window behavior across macOS Spaces and fullscreen (#4756) by @leaanthony ## Changed diff --git a/v3/examples/spotlight/README.md b/v3/examples/spotlight/README.md new file mode 100644 index 000000000..bda8f464e --- /dev/null +++ b/v3/examples/spotlight/README.md @@ -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) | diff --git a/v3/examples/spotlight/main.go b/v3/examples/spotlight/main.go new file mode 100644 index 000000000..d961f1f54 --- /dev/null +++ b/v3/examples/spotlight/main.go @@ -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 = ` + + + Spotlight + + + + +
+ + + + + +
+ +` diff --git a/v3/pkg/application/webview_window_darwin.go b/v3/pkg/application/webview_window_darwin.go index b8d3736d3..fc81974bf 100644 --- a/v3/pkg/application/webview_window_darwin.go +++ b/v3/pkg/application/webview_window_darwin.go @@ -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) diff --git a/v3/pkg/application/webview_window_options.go b/v3/pkg/application/webview_window_options.go index 4c9c0f57a..8ca629559 100644 --- a/v3/pkg/application/webview_window_options.go +++ b/v3/pkg/application/webview_window_options.go @@ -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