Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ jobs:
PROVISIONING_PROFILE_SPECIFIER= \
2>&1 | tee build/xcodebuild-test.log

- name: Validate build script failure handling
run: build/test-build.sh

# actions/upload-artifact v4.6.2
- name: Upload test log
if: failure()
Expand Down
4 changes: 4 additions & 0 deletions MonitorControl.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
FE4E0896249D584C003A50BB /* OSDUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE4E0895249D584C003A50BB /* OSDUtils.swift */; };
E77C0DE000000000000000A3 /* XDRBrightnessTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E77C0DE000000000000000A1 /* XDRBrightnessTests.swift */; };
E77C0DE000000000000000B3 /* SettingsPanesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E77C0DE000000000000000B1 /* SettingsPanesTests.swift */; };
E77C0DE000000000000000C3 /* IntelDDCTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E77C0DE000000000000000C1 /* IntelDDCTests.swift */; };
/* End PBXBuildFile section */

/* Begin PBXCopyFilesBuildPhase section */
Expand Down Expand Up @@ -187,6 +188,7 @@
FE4E0895249D584C003A50BB /* OSDUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSDUtils.swift; sourceTree = "<group>"; };
E77C0DE000000000000000A1 /* XDRBrightnessTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XDRBrightnessTests.swift; sourceTree = "<group>"; };
E77C0DE000000000000000B1 /* SettingsPanesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPanesTests.swift; sourceTree = "<group>"; };
E77C0DE000000000000000C1 /* IntelDDCTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntelDDCTests.swift; sourceTree = "<group>"; };
E77C0DE000000000000000A2 /* MonitorControlTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = MonitorControlTests.xctest; path = MonitorControlTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -393,6 +395,7 @@
children = (
E77C0DE000000000000000A1 /* XDRBrightnessTests.swift */,
E77C0DE000000000000000B1 /* SettingsPanesTests.swift */,
E77C0DE000000000000000C1 /* IntelDDCTests.swift */,
);
path = MonitorControlTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -749,6 +752,7 @@
files = (
E77C0DE000000000000000A3 /* XDRBrightnessTests.swift in Sources */,
E77C0DE000000000000000B3 /* SettingsPanesTests.swift in Sources */,
E77C0DE000000000000000C3 /* IntelDDCTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
3 changes: 2 additions & 1 deletion MonitorControl/Extensions/NSScreen+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ public extension NSScreen {
}

defer {
assert(IOObjectRelease(servicePortIterator) == KERN_SUCCESS)
_ = IOObjectRelease(servicePortIterator)
}

while case let object = IOIteratorNext(servicePortIterator), object != 0 {
defer { _ = IOObjectRelease(object) }
let dict = (IODisplayCreateInfoDictionary(object, UInt32(kIODisplayOnlyPreferredName)).takeRetainedValue() as NSDictionary as? [String: AnyObject])!

if dict[kDisplayVendorID] as? UInt32 == self.vendorNumber, dict[kDisplayProductID] as? UInt32 == self.modelNumber, dict[kDisplaySerialNumber] as? UInt32 == self.serialNumber {
Expand Down
86 changes: 50 additions & 36 deletions MonitorControl/Model/Display.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class Display: Equatable {
var smoothBrightnessRunning: Bool = false
var smoothBrightnessSlow: Bool = false
let swBrightnessSemaphore = DispatchSemaphore(value: 1)
private var swBrightnessGeneration: UInt64 = 0

static func == (lhs: Display, rhs: Display) -> Bool {
lhs.identifier == rhs.identifier
Expand Down Expand Up @@ -180,7 +181,6 @@ class Display: Equatable {
_ = self.setDirectBrightness(self.smoothBrightnessTransient, transient: true)
self.smoothBrightnessRunning = false
}
self.swBrightnessSemaphore.signal()
return true
}

Expand Down Expand Up @@ -227,50 +227,64 @@ class Display: Equatable {

func setSwBrightness(_ value: Float, smooth: Bool = false, noPrefSave: Bool = false) -> Bool {
self.swBrightnessSemaphore.wait()
self.swBrightnessGeneration &+= 1
let generation = self.swBrightnessGeneration
let brightnessValue = min(1, value)
var currentValue = self.readPrefAsFloat(key: .SwBrightness)
let currentValue = self.swBrightnessTransform(value: self.readPrefAsFloat(key: .SwBrightness))
if !noPrefSave {
self.savePref(brightnessValue, key: .SwBrightness)
}
guard !self.isDummy else {
self.swBrightnessSemaphore.signal()
return true
}
var newValue = brightnessValue
currentValue = self.swBrightnessTransform(value: currentValue)
newValue = self.swBrightnessTransform(value: newValue)
let newValue = self.swBrightnessTransform(value: brightnessValue)
if smooth {
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))
} 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)
}
Thread.sleep(forTimeInterval: 0.001) // Let's make things quick if not performed in the background
}
self.swBrightnessSemaphore.signal()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.001) {
self.runSmoothSwBrightnessStep(currentValue: currentValue, targetValue: newValue, generation: generation)
}
return true
} else {
if self.isVirtual || self.readPrefAsBool(key: .avoidGamma) {
self.swBrightnessSemaphore.signal()
return DisplayManager.shared.setShadeAlpha(value: 1 - newValue, displayID: DisplayManager.resolveEffectiveDisplayID(self.identifier))
} else {
let gammaTableRed = self.defaultGammaTableRed.map { $0 * newValue }
let gammaTableGreen = self.defaultGammaTableGreen.map { $0 * newValue }
let gammaTableBlue = self.defaultGammaTableBlue.map { $0 * newValue }
DisplayManager.shared.moveGammaActivityEnforcer(displayID: self.identifier)
CGSetDisplayTransferByTable(self.identifier, self.defaultGammaTableSampleCount, gammaTableRed, gammaTableGreen, gammaTableBlue)
DisplayManager.shared.enforceGammaActivity()
}
let result = self.applySwBrightnessValue(newValue)
self.swBrightnessSemaphore.signal()
return result
}
}

private func runSmoothSwBrightnessStep(currentValue: Float, targetValue: Float, generation: UInt64) {
self.swBrightnessSemaphore.wait()
guard generation == self.swBrightnessGeneration, app.sleepID == 0, app.reconfigureID == 0 else {
self.swBrightnessSemaphore.signal()
return
}
let difference = targetValue - currentValue
let nextValue = abs(difference) <= 0.005 ? targetValue : currentValue + (difference > 0 ? 0.005 : -0.005)
let result = self.applySwBrightnessValue(nextValue, enforceGammaActivity: false)
self.swBrightnessSemaphore.signal()
guard result else {
return
}
if nextValue != targetValue {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.001) {
self.runSmoothSwBrightnessStep(currentValue: nextValue, targetValue: targetValue, generation: generation)
}
}
}

func applySwBrightnessValue(_ value: Float, enforceGammaActivity: Bool = true) -> Bool {
guard !self.isDummy else {
return true
}
if self.isVirtual || self.readPrefAsBool(key: .avoidGamma) {
return DisplayManager.shared.setShadeAlpha(value: 1 - value, displayID: DisplayManager.resolveEffectiveDisplayID(self.identifier))
}
let gammaTableRed = self.defaultGammaTableRed.map { $0 * value }
let gammaTableGreen = self.defaultGammaTableGreen.map { $0 * value }
let gammaTableBlue = self.defaultGammaTableBlue.map { $0 * value }
if enforceGammaActivity {
DisplayManager.shared.moveGammaActivityEnforcer(displayID: self.identifier)
}
CGSetDisplayTransferByTable(self.identifier, self.defaultGammaTableSampleCount, gammaTableRed, gammaTableGreen, gammaTableBlue)
if enforceGammaActivity {
DisplayManager.shared.enforceGammaActivity()
}
return true
}

Expand Down
23 changes: 14 additions & 9 deletions MonitorControl/Support/Arm64DDC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -163,23 +163,25 @@ class Arm64DDC: NSObject {
return matchScore
}

static func ioregIterateToNextObjectOfInterest(interests: [String], iterator: inout io_iterator_t) -> (name: String, entry: io_service_t, preceedingEntry: io_service_t)? {
var entry: io_service_t = IO_OBJECT_NULL
var preceedingEntry: io_service_t = IO_OBJECT_NULL
static func ioregIterateToNextObjectOfInterest(interests: [String], iterator: inout io_iterator_t) -> (name: String, entry: io_service_t)? {
let name = UnsafeMutablePointer<CChar>.allocate(capacity: MemoryLayout<io_name_t>.size)
defer {
name.deallocate()
}
while true {
preceedingEntry = entry
entry = IOIteratorNext(iterator)
guard IORegistryEntryGetName(entry, name) == KERN_SUCCESS, entry != MACH_PORT_NULL else {
let entry = IOIteratorNext(iterator)
guard entry != IO_OBJECT_NULL else {
break
}
guard IORegistryEntryGetName(entry, name) == KERN_SUCCESS else {
_ = IOObjectRelease(entry)
break
}
let nameString = String(cString: name)
for interest in interests where entry != IO_OBJECT_NULL && nameString.contains(interest) {
return (nameString, entry, preceedingEntry)
return (nameString, entry)
}
_ = IOObjectRelease(entry)
}
return nil
}
Expand All @@ -190,8 +192,10 @@ class Arm64DDC: NSObject {
ioregService.edidUUID = edidUUID
}
let cpath = UnsafeMutablePointer<CChar>.allocate(capacity: MemoryLayout<io_string_t>.size)
IORegistryEntryGetPath(entry, kIOServicePlane, cpath)
ioregService.ioDisplayLocation = String(cString: cpath)
defer { cpath.deallocate() }
if IORegistryEntryGetPath(entry, kIOServicePlane, cpath) == KERN_SUCCESS {
ioregService.ioDisplayLocation = String(cString: cpath)
}
if let unmanagedDisplayAttrs = IORegistryEntryCreateCFProperty(entry, "DisplayAttributes" as CFString, kCFAllocatorDefault, IOOptionBits(kIORegistryIterateRecursively)), let displayAttrs = unmanagedDisplayAttrs.takeRetainedValue() as? NSDictionary {
ioregService.displayAttributes = displayAttrs
if let productAttrs = displayAttrs.value(forKey: "ProductAttributes") as? NSDictionary {
Expand Down Expand Up @@ -250,6 +254,7 @@ class Arm64DDC: NSObject {
guard let objectOfInterest = ioregIterateToNextObjectOfInterest(interests: [keyDCPAVServiceProxy] + keysFramebuffer, iterator: &iterator) else {
break
}
defer { _ = IOObjectRelease(objectOfInterest.entry) }
if keysFramebuffer.contains(objectOfInterest.name) {
ioregService = self.getIORegServiceAppleCDC2Properties(entry: objectOfInterest.entry)
serviceLocation += 1
Expand Down
3 changes: 1 addition & 2 deletions MonitorControl/Support/DisplayManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -65,6 +64,7 @@ class DisplayManager {
func createShadeOnDisplay(displayID: CGDirectDisplayID) -> NSWindow? {
if let screen = DisplayManager.getByDisplayID(displayID: displayID) {
let shade = NSWindow(contentRect: .init(origin: NSPoint(x: 0, y: 0), size: .init(width: 10, height: 1)), styleMask: [], backing: .buffered, defer: false)
shade.isReleasedWhenClosed = false
shade.title = "LumaControl Window Shade for Display " + String(displayID)
shade.isMovableByWindowBackground = false
shade.backgroundColor = .clear
Expand Down Expand Up @@ -117,7 +117,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
Expand Down
Loading
Loading