mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-28 07:34:32 +00:00
Update ddc control toggle
This commit is contained in:
parent
9c044237c1
commit
8abd21de31
7 changed files with 305 additions and 261 deletions
|
|
@ -115,6 +115,10 @@ final class BetterDisplayManager: ObservableObject {
|
|||
private var cancellables = Set<AnyCancellable>()
|
||||
private weak var coordinator: DynamicIslandViewCoordinator?
|
||||
|
||||
private var isBetterDisplayIntegrationEnabled: Bool {
|
||||
Defaults[.enableThirdPartyDDCIntegration] && Defaults[.thirdPartyDDCProvider] == .betterDisplay
|
||||
}
|
||||
|
||||
private init() {
|
||||
isDetected = Self.checkInstallation()
|
||||
isRunning = Self.checkRunning()
|
||||
|
|
@ -128,11 +132,7 @@ final class BetterDisplayManager: ObservableObject {
|
|||
/// Configure with the view coordinator for HUD dispatch.
|
||||
func configure(coordinator: DynamicIslandViewCoordinator) {
|
||||
self.coordinator = coordinator
|
||||
|
||||
// Start listening if integration is enabled and BetterDisplay is running
|
||||
if Defaults[.enableBetterDisplayIntegration] && isRunning {
|
||||
startListening()
|
||||
}
|
||||
refreshListeningState()
|
||||
}
|
||||
|
||||
/// Refresh detection status (e.g. after app install/uninstall).
|
||||
|
|
@ -141,11 +141,13 @@ final class BetterDisplayManager: ObservableObject {
|
|||
let wasRunning = isRunning
|
||||
isDetected = Self.checkInstallation()
|
||||
isRunning = Self.checkRunning()
|
||||
if wasRunning && !isRunning {
|
||||
stopListening()
|
||||
} else if !wasRunning && isRunning && Defaults[.enableBetterDisplayIntegration] {
|
||||
startListening()
|
||||
if wasDetected != isDetected {
|
||||
NSLog("📺 BetterDisplay detection changed: detected=\(isDetected)")
|
||||
}
|
||||
if wasRunning != isRunning {
|
||||
NSLog("📺 BetterDisplay running state changed: running=\(isRunning)")
|
||||
}
|
||||
refreshListeningState()
|
||||
}
|
||||
|
||||
// MARK: - Detection
|
||||
|
|
@ -199,7 +201,7 @@ final class BetterDisplayManager: ObservableObject {
|
|||
// MARK: - OSD Handling
|
||||
|
||||
private func handleOSDNotification(_ notification: Notification) {
|
||||
guard Defaults[.enableBetterDisplayIntegration], isRunning else { return }
|
||||
guard isBetterDisplayIntegrationEnabled, isRunning else { return }
|
||||
|
||||
guard let notificationString = notification.object as? String else {
|
||||
NSLog("⚠️ BetterDisplay OSD: unexpected notification format")
|
||||
|
|
@ -213,6 +215,16 @@ final class BetterDisplayManager: ObservableObject {
|
|||
BetterDisplayOSDNotification.self,
|
||||
from: Data(notificationString.utf8)
|
||||
)
|
||||
let displayIDText = osd.displayID.map { String($0) } ?? "nil"
|
||||
let targetText = osd.controlTarget ?? "nil"
|
||||
let iconIDText = osd.systemIconID.map { String($0) } ?? "nil"
|
||||
let valueText = osd.value.map { String($0) } ?? "nil"
|
||||
let maxValueText = osd.maxValue.map { String($0) } ?? "nil"
|
||||
let symbolText = osd.customSymbol ?? "nil"
|
||||
let textValue = osd.text ?? "nil"
|
||||
NSLog(
|
||||
"📺 BetterDisplay decoded payload: displayID=\(displayIDText) target=\(targetText) iconID=\(iconIDText) value=\(valueText) maxValue=\(maxValueText) customSymbol=\(symbolText) text=\(textValue)"
|
||||
)
|
||||
routeOSDToHUD(osd)
|
||||
} catch {
|
||||
NSLog("⚠️ BetterDisplay OSD decode error: \(error.localizedDescription)")
|
||||
|
|
@ -225,8 +237,18 @@ final class BetterDisplayManager: ObservableObject {
|
|||
let normalizedValue = normalizeValue(osd.value, maxValue: osd.maxValue)
|
||||
let targetScreen = resolveScreen(for: osd.displayID)
|
||||
let isExternalDisplay = isExternal(displayID: osd.displayID, resolvedScreen: targetScreen)
|
||||
let inferredMute = osd.controlTarget == "mute" || osd.systemIconID == 4
|
||||
let hasVolumeData = category == .volume && osd.value != nil
|
||||
let targetText = osd.controlTarget ?? "nil"
|
||||
let displayIDText = osd.displayID.map { String($0) } ?? "nil"
|
||||
let resolvedScreenText = targetScreen?.localizedName ?? "nil"
|
||||
let valueText = osd.value.map { String($0) } ?? "nil"
|
||||
let maxValueText = osd.maxValue.map { String($0) } ?? "nil"
|
||||
let normalizedText = String(format: "%.3f", normalizedValue)
|
||||
|
||||
NSLog("📺 BetterDisplay OSD: target=\(osd.controlTarget ?? "nil") displayID=\(osd.displayID.map(String.init) ?? "nil") resolvedScreen=\(targetScreen?.localizedName ?? "nil") isExternal=\(isExternalDisplay) value=\(osd.value ?? -1)")
|
||||
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)"
|
||||
)
|
||||
|
||||
switch category {
|
||||
case .brightness:
|
||||
|
|
@ -365,6 +387,17 @@ final class BetterDisplayManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
private func categoryName(_ category: BetterDisplayControlCategory) -> String {
|
||||
switch category {
|
||||
case .brightness:
|
||||
return "brightness"
|
||||
case .volume:
|
||||
return "volume"
|
||||
case .other:
|
||||
return "other"
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalize a BetterDisplay value (0...maxValue) to 0...1 range.
|
||||
private func normalizeValue(_ value: Double?, maxValue: Double?) -> CGFloat {
|
||||
guard let value else { return 0 }
|
||||
|
|
@ -488,7 +521,7 @@ final class BetterDisplayManager: ObservableObject {
|
|||
Task { @MainActor in
|
||||
NSLog("🔴 BetterDisplay terminated (workspace notification)")
|
||||
self?.isRunning = false
|
||||
self?.stopListening()
|
||||
self?.refreshListeningState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -507,9 +540,7 @@ final class BetterDisplayManager: ObservableObject {
|
|||
NSLog("🟢 BetterDisplay launched notification received")
|
||||
self?.isDetected = true
|
||||
self?.isRunning = true
|
||||
if Defaults[.enableBetterDisplayIntegration] {
|
||||
self?.startListening()
|
||||
}
|
||||
self?.refreshListeningState()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -521,23 +552,34 @@ final class BetterDisplayManager: ObservableObject {
|
|||
Task { @MainActor in
|
||||
NSLog("🔴 BetterDisplay terminated notification received")
|
||||
self?.isRunning = false
|
||||
self?.stopListening()
|
||||
self?.refreshListeningState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Settings Observer
|
||||
|
||||
private func refreshListeningState() {
|
||||
if isBetterDisplayIntegrationEnabled && isRunning {
|
||||
startListening()
|
||||
} else {
|
||||
stopListening()
|
||||
}
|
||||
}
|
||||
|
||||
private func setupSettingsObserver() {
|
||||
Defaults.publisher(.enableBetterDisplayIntegration, options: [])
|
||||
.sink { [weak self] change in
|
||||
Defaults.publisher(.enableThirdPartyDDCIntegration, options: [])
|
||||
.sink { [weak self] _ in
|
||||
Task { @MainActor in
|
||||
guard let self else { return }
|
||||
if change.newValue && self.isRunning {
|
||||
self.startListening()
|
||||
} else {
|
||||
self.stopListening()
|
||||
}
|
||||
self?.refreshListeningState()
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
Defaults.publisher(.thirdPartyDDCProvider, options: [])
|
||||
.sink { [weak self] _ in
|
||||
Task { @MainActor in
|
||||
self?.refreshListeningState()
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
|
|
|||
|
|
@ -87,6 +87,10 @@ final class LunarManager: ObservableObject {
|
|||
private static let maxReconnectAttempts = 5
|
||||
private static let baseReconnectDelay: UInt64 = 2_000_000_000 // 2 seconds
|
||||
|
||||
private var isLunarIntegrationEnabled: Bool {
|
||||
Defaults[.enableThirdPartyDDCIntegration] && Defaults[.thirdPartyDDCProvider] == .lunar
|
||||
}
|
||||
|
||||
private init() {
|
||||
isDetected = Self.checkInstallation()
|
||||
isRunning = Self.checkRunning()
|
||||
|
|
@ -99,10 +103,7 @@ final class LunarManager: ObservableObject {
|
|||
/// Configure with the view coordinator for HUD dispatch.
|
||||
func configure(coordinator: DynamicIslandViewCoordinator) {
|
||||
self.coordinator = coordinator
|
||||
|
||||
if Defaults[.enableLunarIntegration] && isRunning {
|
||||
connectToLunar()
|
||||
}
|
||||
refreshConnectionState()
|
||||
}
|
||||
|
||||
/// Called when Atoll is about to quit so Lunar's native OSD is restored.
|
||||
|
|
@ -115,11 +116,10 @@ final class LunarManager: ObservableObject {
|
|||
let wasRunning = isRunning
|
||||
isDetected = Self.checkInstallation()
|
||||
isRunning = Self.checkRunning()
|
||||
if wasRunning && !isRunning {
|
||||
disconnect()
|
||||
} else if !wasRunning && isRunning && Defaults[.enableLunarIntegration] {
|
||||
connectToLunar()
|
||||
if wasRunning != isRunning {
|
||||
NSLog("🌙 Lunar running state changed: running=\(isRunning)")
|
||||
}
|
||||
refreshConnectionState()
|
||||
}
|
||||
|
||||
// MARK: - Detection
|
||||
|
|
@ -267,11 +267,11 @@ final class LunarManager: ObservableObject {
|
|||
}
|
||||
|
||||
private func handleReceivedData(_ data: Data) {
|
||||
guard Defaults[.enableLunarIntegration], isRunning else { return }
|
||||
guard isLunarIntegrationEnabled, isRunning else { return }
|
||||
|
||||
do {
|
||||
let displayData = try JSONDecoder().decode(LunarDisplayData.self, from: data)
|
||||
NSLog("🌙 Lunar: display=\(displayData.display) brightness=\(displayData.brightness ?? -1) contrast=\(displayData.contrast ?? -1) volume=\(displayData.volume ?? -1) mute=\(displayData.mute.map(String.init) ?? "nil")")
|
||||
logDecodedPayload(displayData, source: "socket")
|
||||
routeToHUD(displayData)
|
||||
} catch {
|
||||
// Lunar may send multi-line JSON or partial data; try splitting by newlines
|
||||
|
|
@ -280,7 +280,7 @@ final class LunarManager: ObservableObject {
|
|||
for line in lines {
|
||||
guard let lineData = line.data(using: .utf8) else { continue }
|
||||
if let displayData = try? JSONDecoder().decode(LunarDisplayData.self, from: lineData) {
|
||||
NSLog("🌙 Lunar: display=\(displayData.display) brightness=\(displayData.brightness ?? -1) contrast=\(displayData.contrast ?? -1) volume=\(displayData.volume ?? -1)")
|
||||
logDecodedPayload(displayData, source: "socket-line")
|
||||
routeToHUD(displayData)
|
||||
}
|
||||
}
|
||||
|
|
@ -292,6 +292,16 @@ final class LunarManager: ObservableObject {
|
|||
private func routeToHUD(_ data: LunarDisplayData) {
|
||||
let targetScreen = resolveScreen(for: data.display)
|
||||
let isExternal = isExternalDisplay(data.display, resolvedScreen: targetScreen)
|
||||
let hasVolumeData = data.volume != nil || data.mute != nil
|
||||
let resolvedScreenText = targetScreen?.localizedName ?? "nil"
|
||||
let brightnessText = data.brightness.map { String($0) } ?? "nil"
|
||||
let contrastText = data.contrast.map { String($0) } ?? "nil"
|
||||
let volumeText = data.volume.map { String($0) } ?? "nil"
|
||||
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)"
|
||||
)
|
||||
|
||||
// Nits-only events are informational (no user-facing HUD needed).
|
||||
let hasActionableChange = data.brightness != nil || data.contrast != nil || data.volume != nil || data.mute != nil
|
||||
|
|
@ -418,10 +428,21 @@ final class LunarManager: ObservableObject {
|
|||
return num.uint32Value
|
||||
}
|
||||
|
||||
private func logDecodedPayload(_ data: LunarDisplayData, source: String) {
|
||||
let hasVolumeData = data.volume != nil || data.mute != nil
|
||||
let brightnessText = data.brightness.map { String($0) } ?? "nil"
|
||||
let contrastText = data.contrast.map { String($0) } ?? "nil"
|
||||
let volumeText = data.volume.map { String($0) } ?? "nil"
|
||||
let muteText = data.mute.map { String($0) } ?? "nil"
|
||||
NSLog(
|
||||
"🌙 Lunar decoded payload (\(source)): display=\(data.display) brightness=\(brightnessText) contrast=\(contrastText) volume=\(volumeText) mute=\(muteText) hasVolumeData=\(hasVolumeData)"
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Reconnection
|
||||
|
||||
private func scheduleReconnect() {
|
||||
guard Defaults[.enableLunarIntegration], isRunning else { return }
|
||||
guard isLunarIntegrationEnabled, isRunning else { return }
|
||||
guard reconnectAttempts < Self.maxReconnectAttempts else {
|
||||
NSLog("🌙 Lunar: max reconnect attempts reached, giving up")
|
||||
return
|
||||
|
|
@ -435,7 +456,7 @@ final class LunarManager: ObservableObject {
|
|||
|
||||
reconnectTask = Task { @MainActor in
|
||||
try? await Task.sleep(nanoseconds: delay)
|
||||
guard !Task.isCancelled, self.isRunning, Defaults[.enableLunarIntegration] else { return }
|
||||
guard !Task.isCancelled, self.isRunning, self.isLunarIntegrationEnabled else { return }
|
||||
NSLog("🌙 Lunar: reconnect attempt \(attempt + 1)/\(Self.maxReconnectAttempts)")
|
||||
self.connectToLunar()
|
||||
}
|
||||
|
|
@ -459,7 +480,7 @@ final class LunarManager: ObservableObject {
|
|||
self?.isDetected = true
|
||||
self?.isRunning = true
|
||||
self?.reconnectAttempts = 0
|
||||
if Defaults[.enableLunarIntegration] {
|
||||
if self?.isLunarIntegrationEnabled == true {
|
||||
// Give Lunar a moment to start its socket server
|
||||
try? await Task.sleep(nanoseconds: 1_500_000_000)
|
||||
self?.connectToLunar()
|
||||
|
|
@ -485,16 +506,27 @@ final class LunarManager: ObservableObject {
|
|||
|
||||
// MARK: - Settings Observer
|
||||
|
||||
private func refreshConnectionState() {
|
||||
if isLunarIntegrationEnabled && isRunning {
|
||||
connectToLunar()
|
||||
} else {
|
||||
disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
private func setupSettingsObserver() {
|
||||
Defaults.publisher(.enableLunarIntegration, options: [])
|
||||
.sink { [weak self] change in
|
||||
Defaults.publisher(.enableThirdPartyDDCIntegration, options: [])
|
||||
.sink { [weak self] _ in
|
||||
Task { @MainActor in
|
||||
guard let self else { return }
|
||||
if change.newValue && self.isRunning {
|
||||
self.connectToLunar()
|
||||
} else {
|
||||
self.disconnect()
|
||||
}
|
||||
self?.refreshConnectionState()
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
Defaults.publisher(.thirdPartyDDCProvider, options: [])
|
||||
.sink { [weak self] _ in
|
||||
Task { @MainActor in
|
||||
self?.refreshConnectionState()
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
|
|
|||
|
|
@ -181,16 +181,15 @@ class SystemHUDManager {
|
|||
)
|
||||
}.store(in: &cancellables)
|
||||
|
||||
// When BetterDisplay integration toggled, restart observer to update Cmd+Brightness key interception
|
||||
Defaults.publisher(.enableBetterDisplayIntegration, options: []).sink { [weak self] _ in
|
||||
// Restart observer when third-party DDC integration state changes.
|
||||
Defaults.publisher(.enableThirdPartyDDCIntegration, options: []).sink { [weak self] _ in
|
||||
guard let self = self, self.isSetupComplete else { return }
|
||||
Task { @MainActor in
|
||||
await self.startSystemObserver()
|
||||
}
|
||||
}.store(in: &cancellables)
|
||||
|
||||
// When Lunar integration toggled, restart observer to update brightness key interception
|
||||
Defaults.publisher(.enableLunarIntegration, options: []).sink { [weak self] _ in
|
||||
Defaults.publisher(.thirdPartyDDCProvider, options: []).sink { [weak self] _ in
|
||||
guard let self = self, self.isSetupComplete else { return }
|
||||
Task { @MainActor in
|
||||
await self.startSystemObserver()
|
||||
|
|
@ -248,9 +247,9 @@ class SystemHUDManager {
|
|||
keyboardBacklightEnabled = Defaults[.enableKeyboardBacklightHUD]
|
||||
}
|
||||
|
||||
// When BetterDisplay or Lunar integration is on, stop intercepting brightness and
|
||||
// When third-party DDC integration is on, stop intercepting brightness and
|
||||
// Cmd+Brightness keys so the external app receives them and sends DDC/OSD events.
|
||||
if Defaults[.enableBetterDisplayIntegration] || Defaults[.enableLunarIntegration] {
|
||||
if Defaults[.enableThirdPartyDDCIntegration] {
|
||||
brightnessEnabled = false
|
||||
keyboardBacklightEnabled = false
|
||||
}
|
||||
|
|
@ -281,7 +280,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]), BD: \(Defaults[.enableBetterDisplayIntegration]))")
|
||||
print("System observer started (HUD: \(Defaults[.enableSystemHUD]), OSD: \(Defaults[.enableCustomOSD]), Vertical: \(Defaults[.enableVerticalHUD]), ThirdPartyDDC: \(Defaults[.enableThirdPartyDDCIntegration]), Provider: \(Defaults[.thirdPartyDDCProvider].displayName))")
|
||||
isSystemOperationInProgress = false
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue