Skip to content
Open
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
1 change: 1 addition & 0 deletions Sources/AppBundle/config/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ struct Config: ConvenienceMutable {
var _nonEmptyWorkspacesRootContainersLayoutOnStartup: Void = ()
var defaultRootContainerLayout: Layout = .tiles
var defaultRootContainerOrientation: DefaultContainerOrientation = .auto
var menuBarItem: Bool = true
var startAtLogin: Bool = false
var autoReloadConfig: Bool = false
var automaticallyUnhideMacosHiddenApps: Bool = false
Expand Down
1 change: 1 addition & 0 deletions Sources/AppBundle/config/parseConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ private let configParser: [String: any ParserProtocol<Config>] = [

"default-root-container-layout": Parser(\.defaultRootContainerLayout, parseLayout),
"default-root-container-orientation": Parser(\.defaultRootContainerOrientation, parseDefaultContainerOrientation),
"menu-bar-item": Parser(\.menuBarItem, parseBool),

"start-at-login": Parser(\.startAtLogin, parseBool),
"auto-reload-config": Parser(\.autoReloadConfig, parseBool),
Expand Down
32 changes: 21 additions & 11 deletions Sources/AppBundle/ui/MenuBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,27 @@ public func menuBar(viewModel: TrayMenuModel) -> some Scene { // todo should it
}
}.keyboardShortcut("Q", modifiers: .command)
} label: {
switch (viewModel.axPermissionStatus, viewModel.isEnabled) {
case (.granted, true):
MenuBarLabel().environmentObject(viewModel)
case (.granted, false):
Image(systemName: "pause.circle.fill")
.resizable()
.aspectRatio(contentMode: .fit)
case (_, _):
Image(systemName: "exclamationmark.triangle.fill")
.resizable()
.aspectRatio(contentMode: .fit)
// `menu-bar-item = false` collapses AeroSpace's menu bar item to a single compact
// icon (instead of the workspace readout). The MenuBarExtra scene must stay
// present — it's the app's only scene, and removing it terminates the app — so
// a full removal isn't possible; the compact icon is the minimal footprint.
if !viewModel.menuBarItemIsShown {
Image(systemName: "square.split.2x2")
.resizable()
.aspectRatio(contentMode: .fit)
} else {
switch (viewModel.axPermissionStatus, viewModel.isEnabled) {
case (.granted, true):
MenuBarLabel().environmentObject(viewModel)
case (.granted, false):
Image(systemName: "pause.circle.fill")
.resizable()
.aspectRatio(contentMode: .fit)
case (_, _):
Image(systemName: "exclamationmark.triangle.fill")
.resizable()
.aspectRatio(contentMode: .fit)
}
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions Sources/AppBundle/ui/TrayMenuModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ public final class TrayMenuModel: ObservableObject {

@Published var trayText: String = ""
@Published var trayItems: [TrayItem] = []
/// Whether AeroSpace shows its workspace readout in the menu bar. Driven by the
/// `menu-bar-item` config key (see updateTrayText). When false, the menu bar item
/// collapses to a single compact icon (it can't be removed outright — it's the
/// app's only scene). For people who already run their own indicator (SketchyBar).
@Published var menuBarItemIsShown: Bool = true
/// Is "layouting" enabled
@Published var isEnabled: Bool = true
@Published var workspaces: [WorkspaceViewModel] = []
Expand All @@ -24,6 +29,9 @@ enum AxPermissionStatus: Equatable {
}

@MainActor func updateTrayText() {
if TrayMenuModel.shared.menuBarItemIsShown != config.menuBarItem {
TrayMenuModel.shared.menuBarItemIsShown = config.menuBarItem
}
let sortedMonitors = sortedMonitorInfos
let focus = focus
TrayMenuModel.shared.trayText = (activeMode?.takeIf { $0 != mainModeId }?.first.map { "(\($0.uppercased())) " } ?? "") +
Expand Down
16 changes: 16 additions & 0 deletions Sources/AppBundleTests/config/ConfigTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,22 @@ final class ConfigTest: XCTestCase {
assertEquals(result.warnings, [])
}

func testParseMenuBarItem() {
// default is true (menu bar item shown)
assertEquals(defaultConfig.menuBarItem, true)

let hidden = parseConfig("menu-bar-item = false")
assertEquals(hidden.errors, [])
assertEquals(hidden.config.menuBarItem, false)

let shown = parseConfig("menu-bar-item = true")
assertEquals(shown.errors, [])
assertEquals(shown.config.menuBarItem, true)

let invalid = parseConfig("menu-bar-item = 'nonsense'")
assertTrue(!invalid.errors.isEmpty)
}

func testConfigVersionOutOfBounds() {
let result = parseConfig(
"""
Expand Down
7 changes: 7 additions & 0 deletions docs/config-examples/default-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ default-root-container-layout = 'tiles'
# tall monitor (anything higher than wide) gets vertical orientation
default-root-container-orientation = 'auto'

# Whether AeroSpace shows its workspace readout in the macOS menu bar.
# Set to false if you use a separate workspace indicator (e.g. SketchyBar) and
# don't want the duplicate. AeroSpace's menu bar item can't be removed entirely
# (it's the app's only scene, and removing it would terminate AeroSpace), so
# false collapses it to a single compact icon instead of the workspace readout.
menu-bar-item = true

# Mouse follows focus when focused monitor changes
# Drop it from your config, if you don't like this behavior
# See https://nikitabobko.github.io/AeroSpace/guide#on-focus-changed-callbacks
Expand Down
Loading