Audiomixer zeigt Programme statt Prozesse
Core Audio meldet Prozesse, nicht Programme. Angezeigt wurden deshalb Kennungen wie „com.apple.WebKit.GPU" und Dutzende Systemdienste, die theoretisch Ton ausgeben könnten, es aber nie tun. ProcessNaming fragt erst das laufende Programm, kürzt sonst bekannte Hilfsprozess-Endungen weg und schlägt das übergeordnete Programm nach — Chromes Ton kommt aus einem Helfer, angezeigt gehört Chrome. Symbole gehen denselben Weg, sonst steht neben dem Namen ein Platzhalter. ProcessVisibility zeigt, was klingt, kürzlich klang oder geregelt wird. Die Nachlaufzeit von zwei Minuten ist der eigentliche Punkt: ohne sie verschwindet Spotify beim Pausieren aus der Liste, und man kann die Lautstärke genau dann nicht mehr einstellen, wenn man es will. Geregelte Programme bleiben immer — wer etwas leiser gedreht hat, muss es auch zurückdrehen können. Die Prozentzahl wandert vom Namen zum Regler. Oben war sie genau die Breite, die den Namen abgeschnitten hat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -93,6 +93,10 @@ private struct ProcessRow: View {
|
||||
.foregroundStyle(process.isPlaying ? Onyx.Color.textPrimary
|
||||
: Onyx.Color.textSecondary)
|
||||
.lineLimit(1)
|
||||
// In der Mitte kürzen statt am Ende: bei zwei ähnlichen
|
||||
// Namen ist das Ende oft das Unterscheidende.
|
||||
.truncationMode(.middle)
|
||||
.layoutPriority(1)
|
||||
|
||||
if process.isPlaying {
|
||||
Circle().fill(Onyx.Color.positive).frame(width: 5, height: 5)
|
||||
@@ -100,11 +104,6 @@ private struct ProcessRow: View {
|
||||
Spacer(minLength: 0)
|
||||
|
||||
if managed {
|
||||
Text("\(Int(mixer.volume(for: process.bundleID) * 100)) %")
|
||||
.font(Onyx.Font.metricSmall)
|
||||
.foregroundStyle(mixer.volume(for: process.bundleID) > 1
|
||||
? Onyx.Color.warning : Onyx.Color.textTertiary)
|
||||
|
||||
Button {
|
||||
mixer.setMuted(!mixer.isMuted(process.bundleID), for: process.bundleID)
|
||||
} label: {
|
||||
@@ -128,30 +127,43 @@ private struct ProcessRow: View {
|
||||
}
|
||||
|
||||
if managed {
|
||||
Slider(value: Binding(
|
||||
get: { mixer.volume(for: process.bundleID) },
|
||||
set: { mixer.setVolume($0, for: process.bundleID) }),
|
||||
in: 0...Gain.maximumVolume)
|
||||
.controlSize(.mini)
|
||||
.disabled(mixer.isMuted(process.bundleID))
|
||||
.overlay(alignment: .leading) {
|
||||
// Markierung bei 100 %: darüber wird verstärkt, und
|
||||
// man soll sehen, wann man diese Grenze überschreitet.
|
||||
GeometryReader { geometry in
|
||||
Rectangle()
|
||||
.fill(Onyx.Color.hairline)
|
||||
.frame(width: 1, height: 8)
|
||||
.offset(x: geometry.size.width / Gain.maximumVolume, y: 4)
|
||||
// Die Prozentzahl steht beim Regler, nicht beim Namen: oben ist
|
||||
// sie die Breite, die den Namen abschneidet, hier gehört sie
|
||||
// ohnehin hin.
|
||||
HStack(spacing: 6) {
|
||||
Slider(value: Binding(
|
||||
get: { mixer.volume(for: process.bundleID) },
|
||||
set: { mixer.setVolume($0, for: process.bundleID) }),
|
||||
in: 0...Gain.maximumVolume)
|
||||
.controlSize(.mini)
|
||||
.disabled(mixer.isMuted(process.bundleID))
|
||||
.overlay(alignment: .leading) {
|
||||
// Markierung bei 100 %: darüber wird verstärkt, und
|
||||
// man soll sehen, wann man diese Grenze überschreitet.
|
||||
GeometryReader { geometry in
|
||||
Rectangle()
|
||||
.fill(Onyx.Color.hairline)
|
||||
.frame(width: 1, height: 8)
|
||||
.offset(x: geometry.size.width / Gain.maximumVolume, y: 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text("\(Int(mixer.volume(for: process.bundleID) * 100)) %")
|
||||
.font(Onyx.Font.metricSmall)
|
||||
.foregroundStyle(mixer.volume(for: process.bundleID) > 1
|
||||
? Onyx.Color.warning : Onyx.Color.textTertiary)
|
||||
// Feste Breite, sonst wandert der Regler beim Ziehen.
|
||||
.frame(width: 34, alignment: .trailing)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var icon: some View {
|
||||
if let app = NSRunningApplication(processIdentifier: process.pid),
|
||||
let image = app.icon {
|
||||
// Auch Hilfsprozesse bekommen das Symbol ihres Programms — sonst steht
|
||||
// neben „Google Chrome" ein leerer Platzhalter.
|
||||
if let image = ProcessNaming.icon(bundleID: process.bundleID, pid: process.pid) {
|
||||
Image(nsImage: image).resizable().frame(width: 14, height: 14)
|
||||
} else {
|
||||
Image(systemName: "app.dashed")
|
||||
|
||||
Reference in New Issue
Block a user