This commit is contained in:
啊铎铎铎仔 2026-06-14 09:35:00 +00:00 committed by GitHub
commit e050646dc6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 557 additions and 5 deletions

View file

@ -38,6 +38,7 @@
AA78BDBD2709FE7B00CA8DF7 /* UpdaterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA78BDBC2709FE7B00CA8DF7 /* UpdaterDelegate.swift */; };
AA99521726FE25AB00612E07 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA99521626FE25AB00612E07 /* AppDelegate.swift */; };
AA99521926FE49A300612E07 /* MenuHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA99521826FE49A300612E07 /* MenuHandler.swift */; };
AAB41E2D2C1F6A8200E22A10 /* DisplayLinkControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAB41E2C2C1F6A8200E22A10 /* DisplayLinkControl.swift */; };
AA9AE86F26B5BF3D00B6CA65 /* OSD.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA9AE86E26B5BF3D00B6CA65 /* OSD.framework */; };
AA9AE87126B5BFB700B6CA65 /* CoreDisplay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA9AE87026B5BFB700B6CA65 /* CoreDisplay.framework */; };
AAB2F638273ED099004AB5A4 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = AAB2F637273ED099004AB5A4 /* .swiftlint.yml */; };
@ -125,6 +126,7 @@
AA90102027C56A0E00CC1DF7 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
AA99521626FE25AB00612E07 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
AA99521826FE49A300612E07 /* MenuHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuHandler.swift; sourceTree = "<group>"; };
AAB41E2C2C1F6A8200E22A10 /* DisplayLinkControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayLinkControl.swift; sourceTree = "<group>"; };
AA99E81527622EBE00413316 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Main.strings"; sourceTree = "<group>"; };
AA99E81627622EBE00413316 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/InternetAccessPolicy.strings"; sourceTree = "<group>"; };
AA99E81727622EBE00413316 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = "<group>"; };
@ -314,6 +316,7 @@
6C85EFD922C941B000227EA1 /* DisplayManager.swift */,
AA25F6D626E68C160087F3A2 /* MediaKeyTapManager.swift */,
AA44E70627038F7F00E06865 /* KeyboardShortcutsManager.swift */,
AAB41E2C2C1F6A8200E22A10 /* DisplayLinkControl.swift */,
AA16139A26BE772E00DCF027 /* Arm64DDC.swift */,
AA4398A826DD55DA00943F16 /* IntelDDC.swift */,
FE4E0895249D584C003A50BB /* OSDUtils.swift */,
@ -636,6 +639,7 @@
AA44E7052703790100E06865 /* KeyboardShortcuts+Extension.swift in Sources */,
F0A489C4279C71B200BEDFD6 /* OnboardingViewController.swift in Sources */,
AA16139B26BE772E00DCF027 /* Arm64DDC.swift in Sources */,
AAB41E2D2C1F6A8200E22A10 /* DisplayLinkControl.swift in Sources */,
F0445D3820023E710025AE82 /* MainPrefsViewController.swift in Sources */,
28D1DDF2227FBE71004CB494 /* NSScreen+Extension.swift in Sources */,
AA99521726FE25AB00612E07 /* AppDelegate.swift in Sources */,

View file

