mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Update Volume listener for ddc controls
This commit is contained in:
parent
8abd21de31
commit
68cb3fd2de
6 changed files with 74 additions and 18 deletions
|
|
@ -4091,6 +4091,10 @@
|
|||
},
|
||||
"shouldTranslate" : false
|
||||
},
|
||||
"Atoll always listens to selected-provider brightness events, and listens to provider volume events only when external volume listener is enabled." : {
|
||||
"comment" : "A footer text that explains the behavior of the third-party DDC integration settings.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Atoll can show all your upcoming events in one place. Access to your calendar is needed to display your schedule." : {
|
||||
"localizations" : {
|
||||
"pt-BR" : {
|
||||
|
|
@ -4111,8 +4115,8 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Atoll listens to the selected provider and renders its brightness/volume HUD updates using your currently active HUD style above." : {
|
||||
"comment" : "A footer text that explains the functionality of the \"Enable third-party DDC app integration\" toggle.",
|
||||
"Atoll keeps native volume key interception. External provider volume payloads are ignored while this is off." : {
|
||||
"comment" : "A description of the behavior of Atoll's volume key interception when the external volume control listener is disabled.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Atoll needs Full Disk Access to read the system notification database. If Atoll does not appear in the list, use 'Reveal App in Finder' and drag it into the settings window." : {
|
||||
|
|
@ -4126,6 +4130,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Atoll's built-in volume key interception is disabled while external volume listening is on. Volume HUD/OSD will follow %@ payloads." : {
|
||||
"comment" : "A description below the toggle that explains the effect of enabling or disabling the feature.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Attached Files" : {
|
||||
"localizations" : {
|
||||
"fr" : {
|
||||
|
|
@ -10140,6 +10148,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Enable external volume control listener" : {
|
||||
"comment" : "A toggle that enables external volume control listener.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Enable global keyboard shortcuts above to customize your shortcuts." : {
|
||||
"localizations" : {
|
||||
"pt-BR" : {
|
||||
|
|
|
|||
|
|
@ -741,6 +741,7 @@ struct SettingsView: View {
|
|||
SettingsSearchEntry(tab: .hudAndOSD, title: "Icon & Progress Color", keywords: ["color", "icon", "white", "black", "gray", "osd"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Icon & Progress Color")),
|
||||
SettingsSearchEntry(tab: .hudAndOSD, title: "Third-party DDC app integration", keywords: ["ddc", "third party", "external", "display", "betterdisplay", "lunar"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Third-party DDC app integration")),
|
||||
SettingsSearchEntry(tab: .hudAndOSD, title: "Third-party DDC provider", keywords: ["provider", "betterdisplay", "lunar", "integration", "refresh detection"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Third-party DDC provider")),
|
||||
SettingsSearchEntry(tab: .hudAndOSD, title: "Enable external volume control listener", keywords: ["external volume", "ddc volume", "betterdisplay volume", "lunar volume", "disable native volume"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Enable external volume control listener")),
|
||||
|
||||
// Media
|
||||
SettingsSearchEntry(tab: .media, title: "Music Source", keywords: ["media source", "controller"], highlightID: SettingsTab.media.highlightID(for: "Music Source")),
|
||||
|
|
@ -1931,6 +1932,7 @@ private struct HUDAndOSDSettingsView: View {
|
|||
private struct ExternalDisplayIntegrationsSection: View {
|
||||
@Default(.enableThirdPartyDDCIntegration) var enableThirdPartyDDCIntegration
|
||||
@Default(.thirdPartyDDCProvider) var thirdPartyDDCProvider
|
||||
@Default(.enableExternalVolumeControlListener) var enableExternalVolumeControlListener
|
||||
@ObservedObject private var betterDisplayManager = BetterDisplayManager.shared
|
||||
@ObservedObject private var lunarManager = LunarManager.shared
|
||||
|
||||
|
|
@ -1970,21 +1972,21 @@ private struct ExternalDisplayIntegrationsSection: View {
|
|||
switch thirdPartyDDCProvider {
|
||||
case .betterDisplay:
|
||||
if !betterDisplayManager.isDetected {
|
||||
return "Install [BetterDisplay](https://betterdisplay.pro) to control external display brightness and volume through Atoll's HUD."
|
||||
return "Install [BetterDisplay](https://betterdisplay.pro) to control external display brightness (and optional volume) through Atoll's HUD."
|
||||
}
|
||||
if !betterDisplayManager.isRunning {
|
||||
return "BetterDisplay is installed but not currently running. Launch BetterDisplay to enable integration."
|
||||
}
|
||||
return "BetterDisplay OSD events will be routed through Atoll's active HUD style. Make sure BetterDisplay's OSD integration is enabled in Settings › Application › Integration."
|
||||
return "BetterDisplay OSD events will be routed through Atoll's active HUD style. Brightness is always routed; volume is routed when external volume control listener is enabled below. Make sure BetterDisplay's OSD integration is enabled in Settings › Application › Integration."
|
||||
case .lunar:
|
||||
if !lunarManager.isDetected {
|
||||
return "Install [Lunar](https://lunar.fyi) to control external display brightness, contrast, and volume through Atoll's HUD via DDC."
|
||||
return "Install [Lunar](https://lunar.fyi) to control external display brightness, contrast, and optional volume through Atoll's HUD via DDC."
|
||||
}
|
||||
if !lunarManager.isRunning {
|
||||
return "Lunar is installed but not currently running. Launch Lunar to enable integration."
|
||||
}
|
||||
if lunarManager.isConnected {
|
||||
return "Connected to Lunar's DDC socket. Brightness, contrast, and volume adjustments will be shown through Atoll's HUD."
|
||||
return "Connected to Lunar's DDC socket. Brightness and contrast adjustments are shown through Atoll's HUD; volume follows when external volume control listener is enabled below."
|
||||
}
|
||||
return "Lunar is running but the socket connection is not yet established. It will connect automatically."
|
||||
}
|
||||
|
|
@ -2013,6 +2015,17 @@ private struct ExternalDisplayIntegrationsSection: View {
|
|||
}
|
||||
.settingsHighlight(id: highlightID("Third-party DDC provider"))
|
||||
|
||||
Toggle("Enable external volume control listener", isOn: $enableExternalVolumeControlListener)
|
||||
.settingsHighlight(id: highlightID("Enable external volume control listener"))
|
||||
|
||||
Text(
|
||||
enableExternalVolumeControlListener
|
||||
? "Atoll's built-in volume key interception is disabled while external volume listening is on. Volume HUD/OSD will follow \(thirdPartyDDCProvider.displayName) payloads."
|
||||
: "Atoll keeps native volume key interception. External provider volume payloads are ignored while this is off."
|
||||
)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
HStack {
|
||||
Text("Status")
|
||||
Spacer()
|
||||
|
|
@ -2039,7 +2052,7 @@ private struct ExternalDisplayIntegrationsSection: View {
|
|||
}
|
||||
} footer: {
|
||||
if enableThirdPartyDDCIntegration {
|
||||
Text("Atoll listens to the selected provider and renders its brightness/volume HUD updates using your currently active HUD style above.")
|
||||
Text("Atoll always listens to selected-provider brightness events, and listens to provider volume events only when external volume listener is enabled.")
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.caption)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,6 +119,10 @@ final class BetterDisplayManager: ObservableObject {
|
|||
Defaults[.enableThirdPartyDDCIntegration] && Defaults[.thirdPartyDDCProvider] == .betterDisplay
|
||||
}
|
||||
|
||||
private var isExternalVolumeListenerEnabled: Bool {
|
||||
isBetterDisplayIntegrationEnabled && Defaults[.enableExternalVolumeControlListener]
|
||||
}
|
||||
|
||||
private init() {
|
||||
isDetected = Self.checkInstallation()
|
||||
isRunning = Self.checkRunning()
|
||||
|
|
@ -239,6 +243,7 @@ final class BetterDisplayManager: ObservableObject {
|
|||
let isExternalDisplay = isExternal(displayID: osd.displayID, resolvedScreen: targetScreen)
|
||||
let inferredMute = osd.controlTarget == "mute" || osd.systemIconID == 4
|
||||
let hasVolumeData = category == .volume && osd.value != nil
|
||||
let externalVolumeListenerEnabled = isExternalVolumeListenerEnabled
|
||||
let targetText = osd.controlTarget ?? "nil"
|
||||
let displayIDText = osd.displayID.map { String($0) } ?? "nil"
|
||||
let resolvedScreenText = targetScreen?.localizedName ?? "nil"
|
||||
|
|
@ -247,7 +252,7 @@ final class BetterDisplayManager: ObservableObject {
|
|||
let normalizedText = String(format: "%.3f", normalizedValue)
|
||||
|
||||
NSLog(
|
||||
"📺 BetterDisplay routed payload: category=\(categoryName(category)) target=\(targetText) displayID=\(displayIDText) resolvedScreen=\(resolvedScreenText) isExternal=\(isExternalDisplay) rawValue=\(valueText) maxValue=\(maxValueText) normalized=\(normalizedText) hasVolumeData=\(hasVolumeData) inferredMute=\(inferredMute)"
|
||||
"📺 BetterDisplay routed payload: category=\(categoryName(category)) target=\(targetText) displayID=\(displayIDText) resolvedScreen=\(resolvedScreenText) isExternal=\(isExternalDisplay) rawValue=\(valueText) maxValue=\(maxValueText) normalized=\(normalizedText) hasVolumeData=\(hasVolumeData) inferredMute=\(inferredMute) externalVolumeListener=\(externalVolumeListenerEnabled)"
|
||||
)
|
||||
|
||||
switch category {
|
||||
|
|
@ -256,6 +261,10 @@ final class BetterDisplayManager: ObservableObject {
|
|||
dispatchBrightnessHUD(value: normalizedValue, customSymbol: icon, onScreen: targetScreen)
|
||||
|
||||
case .volume:
|
||||
guard externalVolumeListenerEnabled else {
|
||||
NSLog("📺 BetterDisplay volume payload ignored because external volume listener is disabled")
|
||||
return
|
||||
}
|
||||
let isMuted = osd.controlTarget == "mute" || osd.systemIconID == 4
|
||||
dispatchVolumeHUD(value: normalizedValue, isMuted: isMuted, onScreen: targetScreen)
|
||||
|
||||
|
|
|
|||
|
|
@ -91,6 +91,10 @@ final class LunarManager: ObservableObject {
|
|||
Defaults[.enableThirdPartyDDCIntegration] && Defaults[.thirdPartyDDCProvider] == .lunar
|
||||
}
|
||||
|
||||
private var isExternalVolumeListenerEnabled: Bool {
|
||||
isLunarIntegrationEnabled && Defaults[.enableExternalVolumeControlListener]
|
||||
}
|
||||
|
||||
private init() {
|
||||
isDetected = Self.checkInstallation()
|
||||
isRunning = Self.checkRunning()
|
||||
|
|
@ -293,6 +297,7 @@ final class LunarManager: ObservableObject {
|
|||
let targetScreen = resolveScreen(for: data.display)
|
||||
let isExternal = isExternalDisplay(data.display, resolvedScreen: targetScreen)
|
||||
let hasVolumeData = data.volume != nil || data.mute != nil
|
||||
let externalVolumeListenerEnabled = isExternalVolumeListenerEnabled
|
||||
let resolvedScreenText = targetScreen?.localizedName ?? "nil"
|
||||
let brightnessText = data.brightness.map { String($0) } ?? "nil"
|
||||
let contrastText = data.contrast.map { String($0) } ?? "nil"
|
||||
|
|
@ -300,7 +305,7 @@ final class LunarManager: ObservableObject {
|
|||
let muteText = data.mute.map { String($0) } ?? "nil"
|
||||
|
||||
NSLog(
|
||||
"🌙 Lunar routed payload: display=\(data.display) resolvedScreen=\(resolvedScreenText) isExternal=\(isExternal) brightness=\(brightnessText) contrast=\(contrastText) volume=\(volumeText) mute=\(muteText) hasVolumeData=\(hasVolumeData)"
|
||||
"🌙 Lunar routed payload: display=\(data.display) resolvedScreen=\(resolvedScreenText) isExternal=\(isExternal) brightness=\(brightnessText) contrast=\(contrastText) volume=\(volumeText) mute=\(muteText) hasVolumeData=\(hasVolumeData) externalVolumeListener=\(externalVolumeListenerEnabled)"
|
||||
)
|
||||
|
||||
// Nits-only events are informational (no user-facing HUD needed).
|
||||
|
|
@ -321,6 +326,7 @@ final class LunarManager: ObservableObject {
|
|||
}
|
||||
|
||||
// Volume
|
||||
if externalVolumeListenerEnabled {
|
||||
if let volume = data.volume {
|
||||
let value = CGFloat(volume)
|
||||
let isMuted = data.mute ?? false
|
||||
|
|
@ -329,6 +335,9 @@ final class LunarManager: ObservableObject {
|
|||
// Mute toggle without a volume value
|
||||
dispatchVolumeHUD(value: mute ? 0 : 1, isMuted: mute, onScreen: targetScreen)
|
||||
}
|
||||
} else if hasVolumeData {
|
||||
NSLog("🌙 Lunar volume payload ignored because external volume listener is disabled")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - HUD Dispatch (mirrors BetterDisplayManager / SystemChangesObserver logic)
|
||||
|
|
|
|||
|
|
@ -195,6 +195,13 @@ class SystemHUDManager {
|
|||
await self.startSystemObserver()
|
||||
}
|
||||
}.store(in: &cancellables)
|
||||
|
||||
Defaults.publisher(.enableExternalVolumeControlListener, options: []).sink { [weak self] _ in
|
||||
guard let self = self, self.isSetupComplete else { return }
|
||||
Task { @MainActor in
|
||||
await self.startSystemObserver()
|
||||
}
|
||||
}.store(in: &cancellables)
|
||||
}
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
|
@ -227,7 +234,7 @@ class SystemHUDManager {
|
|||
}
|
||||
}
|
||||
|
||||
/// Resolves the effective control flags, applying BetterDisplay overrides.
|
||||
/// Resolves the effective control flags, applying third-party DDC overrides.
|
||||
private func resolvedControlFlags() -> (volume: Bool, brightness: Bool, backlight: Bool) {
|
||||
var volumeEnabled: Bool
|
||||
var brightnessEnabled: Bool
|
||||
|
|
@ -252,6 +259,11 @@ class SystemHUDManager {
|
|||
if Defaults[.enableThirdPartyDDCIntegration] {
|
||||
brightnessEnabled = false
|
||||
keyboardBacklightEnabled = false
|
||||
|
||||
// Optional: route volume exclusively from the selected external provider.
|
||||
if Defaults[.enableExternalVolumeControlListener] {
|
||||
volumeEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
return (volumeEnabled, brightnessEnabled, keyboardBacklightEnabled)
|
||||
|
|
@ -280,7 +292,7 @@ class SystemHUDManager {
|
|||
// Force disable system HUD to ensure no duplicates
|
||||
SystemOSDManager.disableSystemHUD()
|
||||
|
||||
print("System observer started (HUD: \(Defaults[.enableSystemHUD]), OSD: \(Defaults[.enableCustomOSD]), Vertical: \(Defaults[.enableVerticalHUD]), ThirdPartyDDC: \(Defaults[.enableThirdPartyDDCIntegration]), Provider: \(Defaults[.thirdPartyDDCProvider].displayName))")
|
||||
print("System observer started (HUD: \(Defaults[.enableSystemHUD]), OSD: \(Defaults[.enableCustomOSD]), Vertical: \(Defaults[.enableVerticalHUD]), ThirdPartyDDC: \(Defaults[.enableThirdPartyDDCIntegration]), Provider: \(Defaults[.thirdPartyDDCProvider].displayName), ExternalVolumeListener: \(Defaults[.enableExternalVolumeControlListener]))")
|
||||
isSystemOperationInProgress = false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1048,6 +1048,7 @@ extension Defaults.Keys {
|
|||
// MARK: Third-Party DDC Integration
|
||||
static let enableThirdPartyDDCIntegration = Key<Bool>("enableThirdPartyDDCIntegration", default: false)
|
||||
static let thirdPartyDDCProvider = Key<ThirdPartyDDCProvider>("thirdPartyDDCProvider", default: .betterDisplay)
|
||||
static let enableExternalVolumeControlListener = Key<Bool>("enableExternalVolumeControlListener", default: false)
|
||||
static let didMigrateThirdPartyDDCIntegration = Key<Bool>("didMigrateThirdPartyDDCIntegration", default: false)
|
||||
|
||||
// Legacy keys retained for migration/backward compatibility
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue