Das Aufnahmesymbol kam vom Mixer, nicht vom Pegel

Gemessen über kAudioProcessPropertyIsRunningInput: mit laufendem Mixer nimmt
Onyx auf, ohne ihn nicht. Ein Process Tap ist für macOS eine Tonaufnahme —
auch wenn damit nur die Lautstärke geregelt wird. Solange der Mixer Chrome und
Firefox abgreift, steht das orange Symbol in der Leiste.

Der Mixer stand seit der Fehlersuche am Wochenende auf „automatisch starten",
deshalb war es dauerhaft. Das Häkchen steht jetzt wieder aus.

Ein Hinweis unter dem Häkchen sagt das jetzt, wo die Entscheidung fällt: wer
das Symbol ohne Erklärung sieht, sucht den Fehler bei sich oder hält die App
für unsauber.

Dazu ein echter Fund an derselben Stelle: der Mikrofonpegel hing an
`onDisappear` der View — und das bleibt bei einem NSPopover aus. Dessen
Controller lebt nach dem Schließen weiter, die View verschwindet nie aus der
Hierarchie. Die Messung wäre also beim ersten Öffnen angegangen und nie wieder
aus. Sie hängt jetzt am Popover selbst: `MenuBarModule` bekommt
`popoverDidOpen`/`popoverDidClose`, gemeldet auch dann, wenn das Popover von
selbst zugeht — beim Wechsel in eine andere App etwa.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-08-14 15:20:07 +02:00
parent c94c6166f5
commit ee848b86cc
5 changed files with 93 additions and 6 deletions

View File

@@ -10,6 +10,9 @@ import SwiftUI
@MainActor
public final class MenuBarController {
/// Hält den Delegaten am Leben `NSPopover` merkt ihn sich nur schwach.
private var popoverWatcher: PopoverWatcher?
/// Ein Modul, dessen Element keine Breite bekommen hat.
///
/// Die Menüleiste ist endlich, und auf einem Notch-Display ist die Mitte
@@ -148,7 +151,11 @@ public final class MenuBarController {
rootView: entry.module.makePopoverView()
.tint(Onyx.Color.accent)
.labelStyle(.onyx))
let watcher = PopoverWatcher { [weak self] in self?.popoverClosedByItself() }
popoverWatcher = watcher
entry.popover.delegate = watcher
entry.popover.show(relativeTo: sender.bounds, of: sender, preferredEdge: .minY)
entry.module.popoverDidOpen()
// Ohne das bleibt das Popover hinter dem aktiven Fenster.
entry.popover.contentViewController?.view.window?.makeKey()
openPopoverID = id
@@ -190,11 +197,21 @@ public final class MenuBarController {
}
}
/// Zugegangen, ohne dass wir es veranlasst haben.
private func popoverClosedByItself() {
guard let id = openPopoverID, let entry = entries[id] else { return }
entry.module.popoverDidClose()
openPopoverID = nil
}
private func closeOpenPopover() {
if let id = openPopoverID, let entry = entries[id] {
entry.popover.performClose(nil)
}
openPopoverID = nil
// Über denselben Weg wie das Zugehen von selbst: der räumt
// `openPopoverID` ab und ist damit genau einmal wirksam, egal ob der
// Delegat vorher, nachher oder gar nicht meldet.
popoverClosedByItself()
globalMonitor.map(NSEvent.removeMonitor)
localMonitor.map(NSEvent.removeMonitor)
keyMonitor.map(NSEvent.removeMonitor)
@@ -215,3 +232,21 @@ public final class MenuBarController {
onVisibilityChanged?(hidden)
}
}
/// Horcht, ob das Popover zugegangen ist.
///
/// Ein eigener Typ, weil ein Delegat von AppKit ein `NSObject` sein muss und
/// der Controller keines ist. Nötig ist er trotzdem: das Popover geht auch
/// ohne Zutun zu beim Wechsel in eine andere App etwa , und ohne diese
/// Meldung erfährt eine laufende Messung nie, dass niemand mehr hinsieht.
final class PopoverWatcher: NSObject, NSPopoverDelegate {
private let onClose: @MainActor () -> Void
init(onClose: @escaping @MainActor () -> Void) {
self.onClose = onClose
}
func popoverDidClose(_ notification: Notification) {
MainActor.assumeIsolated { onClose() }
}
}