Skip to content

Commit 9d60d93

Browse files
GordonBeemingclaudegitbutler-client
committed
feat: Make all windows float above other apps
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) <noreply@anthropic.com> Co-authored-by: GitButler <gitbutler@gitbutler.com>
1 parent 85cdfd5 commit 9d60d93

2 files changed

Lines changed: 48 additions & 0 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// FloatingWindowModifier.swift — Insomnia GUI
2+
//
3+
// A SwiftUI ViewModifier that makes a window float above all other windows.
4+
// Used for LSUIElement menu bar apps where windows should stay visible
5+
// even when the user clicks on other applications.
6+
7+
import SwiftUI
8+
9+
/// Makes the hosting NSWindow float above all other windows.
10+
///
11+
/// Finds the NSWindow backing the SwiftUI view on appear and sets its
12+
/// level to `.floating`. This keeps Insomnia's windows visible even when
13+
/// focus moves to another app — important for a menu bar utility.
14+
struct FloatingWindow: ViewModifier {
15+
/// Applies the floating window level to the view's hosting window.
16+
func body(content: Content) -> some View {
17+
content
18+
.onAppear {
19+
// Find the NSWindow hosting this SwiftUI view and float it
20+
setFloatingLevel()
21+
}
22+
}
23+
24+
/// Searches the app's windows for the one hosting this view and sets it to float.
25+
private func setFloatingLevel() {
26+
// Slight delay to ensure the window is created and visible
27+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
28+
// Set all non-main windows to floating level
29+
// (MenuBarExtra doesn't create a standard NSWindow)
30+
for window in NSApp.windows where window.isVisible {
31+
window.level = .floating
32+
}
33+
}
34+
}
35+
}
36+
37+
/// Convenience extension for applying the floating window modifier.
38+
extension View {
39+
/// Makes the hosting window float above all other windows.
40+
func floatingWindow() -> some View {
41+
modifier(FloatingWindow())
42+
}
43+
}

Sources/Insomnia/InsomniaApp.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,23 @@ struct InsomniaApp: App {
4444
Window("Settings", id: "settings") {
4545
if let viewModel {
4646
SettingsView(viewModel: SettingsViewModel(configuration: viewModel.configuration))
47+
.floatingWindow()
4748
}
4849
}
4950
.windowResizability(.contentSize)
5051

5152
// About dialog window — uses BuildEnvironment for variant-aware title
5253
Window("About \(BuildEnvironment.appName)", id: "about") {
5354
AboutView()
55+
.floatingWindow()
5456
}
5557
.windowResizability(.contentSize)
5658

5759
// Custom duration picker window
5860
Window("Custom Duration", id: "duration-picker") {
5961
if let viewModel {
6062
DurationPickerView(viewModel: viewModel)
63+
.floatingWindow()
6164
}
6265
}
6366
.windowResizability(.contentSize)
@@ -66,6 +69,7 @@ struct InsomniaApp: App {
6669
Window("Caffeinate Until", id: "time-picker") {
6770
if let viewModel {
6871
TimePickerView(viewModel: viewModel)
72+
.floatingWindow()
6973
}
7074
}
7175
.windowResizability(.contentSize)
@@ -74,6 +78,7 @@ struct InsomniaApp: App {
7478
Window("Schedules", id: "schedules") {
7579
if let viewModel {
7680
ScheduleEditorView(viewModel: viewModel)
81+
.floatingWindow()
7782
}
7883
}
7984
.windowResizability(.contentSize)

0 commit comments

Comments
 (0)