diff --git a/MonitorControl/AppDelegate.swift b/MonitorControl/AppDelegate.swift
index 96548e9..e61ae91 100644
--- a/MonitorControl/AppDelegate.swift
+++ b/MonitorControl/AppDelegate.swift
@@ -124,8 +124,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
func displayReconfigured() {
+ self.reconfigureID += 1
+ os_log("Bumping reconfigureID to %{public}@", type: .info, String(self.reconfigureID))
if self.sleepID == 0 {
- self.reconfigureID += 1
let dispatchedReconfigureID = self.reconfigureID
os_log("Display to be reconfigured with reconfigureID %{public}@", type: .info, String(dispatchedReconfigureID))
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
@@ -255,6 +256,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
NotificationCenter.default.addObserver(self, selector: #selector(handleFriendlyNameChanged), name: .friendlyName, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handlePreferenceReset), name: .preferenceReset, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(audioDeviceChanged), name: Notification.Name.defaultOutputDeviceChanged, object: nil) // subscribe Audio output detector (SimplyCoreAudio)
+ NotificationCenter.default.addObserver(self, selector: #selector(colorSyncSettingsChanged), name: NSNotification.Name(rawValue: kColorSyncDisplayDeviceProfilesNotification.takeRetainedValue() as String), object: nil) // ColorSync change
+
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.sleepNotification), name: NSWorkspace.screensDidSleepNotification, object: nil) // sleep and wake listeners
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.wakeNotofication), name: NSWorkspace.screensDidWakeNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.sleepNotification), name: NSWorkspace.willSleepNotification, object: nil)
@@ -284,7 +287,11 @@ class AppDelegate: NSObject, NSApplicationDelegate {
if self.sleepID == dispatchedSleepID {
os_log("Sober from sleep %{public}@", type: .info, String(self.sleepID))
self.sleepID = 0
- self.updateDisplays()
+ if self.reconfigureID != 0 {
+ let dispatchedReconfigureID = self.reconfigureID
+ os_log("Display needs reconfig after sober with reconfigureID %{public}@", type: .info, String(dispatchedReconfigureID))
+ self.updateDisplays(dispatchedReconfigureID: dispatchedReconfigureID)
+ }
}
}
@@ -480,4 +487,9 @@ extension AppDelegate: MediaKeyTapDelegate {
#endif
self.updateMediaKeyTap()
}
+
+ @objc private func colorSyncSettingsChanged() {
+ // We should perform a protected colorsync reset here using CGDisplayRestoreColorSyncSettings(), black bug check and recovery if needed
+ // Afterwards we should perform a swUpdateDefaultGammaTable() for every display
+ }
}
diff --git a/MonitorControl/Info.plist b/MonitorControl/Info.plist
index d159dbb..bed639f 100644
--- a/MonitorControl/Info.plist
+++ b/MonitorControl/Info.plist
@@ -19,7 +19,7 @@
CFBundleShortVersionString
$(MARKETING_VERSION)
CFBundleVersion
- 2156
+ 2240
LSApplicationCategoryType
public.app-category.utilities
LSMinimumSystemVersion
diff --git a/MonitorControl/Manager/DisplayManager.swift b/MonitorControl/Manager/DisplayManager.swift
index 2a819cd..f8c4f5b 100644
--- a/MonitorControl/Manager/DisplayManager.swift
+++ b/MonitorControl/Manager/DisplayManager.swift
@@ -87,8 +87,7 @@ class DisplayManager {
func restoreSwBrightnessForAllDisplays() {
for externalDisplay in self.getExternalDisplays() {
if externalDisplay.getValue(for: .brightness) == 0 || externalDisplay.isSw() {
- // Out of caution we won't let it restore to complete darkness, not to interfere with login, etc. This is how Apple devices work as well.
- _ = externalDisplay.setSwBrightness(value: UInt8(max(externalDisplay.getSwBrightnessPrefValue(), 20)))
+ _ = externalDisplay.setSwBrightness(value: UInt8(externalDisplay.getSwBrightnessPrefValue()))
} else {
_ = externalDisplay.setSwBrightness(value: externalDisplay.getSwMaxBrightness())
}
diff --git a/MonitorControl/Model/Display.swift b/MonitorControl/Model/Display.swift
index fbcc836..7e0a4da 100644
--- a/MonitorControl/Model/Display.swift
+++ b/MonitorControl/Model/Display.swift
@@ -32,6 +32,12 @@ class Display {
var isVirtual: Bool = false
+ var defaultGammaTableRed = [CGGammaValue](repeating: 0, count: 256)
+ var defaultGammaTableGreen = [CGGammaValue](repeating: 0, count: 256)
+ var defaultGammaTableBlue = [CGGammaValue](repeating: 0, count: 256)
+ var defaultGammaTableSampleCount: UInt32 = 0
+ var defaultGammaTablePeak: Float = 1
+
private let prefs = UserDefaults.standard
internal init(_ identifier: CGDirectDisplayID, name: String, vendorNumber: UInt32?, modelNumber: UInt32?, isVirtual: Bool = false) {
@@ -40,6 +46,7 @@ class Display {
self.vendorNumber = vendorNumber
self.modelNumber = modelNumber
self.isVirtual = isVirtual
+ self.swUpdateDefaultGammaTable()
}
func stepBrightness(isUp _: Bool, isSmallIncrement _: Bool) {}
@@ -69,8 +76,16 @@ class Display {
return self.identifier
}
+ func swUpdateDefaultGammaTable() {
+ CGGetDisplayTransferByTable(self.identifier, 256, &self.defaultGammaTableRed, &self.defaultGammaTableGreen, &self.defaultGammaTableBlue, &self.defaultGammaTableSampleCount)
+ let redPeak = self.defaultGammaTableRed.max() ?? 0
+ let greenPeak = self.defaultGammaTableGreen.max() ?? 0
+ let bluePeak = self.defaultGammaTableBlue.max() ?? 0
+ self.defaultGammaTablePeak = max(redPeak, greenPeak, bluePeak)
+ }
+
func swBrightnessTransform(value: Float, reverse: Bool = false) -> Float {
- let lowTreshold: Float = 0.05 // We don't allow decrease lower than 5% for safety reasons and because some displays blank off after a while on full screen black
+ let lowTreshold: Float = 0.05 // We don't allow decrease lower than 5% for safety reasons and because some displays blank off after a while on full black screen due to energy saving settings
if !reverse {
return value * (1 - lowTreshold) + lowTreshold
} else {
@@ -78,50 +93,44 @@ class Display {
}
}
- func setSwBrightness(value: UInt8, fast: Bool = false) -> Bool {
- var redMin: CGGammaValue = 0
- var redMax: CGGammaValue = 0
- var redGamma: CGGammaValue = 0
- var greenMin: CGGammaValue = 0
- var greenMax: CGGammaValue = 0
- var greenGamma: CGGammaValue = 0
- var blueMin: CGGammaValue = 0
- var blueMax: CGGammaValue = 0
- var blueGamma: CGGammaValue = 0
+ func setSwBrightness(value: UInt8, fast _: Bool = false) -> Bool {
let brightnessValue: UInt8 = min(getSwMaxBrightness(), value)
- var floatValue = Float(Float(brightnessValue) / Float(self.getSwMaxBrightness()))
- floatValue = self.swBrightnessTransform(value: floatValue)
- os_log("setting software brightness to: %{public}@", type: .debug, String(floatValue))
- if CGGetDisplayTransferByFormula(self.identifier, &redMin, &redMax, &redGamma, &greenMin, &greenMax, &greenGamma, &blueMin, &blueMax, &blueGamma) == CGError.success {
- if !fast {
- DispatchQueue.global(qos: .userInitiated).async {
- for value in stride(from: redMax, to: floatValue, by: 0.0025 * (redMax > floatValue ? -1 : 1)) {
- CGSetDisplayTransferByFormula(self.identifier, 0, value, redGamma, 0, value, greenGamma, 0, value, blueGamma)
- Thread.sleep(forTimeInterval: 0.001)
- }
- }
- } else {
- CGSetDisplayTransferByFormula(self.identifier, 0, floatValue, redGamma, 0, floatValue, greenGamma, 0, floatValue, blueGamma)
- }
- self.saveSwBirghtnessPrefValue(Int(brightnessValue))
- return true
- }
- return false
+ var currentValue = Float(self.getSwBrightness()) / Float(self.getSwMaxBrightness())
+// var currentValue = Float(self.getSwBrightnessPrefValue()) / Float(self.getSwMaxBrightness()) // Better for async version but is not right after display reconfiguration
+ self.saveSwBirghtnessPrefValue(Int(brightnessValue))
+ var newValue = Float(Float(brightnessValue)) / Float(self.getSwMaxBrightness())
+ currentValue = self.swBrightnessTransform(value: currentValue)
+ newValue = self.swBrightnessTransform(value: newValue)
+ os_log("setting software brightness to: %{public}@", type: .debug, String(newValue))
+ let gammaTableRed = self.defaultGammaTableRed.map { $0 * /* transientValue */ newValue }
+ let gammaTableGreen = self.defaultGammaTableGreen.map { $0 * /* transientValue */ newValue }
+ let gammaTableBlue = self.defaultGammaTableBlue.map { $0 * /* transientValue */ newValue }
+ CGSetDisplayTransferByTable(self.identifier, self.defaultGammaTableSampleCount, gammaTableRed, gammaTableGreen, gammaTableBlue)
+// DispatchQueue.global(qos: .userInitiated).async {
+// for transientValue in stride(from: currentValue, to: newValue, by: 0.0025 * (currentValue > newValue ? -1 : 1)) {
+// let gammaTableRed = self.defaultGammaTableRed.map { $0 * transientValue }
+// let gammaTableGreen = self.defaultGammaTableGreen.map { $0 * transientValue }
+// let gammaTableBlue = self.defaultGammaTableBlue.map { $0 * transientValue }
+// CGSetDisplayTransferByTable(self.identifier, self.defaultGammaTableSampleCount, gammaTableRed, gammaTableGreen, gammaTableBlue)
+// Thread.sleep(forTimeInterval: 0.0005) // This will make things a bit nicer and mimic delay of DDC communication for consistency
+// }
+// }
+ return true
}
func getSwBrightness() -> UInt8 {
- var redMin: CGGammaValue = 0
- var redMax: CGGammaValue = 0
- var redGamma: CGGammaValue = 0
- var greenMin: CGGammaValue = 0
- var greenMax: CGGammaValue = 0
- var greenGamma: CGGammaValue = 0
- var blueMin: CGGammaValue = 0
- var blueMax: CGGammaValue = 0
- var blueGamma: CGGammaValue = 0
- if CGGetDisplayTransferByFormula(self.identifier, &redMin, &redMax, &redGamma, &greenMin, &greenMax, &greenGamma, &blueMin, &blueMax, &blueGamma) == CGError.success {
- let brightnessValue = UInt8(round(swBrightnessTransform(value: max(redMax, greenMax, blueMax), reverse: true) * Float(self.getSwMaxBrightness())))
- os_log("Current read software brightness is: %{public}@", type: .debug, String(brightnessValue))
+ var gammaTableRed = [CGGammaValue](repeating: 0, count: 256)
+ var gammaTableGreen = [CGGammaValue](repeating: 0, count: 256)
+ var gammaTableBlue = [CGGammaValue](repeating: 0, count: 256)
+ var gammaTableSampleCount: UInt32 = 0
+ if CGGetDisplayTransferByTable(self.identifier, 256, &gammaTableRed, &gammaTableGreen, &gammaTableBlue, &gammaTableSampleCount) == CGError.success {
+ let redPeak = gammaTableRed.max() ?? 0
+ let greenPeak = gammaTableGreen.max() ?? 0
+ let bluePeak = gammaTableBlue.max() ?? 0
+ let gammaTablePeak = max(redPeak, greenPeak, bluePeak)
+ let peakRatio = gammaTablePeak / self.defaultGammaTablePeak
+ let brightnessValue = UInt8(round(self.swBrightnessTransform(value: peakRatio, reverse: true) * Float(self.getSwMaxBrightness())))
+ os_log("Current software gammatable brightness is: %{public}@", type: .debug, String(brightnessValue))
return brightnessValue
}
return self.getSwMaxBrightness()
diff --git a/MonitorControl/UI/Base.lproj/Main.storyboard b/MonitorControl/UI/Base.lproj/Main.storyboard
index 54db928..511d088 100644
--- a/MonitorControl/UI/Base.lproj/Main.storyboard
+++ b/MonitorControl/UI/Base.lproj/Main.storyboard
@@ -309,10 +309,10 @@
-
+
-
+
@@ -325,7 +325,6 @@
-
@@ -338,7 +337,7 @@
-
+
@@ -348,7 +347,7 @@