From 9d60d93092c1f7aeab51ccc06427e7eeae74db4a Mon Sep 17 00:00:00 2001 From: Gordon Beeming Date: Fri, 3 Apr 2026 11:23:18 +1000 Subject: [PATCH 1/2] feat: Make all windows float above other apps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add FloatingWindow ViewModifier that sets NSWindow.level to .floating on appear. Applied to all 5 utility windows (Settings, About, Duration Picker, Time Picker, Schedules) so they stay visible when focus moves to another app — important for a menu bar-only utility. Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: GitButler --- Sources/Insomnia/FloatingWindowModifier.swift | 43 +++++++++++++++++++ Sources/Insomnia/InsomniaApp.swift | 5 +++ 2 files changed, 48 insertions(+) create mode 100644 Sources/Insomnia/FloatingWindowModifier.swift diff --git a/Sources/Insomnia/FloatingWindowModifier.swift b/Sources/Insomnia/FloatingWindowModifier.swift new file mode 100644 index 0000000..372d60f --- /dev/null +++ b/Sources/Insomnia/FloatingWindowModifier.swift @@ -0,0 +1,43 @@ +// FloatingWindowModifier.swift — Insomnia GUI +// +// A SwiftUI ViewModifier that makes a window float above all other windows. +// Used for LSUIElement menu bar apps where windows should stay visible +// even when the user clicks on other applications. + +import SwiftUI + +/// Makes the hosting NSWindow float above all other windows. +/// +/// Finds the NSWindow backing the SwiftUI view on appear and sets its +/// level to `.floating`. This keeps Insomnia's windows visible even when +/// focus moves to another app — important for a menu bar utility. +struct FloatingWindow: ViewModifier { + /// Applies the floating window level to the view's hosting window. + func body(content: Content) -> some View { + content + .onAppear { + // Find the NSWindow hosting this SwiftUI view and float it + setFloatingLevel() + } + } + + /// Searches the app's windows for the one hosting this view and sets it to float. + private func setFloatingLevel() { + // Slight delay to ensure the window is created and visible + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + // Set all non-main windows to floating level + // (MenuBarExtra doesn't create a standard NSWindow) + for window in NSApp.windows where window.isVisible { + window.level = .floating + } + } + } +} + +/// Convenience extension for applying the floating window modifier. +extension View { + /// Makes the hosting window float above all other windows. + func floatingWindow() -> some View { + modifier(FloatingWindow()) + } +} diff --git a/Sources/Insomnia/InsomniaApp.swift b/Sources/Insomnia/InsomniaApp.swift index 9bd8aac..48fef72 100644 --- a/Sources/Insomnia/InsomniaApp.swift +++ b/Sources/Insomnia/InsomniaApp.swift @@ -44,6 +44,7 @@ struct InsomniaApp: App { Window("Settings", id: "settings") { if let viewModel { SettingsView(viewModel: SettingsViewModel(configuration: viewModel.configuration)) + .floatingWindow() } } .windowResizability(.contentSize) @@ -51,6 +52,7 @@ struct InsomniaApp: App { // About dialog window — uses BuildEnvironment for variant-aware title Window("About \(BuildEnvironment.appName)", id: "about") { AboutView() + .floatingWindow() } .windowResizability(.contentSize) @@ -58,6 +60,7 @@ struct InsomniaApp: App { Window("Custom Duration", id: "duration-picker") { if let viewModel { DurationPickerView(viewModel: viewModel) + .floatingWindow() } } .windowResizability(.contentSize) @@ -66,6 +69,7 @@ struct InsomniaApp: App { Window("Caffeinate Until", id: "time-picker") { if let viewModel { TimePickerView(viewModel: viewModel) + .floatingWindow() } } .windowResizability(.contentSize) @@ -74,6 +78,7 @@ struct InsomniaApp: App { Window("Schedules", id: "schedules") { if let viewModel { ScheduleEditorView(viewModel: viewModel) + .floatingWindow() } } .windowResizability(.contentSize) From d33d31222c55f701fa2843d0e7d4114692f05e59 Mon Sep 17 00:00:00 2001 From: Gordon Beeming Date: Fri, 3 Apr 2026 12:33:24 +1000 Subject: [PATCH 2/2] Use NSViewRepresentable to scope floating level to hosting window Replace window iteration + timing delay with NSViewRepresentable that reads the specific hosting NSView's .window property. Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: GitButler --- Sources/Insomnia/FloatingWindowModifier.swift | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/Sources/Insomnia/FloatingWindowModifier.swift b/Sources/Insomnia/FloatingWindowModifier.swift index 372d60f..ce57464 100644 --- a/Sources/Insomnia/FloatingWindowModifier.swift +++ b/Sources/Insomnia/FloatingWindowModifier.swift @@ -1,35 +1,46 @@ // FloatingWindowModifier.swift — Insomnia GUI // // A SwiftUI ViewModifier that makes a window float above all other windows. -// Used for LSUIElement menu bar apps where windows should stay visible -// even when the user clicks on other applications. +// Uses NSViewRepresentable to find the specific hosting NSWindow rather than +// iterating all app windows. Important for LSUIElement menu bar apps where +// windows should stay visible even when the user clicks on other applications. +import AppKit import SwiftUI /// Makes the hosting NSWindow float above all other windows. /// -/// Finds the NSWindow backing the SwiftUI view on appear and sets its +/// Finds the NSWindow backing the SwiftUI view and sets its /// level to `.floating`. This keeps Insomnia's windows visible even when /// focus moves to another app — important for a menu bar utility. struct FloatingWindow: ViewModifier { /// Applies the floating window level to the view's hosting window. func body(content: Content) -> some View { content - .onAppear { - // Find the NSWindow hosting this SwiftUI view and float it - setFloatingLevel() - } + .background(FloatingWindowAccessor()) + } +} + +/// Resolves the specific NSWindow hosting the modified SwiftUI view. +/// +/// Uses `NSViewRepresentable` to access the underlying `NSView`, then +/// reads its `.window` property to set the level. This avoids iterating +/// all app windows and eliminates timing-based delays. +private struct FloatingWindowAccessor: NSViewRepresentable { + /// Creates the backing NSView and sets its window to floating level. + func makeNSView(context: Context) -> NSView { + let view = NSView() + // Set floating level once the view is attached to a window + DispatchQueue.main.async { + view.window?.level = .floating + } + return view } - /// Searches the app's windows for the one hosting this view and sets it to float. - private func setFloatingLevel() { - // Slight delay to ensure the window is created and visible - DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { - // Set all non-main windows to floating level - // (MenuBarExtra doesn't create a standard NSWindow) - for window in NSApp.windows where window.isVisible { - window.level = .floating - } + /// Re-applies floating level when the view updates (e.g., window reappears). + func updateNSView(_ nsView: NSView, context: Context) { + DispatchQueue.main.async { + nsView.window?.level = .floating } } }