Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/b4ebf24f.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
bump: patch
---

Add a display-mode setting: dock, menu bar, or hidden.
10 changes: 9 additions & 1 deletion Sources/cmdcmd/Config.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import Foundation

enum DisplayMode: String, Codable, CaseIterable {
case dock
case menuBar = "menu-bar"
case hidden
}

struct Config: Codable {
var animations: Bool
var trigger: String?
var bindings: [String: Action]
var livePreviews: Bool?
var displayMode: DisplayMode?

var triggerSpec: String { trigger ?? "cmd-cmd" }
var livePreviewsEnabled: Bool { livePreviews ?? true }
var displayModeOrDefault: DisplayMode { displayMode ?? .dock }

static let `default` = Config(animations: true, trigger: nil, bindings: [:], livePreviews: nil)
static let `default` = Config(animations: true, trigger: nil, bindings: [:], livePreviews: nil, displayMode: nil)

static var fileURL: URL {
URL(fileURLWithPath: NSHomeDirectory())
Expand Down
22 changes: 20 additions & 2 deletions Sources/cmdcmd/SettingsWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ final class SettingsWindowController: NSWindowController {
init(config: Config) {
model = SettingsModel(config: config)
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 460, height: 320),
contentRect: NSRect(x: 0, y: 0, width: 460, height: 400),
styleMask: [.titled, .closable, .miniaturizable],
backing: .buffered,
defer: false
Expand All @@ -29,24 +29,28 @@ final class SettingsWindowController: NSWindowController {
private final class SettingsModel: ObservableObject {
@Published var animations: Bool { didSet { save() } }
@Published var livePreviews: Bool { didSet { save() } }
@Published var displayMode: DisplayMode { didSet { save() } }
private var base: Config
@Published var status: String = ""
var onSave: ((Config) -> Void)?

init(config: Config) {
animations = config.animations
livePreviews = config.livePreviewsEnabled
displayMode = config.displayModeOrDefault
base = config
}

func save() {
var config = base
config.animations = animations
config.livePreviews = livePreviews
config.displayMode = displayMode
do {
try Config.patchOnDisk([
("animations", animations ? "true" : "false"),
("livePreviews", livePreviews ? "true" : "false"),
("displayMode", "\"\(displayMode.rawValue)\""),
])
base = config
onSave?(config)
Expand Down Expand Up @@ -96,6 +100,20 @@ private struct SettingsRootView: View {
}
.toggleStyle(.switch)

VStack(alignment: .leading, spacing: 6) {
Text("Show app in").font(.system(size: 13, weight: .medium))
Picker("", selection: $model.displayMode) {
Text("Dock").tag(DisplayMode.dock)
Text("Menu Bar").tag(DisplayMode.menuBar)
Text("Hidden").tag(DisplayMode.hidden)
}
.labelsHidden()
.pickerStyle(.segmented)
Text("Hidden mode keeps the app running with no Dock or menu bar UI. Re-launch cmdcmd.app to bring Settings back.")
.font(.caption)
.foregroundStyle(.secondary)
}

Spacer(minLength: 0)

HStack(spacing: 10) {
Expand All @@ -108,6 +126,6 @@ private struct SettingsRootView: View {
}
}
.padding(24)
.frame(minWidth: 420, minHeight: 280)
.frame(minWidth: 420, minHeight: 360)
}
}
42 changes: 41 additions & 1 deletion Sources/cmdcmd/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ if let i = args.firstIndex(of: "--render-iconset"), i + 1 < args.count {
}

let app = NSApplication.shared
app.setActivationPolicy(.regular)
app.applicationIconImage = AppIcon.makePlaceholder()

final class AppDelegate: NSObject, NSApplicationDelegate {
Expand All @@ -28,8 +27,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

var settingsFactory: (() -> SettingsWindowController)?
private var settingsController: SettingsWindowController?
private var statusItem: NSStatusItem?

func applicationDockMenu(_ sender: NSApplication) -> NSMenu? {
return buildAppMenu()
}

private func buildAppMenu() -> NSMenu {
let menu = NSMenu()
let settingsItem = NSMenuItem(title: "Settings…", action: #selector(openSettings), keyEquivalent: "")
settingsItem.target = self
Expand All @@ -42,9 +46,43 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
keyEquivalent: "")
checkItem.target = updaterController
menu.addItem(checkItem)
menu.addItem(.separator())
menu.addItem(NSMenuItem(title: "Quit cmdcmd", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
return menu
}

func applyDisplayMode(_ mode: DisplayMode) {
switch mode {
case .dock:
removeStatusItem()
NSApp.setActivationPolicy(.regular)
case .menuBar:
NSApp.setActivationPolicy(.accessory)
installStatusItem()
case .hidden:
removeStatusItem()
NSApp.setActivationPolicy(.accessory)
}
}

private func installStatusItem() {
if statusItem == nil {
let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
let icon = NSImage(systemSymbolName: "command", accessibilityDescription: "cmdcmd")
icon?.isTemplate = true
item.button?.image = icon
item.menu = buildAppMenu()
statusItem = item
}
}

private func removeStatusItem() {
if let s = statusItem {
NSStatusBar.system.removeStatusItem(s)
}
statusItem = nil
}

func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
if !flag { openSettings() }
return true
Expand Down Expand Up @@ -73,6 +111,7 @@ app.finishLaunching()

_ = try? Config.ensureExists()
var appConfig = Config.load()
appDelegate.applyDisplayMode(appConfig.displayModeOrDefault)
let tracker = SpaceTracker()
let overlay = Overlay(tracker: tracker, config: appConfig)
var trigger: AnyObject?
Expand All @@ -82,6 +121,7 @@ appDelegate.settingsFactory = {
controller.onSave = { newConfig in
appConfig = newConfig
overlay.updateConfig(newConfig)
appDelegate.applyDisplayMode(newConfig.displayModeOrDefault)
}
return controller
}
Expand Down
Loading