Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: automatically-quit-apps-with-zero-windows #962

Open
nikitabobko opened this issue Jan 11, 2025 · 1 comment
Open

Feat: automatically-quit-apps-with-zero-windows #962

nikitabobko opened this issue Jan 11, 2025 · 1 comment
Labels
feature-proposal A well defined feature proposal triaged The issue makes sense to maintainers

Comments

@nikitabobko
Copy link
Owner

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]
@nikitabobko
Copy link
Owner Author

nikitabobko commented Jan 11, 2025

Blocked by:

  • What to do with apps that uses "hide app" instead of closing windows for real? (e.g. Discord)

@nikitabobko nikitabobko added feature-proposal A well defined feature proposal triaged The issue makes sense to maintainers labels Jan 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-proposal A well defined feature proposal triaged The issue makes sense to maintainers
Projects
None yet
Development

No branches or pull requests

1 participant