This commit is contained in:
Myron Koch 2026-05-29 00:18:38 -04:00 committed by GitHub
commit d0bc49d380
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 127 additions and 80 deletions

View file

@ -165,7 +165,6 @@ class Display: Equatable {
_ = self.setDirectBrightness(self.smoothBrightnessTransient, transient: true)
self.smoothBrightnessRunning = false
}
self.swBrightnessSemaphore.signal()
return true
}
@ -194,7 +193,10 @@ class Display: Equatable {
guard !self.isDummy else {
return
}
CGGetDisplayTransferByTable(self.identifier, 256, &self.defaultGammaTableRed, &self.defaultGammaTableGreen, &self.defaultGammaTableBlue, &self.defaultGammaTableSampleCount)
guard CGGetDisplayTransferByTable(self.identifier, 256, &self.defaultGammaTableRed, &self.defaultGammaTableGreen, &self.defaultGammaTableBlue, &self.defaultGammaTableSampleCount) == CGError.success else {
self.defaultGammaTablePeak = 1.0
return
}
let redPeak = self.defaultGammaTableRed.max() ?? 0
let greenPeak = self.defaultGammaTableGreen.max() ?? 0
let bluePeak = self.defaultGammaTableBlue.max() ?? 0
@ -225,23 +227,28 @@ class Display: Equatable {
currentValue = self.swBrightnessTransform(value: currentValue)
newValue = self.swBrightnessTransform(value: newValue)
if smooth {
self.swBrightnessSemaphore.signal()
DispatchQueue.global(qos: .userInteractive).async {
for transientValue in stride(from: currentValue, to: newValue, by: 0.005 * (currentValue > newValue ? -1 : 1)) {
guard app.reconfigureID == 0 else {
self.swBrightnessSemaphore.signal()
return
}
if self.isVirtual || self.readPrefAsBool(key: .avoidGamma) {
_ = DisplayManager.shared.setShadeAlpha(value: 1 - transientValue, displayID: DisplayManager.resolveEffectiveDisplayID(self.identifier))
DispatchQueue.main.async {
_ = DisplayManager.shared.setShadeAlpha(value: 1 - transientValue, displayID: DisplayManager.resolveEffectiveDisplayID(self.identifier))
}
} else {
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)
DispatchQueue.main.sync {
CGSetDisplayTransferByTable(self.identifier, self.defaultGammaTableSampleCount, gammaTableRed, gammaTableGreen, gammaTableBlue)
}
}
Thread.sleep(forTimeInterval: 0.001) // Let's make things quick if not performed in the background
}
}
return true
} else {
if self.isVirtual || self.readPrefAsBool(key: .avoidGamma) {
self.swBrightnessSemaphore.signal()
@ -273,6 +280,10 @@ class Display: Equatable {
self.swBrightnessSemaphore.signal()
return self.swBrightnessTransform(value: rawBrightnessValue, reverse: true)
}
guard self.defaultGammaTablePeak != 0 else {
self.swBrightnessSemaphore.signal()
return 1.0
}
var gammaTableRed = [CGGammaValue](repeating: 0, count: 256)
var gammaTableGreen = [CGGammaValue](repeating: 0, count: 256)
var gammaTableBlue = [CGGammaValue](repeating: 0, count: 256)
@ -300,24 +311,26 @@ class Display: Equatable {
os_log("Gamma table interference detected, number of events: %{public}@", type: .info, String(DisplayManager.shared.gammaInterferenceCounter))
if DisplayManager.shared.gammaInterferenceCounter >= 3 {
DisplayManager.shared.gammaInterferenceWarningShown = true
let alert = NSAlert()
alert.messageText = NSLocalizedString("Is f.lux or similar running?", comment: "Shown in the alert dialog")
alert.informativeText = NSLocalizedString("An other app seems to change the brightness or colors which causes issues.\n\nTo solve this, you need to quit the other app or disable gamma control for your displays in MonitorControl!", comment: "Shown in the alert dialog")
alert.addButton(withTitle: NSLocalizedString("I'll quit the other app", comment: "Shown in the alert dialog"))
alert.addButton(withTitle: NSLocalizedString("Disable gamma control for my displays", comment: "Shown in the alert dialog"))
alert.alertStyle = NSAlert.Style.critical
if alert.runModal() != .alertFirstButtonReturn {
for otherDisplay in DisplayManager.shared.getOtherDisplays() {
_ = otherDisplay.setSwBrightness(1)
_ = otherDisplay.setDirectBrightness(1)
otherDisplay.savePref(true, key: .avoidGamma)
_ = otherDisplay.setSwBrightness(1)
DisplayManager.shared.gammaInterferenceWarningShown = false
DisplayManager.shared.gammaInterferenceCounter = 0
displaysPrefsVc?.loadDisplayList()
DispatchQueue.main.async {
let alert = NSAlert()
alert.messageText = NSLocalizedString("Is f.lux or similar running?", comment: "Shown in the alert dialog")
alert.informativeText = NSLocalizedString("An other app seems to change the brightness or colors which causes issues.\n\nTo solve this, you need to quit the other app or disable gamma control for your displays in MonitorControl!", comment: "Shown in the alert dialog")
alert.addButton(withTitle: NSLocalizedString("I'll quit the other app", comment: "Shown in the alert dialog"))
alert.addButton(withTitle: NSLocalizedString("Disable gamma control for my displays", comment: "Shown in the alert dialog"))
alert.alertStyle = NSAlert.Style.critical
if alert.runModal() != .alertFirstButtonReturn {
for otherDisplay in DisplayManager.shared.getOtherDisplays() {
_ = otherDisplay.setSwBrightness(1)
_ = otherDisplay.setDirectBrightness(1)
otherDisplay.savePref(true, key: .avoidGamma)
_ = otherDisplay.setSwBrightness(1)
DisplayManager.shared.gammaInterferenceWarningShown = false
DisplayManager.shared.gammaInterferenceCounter = 0
displaysPrefsVc?.loadDisplayList()
}
} else {
os_log("We won't watch for gamma table interference anymore", type: .info)
}
} else {
os_log("We won't watch for gamma table interference anymore", type: .info)
}
}
}

View file

@ -97,9 +97,6 @@ class OtherDisplay: Display {
case .contrast: currentDDCValue = UInt16(Float(DDC_MAX_DETECT_LIMIT) * 0.750)
default: currentDDCValue = UInt16(Float(DDC_MAX_DETECT_LIMIT) * 1.000)
}
if command == .audioSpeakerVolume {
currentDDCValue = UInt16(Float(DDC_MAX_DETECT_LIMIT) * 0.125) // lower default audio value as high volume might rattle the user.
}
os_log("Setting up display %{public}@ for %{public}@", type: .info, String(self.identifier), String(reflecting: command))
if !self.isSw() {
if prefs.integer(forKey: PrefKey.startupAction.rawValue) == StartupAction.read.rawValue, self.pollingCount != 0, !app.safeMode {
@ -399,21 +396,32 @@ class OtherDisplay: Display {
guard value != UInt16.max, value != lastValue else {
return
}
self.writeDDCQueue.async(flags: .barrier) {
self.writeDDCLastSavedValue[command] = value
self.savePref(true, key: PrefKey.isTouched, for: command)
}
var controlCodes = self.getRemapControlCodes(command: command)
if controlCodes.count == 0 {
controlCodes.append(command.rawValue)
}
var writeAttempted = false
var writeSucceeded = true
for controlCode in controlCodes {
if Arm64DDC.isArm64 {
if self.arm64ddc {
_ = Arm64DDC.write(service: self.arm64avService, command: controlCode, value: value)
writeAttempted = true
writeSucceeded = Arm64DDC.write(service: self.arm64avService, command: controlCode, value: value)
}
} else {
_ = self.ddc?.write(command: controlCode, value: value, errorRecoveryWaitTime: 2000) ?? false
if self.ddc != nil {
writeAttempted = true
writeSucceeded = self.ddc?.write(command: controlCode, value: value, errorRecoveryWaitTime: 2000) ?? false
}
}
if writeAttempted, !writeSucceeded {
break
}
}
if writeAttempted, writeSucceeded {
self.writeDDCQueue.async(flags: .barrier) {
self.writeDDCLastSavedValue[command] = value
self.savePref(true, key: PrefKey.isTouched, for: command)
}
}
}
@ -487,6 +495,9 @@ class OtherDisplay: Display {
let curveMultiplier = self.getCurveMultiplier(self.readPrefAsInt(key: .curveDDC, for: command))
let minDDCValue = Float(self.readPrefAsInt(key: .minDDCOverride, for: command))
let maxDDCValue = Float(self.readPrefAsInt(key: .maxDDC, for: command))
guard maxDDCValue != minDDCValue else {
return UInt16(minDDCValue)
}
let curvedValue = pow(max(min(value, 1), 0), curveMultiplier)
let deNormalizedValue = (maxDDCValue - minDDCValue) * curvedValue + minDDCValue
var intDDCValue = UInt16(min(max(deNormalizedValue, minDDCValue), maxDDCValue))
@ -500,6 +511,9 @@ class OtherDisplay: Display {
let curveMultiplier = self.getCurveMultiplier(self.readPrefAsInt(key: .curveDDC, for: command))
let minDDCValue = Float(self.readPrefAsInt(key: .minDDCOverride, for: command))
let maxDDCValue = Float(self.readPrefAsInt(key: .maxDDC, for: command))
guard maxDDCValue != minDDCValue else {
return 0
}
let normalizedValue = ((min(max(Float(from), minDDCValue), maxDDCValue) - minDDCValue) / (maxDDCValue - minDDCValue))
let deCurvedValue = pow(normalizedValue, 1.0 / curveMultiplier)
var value = deCurvedValue

View file

@ -22,6 +22,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
var accessibilityObserver: NSObjectProtocol!
var statusItemObserver: NSObjectProtocol!
var statusItemVisibilityChangedByUser = true
var configureID: Int = 0 // dispatched configure command ID
var reconfigureID: Int = 0 // dispatched reconfigure command ID
var sleepID: Int = 0 // sleep event ID
var safeMode = false
@ -137,6 +138,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
os_log("Request for configuration with reconfigreID %{public}@", type: .info, String(dispatchedReconfigureID))
self.reconfigureID = 0
self.configureID += 1
let dispatchedConfigureID = self.configureID
DisplayManager.shared.gammaInterferenceCounter = 0
DisplayManager.shared.configureDisplays()
DisplayManager.shared.addDisplayCounterSuffixes()
@ -144,15 +147,36 @@ class AppDelegate: NSObject, NSApplicationDelegate {
if firstrun && prefs.integer(forKey: PrefKey.startupAction.rawValue) != StartupAction.write.rawValue {
DisplayManager.shared.resetSwBrightnessForAllDisplays(prefsOnly: true)
}
DisplayManager.shared.setupOtherDisplays(firstrun: firstrun)
self.updateMenusAndKeys()
if !firstrun || prefs.integer(forKey: PrefKey.startupAction.rawValue) == StartupAction.write.rawValue {
if !prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue) {
DisplayManager.shared.restoreSwBrightnessForAllDisplays(async: !prefs.bool(forKey: PrefKey.disableSmoothBrightness.rawValue))
DispatchQueue.global(qos: .userInitiated).async {
guard self.sleepID == 0, self.reconfigureID == 0, dispatchedConfigureID == self.configureID else {
return
}
DisplayManager.shared.setupOtherDisplays(firstrun: firstrun)
guard self.sleepID == 0, self.reconfigureID == 0, dispatchedConfigureID == self.configureID else {
return
}
DispatchQueue.main.async {
guard self.sleepID == 0, self.reconfigureID == 0, dispatchedConfigureID == self.configureID else {
return
}
self.updateMenusAndKeys()
}
guard self.sleepID == 0, self.reconfigureID == 0, dispatchedConfigureID == self.configureID else {
return
}
if !firstrun || prefs.integer(forKey: PrefKey.startupAction.rawValue) == StartupAction.write.rawValue {
if !prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue) {
DisplayManager.shared.restoreSwBrightnessForAllDisplays(async: !prefs.bool(forKey: PrefKey.disableSmoothBrightness.rawValue))
}
}
DispatchQueue.main.async {
guard self.sleepID == 0, self.reconfigureID == 0, dispatchedConfigureID == self.configureID else {
return
}
displaysPrefsVc?.loadDisplayList()
self.job(start: true)
}
}
displaysPrefsVc?.loadDisplayList()
self.job(start: true)
}
func updateMenusAndKeys() {
@ -298,22 +322,25 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
func setStartAtLogin(enabled: Bool) {
let identifier = "\(Bundle.main.bundleIdentifier!)Helper" as CFString
SMLoginItemSetEnabled(identifier, enabled)
let identifier = "\(Bundle.main.bundleIdentifier!)Helper"
if #available(macOS 13.0, *) {
let service = SMAppService.loginItem(identifier: identifier)
do {
if enabled {
try service.register()
} else {
try service.unregister()
}
} catch {
os_log("Unable to update login item registration: %{public}@", type: .error, error.localizedDescription)
}
} else {
SMLoginItemSetEnabled(identifier as CFString, enabled)
}
}
func getSystemSettings() -> [String: AnyObject]? {
var propertyListFormat = PropertyListSerialization.PropertyListFormat.xml
let plistPath = NSString(string: "~/Library/Preferences/.GlobalPreferences.plist").expandingTildeInPath
guard let plistXML = FileManager.default.contents(atPath: plistPath) else {
return nil
}
do {
return try PropertyListSerialization.propertyList(from: plistXML, options: .mutableContainersAndLeaves, format: &propertyListFormat) as? [String: AnyObject]
} catch {
os_log("Error reading system prefs plist: %{public}@", type: .info, error.localizedDescription)
return nil
}
func getSystemSettings() -> [String: Any]? {
UserDefaults.standard.persistentDomain(forName: ".GlobalPreferences")
}
func macOS10() -> Bool {

View file

@ -74,6 +74,9 @@ class Arm64DDC: NSObject {
var send: [UInt8] = [command]
var reply = [UInt8](repeating: 0, count: 11)
if Self.performDDCCommunication(service: service, send: &send, reply: &reply, writeSleepTime: writeSleepTime, numOfWriteCycles: numOfWriteCycles, readSleepTime: readSleepTime, numOfRetryAttemps: numOfRetryAttemps, retrySleepTime: retrySleepTime) {
guard reply.count > 9, reply[0] == ARM64_DDC_7BIT_ADDRESS << 1, reply[2] == 0x02, reply[4] == command else {
return nil
}
let max = UInt16(reply[6]) * 256 + UInt16(reply[7])
let current = UInt16(reply[8]) * 256 + UInt16(reply[9])
values = (current, max)
@ -97,7 +100,8 @@ class Arm64DDC: NSObject {
}
var packet: [UInt8] = [UInt8(0x80 | (send.count + 1)), UInt8(send.count)] + send + [0] // Note: the last byte is the place of the checksum, see next line!
packet[packet.count - 1] = self.checksum(chk: send.count == 1 ? ARM64_DDC_7BIT_ADDRESS << 1 : ARM64_DDC_7BIT_ADDRESS << 1 ^ dataAddress, data: &packet, start: 0, end: packet.count - 2)
for _ in 1 ... (numOfRetryAttemps ?? 4) + 1 {
let attempts = max(1, Int(min(numOfRetryAttemps ?? 4, UInt8(30))))
for _ in 0 ..< attempts {
for _ in 1 ... max((numOfWriteCycles ?? 2) + 0, 1) {
usleep(writeSleepTime ?? 10000)
success = IOAVServiceWriteI2C(service, UInt32(ARM64_DDC_7BIT_ADDRESS), UInt32(dataAddress), &packet, UInt32(packet.count)) == 0
@ -232,7 +236,13 @@ class Arm64DDC: NSObject {
static func getIoregServicesForMatching() -> [IOregService] {
var serviceLocation = 0
var ioregServicesForMatching: [IOregService] = []
let ioregRoot: io_registry_entry_t = IORegistryGetRootEntry(kIOMasterPortDefault)
let masterPort: mach_port_t
if #available(macOS 12.0, *) {
masterPort = kIOMainPortDefault
} else {
masterPort = kIOMasterPortDefault
}
let ioregRoot: io_registry_entry_t = IORegistryGetRootEntry(masterPort)
defer {
IOObjectRelease(ioregRoot)
}

View file

@ -42,7 +42,6 @@ class DisplayManager {
}
var shades: [CGDirectDisplayID: NSWindow] = [:]
var shadeGrave: [NSWindow] = []
func isDisqualifiedFromShade(_ displayID: CGDirectDisplayID) -> Bool {
if CGDisplayIsInHWMirrorSet(displayID) != 0 || CGDisplayIsInMirrorSet(displayID) != 0 {
@ -117,7 +116,6 @@ class DisplayManager {
func destroyShade(displayID: CGDirectDisplayID) -> Bool {
if let shade = shades[displayID] {
os_log("Destroying shade for display %{public}@", type: .info, String(displayID))
self.shadeGrave.append(shade)
self.shades.removeValue(forKey: displayID)
shade.close()
return true
@ -241,21 +239,6 @@ class DisplayManager {
self.displays.compactMap { $0 as? OtherDisplay }
}
func sortDisplays() {
// Opsiyonel: sıralamadan önce log al
let before = displays.map { $0.name }
os_log("Displays before sorting: %{public}@", before)
// Inplace sıralama
displays.sort { lhs, rhs in
lhs.name.localizedStandardCompare(rhs.name) == .orderedAscending
}
// Opsiyonel: sıralamadan sonra log al
let after = displays.map { $0.name }
os_log("Displays after sorting: %{public}@", after)
}
func sortDisplaysByFriendlyName() -> [Display] {
return displays.sorted { lhs, rhs in
let lhsTitle = lhs.readPrefAsString(key: .friendlyName).isEmpty
@ -337,11 +320,14 @@ class DisplayManager {
if Arm64DDC.isArm64 {
os_log("arm64 AVService update requested", type: .info)
var displayIDs: [CGDirectDisplayID] = []
for otherDisplay in self.getOtherDisplays() {
let otherDisplays = self.getOtherDisplays()
for otherDisplay in otherDisplays {
otherDisplay.arm64avService = nil
otherDisplay.arm64ddc = false
displayIDs.append(otherDisplay.identifier)
}
for serviceMatch in Arm64DDC.getServiceMatches(displayIDs: displayIDs) {
for otherDisplay in self.getOtherDisplays() where otherDisplay.identifier == serviceMatch.displayID && serviceMatch.service != nil {
for otherDisplay in otherDisplays where otherDisplay.identifier == serviceMatch.displayID && serviceMatch.service != nil {
otherDisplay.arm64avService = serviceMatch.service
os_log("Display service match successful for display %{public}@", type: .info, String(serviceMatch.displayID))
if serviceMatch.discouraged {

View file

@ -56,11 +56,12 @@ class MenuHandler: NSMenu, NSMenuDelegate {
displays = DisplayManager.shared.sortDisplaysByFriendlyName()
let relevant = prefs.integer(forKey: PrefKey.multiSliders.rawValue) == MultiSliders.relevant.rawValue
let combine = prefs.integer(forKey: PrefKey.multiSliders.rawValue) == MultiSliders.combine.rawValue
let currentDisplayID = currentDisplay.map { DisplayManager.resolveEffectiveDisplayID($0.identifier) }
let numOfDisplays = displays.filter { !$0.isDummy }.count
if numOfDisplays != 0 {
let asSubMenu: Bool = (displays.count > 3 && !relevant && !combine && app.macOS10()) ? true : false
var iterator = 0
for display in displays where (!relevant || DisplayManager.resolveEffectiveDisplayID(display.identifier) == DisplayManager.resolveEffectiveDisplayID(currentDisplay!.identifier)) && !display.isDummy {
for display in displays where (!relevant || currentDisplayID.map { DisplayManager.resolveEffectiveDisplayID(display.identifier) == $0 } ?? false) && !display.isDummy {
iterator += 1
if !relevant, !combine, iterator != 1, app.macOS10() {
self.insertItem(NSMenuItem.separator(), at: 0)

View file

@ -320,9 +320,7 @@ class SliderHandler {
slider.floatValue = value
}
}
if self.percentageBox == self.percentageBox {
self.percentageBox?.stringValue = "" + String(Int(value * 100)) + "%"
}
self.percentageBox?.stringValue = "" + String(Int(value * 100)) + "%"
for display in self.displays {
slider.setHighlightItem(display.identifier, value: value)
if self.command == .brightness, let appleDisplay = display as? AppleDisplay {
@ -378,9 +376,7 @@ class SliderHandler {
} else {
slider.setDisplayHighlightItems(false)
}
if self.percentageBox == self.percentageBox {
self.percentageBox?.stringValue = "\(String(format: "%.0f%%", Double(value) * 100))"
}
self.percentageBox?.stringValue = "\(String(format: "%.0f%%", Double(value) * 100))"
}
}
}