Files
wehub-resource-sync 1d1286fadb
Build / Build and test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:18:38 +08:00

415 lines
14 KiB
Swift

import AppKit
import CoreGraphics
import Foundation
import MacToolsPluginKit
private final class WeakBrightnessControllerRef: @unchecked Sendable {
weak var value: DisplayBrightnessController?
init(_ value: DisplayBrightnessController?) {
self.value = value
}
}
private final class BrightnessWriteGate: @unchecked Sendable {
private let lock = NSLock()
private var nextWriteDate: [CGDirectDisplayID: Date] = [:]
func waitTurn(for displayID: CGDirectDisplayID, minimumInterval: TimeInterval) {
let delay = lock.withLock { () -> TimeInterval in
let now = Date()
let scheduledDate = max(now, nextWriteDate[displayID] ?? now)
nextWriteDate[displayID] = scheduledDate.addingTimeInterval(minimumInterval)
return max(0, scheduledDate.timeIntervalSince(now))
}
guard delay > 0 else {
return
}
Thread.sleep(forTimeInterval: delay)
}
}
@MainActor
final class DisplayBrightnessController: DisplayBrightnessControlling {
private struct ManagedDisplay {
var display: DisplayInfo
var backend: any DisplayBrightnessBackend
var currentBrightness: Double
var lastCommittedBrightness: Double
var pendingBrightness: Double?
var writeInFlight = false
var scheduledFlush: DispatchWorkItem?
var pendingReadbackAfterWrite = false
}
var onStateChange: (() -> Void)?
private let displayProvider: DisplayProviding
private let backendBuilder: DisplayBrightnessBackendBuilding
private let localization: PluginLocalization
private let logger = DisplayBrightnessLog.controller
private let shortWriteDelay: TimeInterval
private let minimumWriteInterval: TimeInterval
private let writeGate = BrightnessWriteGate()
private var managedDisplays: [CGDirectDisplayID: ManagedDisplay] = [:]
private var displayOrder: [CGDirectDisplayID] = []
private var lastErrorMessage: String?
private var terminateObserver: NSObjectProtocol?
init(
displayProvider: DisplayProviding = SystemDisplayService(),
backendBuilder: DisplayBrightnessBackendBuilding? = nil,
localization: PluginLocalization = PluginLocalization(bundle: .main),
shortWriteDelay: TimeInterval = 0.08,
minimumWriteInterval: TimeInterval = 0.08
) {
self.displayProvider = displayProvider
self.localization = localization
self.backendBuilder = backendBuilder ?? SystemDisplayBrightnessBackendBuilder(
resolveArm64Services: Arm64DDCServiceMatcher.resolveServices
)
self.shortWriteDelay = shortWriteDelay
self.minimumWriteInterval = minimumWriteInterval
terminateObserver = NotificationCenter.default.addObserver(
forName: NSApplication.willTerminateNotification,
object: nil,
queue: .main
) { [weak self] _ in
Task { @MainActor in
self?.cleanupAll()
}
}
}
func refresh() {
let displays = displayProvider.listConnectedDisplays()
let previousBackends = Dictionary(
uniqueKeysWithValues: managedDisplays.map { ($0.key, $0.value.backend) }
)
let nextBackends = backendBuilder.backends(for: displays, previous: previousBackends)
let nextDisplayIDs = Set(displays.map(\.id))
cleanupDisconnectedDisplays(keeping: nextDisplayIDs)
var nextManagedDisplays: [CGDirectDisplayID: ManagedDisplay] = [:]
var nextDisplayOrder: [CGDirectDisplayID] = []
for display in displays {
guard let backend = nextBackends[display.id] else {
continue
}
let previous = managedDisplays[display.id]
let brightness = resolvedBrightness(for: display, backend: backend, previous: previous)
nextManagedDisplays[display.id] = ManagedDisplay(
display: display,
backend: backend,
currentBrightness: previous?.pendingBrightness ?? brightness,
lastCommittedBrightness: brightness,
pendingBrightness: previous?.pendingBrightness,
writeInFlight: previous?.writeInFlight ?? false,
scheduledFlush: previous?.scheduledFlush,
pendingReadbackAfterWrite: previous?.pendingReadbackAfterWrite ?? false
)
nextDisplayOrder.append(display.id)
}
managedDisplays = nextManagedDisplays
displayOrder = nextDisplayOrder
if !nextManagedDisplays.isEmpty {
lastErrorMessage = nil
}
}
func snapshot() -> DisplayBrightnessSnapshot {
let displays = displayOrder.compactMap { displayID -> DisplayBrightnessDisplay? in
guard let managedDisplay = managedDisplays[displayID] else {
return nil
}
return DisplayBrightnessDisplay(
display: managedDisplay.display,
brightness: managedDisplay.currentBrightness,
isPendingWrite: managedDisplay.pendingBrightness != nil || managedDisplay.writeInFlight
)
}
return DisplayBrightnessSnapshot(
displays: displays,
errorMessage: lastErrorMessage
)
}
func setBrightness(
_ value: Double,
for displayID: CGDirectDisplayID,
phase: PluginPanelAction.SliderPhase
) {
if managedDisplays[displayID] == nil {
refresh()
}
guard var managedDisplay = managedDisplays[displayID] else {
lastErrorMessage = DisplayBrightnessControllerError.displayUnavailable(
displayID: displayID
).localizedDescription(localization: localization)
onStateChange?()
return
}
let clampedValue = Self.clamp(value)
managedDisplay.currentBrightness = clampedValue
managedDisplay.pendingBrightness = clampedValue
managedDisplay.scheduledFlush?.cancel()
managedDisplay.scheduledFlush = nil
managedDisplay.pendingReadbackAfterWrite = phase == .ended
lastErrorMessage = nil
managedDisplays[displayID] = managedDisplay
let delay = phase == .ended ? 0 : shortWriteDelay
scheduleWrite(for: displayID, delay: delay)
onStateChange?()
}
private func resolvedBrightness(
for display: DisplayInfo,
backend: any DisplayBrightnessBackend,
previous: ManagedDisplay?
) -> Double {
do {
return Self.clamp(try backend.readBrightness())
} catch {
if let previous {
return previous.currentBrightness
}
logger.error(
"failed to read brightness for \(display.name, privacy: .public): \(error.localizedDescription, privacy: .public)"
)
return 1
}
}
private func scheduleWrite(for displayID: CGDirectDisplayID, delay: TimeInterval) {
guard var managedDisplay = managedDisplays[displayID] else {
return
}
let controllerRef = WeakBrightnessControllerRef(self)
let workItem = Self.makeScheduledWriteWorkItem(
controllerRef: controllerRef,
displayID: displayID
)
managedDisplay.scheduledFlush = workItem
managedDisplays[displayID] = managedDisplay
DispatchQueue.global(qos: .userInitiated).asyncAfter(
deadline: .now() + delay,
execute: workItem
)
}
private func beginWriteIfNeeded(for displayID: CGDirectDisplayID) {
guard var managedDisplay = managedDisplays[displayID] else {
return
}
guard !managedDisplay.writeInFlight, let targetValue = managedDisplay.pendingBrightness else {
return
}
managedDisplay.writeInFlight = true
managedDisplay.pendingBrightness = nil
managedDisplay.scheduledFlush = nil
let needsReadback = managedDisplay.pendingReadbackAfterWrite
managedDisplay.pendingReadbackAfterWrite = false
let backend = managedDisplay.backend
let displayName = managedDisplay.display.name
let controllerRef = WeakBrightnessControllerRef(self)
managedDisplays[displayID] = managedDisplay
DispatchQueue.global(qos: .userInitiated).async(
execute: Self.makeWriteWorkItem(
controllerRef: controllerRef,
backend: backend,
displayID: displayID,
targetValue: targetValue,
needsReadback: needsReadback,
displayName: displayName,
writeGate: writeGate,
minimumWriteInterval: minimumWriteInterval
)
)
}
private func finishWrite(
for displayID: CGDirectDisplayID,
targetValue: Double,
readbackValue: Double?,
displayName: String,
result: Result<Void, Error>
) {
guard var managedDisplay = managedDisplays[displayID] else {
return
}
managedDisplay.writeInFlight = false
switch result {
case .success:
let committedBrightness = readbackValue.map(Self.clamp) ?? targetValue
managedDisplay.lastCommittedBrightness = committedBrightness
if managedDisplay.pendingBrightness == nil {
managedDisplay.currentBrightness = committedBrightness
}
lastErrorMessage = nil
case .failure(let error):
let localizedDescription = localizedDescription(for: error)
logger.error(
"write failed for \(displayName, privacy: .public): \(localizedDescription, privacy: .public)"
)
if let fallbackBackend = fallbackBackend(for: displayID, failedBackend: managedDisplay.backend) {
logger.info(
"retrying brightness write for \(displayName, privacy: .public) with \(String(describing: fallbackBackend.kind), privacy: .public) fallback"
)
let retryBrightness = managedDisplay.pendingBrightness ?? targetValue
managedDisplay.backend.cleanup()
managedDisplay.backend = fallbackBackend
managedDisplay.pendingBrightness = retryBrightness
managedDisplay.pendingReadbackAfterWrite = true
managedDisplay.currentBrightness = retryBrightness
lastErrorMessage = nil
} else {
if managedDisplay.pendingBrightness == nil {
managedDisplay.currentBrightness = managedDisplay.lastCommittedBrightness
}
lastErrorMessage = localization.format(
"error.adjustFailedFormat",
defaultValue: "调节失败:%@",
localizedDescription
)
}
}
managedDisplays[displayID] = managedDisplay
onStateChange?()
if managedDisplay.pendingBrightness != nil {
scheduleWrite(for: displayID, delay: 0)
}
}
private func localizedDescription(for error: Error) -> String {
guard let brightnessError = error as? DisplayBrightnessControllerError else {
return error.localizedDescription
}
return brightnessError.localizedDescription(localization: localization)
}
private func fallbackBackend(
for displayID: CGDirectDisplayID,
failedBackend: any DisplayBrightnessBackend
) -> (any DisplayBrightnessBackend)? {
guard let managedDisplay = managedDisplays[displayID] else {
return nil
}
let previousBackends = Dictionary(
uniqueKeysWithValues: managedDisplays.map { ($0.key, $0.value.backend) }
)
return backendBuilder.fallbackBackend(
after: failedBackend,
for: managedDisplay.display,
previous: previousBackends
)
}
private func cleanupDisconnectedDisplays(keeping displayIDs: Set<CGDirectDisplayID>) {
for (displayID, managedDisplay) in managedDisplays where !displayIDs.contains(displayID) {
managedDisplay.scheduledFlush?.cancel()
managedDisplay.backend.cleanup()
}
}
private func cleanupAll() {
for (_, managedDisplay) in managedDisplays {
managedDisplay.scheduledFlush?.cancel()
managedDisplay.backend.cleanup()
}
}
private static func clamp(_ value: Double) -> Double {
min(max(value, 0), 1)
}
nonisolated private static func makeScheduledWriteWorkItem(
controllerRef: WeakBrightnessControllerRef,
displayID: CGDirectDisplayID
) -> DispatchWorkItem {
DispatchWorkItem {
Task { @MainActor in
controllerRef.value?.beginWriteIfNeeded(for: displayID)
}
}
}
nonisolated private static func makeWriteWorkItem(
controllerRef: WeakBrightnessControllerRef,
backend: any DisplayBrightnessBackend,
displayID: CGDirectDisplayID,
targetValue: Double,
needsReadback: Bool,
displayName: String,
writeGate: BrightnessWriteGate,
minimumWriteInterval: TimeInterval
) -> DispatchWorkItem {
DispatchWorkItem {
let result: Result<Void, Error>
var readbackValue: Double?
do {
writeGate.waitTurn(for: displayID, minimumInterval: minimumWriteInterval)
try backend.writeBrightness(targetValue)
if needsReadback {
readbackValue = try? backend.readBrightness()
}
result = .success(())
} catch {
result = .failure(error)
}
Task { @MainActor in
controllerRef.value?.finishWrite(
for: displayID,
targetValue: targetValue,
readbackValue: readbackValue,
displayName: displayName,
result: result
)
}
}
}
}
private extension NSLock {
func withLock<T>(_ body: () throws -> T) rethrows -> T {
lock()
defer {
unlock()
}
return try body()
}
}