diff --git a/slab/menubar-swift/Sources/SlabMenubar/AppDelegate.swift b/slab/menubar-swift/Sources/SlabMenubar/AppDelegate.swift index 53b8276ec7..5bc1719876 100644 --- a/slab/menubar-swift/Sources/SlabMenubar/AppDelegate.swift +++ b/slab/menubar-swift/Sources/SlabMenubar/AppDelegate.swift @@ -239,15 +239,16 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { self.updateAnimTimer() self.updateBlinkTimer() - // Suffocation alarm — fire the fullscreen red klaxon the moment - // the whole machine crosses into imminent-thrash (the 8 GB Macs - // give no warning of their own before swap locks them out). The - // controller owns arm/clear hysteresis and the snooze re-arm; - // the sound kill switch + hidden test trigger are on-disk flags. + // Suffocation alarm — fire the peripheral red klaxon when the + // whole machine crosses into imminent-thrash (the 8 GB Macs give + // no warning of their own before swap locks them out). OFF by + // default; the "Performance alarm" menubar checkbox is the master + // switch. The controller owns arm/clear hysteresis + snooze; the + // hidden test trigger is an on-disk flag. let alarmFM = FileManager.default PerformanceAlarmController.shared.update( stats: self.state.system, - soundOff: alarmFM.fileExists(atPath: Paths.alarmMutedFlag), + enabled: alarmFM.fileExists(atPath: Paths.alarmEnabledFlag), forceTest: alarmFM.fileExists(atPath: Paths.alarmTestFlag)) self.mailTickCount += 1 @@ -984,11 +985,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate { refresh() } - /// Kill switch for the suffocation alarm's chord. Flag present = silent - /// (the red blink still fires — it's a safety alarm). Toggled from the - /// System submenu's "Alarm sound" item. - @objc func toggleAlarmSound() { - let path = Paths.alarmMutedFlag + /// Master switch for the performance-alarm feature (OFF by default). Flag + /// present = enabled. Toggled from the System submenu's "Performance alarm" + /// checkbox. + @objc func togglePerfAlarm() { + let path = Paths.alarmEnabledFlag let fm = FileManager.default if fm.fileExists(atPath: path) { try? fm.removeItem(atPath: path) diff --git a/slab/menubar-swift/Sources/SlabMenubar/MenuBuilder.swift b/slab/menubar-swift/Sources/SlabMenubar/MenuBuilder.swift index f742c3a21a..f10970d632 100644 --- a/slab/menubar-swift/Sources/SlabMenubar/MenuBuilder.swift +++ b/slab/menubar-swift/Sources/SlabMenubar/MenuBuilder.swift @@ -188,13 +188,14 @@ enum MenuBuilder { selector: #selector(AppDelegate.openActivityMonitor), target: target) sub.addItem(open) - // Kill switch for the suffocation-alarm chord (the fullscreen red blink - // that fires on imminent thrash). Checkmark = sound ON. - let alarmSound = item("Alarm sound", - selector: #selector(AppDelegate.toggleAlarmSound), - target: target) - alarmSound.state = FileManager.default.fileExists(atPath: Paths.alarmMutedFlag) ? .off : .on - sub.addItem(alarmSound) + // Master switch for the suffocation performance alarm (peripheral red + // klaxon + ding on imminent thrash). OFF by default; checkmark = enabled. + let alarmToggle = item("Performance alarm", + selector: #selector(AppDelegate.togglePerfAlarm), + target: target) + alarmToggle.state = FileManager.default.fileExists(atPath: Paths.alarmEnabledFlag) ? .on : .off + alarmToggle.toolTip = "Fullscreen red edge-glow + ding when this Mac is about to swap-thrash (8 GB fleet safety). Off by default. See ~/Desktop/Performance-Alarm.md." + sub.addItem(alarmToggle) parent.submenu = sub return parent } diff --git a/slab/menubar-swift/Sources/SlabMenubar/Paths.swift b/slab/menubar-swift/Sources/SlabMenubar/Paths.swift index e6c5602339..2ddf9af439 100644 --- a/slab/menubar-swift/Sources/SlabMenubar/Paths.swift +++ b/slab/menubar-swift/Sources/SlabMenubar/Paths.swift @@ -156,15 +156,17 @@ enum Paths { /// exists only when the user has explicitly turned them OFF from the menu. static var promptSigilsDisabledFlag: String { "\(slabHome)/state/prompt-sigils-off" } - /// Kill switch for the suffocation alarm's CHORD. When this file exists the - /// fullscreen red blink still fires (it's a safety alarm), but the audio - /// stays silent — toggled from the menubar's "Alarm sound" item. - static var alarmMutedFlag: String { "\(slabHome)/state/alarm-muted" } - - /// Hidden debug trigger: while this file exists the suffocation alarm is - /// FORCED on with synthetic critical stats, regardless of real memory — so - /// the blink + chord can be seen/heard without actually thrashing the box. - /// `touch` it to test, `rm` it to restore normal behavior. + /// Master switch for the "suffocating machine" performance alarm — the + /// peripheral red/klaxon overlay + ding that fires on imminent memory thrash. + /// The feature is OFF by default; this file exists only when the user has + /// turned it ON from the menubar's "Performance alarm" checkbox. See the + /// desktop doc "Performance-Alarm.md" for what it's tied to. + static var alarmEnabledFlag: String { "\(slabHome)/state/perf-alarm-enabled" } + + /// Hidden debug trigger: while this file exists (AND the feature is enabled) + /// the alarm is FORCED on with synthetic critical stats, regardless of real + /// memory — so the overlay + ding can be seen/heard without thrashing the + /// box. `touch` to test, `rm` to restore normal behavior. static var alarmTestFlag: String { "\(slabHome)/state/alarm-test" } } diff --git a/slab/menubar-swift/Sources/SlabMenubar/PerformanceAlarm.swift b/slab/menubar-swift/Sources/SlabMenubar/PerformanceAlarm.swift index b4d8bef241..36a1e810c0 100644 --- a/slab/menubar-swift/Sources/SlabMenubar/PerformanceAlarm.swift +++ b/slab/menubar-swift/Sources/SlabMenubar/PerformanceAlarm.swift @@ -301,7 +301,6 @@ final class PerformanceAlarmController { private let fps: CGFloat = 60 private var armed = false private var severity: CGFloat = 0 - private var muted = false /// Suppress re-arming until this time after a manual dismiss, so Esc/click /// actually gets you a breather instead of the alarm snapping back instantly. private var snoozeUntil: Date? @@ -318,10 +317,16 @@ final class PerformanceAlarmController { private var history: [Double] = [] private let maxSamples = 90 - /// Called each refresh tick with the freshest whole-machine stats. `soundOff` - /// is the kill switch (menu toggle / flag). Owns the arm/clear decision. - func update(stats: SystemStats, soundOff: Bool, forceTest: Bool) { - muted = soundOff + /// Called each refresh tick with the freshest whole-machine stats. `enabled` + /// is the master switch (menubar checkbox) — the whole feature is OFF by + /// default. Owns the arm/clear decision. + func update(stats: SystemStats, enabled: Bool, forceTest: Bool) { + // Master switch off: make sure nothing is lit and stop here. + if !enabled { + if armed { disarm() } + criticalStreak = 0 + return + } // Debug trigger: pretend the box is suffocating so the alarm can be // seen/heard without actually thrashing it. let s = forceTest @@ -348,7 +353,7 @@ final class PerformanceAlarmController { } // Still critical — refresh severity + readout live. severity = CGFloat(max(s.severity, forceTest ? 0.9 : 0)) - ding.update(volume: muted ? 0 : volumeForSeverity(), severity: severity) + ding.update(volume: volumeForSeverity(), severity: severity) setReadout(for: s) } @@ -363,7 +368,7 @@ final class PerformanceAlarmController { severity = CGFloat(max(s.severity, 0.05)) buildWindows() setReadout(for: s) - if !muted { ding.start(volume: volumeForSeverity(), severity: severity) } + ding.start(volume: volumeForSeverity(), severity: severity) startBlink() installEscMonitor() }