Open
Description
Draft:
diff --git a/Sources/AppBundle/config/Config.swift b/Sources/AppBundle/config/Config.swift
index 2c0aad91..19009368 100644
--- a/Sources/AppBundle/config/Config.swift
+++ b/Sources/AppBundle/config/Config.swift
@@ -40,6 +40,7 @@ struct Config: Copyable {
var defaultRootContainerOrientation: DefaultContainerOrientation = .auto
var startAtLogin: Bool = false
var automaticallyUnhideMacosHiddenApps: Bool = false
+ var automaticallyQuitAppsWithZeroWindows: AutomaticallyQuitAppsWithZeroWindows = .off
var accordionPadding: Int = 30
var enableNormalizationOppositeOrientationForNestedContainers: Bool = true
var execOnWorkspaceChange: [String] = [] // todo deprecate
@@ -58,6 +59,12 @@ struct Config: Copyable {
var preservedWorkspaceNames: [String] = []
}
+enum AutomaticallyQuitAppsWithZeroWindows: String, CaseIterable {
+ case off
+ case all
+ case allExceptFinder = "all-except-finder"
+}
+
enum DefaultContainerOrientation: String {
case horizontal, vertical, auto
}
diff --git a/Sources/AppBundle/config/parseConfig.swift b/Sources/AppBundle/config/parseConfig.swift
index bca2b5d6..bd9bef95 100644
--- a/Sources/AppBundle/config/parseConfig.swift
+++ b/Sources/AppBundle/config/parseConfig.swift
@@ -102,6 +102,7 @@ private let configParser: [String: any ParserProtocol<Config>] = [
"start-at-login": Parser(\.startAtLogin, parseBool),
"automatically-unhide-macos-hidden-apps": Parser(\.automaticallyUnhideMacosHiddenApps, parseBool),
+ "automatically-quit-apps-with-zero-windows": Parser(\.automaticallyQuitAppsWithZeroWindows, parseAutomaticallyQuitApps),
"accordion-padding": Parser(\.accordionPadding, parseInt),
"exec-on-workspace-change": Parser(\.execOnWorkspaceChange, parseExecOnWorkspaceChange),
"exec": Parser(\.execConfig, parseExecConfig),
@@ -275,6 +276,11 @@ private func parseLayout(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace
.flatMap { $0.parseLayout().orFailure(.semantic(backtrace, "Can't parse layout '\($0)'")) }
}
+private func parseAutomaticallyQuitApps(_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> ParsedToml<AutomaticallyQuitAppsWithZeroWindows> {
+ parseString(raw, backtrace)
+ .flatMap { parseEnum($0, AutomaticallyQuitAppsWithZeroWindows.self).toParsedToml(backtrace) }
+}
+
private func skipParsing<T>(_ value: T) -> (_ raw: TOMLValueConvertible, _ backtrace: TomlBacktrace) -> ParsedToml<T> {
{ _, _ in .success(value) }
}
diff --git a/Sources/AppBundle/layout/refresh.swift b/Sources/AppBundle/layout/refresh.swift
index 141e50b7..ca5bc5ed 100644
--- a/Sources/AppBundle/layout/refresh.swift
+++ b/Sources/AppBundle/layout/refresh.swift
@@ -35,10 +35,28 @@ func refreshSession<T>(screenIsDefinitelyUnlocked: Bool, startup: Bool = false,
updateTrayText()
normalizeLayoutReason(startup: startup)
layoutWorkspaces()
+
+ if config.automaticallyQuitAppsWithZeroWindows != .off { quitAppsWithZeroWindows() }
}
return result
}
+func quitAppsWithZeroWindows() {
+ let exceptFinder = config.automaticallyQuitAppsWithZeroWindows == .allExceptFinder
+ let appsWithWindows = MacWindow.allWindowsMap.map { $0.value.app.pid }.toSet()
+ let frontmostApp = NSWorkspace.shared.frontmostApplication
+ let menuBarApp = NSWorkspace.shared.menuBarOwningApplication
+ for (pid, app) in MacApp.allAppsMap {
+ if pid == frontmostApp?.processIdentifier || pid == menuBarApp?.processIdentifier || appsWithWindows.contains(pid) {
+ continue
+ }
+ if exceptFinder && app.nsApp.bundleIdentifier == finderAppBundleId {
+ continue
+ }
+ app.nsApp.terminate()
+ }
+}
+
func refreshAndLayout(screenIsDefinitelyUnlocked: Bool, startup: Bool = false) {
refreshSession(screenIsDefinitelyUnlocked: screenIsDefinitelyUnlocked, startup: startup, body: {})
}
diff --git a/Sources/AppBundle/util/appBundleUtil.swift b/Sources/AppBundle/util/appBundleUtil.swift
index 5802a18f..84b35fca 100644
--- a/Sources/AppBundle/util/appBundleUtil.swift
+++ b/Sources/AppBundle/util/appBundleUtil.swift
@@ -2,7 +2,8 @@ import AppKit
import Common
import Foundation
-let lockScreenAppBundleId = "com.apple.loginwindow"
+let lockScreenAppBundleId = "com.apple.loginwindow" // /System/Library/CoreServices/[loginwindow.app](http://loginwindow.app/)
+let finderAppBundleId = "com.apple.finder" // /System/Library/CoreServices/Finder.app
let AEROSPACE_WINDOW_ID = "AEROSPACE_WINDOW_ID" // env var
let AEROSPACE_WORKSPACE = "AEROSPACE_WORKSPACE" // env var
diff --git a/docs/config-examples/default-config.toml b/docs/config-examples/default-config.toml
index 705a4e9c..9147796c 100644
--- a/docs/config-examples/default-config.toml
+++ b/docs/config-examples/default-config.toml
@@ -43,6 +43,10 @@ on-focused-monitor-changed = ['move-mouse monitor-lazy-center']
# Also see: https://nikitabobko.github.io/AeroSpace/goodies#disable-hide-app
automatically-unhide-macos-hidden-apps = false
+# Quit apps when they have zero windows and when they are unfocused.
+# Possible values: off|all-except-finder|all
+automatically-quit-apps-with-zero-windows = 'off'
+
# Possible values: (qwerty|dvorak)
# See https://nikitabobko.github.io/AeroSpace/guide#key-mapping
[key-mapping]