macos: refactor command finish notification duration handling

This commit is contained in:
Joseph Martinsen
2026-02-21 16:51:32 -06:00
committed by Mitchell Hashimoto
parent 3b5a7b77d3
commit f4ddddc4b7
2 changed files with 27 additions and 34 deletions

View File

@@ -638,7 +638,6 @@ extension Ghostty {
case GHOSTTY_ACTION_PRESENT_TERMINAL:
return presentTerminal(app, target: target)
case GHOSTTY_ACTION_TOGGLE_TAB_OVERVIEW:
fallthrough
case GHOSTTY_ACTION_TOGGLE_WINDOW_DECORATIONS:
@@ -1403,15 +1402,22 @@ extension Ghostty {
guard let surface = target.target.surface else { return }
guard let surfaceView = self.surfaceView(from: surface) else { return }
guard let appState = (NSApplication.shared.delegate as? AppDelegate)?.ghostty else { return }
let config = appState.config
// Determine if we even care about command finish notifications
guard let config = (NSApplication.shared.delegate as? AppDelegate)?.ghostty.config else { return }
switch config.notifyOnCommandFinish {
case .never:
return
let mode = config.notifyOnCommandFinish
if mode == .never { return }
if mode == .unfocused && surfaceView.focused { return }
case .unfocused:
if surfaceView.focused { return }
let durationMs = v.duration / 1_000_000
if durationMs < config.notifyOnCommandFinishAfter { return }
case .always:
break
}
// Determine if the command was slow enough
let duration = Duration.nanoseconds(v.duration)
guard Duration.nanoseconds(v.duration) >= config.notifyOnCommandFinishAfter else { return }
let actions = config.notifyOnCommandFinishAction
@@ -1433,7 +1439,13 @@ extension Ghostty {
}
let body: String
let formattedDuration = Self.formatDuration(ns: v.duration)
let formattedDuration = duration.formatted(
.units(
allowed: [.hours, .minutes, .seconds, .milliseconds],
width: .abbreviated,
fractionalPart: .hide
)
)
if v.exit_code < 0 {
body = "Command took \(formattedDuration)."
} else {
@@ -1448,26 +1460,6 @@ extension Ghostty {
}
}
private static func formatDuration(ns: UInt64) -> String {
let totalSeconds = ns / 1_000_000_000
let ms = (ns / 1_000_000) % 1000
if totalSeconds == 0 {
return "\(ms)ms"
}
let seconds = totalSeconds % 60
let minutes = (totalSeconds / 60) % 60
let hours = totalSeconds / 3600
var parts: [String] = []
if hours > 0 { parts.append("\(hours)h") }
if minutes > 0 { parts.append("\(minutes)m") }
if seconds > 0 || (hours == 0 && minutes == 0) { parts.append("\(seconds)s") }
return parts.joined(separator: " ")
}
private static func toggleFloatWindow(
_ app: ghostty_app_t,
target: ghostty_target_s,

View File

@@ -144,19 +144,20 @@ extension Ghostty {
}
var notifyOnCommandFinishAction: NotifyOnCommandFinishAction {
guard let config = self.config else { return .bell }
let defaultValue = NotifyOnCommandFinishAction.bell
guard let config = self.config else { return defaultValue }
var v: CUnsignedInt = 0
let key = "notify-on-command-finish-action"
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return .bell }
guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return defaultValue }
return .init(rawValue: v)
}
var notifyOnCommandFinishAfter: UInt {
guard let config = self.config else { return 5000 }
var notifyOnCommandFinishAfter: Duration {
guard let config = self.config else { return .seconds(5) }
var v: UInt = 0
let key = "notify-on-command-finish-after"
_ = ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8)))
return v
return .milliseconds(v)
}
var splitPreserveZoom: SplitPreserveZoom {