From 762edea147f9123238f95a993c2e99ea28966779 Mon Sep 17 00:00:00 2001 From: neo773 Date: Wed, 21 Jan 2026 01:30:13 +0530 Subject: [PATCH] Hide App Dock Icon --- .../Packages/Resources/Localizable.xcstrings | 18 ++++++ Sources/Secretive.xcodeproj/project.pbxproj | 8 +++ Sources/Secretive/App.swift | 11 +++- .../DockVisibilityController.swift | 37 +++++++++++ .../Secretive/Views/Views/ContentView.swift | 13 ++++ .../Secretive/Views/Views/MenuBarView.swift | 62 +++++++++++++++++++ 6 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 Sources/Secretive/Controllers/DockVisibilityController.swift create mode 100644 Sources/Secretive/Views/Views/MenuBarView.swift diff --git a/Sources/Packages/Resources/Localizable.xcstrings b/Sources/Packages/Resources/Localizable.xcstrings index 3513013..aec8707 100644 --- a/Sources/Packages/Resources/Localizable.xcstrings +++ b/Sources/Packages/Resources/Localizable.xcstrings @@ -364,6 +364,9 @@ } }, "shouldTranslate" : false + }, + "About Secretive" : { + }, "about_build_log_button" : { "extractionState" : "manual", @@ -1289,6 +1292,12 @@ } } } + }, + "Agent Not Running" : { + + }, + "Agent Running" : { + }, "agent_details_could_not_start_error" : { "extractionState" : "manual", @@ -18524,6 +18533,9 @@ } } } + }, + "Integrations..." : { + }, "integrationsGitStepGitconfigSectionNote" : { "extractionState" : "manual", @@ -19264,6 +19276,9 @@ } } } + }, + "Open Secretive" : { + }, "persist_authentication_accept_button" : { "comment" : "When the user authorizes an action using a secret that requires unlock, they're shown a notification offering to leave the secret unlocked for a set period of time. This is the title for the notification.", @@ -19636,6 +19651,9 @@ } } } + }, + "Quit Secretive" : { + }, "reveal_in_finder_button" : { "extractionState" : "manual", diff --git a/Sources/Secretive.xcodeproj/project.pbxproj b/Sources/Secretive.xcodeproj/project.pbxproj index b6f319d..eca0d2c 100644 --- a/Sources/Secretive.xcodeproj/project.pbxproj +++ b/Sources/Secretive.xcodeproj/project.pbxproj @@ -74,6 +74,8 @@ 50E4C4C32E7765DF00C73783 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E4C4C22E7765DF00C73783 /* AboutView.swift */; }; 50E4C4C82E777E4200C73783 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 50E4C4C72E777E4200C73783 /* AppIcon.icon */; }; 50E4C4C92E777E4200C73783 /* AppIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = 50E4C4C72E777E4200C73783 /* AppIcon.icon */; }; + B3AE1BB82F201243007B797C /* DockVisibilityController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3AE1BB72F201243007B797C /* DockVisibilityController.swift */; }; + B3AE1BBA2F20125C007B797C /* MenuBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3AE1BB92F20125C007B797C /* MenuBarView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -240,6 +242,8 @@ 50E4C4522E73C78900C73783 /* WindowBackgroundStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowBackgroundStyle.swift; sourceTree = ""; }; 50E4C4C22E7765DF00C73783 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; 50E4C4C72E777E4200C73783 /* AppIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = AppIcon.icon; sourceTree = ""; }; + B3AE1BB72F201243007B797C /* DockVisibilityController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DockVisibilityController.swift; sourceTree = ""; }; + B3AE1BB92F20125C007B797C /* MenuBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuBarView.swift; sourceTree = ""; }; F418C9A82F0C57F000E9ADF8 /* OpenSource.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = OpenSource.xcconfig; sourceTree = ""; }; /* End PBXFileReference section */ @@ -333,6 +337,7 @@ 504788F02E681F0100B4556F /* Views */ = { isa = PBXGroup; children = ( + B3AE1BB92F20125C007B797C /* MenuBarView.swift */, 50E4C4C22E7765DF00C73783 /* AboutView.swift */, 50BDCB712E63BAF20072D2E7 /* AgentStatusView.swift */, 50617D8423FCE48E0099B055 /* ContentView.swift */, @@ -441,6 +446,7 @@ 508A58B1241ED1EA0069DC07 /* Controllers */ = { isa = PBXGroup; children = ( + B3AE1BB72F201243007B797C /* DockVisibilityController.swift */, 508A58B2241ED2180069DC07 /* AgentStatusChecker.swift */, 5091D2BB25183B830049FD9B /* ApplicationDirectoryController.swift */, 50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */, @@ -688,6 +694,7 @@ 2C4A9D2F2636FFD3008CC8E2 /* EditSecretView.swift in Sources */, 50E4C4532E73C78C00C73783 /* WindowBackgroundStyle.swift in Sources */, 5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */, + B3AE1BB82F201243007B797C /* DockVisibilityController.swift in Sources */, 504789232E697DD300B4556F /* BoxBackgroundStyle.swift in Sources */, 5066A6C22516F303004B5A36 /* SetupView.swift in Sources */, 5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */, @@ -702,6 +709,7 @@ 50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */, 5099A02423FD2AAA0062B6F2 /* CreateSecretView.swift in Sources */, 50AE97002E5C1A420018C710 /* IntegrationsView.swift in Sources */, + B3AE1BBA2F20125C007B797C /* MenuBarView.swift in Sources */, 50153E20250AFCB200525160 /* UpdateView.swift in Sources */, 5066A6C82516FE6E004B5A36 /* CopyableView.swift in Sources */, 50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */, diff --git a/Sources/Secretive/App.swift b/Sources/Secretive/App.swift index ea2f156..bb46357 100644 --- a/Sources/Secretive/App.swift +++ b/Sources/Secretive/App.swift @@ -9,9 +9,10 @@ struct Secretive: App { @Environment(\.agentLaunchController) var agentLaunchController @Environment(\.justUpdatedChecker) var justUpdatedChecker + @Environment(\.dockVisibilityController) var dockVisibilityController @SceneBuilder var body: some Scene { - WindowGroup { + WindowGroup(id: "main") { ContentView() .environment(EnvironmentValues._secretStoreList) .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in @@ -31,6 +32,11 @@ struct Secretive: App { .commands { AppCommands() } + MenuBarExtra { + MenuBarView() + } label: { + Image(systemName: "key.fill") + } WindowGroup(id: String(describing: IntegrationsView.self)) { IntegrationsView() } @@ -110,6 +116,9 @@ extension EnvironmentValues { private static let _justUpdatedChecker = JustUpdatedChecker() @Entry var justUpdatedChecker: any JustUpdatedCheckerProtocol = _justUpdatedChecker + private static let _dockVisibilityController = DockVisibilityController() + @Entry var dockVisibilityController: DockVisibilityController = _dockVisibilityController + @MainActor var secretStoreList: SecretStoreList { EnvironmentValues._secretStoreList } diff --git a/Sources/Secretive/Controllers/DockVisibilityController.swift b/Sources/Secretive/Controllers/DockVisibilityController.swift new file mode 100644 index 0000000..1dc3164 --- /dev/null +++ b/Sources/Secretive/Controllers/DockVisibilityController.swift @@ -0,0 +1,37 @@ +import AppKit +import Observation + +@MainActor protocol DockVisibilityControllerProtocol: Observable, Sendable { + var isDockIconVisible: Bool { get } + func showDockIcon() + func hideDockIcon() + func updateVisibility(hasRunSetup: Bool, hasOpenWindows: Bool) +} + +@Observable @MainActor final class DockVisibilityController: DockVisibilityControllerProtocol { + + private(set) var isDockIconVisible: Bool = true + + nonisolated init() {} + + func showDockIcon() { + guard !isDockIconVisible else { return } + NSApp.setActivationPolicy(.regular) + isDockIconVisible = true + NSApp.activate(ignoringOtherApps: true) + } + + func hideDockIcon() { + guard isDockIconVisible else { return } + NSApp.setActivationPolicy(.accessory) + isDockIconVisible = false + } + + func updateVisibility(hasRunSetup: Bool, hasOpenWindows: Bool) { + if !hasRunSetup || hasOpenWindows { + showDockIcon() + } else { + hideDockIcon() + } + } +} diff --git a/Sources/Secretive/Views/Views/ContentView.swift b/Sources/Secretive/Views/Views/ContentView.swift index c44f0da..188fe8e 100644 --- a/Sources/Secretive/Views/Views/ContentView.swift +++ b/Sources/Secretive/Views/Views/ContentView.swift @@ -15,6 +15,7 @@ struct ContentView: View { @Environment(\.secretStoreList) private var storeList @Environment(\.updater) private var updater @Environment(\.agentLaunchController) private var agentLaunchController + @Environment(\.dockVisibilityController) private var dockVisibilityController @AppStorage("defaultsHasRunSetup") private var hasRunSetup = false @State private var showingCreation = false @@ -41,6 +42,18 @@ struct ContentView: View { if !hasRunSetup { runningSetup = true } + dockVisibilityController.showDockIcon() + } + .onReceive(NotificationCenter.default.publisher(for: NSWindow.willCloseNotification)) { notification in + // When a window closes, check if we should hide the dock icon + Task { @MainActor in + // Small delay to let the window actually close + try? await Task.sleep(for: .milliseconds(100)) + let hasVisibleWindows = NSApp.windows.contains { window in + window.isVisible && window.level == .normal && !window.title.isEmpty + } + dockVisibilityController.updateVisibility(hasRunSetup: hasRunSetup, hasOpenWindows: hasVisibleWindows) + } } .focusedSceneValue(\.showCreateSecret, .init(isEnabled: !runningSetup) { showingCreation = true diff --git a/Sources/Secretive/Views/Views/MenuBarView.swift b/Sources/Secretive/Views/Views/MenuBarView.swift new file mode 100644 index 0000000..7ce0cac --- /dev/null +++ b/Sources/Secretive/Views/Views/MenuBarView.swift @@ -0,0 +1,62 @@ +import SwiftUI + +struct MenuBarView: View { + + @Environment(\.openWindow) private var openWindow + @Environment(\.agentLaunchController) private var agentLaunchController + + var body: some View { + VStack(alignment: .leading, spacing: 4) { + agentStatusSection + Divider() + actionsSection + Divider() + appSection + } + .padding(.vertical, 4) + } + + @ViewBuilder + private var agentStatusSection: some View { + HStack(spacing: 6) { + Circle() + .fill(agentLaunchController.running ? Color.green : Color.red) + .frame(width: 8, height: 8) + Text(agentLaunchController.running ? "Agent Running" : "Agent Not Running") + .font(.caption) + } + .padding(.horizontal, 8) + .padding(.vertical, 4) + } + + @ViewBuilder + private var actionsSection: some View { + Button("Open Secretive") { + openMainWindow() + } + .keyboardShortcut("O", modifiers: [.command]) + + Button("Integrations...") { + openWindow(id: String(describing: IntegrationsView.self)) + NSApp.activate(ignoringOtherApps: true) + } + } + + @ViewBuilder + private var appSection: some View { + Button("About Secretive") { + openWindow(id: String(describing: AboutView.self)) + NSApp.activate(ignoringOtherApps: true) + } + + Button("Quit Secretive") { + NSApplication.shared.terminate(nil) + } + .keyboardShortcut("Q", modifiers: [.command]) + } + + private func openMainWindow() { + openWindow(id: "main") + NSApp.activate(ignoringOtherApps: true) + } +}