Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions Sources/Insomnia/FloatingWindowModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// FloatingWindowModifier.swift — Insomnia GUI
//
// A SwiftUI ViewModifier that makes a window float above all other windows.
// 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 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
.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
}

/// 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
}
}
Comment thread
GordonBeeming marked this conversation as resolved.
}

/// 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())
}
}
5 changes: 5 additions & 0 deletions Sources/Insomnia/InsomniaApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,23 @@ struct InsomniaApp: App {
Window("Settings", id: "settings") {
if let viewModel {
SettingsView(viewModel: SettingsViewModel(configuration: viewModel.configuration))
.floatingWindow()
}
}
.windowResizability(.contentSize)

// About dialog window — uses BuildEnvironment for variant-aware title
Window("About \(BuildEnvironment.appName)", id: "about") {
AboutView()
.floatingWindow()
}
.windowResizability(.contentSize)

// Custom duration picker window
Window("Custom Duration", id: "duration-picker") {
if let viewModel {
DurationPickerView(viewModel: viewModel)
.floatingWindow()
}
}
.windowResizability(.contentSize)
Expand All @@ -66,6 +69,7 @@ struct InsomniaApp: App {
Window("Caffeinate Until", id: "time-picker") {
if let viewModel {
TimePickerView(viewModel: viewModel)
.floatingWindow()
}
}
.windowResizability(.contentSize)
Expand All @@ -74,6 +78,7 @@ struct InsomniaApp: App {
Window("Schedules", id: "schedules") {
if let viewModel {
ScheduleEditorView(viewModel: viewModel)
.floatingWindow()
}
}
.windowResizability(.contentSize)
Expand Down
Loading