@ -8,6 +8,7 @@ class OtherDisplay: Display {
var ddc: IntelDDC?
var arm64ddc: Bool = false
var arm64avService: IOAVService?
var displayLinkDisplay: DisplayLinkDisplay?
var isDiscouraged: Bool = false
let writeDDCQueue = DispatchQueue(label: "Local write DDC queue")
var writeDDCNextValue: [Command: UInt16] = [:]
@ -200,14 +201,20 @@ class OtherDisplay: Display {
}
func stepContrast(isUp: Bool, isSmallIncrement: Bool) {
guard !self.readPrefAsBool(key: .unavailableDDC, for: .contrast), !self.isSw() else {
guard !self.readPrefAsBool(key: .unavailableDDC, for: .contrast), !self.isSw() || self.hasDisplayLinkContrastControl() else {
return
}
let currentValue = self.readPrefAsFloat(for: .contrast)
let contrastOSDValue = self.calcNewValue(currentValue: currentValue, isUp: isUp, isSmallIncrement: isSmallIncrement)
let isAlreadySet = contrastOSDValue == self.readPrefAsFloat(for: .contrast)
if !isAlreadySet {
self.writeDDCValues(command: .contrast, value: self.convValueToDDC(for: .contrast, from: contrastOSDValue))
if self.hasDisplayLinkContrastControl() {
if !self.setDisplayLinkContrast(contrastOSDValue) {
return
}
} else {
self.writeDDCValues(command: .contrast, value: self.convValueToDDC(for: .contrast, from: contrastOSDValue))
}
}
OSDUtils.showOsd(displayID: self.identifier, command: .contrast, value: contrastOSDValue, roundChiclet: !isSmallIncrement)
if !isAlreadySet {
@ -328,11 +335,34 @@ class OtherDisplay: Display {
override func setBrightness(_ to: Float = -1, slow: Bool = false) -> Bool {
self.checkGammaInterference()
if self.hasDisplayLinkBrightnessControl() {
let value = to == -1 ? self.readPrefAsFloat(for: .brightness) : to
return self.setDirectBrightness(value)
}
return super.setBrightness(to, slow: slow)
}
override func setDirectBrightness(_ to: Float, transient: Bool = false) -> Bool {
let value = max(min(to, 1), 0)
if self.hasDisplayLinkBrightnessControl() {
if DisplayLinkControl.shared.setBrightness(for: self.identifier, value: value) {
if self.readPrefAsFloat(key: .SwBrightness) != 1 {
_ = self.setSwBrightness(1)
} else {
self.savePref(1, key: .SwBrightness)
}
_ = DisplayManager.shared.destroyShade(displayID: DisplayManager.resolveEffectiveDisplayID(self.identifier))
if !transient {
self.savePref(value, for: .brightness)
self.brightnessSyncSourceValue = value
self.smoothBrightnessTransient = value
}
return true
}
os_log("DisplayLink brightness write failed for display %{public}@. Falling back to software brightness.", type: .info, String(self.identifier))
_ = super.setDirectBrightness(to, transient: transient)
return true
}
if !self.isSw() {
if !prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue) {
var brightnessValue: Float = 0
@ -362,7 +392,58 @@ class OtherDisplay: Display {
}
override func getBrightness() -> Float {
self.prefExists(for: .brightness) ? self.readPrefAsFloat(for: .brightness) : 1
if let displayLinkDisplay = self.displayLinkDisplay, !self.prefExists(for: .brightness) {
return displayLinkDisplay.brightness
}
return self.prefExists(for: .brightness) ? self.readPrefAsFloat(for: .brightness) : 1
}
func bindDisplayLinkDisplay(_ display: DisplayLinkDisplay) {
self.applyDisplayLinkDisplay(display, updateSliders: false)
}
func applyDisplayLinkDisplay(_ display: DisplayLinkDisplay, updateSliders: Bool = true) {
self.displayLinkDisplay = display
guard display.isEnabled else {
return
}
self.savePref(display.brightness, for: .brightness)
self.savePref(1, key: .SwBrightness)
self.brightnessSyncSourceValue = display.brightness
self.smoothBrightnessTransient = display.brightness
if updateSliders, let slider = self.sliderHandler[.brightness] {
slider.setValue(display.brightness, displayID: self.identifier)
}
if let contrast = display.contrast {
self.savePref(contrast, for: .contrast)
self.savePref(DDC_MAX_DETECT_LIMIT, key: .maxDDC, for: .contrast)
if updateSliders, let slider = self.sliderHandler[.contrast] {
slider.setValue(contrast, displayID: self.identifier)
}
}
self.savePref(DDC_MAX_DETECT_LIMIT, key: .maxDDC, for: .brightness)
_ = DisplayManager.shared.destroyShade(displayID: DisplayManager.resolveEffectiveDisplayID(self.identifier))
}
func hasDisplayLinkBrightnessControl() -> Bool {
self.displayLinkDisplay?.isEnabled ?? false
}
func hasDisplayLinkContrastControl() -> Bool {
(self.displayLinkDisplay?.isEnabled ?? false) && self.displayLinkDisplay?.contrast != nil
}
@discardableResult
func setDisplayLinkContrast(_ value: Float) -> Bool {
guard self.hasDisplayLinkContrastControl() else {
return false
}
if DisplayLinkControl.shared.setContrast(for: self.identifier, value: value) {
self.savePref(value, for: .contrast)
return true
}
os_log("DisplayLink contrast write failed for display %{public}@.", type: .info, String(self.identifier))
return false
}
func getRemapControlCodes(command: Command) -> [UInt8] {

View file

@ -0,0 +1,398 @@
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
import CoreGraphics
import Foundation
import os.log
struct DisplayLinkDisplay {
let cgID: CGDirectDisplayID
let persistentDisplayId: String
let name: String
let isEnabled: Bool
let brightness: Float
let contrast: Float?
}
final class DisplayLinkControl {
static let shared = DisplayLinkControl()
static let displayDidUpdateNotification = Notification.Name("MonitorControl.DisplayLinkDisplayDidUpdate")
static let userInfoDisplayIDKey = "displayID"
static let userInfoBrightnessKey = "brightness"
static let userInfoContrastKey = "contrast"
private enum ControlKind: Hashable {
case brightness
case contrast
var requestName: String {
switch self {
case .brightness: return "com.displaylink.SetBrightness"
case .contrast: return "com.displaylink.SetContrast"
}
}
var updateName: String {
switch self {
case .brightness: return "com.displaylink.BrightnessUpdated"
case .contrast: return "com.displaylink.ContrastUpdated"
}
}
var payloadKey: String {
switch self {
case .brightness: return "brightness"
case .contrast: return "contrast"
}
}
}
private struct DisplayPayload: Decodable {
let persistentDisplayId: String
let isEnabled: Bool?
let brightness: Float?
let contrast: Float?
let CGID: UInt32?
let name: String?
}
private struct UpdatePayload: Decodable {
let persistentDisplayId: String?
let statusCode: Int?
let brightness: Float?
let contrast: Float?
}
private struct DisplayWriteKey: Hashable {
let displayID: CGDirectDisplayID
let kind: ControlKind
}
private struct LocalWriteTarget {
let value: Float
let createdAt: Date
}
private let notificationCenter = DistributedNotificationCenter.default()
private let timeout: TimeInterval = 1.5
private let writeCoalescingDelay: TimeInterval = 0.08
private let localWriteIgnoreInterval: TimeInterval = 3
private let stateQueue = DispatchQueue(label: "MonitorControl DisplayLink state queue")
private let writeQueue = DispatchQueue(label: "MonitorControl DisplayLink write queue")
private var observerTokens: [NSObjectProtocol] = []
private var displaysByID: [CGDirectDisplayID: DisplayLinkDisplay] = [:]
private var displayIDsByPersistentID: [String: CGDirectDisplayID] = [:]
private var pendingWrites: [DisplayWriteKey: Float] = [:]
private var scheduledWrites: Set<DisplayWriteKey> = []
private var localWriteTargets: [DisplayWriteKey: LocalWriteTarget] = [:]
private init() {
self.startObserving()
}
@discardableResult
func refreshDisplays() -> [DisplayLinkDisplay] {
let note = self.waitForNotification(name: "com.displaylink.DisplayListUpdated", timeout: self.timeout) {
self.notificationCenter.postNotificationName(Notification.Name("com.displaylink.GetDisplays"), object: nil, userInfo: nil, deliverImmediately: true)
}
guard let raw = Self.objectString(note) else {
os_log("DisplayLink display query timed out or returned no data.", type: .info)
self.replaceDisplays([], notify: false)
return []
}
guard let displays = Self.decodeDisplays(from: raw) else {
os_log("DisplayLink display query returned unparseable payload: %{public}@", type: .error, raw)
self.replaceDisplays([], notify: false)
return []
}
self.replaceDisplays(displays, notify: false)
os_log("DisplayLink display query found %{public}@ display(s).", type: .info, String(displays.count))
return displays
}
func display(for displayID: CGDirectDisplayID) -> DisplayLinkDisplay? {
self.stateQueue.sync {
self.displaysByID[displayID]
}
}
func setBrightness(for displayID: CGDirectDisplayID, value: Float) -> Bool {
self.enqueueValue(kind: .brightness, for: displayID, value: value)
}
func setContrast(for displayID: CGDirectDisplayID, value: Float) -> Bool {
self.enqueueValue(kind: .contrast, for: displayID, value: value)
}
private func startObserving() {
self.observerTokens.append(self.notificationCenter.addObserver(forName: Notification.Name("com.displaylink.DisplayListUpdated"), object: nil, queue: .main) { [weak self] note in
self?.handleDisplayListNotification(note)
})
self.observerTokens.append(self.notificationCenter.addObserver(forName: Notification.Name("com.displaylink.BrightnessUpdated"), object: nil, queue: .main) { [weak self] note in
self?.handleUpdateNotification(note, kind: .brightness)
})
self.observerTokens.append(self.notificationCenter.addObserver(forName: Notification.Name("com.displaylink.ContrastUpdated"), object: nil, queue: .main) { [weak self] note in
self?.handleUpdateNotification(note, kind: .contrast)
})
}
private func enqueueValue(kind: ControlKind, for displayID: CGDirectDisplayID, value: Float) -> Bool {
guard let display = self.display(for: displayID), display.isEnabled else {
return false
}
let normalizedValue = max(min(value, 1), 0)
let key = DisplayWriteKey(displayID: displayID, kind: kind)
self.setLocalWriteTarget(key, value: normalizedValue)
self.writeQueue.async {
self.pendingWrites[key] = normalizedValue
guard !self.scheduledWrites.contains(key) else {
return
}
self.scheduledWrites.insert(key)
self.writeQueue.asyncAfter(deadline: .now() + self.writeCoalescingDelay) {
self.flushQueuedValue(for: key)
}
}
return true
}
private func flushQueuedValue(for key: DisplayWriteKey) {
guard let value = self.pendingWrites.removeValue(forKey: key) else {
self.scheduledWrites.remove(key)
return
}
if !self.writeValueSynchronously(kind: key.kind, for: key.displayID, value: value) {
self.clearLocalWriteTarget(key, force: true)
}
if self.pendingWrites[key] != nil {
self.writeQueue.asyncAfter(deadline: .now() + self.writeCoalescingDelay) {
self.flushQueuedValue(for: key)
}
} else {
self.scheduledWrites.remove(key)
}
}
private func writeValueSynchronously(kind: ControlKind, for displayID: CGDirectDisplayID, value: Float) -> Bool {
guard let display = self.display(for: displayID), display.isEnabled else {
return false
}
guard let payload = Self.jsonString([
"persistentDisplayId": display.persistentDisplayId,
kind.payloadKey: Double(value),
]) else {
return false
}
let note = self.waitForNotification(name: kind.updateName, timeout: self.timeout) {
self.notificationCenter.postNotificationName(Notification.Name(kind.requestName), object: payload, userInfo: nil, deliverImmediately: true)
} filter: { note in
guard let raw = Self.objectString(note),
let update = try? JSONDecoder().decode(UpdatePayload.self, from: Data(raw.utf8)) else {
return false
}
return update.persistentDisplayId == display.persistentDisplayId
}
guard let raw = Self.objectString(note),
let update = try? JSONDecoder().decode(UpdatePayload.self, from: Data(raw.utf8)),
update.statusCode == 0 else {
os_log("DisplayLink %{public}@ write failed for display %{public}@.", type: .info, kind.payloadKey, display.persistentDisplayId)
return false
}
guard let acknowledgedValue = self.updatedValue(kind: kind, fallback: value, update: update) else {
return false
}
self.updateCache(displayID: displayID, kind: kind, value: acknowledgedValue, notify: !self.hasActiveLocalWriteTarget(key: DisplayWriteKey(displayID: displayID, kind: kind)))
self.clearLocalWriteTarget(DisplayWriteKey(displayID: displayID, kind: kind), acknowledgedValue: acknowledgedValue)
return true
}
private func handleDisplayListNotification(_ note: Notification) {
guard let raw = Self.objectString(note), let displays = Self.decodeDisplays(from: raw) else {
return
}
self.replaceDisplays(displays, notify: true)
}
private func handleUpdateNotification(_ note: Notification, kind: ControlKind) {
guard let raw = Self.objectString(note),
let update = try? JSONDecoder().decode(UpdatePayload.self, from: Data(raw.utf8)),
(update.statusCode ?? 0) == 0,
let persistentDisplayId = update.persistentDisplayId else {
return
}
let value = self.updatedValue(kind: kind, fallback: nil, update: update)
guard let value else {
return
}
guard let displayID = self.displayID(forPersistentDisplayId: persistentDisplayId) else {
return
}
let key = DisplayWriteKey(displayID: displayID, kind: kind)
self.updateCache(displayID: displayID, kind: kind, value: value, notify: !self.hasActiveLocalWriteTarget(key: key))
self.clearLocalWriteTarget(key, acknowledgedValue: value)
}
private func displayID(forPersistentDisplayId persistentDisplayId: String) -> CGDirectDisplayID? {
self.stateQueue.sync {
self.displayIDsByPersistentID[persistentDisplayId]
}
}
@discardableResult
private func updateCache(displayID: CGDirectDisplayID, kind: ControlKind, value: Float, notify: Bool) -> DisplayLinkDisplay? {
var updatedDisplay: DisplayLinkDisplay?
self.stateQueue.sync {
guard let current = self.displaysByID[displayID] else {
return
}
let brightness = kind == .brightness ? value : current.brightness
let contrast = kind == .contrast ? value : current.contrast
let updated = DisplayLinkDisplay(
cgID: current.cgID,
persistentDisplayId: current.persistentDisplayId,
name: current.name,
isEnabled: current.isEnabled,
brightness: brightness,
contrast: contrast
)
self.displaysByID[displayID] = updated
self.displayIDsByPersistentID[updated.persistentDisplayId] = displayID
updatedDisplay = updated
}
if notify, let updatedDisplay {
self.postDisplayUpdate(updatedDisplay)
}
return updatedDisplay
}
private func replaceDisplays(_ displays: [DisplayLinkDisplay], notify: Bool) {
self.stateQueue.sync {
var displaysByID: [CGDirectDisplayID: DisplayLinkDisplay] = [:]
var displayIDsByPersistentID: [String: CGDirectDisplayID] = [:]
for display in displays {
displaysByID[display.cgID] = display
displayIDsByPersistentID[display.persistentDisplayId] = display.cgID
}
self.displaysByID = displaysByID
self.displayIDsByPersistentID = displayIDsByPersistentID
}
if notify {
for display in displays {
if self.shouldNotifyUpdate(for: display.cgID) {
self.postDisplayUpdate(display)
}
}
}
}
private func postDisplayUpdate(_ display: DisplayLinkDisplay) {
var userInfo: [String: Any] = [
Self.userInfoDisplayIDKey: display.cgID,
Self.userInfoBrightnessKey: display.brightness,
]
if let contrast = display.contrast {
userInfo[Self.userInfoContrastKey] = contrast
}
DispatchQueue.main.async {
NotificationCenter.default.post(name: Self.displayDidUpdateNotification, object: self, userInfo: userInfo)
}
}
private func setLocalWriteTarget(_ key: DisplayWriteKey, value: Float) {
self.stateQueue.sync {
self.localWriteTargets[key] = LocalWriteTarget(value: value, createdAt: Date())
}
}
private func hasActiveLocalWriteTarget(key: DisplayWriteKey) -> Bool {
self.stateQueue.sync {
guard let target = self.localWriteTargets[key] else {
return false
}
if Date().timeIntervalSince(target.createdAt) > self.localWriteIgnoreInterval {
self.localWriteTargets.removeValue(forKey: key)
return false
}
return true
}
}
private func clearLocalWriteTarget(_ key: DisplayWriteKey, acknowledgedValue: Float? = nil, force: Bool = false) {
self.stateQueue.sync {
guard force || acknowledgedValue != nil else {
return
}
if force {
self.localWriteTargets.removeValue(forKey: key)
return
}
if let target = self.localWriteTargets[key], let acknowledgedValue, abs(target.value - acknowledgedValue) < 0.002 {
self.localWriteTargets.removeValue(forKey: key)
}
}
}
private func shouldNotifyUpdate(for displayID: CGDirectDisplayID) -> Bool {
!self.hasActiveLocalWriteTarget(key: DisplayWriteKey(displayID: displayID, kind: .brightness)) && !self.hasActiveLocalWriteTarget(key: DisplayWriteKey(displayID: displayID, kind: .contrast))
}
private func updatedValue(kind: ControlKind, fallback: Float?, update: UpdatePayload) -> Float? {
switch kind {
case .brightness:
return update.brightness ?? fallback
case .contrast:
return update.contrast ?? fallback
}
}
private func waitForNotification(name: String, timeout: TimeInterval, trigger: () -> Void, filter: ((Notification) -> Bool)? = nil) -> Notification? {
var received: Notification?
let token = self.notificationCenter.addObserver(forName: Notification.Name(name), object: nil, queue: nil) { note in
if filter?(note) ?? true {
received = note
}
}
trigger()
let until = Date().addingTimeInterval(timeout)
while received == nil, Date() < until {
RunLoop.current.run(mode: .default, before: Date().addingTimeInterval(0.05))
}
self.notificationCenter.removeObserver(token)
return received
}
private static func objectString(_ note: Notification?) -> String? {
if let string = note?.object as? String {
return string
}
if let string = note?.object as? NSString {
return string as String
}
return nil
}
private static func decodeDisplays(from raw: String) -> [DisplayLinkDisplay]? {
guard let payloads = try? JSONDecoder().decode([DisplayPayload].self, from: Data(raw.utf8)) else {
return nil
}
return payloads.compactMap { payload -> DisplayLinkDisplay? in
guard let cgID = payload.CGID, let brightness = payload.brightness else {
return nil
}
return DisplayLinkDisplay(
cgID: CGDirectDisplayID(cgID),
persistentDisplayId: payload.persistentDisplayId,
name: payload.name ?? payload.persistentDisplayId,
isEnabled: payload.isEnabled ?? true,
brightness: brightness,
contrast: payload.contrast
)
}
}
private static func jsonString(_ object: [String: Any]) -> String? {
guard let data = try? JSONSerialization.data(withJSONObject: object, options: []) else {
return nil
}
return String(data: data, encoding: .utf8)
}
}

View file

@ -13,6 +13,13 @@ class DisplayManager {
let gammaActivityEnforcer = NSWindow(contentRect: .init(origin: NSPoint(x: 0, y: 0), size: .init(width: DEBUG_GAMMA_ENFORCER ? 15 : 1, height: DEBUG_GAMMA_ENFORCER ? 15 : 1)), styleMask: [], backing: .buffered, defer: false)
var gammaInterferenceCounter = 0
var gammaInterferenceWarningShown = false
private var displayLinkUpdateObserver: NSObjectProtocol?
private init() {
self.displayLinkUpdateObserver = NotificationCenter.default.addObserver(forName: DisplayLinkControl.displayDidUpdateNotification, object: nil, queue: .main) { [weak self] note in
self?.displayLinkDisplayDidUpdate(note)
}
}
func createGammaActivityEnforcer() {
self.gammaActivityEnforcer.title = "Monitor Control Gamma Activity Enforcer"
@ -160,8 +167,35 @@ class DisplayManager {
return false
}
private func displayLinkDisplayDidUpdate(_ note: Notification) {
guard app != nil, app.sleepID == 0, app.reconfigureID == 0,
let displayID = self.displayLinkDisplayID(from: note.userInfo),
let displayLinkDisplay = DisplayLinkControl.shared.display(for: displayID),
let otherDisplay = self.getOtherDisplays().first(where: { $0.identifier == displayID }) else {
return
}
otherDisplay.applyDisplayLinkDisplay(displayLinkDisplay)
}
private func displayLinkDisplayID(from userInfo: [AnyHashable: Any]?) -> CGDirectDisplayID? {
guard let rawDisplayID = userInfo?[DisplayLinkControl.userInfoDisplayIDKey] else {
return nil
}
if let displayID = rawDisplayID as? CGDirectDisplayID {
return displayID
}
if let displayID = rawDisplayID as? UInt32 {
return CGDirectDisplayID(displayID)
}
if let displayID = rawDisplayID as? NSNumber {
return CGDirectDisplayID(displayID.uint32Value)
}
return nil
}
func configureDisplays() {
self.clearDisplays()
DisplayLinkControl.shared.refreshDisplays()
var onlineDisplayIDs = [CGDirectDisplayID](repeating: 0, count: 16)
var displayCount: UInt32 = 0
guard CGGetOnlineDisplayList(16, &onlineDisplayIDs, &displayCount) == .success else {
@ -183,6 +217,10 @@ class DisplayManager {
self.addDisplay(display: appleDisplay)
} else {
let otherDisplay = OtherDisplay(id, name: name, vendorNumber: vendorNumber, modelNumber: modelNumber, serialNumber: serialNumber, isVirtual: isVirtual, isDummy: isDummy)
if let displayLinkDisplay = DisplayLinkControl.shared.display(for: id) {
otherDisplay.bindDisplayLinkDisplay(displayLinkDisplay)
os_log("DisplayLink display matched - %{public}@", type: .info, "ID: \(otherDisplay.identifier), Persistent ID: \(displayLinkDisplay.persistentDisplayId)")
}
os_log("Other display found - %{public}@", type: .info, "ID: \(otherDisplay.identifier), Name: \(otherDisplay.name) (Vendor: \(otherDisplay.vendorNumber ?? 0), Model: \(otherDisplay.modelNumber ?? 0))")
self.addDisplay(display: otherDisplay)
}
@ -191,6 +229,21 @@ class DisplayManager {
func setupOtherDisplays(firstrun: Bool = false) {
for otherDisplay in self.getOtherDisplays() {
if otherDisplay.hasDisplayLinkBrightnessControl() {
if !otherDisplay.readPrefAsBool(key: .unavailableDDC, for: .brightness) {
let brightness = otherDisplay.displayLinkDisplay?.brightness ?? otherDisplay.readPrefAsFloat(for: .brightness)
otherDisplay.savePref(brightness, for: .brightness)
otherDisplay.savePref(1, key: .SwBrightness)
otherDisplay.brightnessSyncSourceValue = brightness
otherDisplay.smoothBrightnessTransient = brightness
_ = DisplayManager.shared.destroyShade(displayID: DisplayManager.resolveEffectiveDisplayID(otherDisplay.identifier))
}
if otherDisplay.hasDisplayLinkContrastControl(), !otherDisplay.readPrefAsBool(key: .unavailableDDC, for: .contrast), let contrast = otherDisplay.displayLinkDisplay?.contrast {
otherDisplay.savePref(contrast, for: .contrast)
otherDisplay.savePref(DDC_MAX_DETECT_LIMIT, key: .maxDDC, for: .contrast)
}
continue
}
for command in [Command.audioSpeakerVolume, Command.contrast] where !otherDisplay.readPrefAsBool(key: .unavailableDDC, for: command) && !otherDisplay.isSw() {
otherDisplay.setupCurrentAndMaxValues(command: command, firstrun: firstrun)
}
@ -379,6 +432,14 @@ class DisplayManager {
func restoreSwBrightnessForAllDisplays(async: Bool = false) {
for otherDisplay in self.getOtherDisplays() {
if otherDisplay.hasDisplayLinkBrightnessControl() {
_ = otherDisplay.setSwBrightness(1, smooth: async)
_ = DisplayManager.shared.destroyShade(displayID: DisplayManager.resolveEffectiveDisplayID(otherDisplay.identifier))
if let slider = otherDisplay.sliderHandler[.brightness] {
slider.setValue(otherDisplay.readPrefAsFloat(for: .brightness), displayID: otherDisplay.identifier)
}
continue
}
if (otherDisplay.readPrefAsFloat(for: .brightness) == 0 && !prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue)) || (otherDisplay.readPrefAsFloat(for: .brightness) < otherDisplay.combinedBrightnessSwitchingValue() && !prefs.bool(forKey: PrefKey.separateCombinedScale.rawValue) && !prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue)) || otherDisplay.isSw() {
let savedPrefValue = otherDisplay.readPrefAsFloat(key: .SwBrightness)
if otherDisplay.getSwBrightness() != savedPrefValue {

View file

@ -185,7 +185,7 @@ class MenuHandler: NSMenu, NSMenuDelegate {
addedSliderHandlers.append(self.setupMenuSliderHandler(command: .audioSpeakerVolume, display: display, title: title))
}
display.sliderHandler[.contrast] = nil
if let otherDisplay = display as? OtherDisplay, !otherDisplay.isSw(), !display.readPrefAsBool(key: .unavailableDDC, for: .contrast), prefs.bool(forKey: PrefKey.showContrast.rawValue) {
if let otherDisplay = display as? OtherDisplay, (!otherDisplay.isSw() || otherDisplay.hasDisplayLinkContrastControl()), !display.readPrefAsBool(key: .unavailableDDC, for: .contrast), prefs.bool(forKey: PrefKey.showContrast.rawValue) {
let title = NSLocalizedString("Contrast", comment: "Shown in menu")
addedSliderHandlers.append(self.setupMenuSliderHandler(command: .contrast, display: display, title: title))
}

View file

@ -292,6 +292,9 @@ class SliderHandler {
if self.command == Command.brightness {
_ = otherDisplay.setBrightness(value)
return
} else if self.command == Command.contrast, otherDisplay.hasDisplayLinkContrastControl() {
_ = otherDisplay.setDisplayLinkContrast(value)
return
} else if !otherDisplay.isSw() {
if self.command == Command.audioSpeakerVolume {
if !otherDisplay.readPrefAsBool(key: .enableMuteUnmute) || value != 0 {

View file

@ -87,7 +87,12 @@ class DisplaysPrefsViewController: NSViewController, SettingsPane, NSTableViewDa
var displayImage = "display.trianglebadge.exclamationmark"
var controlMethod = NSLocalizedString("No Control", comment: "Shown in the Display Settings") + " ⚠️"
var controlStatus = NSLocalizedString("This display has an unspecified control status.", comment: "Shown in the Display Settings")
if display.isVirtual, !display.isDummy {
if let otherDisplay = display as? OtherDisplay, otherDisplay.hasDisplayLinkBrightnessControl(), !display.isDummy {
displayType = NSLocalizedString("Virtual Display", comment: "Shown in the Display Settings")
displayImage = "tv.and.mediabox"
controlMethod = NSLocalizedString("Hardware (DisplayLink)", comment: "Shown in the Display Settings")
controlStatus = NSLocalizedString("This display supports native DisplayLink brightness and contrast control.", comment: "Shown in the Display Settings")
} else if display.isVirtual, !display.isDummy {
displayType = NSLocalizedString("Virtual Display", comment: "Shown in the Display Settings")
displayImage = "tv.and.mediabox"
controlMethod = NSLocalizedString("Software (shade)", comment: "Shown in the Display Settings") + " ⚠️"