From cfc77d53d6aea9863cec0f9db409c4154b5d976b Mon Sep 17 00:00:00 2001
From: Istvan T <37590873+waydabber@users.noreply.github.com>
Date: Thu, 4 Nov 2021 17:56:23 +0100
Subject: [PATCH] Various fixes and improvements for v4.0.1 (#769)
- Fixed: display properties reset turns off hardware DDC
- Fixed: brief black screen upon changing space when using shade dimming
- Asynchronous thread-safe debouncing DDC write for smoother sliders
- Improved support for [BetterDummy](https://github.com/waydabber/BetterDummy)
- Better support for common physical dummies identifying as 28E850
- Inert dummy menu sliders are now hidden
- Improved support for nongamma->nongamma mirroring scenarios
- Compiled to run 10.14 (needed some minor changes) - compatibility is unofficial!
- Updated README
- Bumped version number to 4.0.1 (service release)
---
.swiftlint.yml | 2 +-
MonitorControl.xcodeproj/project.pbxproj | 12 +--
MonitorControl/Info.plist | 2 +-
MonitorControl/Model/Display.swift | 6 +-
MonitorControl/Model/OtherDisplay.swift | 70 ++++++++++------
MonitorControl/Support/DisplayManager.swift | 82 +++++++++++++------
MonitorControl/Support/MenuHandler.swift | 4 +-
MonitorControl/Support/SliderHandler.swift | 4 +-
.../DisplaysPrefsCellView.swift | 2 +-
MonitorControlHelper/Info.plist | 2 +-
README.md | 45 +++++-----
11 files changed, 142 insertions(+), 89 deletions(-)
diff --git a/.swiftlint.yml b/.swiftlint.yml
index 9252648..7cc44de 100644
--- a/.swiftlint.yml
+++ b/.swiftlint.yml
@@ -4,7 +4,7 @@ disabled_rules:
- identifier_name
- trailing_comma
type_body_length: 500
-file_length: 500
+file_length: 750
cyclomatic_complexity:
ignores_case_statements: true
opening_brace:
diff --git a/MonitorControl.xcodeproj/project.pbxproj b/MonitorControl.xcodeproj/project.pbxproj
index cdc9cba..95fc229 100644
--- a/MonitorControl.xcodeproj/project.pbxproj
+++ b/MonitorControl.xcodeproj/project.pbxproj
@@ -751,8 +751,8 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
- MACOSX_DEPLOYMENT_TARGET = 10.15;
- MARKETING_VERSION = 4.0.0;
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MARKETING_VERSION = 4.0.1;
PRODUCT_BUNDLE_IDENTIFIER = me.guillaumeb.MonitorControl;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -786,8 +786,8 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
- MACOSX_DEPLOYMENT_TARGET = 10.15;
- MARKETING_VERSION = 4.0.0;
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MARKETING_VERSION = 4.0.1;
PRODUCT_BUNDLE_IDENTIFIER = me.guillaumeb.MonitorControl;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -821,7 +821,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
- MACOSX_DEPLOYMENT_TARGET = 10.15;
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 4.0.1;
PRODUCT_BUNDLE_IDENTIFIER = me.guillaumeb.MonitorControlHelper;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -850,7 +850,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
- MACOSX_DEPLOYMENT_TARGET = 10.15;
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
MARKETING_VERSION = 4.0.1;
PRODUCT_BUNDLE_IDENTIFIER = me.guillaumeb.MonitorControlHelper;
PRODUCT_NAME = "$(TARGET_NAME)";
diff --git a/MonitorControl/Info.plist b/MonitorControl/Info.plist
index 1c3560c..1b6ff21 100644
--- a/MonitorControl/Info.plist
+++ b/MonitorControl/Info.plist
@@ -19,7 +19,7 @@
CFBundleShortVersionString
$(MARKETING_VERSION)
CFBundleVersion
- 6851
+ 6941
LSApplicationCategoryType
public.app-category.utilities
LSMinimumSystemVersion
diff --git a/MonitorControl/Model/Display.swift b/MonitorControl/Model/Display.swift
index b68e518..0563b50 100644
--- a/MonitorControl/Model/Display.swift
+++ b/MonitorControl/Model/Display.swift
@@ -230,7 +230,7 @@ class Display: Equatable {
return
}
if self.isVirtual || self.readPrefAsBool(key: .avoidGamma) {
- _ = DisplayManager.shared.setShadeAlpha(value: 1 - transientValue, displayID: self.identifier)
+ _ = 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 }
@@ -243,7 +243,7 @@ class Display: Equatable {
} else {
if self.isVirtual || self.readPrefAsBool(key: .avoidGamma) {
self.swBrightnessSemaphore.signal()
- return DisplayManager.shared.setShadeAlpha(value: 1 - newValue, displayID: self.identifier)
+ 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 }
@@ -267,7 +267,7 @@ class Display: Equatable {
}
self.swBrightnessSemaphore.wait()
if self.isVirtual || self.readPrefAsBool(key: .avoidGamma) {
- let rawBrightnessValue = 1 - (DisplayManager.shared.getShadeAlpha(displayID: self.identifier) ?? 1)
+ let rawBrightnessValue = 1 - (DisplayManager.shared.getShadeAlpha(displayID: DisplayManager.resolveEffectiveDisplayID(self.identifier)) ?? 1)
self.swBrightnessSemaphore.signal()
return self.swBrightnessTransform(value: rawBrightnessValue, reverse: true)
}
diff --git a/MonitorControl/Model/OtherDisplay.swift b/MonitorControl/Model/OtherDisplay.swift
index f95afcc..9c95cb5 100644
--- a/MonitorControl/Model/OtherDisplay.swift
+++ b/MonitorControl/Model/OtherDisplay.swift
@@ -9,6 +9,9 @@ class OtherDisplay: Display {
var arm64ddc: Bool = false
var arm64avService: IOAVService?
var isDiscouraged: Bool = false
+ let writeDDCQueue = DispatchQueue(label: "Local write DDC queue")
+ var writeDDCNextValue: [Command: UInt16] = [:]
+ var writeDDCLastSavedValue: [Command: UInt16] = [:]
var pollingCount: Int {
get {
switch self.readPrefAsInt(key: .pollingMode) {
@@ -76,11 +79,11 @@ class OtherDisplay: Display {
if !self.smoothBrightnessRunning, !self.isSw(), !self.readPrefAsBool(key: .unavailableDDC, for: command), self.readPrefAsBool(key: .isTouched, for: command), prefs.integer(forKey: PrefKey.startupAction.rawValue) == StartupAction.write.rawValue, !app.safeMode {
let restoreValue = self.getDDCValueFromPrefs(command)
os_log("Restoring %{public}@ DDC value %{public}@ for %{public}@", type: .info, String(reflecting: command), String(restoreValue), self.name)
- _ = self.writeDDCValues(command: command, value: restoreValue)
+ self.writeDDCValues(command: command, value: restoreValue)
if command == .audioSpeakerVolume, self.readPrefAsBool(key: .enableMuteUnmute) {
let currentMuteValue = self.readPrefAsInt(for: .audioMuteScreenBlank) == 0 ? 2 : self.readPrefAsInt(for: .audioMuteScreenBlank)
os_log("- Writing last saved DDC value for Mute: %{public}@", type: .info, String(currentMuteValue))
- _ = self.writeDDCValues(command: .audioMuteScreenBlank, value: UInt16(currentMuteValue))
+ self.writeDDCValues(command: .audioMuteScreenBlank, value: UInt16(currentMuteValue))
}
}
}
@@ -178,13 +181,11 @@ class OtherDisplay: Display {
let isAlreadySet = volumeOSDValue == self.readPrefAsFloat(for: .audioSpeakerVolume)
if !isAlreadySet {
if let muteValue = muteValue, self.readPrefAsBool(key: .enableMuteUnmute) {
- guard self.writeDDCValues(command: .audioMuteScreenBlank, value: UInt16(muteValue)) == true else {
- return
- }
+ self.writeDDCValues(command: .audioMuteScreenBlank, value: UInt16(muteValue))
self.savePref(muteValue, for: .audioMuteScreenBlank)
}
if !self.readPrefAsBool(key: .enableMuteUnmute) || volumeOSDValue != 0 {
- _ = self.writeDDCValues(command: .audioSpeakerVolume, value: self.convValueToDDC(for: .audioSpeakerVolume, from: volumeOSDValue))
+ self.writeDDCValues(command: .audioSpeakerVolume, value: self.convValueToDDC(for: .audioSpeakerVolume, from: volumeOSDValue))
}
}
if !self.readPrefAsBool(key: .hideOsd) {
@@ -206,7 +207,7 @@ class OtherDisplay: Display {
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))
+ self.writeDDCValues(command: .contrast, value: self.convValueToDDC(for: .contrast, from: contrastOSDValue))
}
OSDUtils.showOsd(displayID: self.identifier, command: .contrast, value: contrastOSDValue, roundChiclet: !isSmallIncrement)
if !isAlreadySet {
@@ -237,13 +238,11 @@ class OtherDisplay: Display {
}
}
if self.readPrefAsBool(key: .enableMuteUnmute) {
- guard self.writeDDCValues(command: .audioMuteScreenBlank, value: UInt16(muteValue)) == true else {
- return
- }
+ self.writeDDCValues(command: .audioMuteScreenBlank, value: UInt16(muteValue))
}
self.savePref(muteValue, for: .audioMuteScreenBlank)
if !self.readPrefAsBool(key: .enableMuteUnmute) || volumeOSDValue > 0 {
- _ = self.writeDDCValues(command: .audioSpeakerVolume, value: self.convValueToDDC(for: .audioSpeakerVolume, from: volumeOSDValue))
+ self.writeDDCValues(command: .audioSpeakerVolume, value: self.convValueToDDC(for: .audioSpeakerVolume, from: volumeOSDValue))
}
if !fromVolumeSlider {
if !self.readPrefAsBool(key: .hideOsd) {
@@ -345,12 +344,12 @@ class OtherDisplay: Display {
brightnessValue = 0
brightnessSwValue = (value / self.combinedBrightnessSwitchingValue())
}
- _ = self.writeDDCValues(command: .brightness, value: self.convValueToDDC(for: .brightness, from: brightnessValue))
+ self.writeDDCValues(command: .brightness, value: self.convValueToDDC(for: .brightness, from: brightnessValue))
if self.readPrefAsFloat(key: .SwBrightness) != brightnessSwValue {
_ = self.setSwBrightness(brightnessSwValue)
}
} else {
- _ = self.writeDDCValues(command: .brightness, value: self.convValueToDDC(for: .brightness, from: value))
+ self.writeDDCValues(command: .brightness, value: self.convValueToDDC(for: .brightness, from: value))
}
if !transient {
self.savePref(value, for: .brightness)
@@ -378,28 +377,45 @@ class OtherDisplay: Display {
return intCodes
}
- public func writeDDCValues(command: Command, value: UInt16, errorRecoveryWaitTime _: UInt32? = nil) -> Bool? {
+ public func writeDDCValues(command: Command, value: UInt16) {
guard app.sleepID == 0, app.reconfigureID == 0, !self.readPrefAsBool(key: .forceSw), !self.readPrefAsBool(key: .unavailableDDC, for: command) else {
- return false
+ return
+ }
+ self.writeDDCQueue.async(flags: .barrier) {
+ self.writeDDCNextValue[command] = value
+ }
+ DisplayManager.shared.globalDDCQueue.async(flags: .barrier) {
+ self.asyncPerformWriteDDCValues(command: command)
+ }
+ }
+
+ func asyncPerformWriteDDCValues(command: Command) {
+ var value = UInt16.max
+ var lastValue = UInt16.max
+ self.writeDDCQueue.sync {
+ value = self.writeDDCNextValue[command] ?? UInt16.max
+ lastValue = self.writeDDCLastSavedValue[command] ?? UInt16.max
+ }
+ 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 success: Bool = false
var controlCodes = self.getRemapControlCodes(command: command)
if controlCodes.count == 0 {
controlCodes.append(command.rawValue)
}
for controlCode in controlCodes {
- DisplayManager.shared.ddcQueue.sync {
- if Arm64DDC.isArm64 {
- if self.arm64ddc {
- success = Arm64DDC.write(service: self.arm64avService, command: controlCode, value: value)
- }
- } else {
- success = self.ddc?.write(command: command.rawValue, value: value, errorRecoveryWaitTime: 2000) ?? false
+ if Arm64DDC.isArm64 {
+ if self.arm64ddc {
+ _ = Arm64DDC.write(service: self.arm64avService, command: controlCode, value: value)
}
- self.savePref(true, key: PrefKey.isTouched, for: command) // We deliberatly consider the value tuched no matter if the call succeeded
+ } else {
+ _ = self.ddc?.write(command: controlCode, value: value, errorRecoveryWaitTime: 2000) ?? false
}
}
- return success
}
func readDDCValues(for command: Command, tries: UInt, minReplyDelay delay: UInt64?) -> (current: UInt16, max: UInt16)? {
@@ -413,7 +429,7 @@ class OtherDisplay: Display {
guard self.arm64ddc else {
return nil
}
- DisplayManager.shared.ddcQueue.sync {
+ DisplayManager.shared.globalDDCQueue.sync {
if let unwrappedDelay = delay {
values = Arm64DDC.read(service: self.arm64avService, command: controlCode, tries: UInt8(min(tries, 255)), minReplyDelay: UInt32(unwrappedDelay / 1000))
} else {
@@ -421,7 +437,7 @@ class OtherDisplay: Display {
}
}
} else {
- DisplayManager.shared.ddcQueue.sync {
+ DisplayManager.shared.globalDDCQueue.sync {
values = self.ddc?.read(command: controlCode, tries: tries, minReplyDelay: delay)
}
}
diff --git a/MonitorControl/Support/DisplayManager.swift b/MonitorControl/Support/DisplayManager.swift
index c98c476..f426972 100644
--- a/MonitorControl/Support/DisplayManager.swift
+++ b/MonitorControl/Support/DisplayManager.swift
@@ -9,7 +9,7 @@ class DisplayManager {
var displays: [Display] = []
var audioControlTargetDisplays: [OtherDisplay] = []
- let ddcQueue = DispatchQueue(label: "DDC queue")
+ let globalDDCQueue = DispatchQueue(label: "Global DDC queue")
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
@@ -43,8 +43,22 @@ class DisplayManager {
internal var shades: [CGDirectDisplayID: NSWindow] = [:]
internal var shadeGrave: [NSWindow] = []
- func isDisqualifiedFromShade(_ displayID: CGDirectDisplayID) -> Bool { // We ban mirror members from shade control as it might lead to double control
- return (CGDisplayIsInHWMirrorSet(displayID) != 0 || CGDisplayIsInMirrorSet(displayID) != 0) ? true : false
+ func isDisqualifiedFromShade(_ displayID: CGDirectDisplayID) -> Bool {
+ if CGDisplayIsInHWMirrorSet(displayID) != 0 || CGDisplayIsInMirrorSet(displayID) != 0 {
+ if displayID == DisplayManager.resolveEffectiveDisplayID(displayID), DisplayManager.isVirtual(displayID: displayID) || DisplayManager.isDummy(displayID: displayID) {
+ var displayIDs = [CGDirectDisplayID](repeating: 0, count: 16)
+ var displayCount: UInt32 = 0
+ guard CGGetOnlineDisplayList(16, &displayIDs, &displayCount) == .success else {
+ return true
+ }
+ for displayId in displayIDs where CGDisplayMirrorsDisplay(displayId) == displayID && !DisplayManager.isVirtual(displayID: displayID) {
+ return true
+ }
+ return false
+ }
+ return true
+ }
+ return false
}
internal func createShadeOnDisplay(displayID: CGDirectDisplayID) -> NSWindow? {
@@ -52,13 +66,16 @@ class DisplayManager {
let shade = NSWindow(contentRect: .init(origin: NSPoint(x: 0, y: 0), size: .init(width: 10, height: 1)), styleMask: [], backing: .buffered, defer: false)
shade.title = "Monitor Control Window Shade for Display " + String(displayID)
shade.isMovableByWindowBackground = false
- shade.backgroundColor = .black
+ shade.backgroundColor = .clear
shade.ignoresMouseEvents = true
shade.level = NSWindow.Level(rawValue: Int(CGShieldingWindowLevel()))
- shade.alphaValue = 0
shade.orderFrontRegardless()
shade.collectionBehavior = [.stationary, .canJoinAllSpaces, .ignoresCycle]
shade.setFrame(screen.frame, display: true)
+ shade.contentView?.wantsLayer = true
+ shade.contentView?.alphaValue = 0.0
+ shade.contentView?.layer?.backgroundColor = .black
+ shade.contentView?.setNeedsDisplay(shade.frame)
os_log("Window shade created for display %{public}@", type: .info, String(displayID))
return shade
}
@@ -125,7 +142,7 @@ class DisplayManager {
return 1
}
if let shade = getShade(displayID: displayID) {
- return Float(shade.alphaValue)
+ return Float(shade.contentView?.alphaValue ?? 1)
} else {
return 1
}
@@ -136,7 +153,7 @@ class DisplayManager {
return false
}
if let shade = getShade(displayID: displayID) {
- shade.alphaValue = CGFloat(value)
+ shade.contentView?.alphaValue = CGFloat(value)
return true
}
return false
@@ -151,27 +168,12 @@ class DisplayManager {
return
}
for onlineDisplayID in onlineDisplayIDs where onlineDisplayID != 0 {
- let rawName = DisplayManager.getDisplayRawNameByID(displayID: onlineDisplayID)
let name = DisplayManager.getDisplayNameByID(displayID: onlineDisplayID)
let id = onlineDisplayID
let vendorNumber = CGDisplayVendorNumber(onlineDisplayID)
let modelNumber = CGDisplayModelNumber(onlineDisplayID)
- var isDummy: Bool = false
- var isVirtual: Bool = false
- if rawName == "28E850" || rawName.lowercased().contains("dummy") {
- os_log("NOTE: Display is a dummy!", type: .info)
- isDummy = true
- }
- if !DEBUG_MACOS10, #available(macOS 11.0, *) {
- if let dictionary = ((CoreDisplay_DisplayCreateInfoDictionary(onlineDisplayID))?.takeRetainedValue() as NSDictionary?) {
- let isVirtualDevice = dictionary["kCGDisplayIsVirtualDevice"] as? Bool
- let displayIsAirplay = dictionary["kCGDisplayIsAirPlay"] as? Bool
- if isVirtualDevice ?? displayIsAirplay ?? false {
- os_log("NOTE: Display is virtual!", type: .info)
- isVirtual = true
- }
- }
- }
+ let isDummy: Bool = DisplayManager.isDummy(displayID: onlineDisplayID)
+ let isVirtual: Bool = DisplayManager.isVirtual(displayID: onlineDisplayID)
if !DEBUG_SW, DisplayManager.isAppleDisplay(displayID: onlineDisplayID) { // MARK: (point of interest for testing)
let appleDisplay = AppleDisplay(id, name: name, vendorNumber: vendorNumber, modelNumber: modelNumber, isVirtual: isVirtual, isDummy: isDummy)
os_log("Apple display found - %{public}@", type: .info, "ID: \(appleDisplay.identifier), Name: \(appleDisplay.name) (Vendor: \(appleDisplay.vendorNumber ?? 0), Model: \(appleDisplay.modelNumber ?? 0))")
@@ -391,6 +393,30 @@ class DisplayManager {
return affectedDisplays
}
+ static func isDummy(displayID: CGDirectDisplayID) -> Bool {
+ let rawName = DisplayManager.getDisplayRawNameByID(displayID: displayID)
+ var isDummy: Bool = false
+ if rawName == "28E850" || rawName.lowercased().contains("dummy") {
+ os_log("NOTE: Display is a dummy!", type: .info)
+ isDummy = true
+ }
+ return isDummy
+ }
+
+ static func isVirtual(displayID: CGDirectDisplayID) -> Bool {
+ var isVirtual: Bool = false
+ if !DEBUG_MACOS10, #available(macOS 11.0, *) {
+ if let dictionary = ((CoreDisplay_DisplayCreateInfoDictionary(displayID))?.takeRetainedValue() as NSDictionary?) {
+ let isVirtualDevice = dictionary["kCGDisplayIsVirtualDevice"] as? Bool
+ let displayIsAirplay = dictionary["kCGDisplayIsAirPlay"] as? Bool
+ if isVirtualDevice ?? displayIsAirplay ?? false {
+ isVirtual = true
+ }
+ }
+ }
+ return isVirtual
+ }
+
static func engageMirror() -> Bool {
var onlineDisplayIDs = [CGDirectDisplayID](repeating: 0, count: 16)
var displayCount: UInt32 = 0
@@ -478,14 +504,18 @@ class DisplayManager {
if CGDisplayIsInHWMirrorSet(displayID) != 0 || CGDisplayIsInMirrorSet(displayID) != 0 {
let mirroredDisplayID = CGDisplayMirrorsDisplay(displayID)
if mirroredDisplayID != 0, let dictionary = ((CoreDisplay_DisplayCreateInfoDictionary(mirroredDisplayID))?.takeRetainedValue() as NSDictionary?), let nameList = dictionary["DisplayProductName"] as? [String: String], let mirroredName = nameList[Locale.current.identifier] ?? nameList["en_US"] ?? nameList.first?.value {
- name.append("~" + mirroredName)
+ name.append(" | " + mirroredName)
}
}
return name
}
}
if let screen = getByDisplayID(displayID: displayID) { // MARK: This, and NSScreen+Extension.swift will not be needed when we drop MacOS 10 support.
- return screen.localizedName
+ if #available(macOS 10.15, *) {
+ return screen.localizedName
+ } else {
+ return screen.displayName ?? defaultName
+ }
}
return defaultName
}
diff --git a/MonitorControl/Support/MenuHandler.swift b/MonitorControl/Support/MenuHandler.swift
index 2a61c19..34615a7 100644
--- a/MonitorControl/Support/MenuHandler.swift
+++ b/MonitorControl/Support/MenuHandler.swift
@@ -39,11 +39,11 @@ class MenuHandler: NSMenu, NSMenuDelegate {
displays.append(contentsOf: DisplayManager.shared.getOtherDisplays())
let relevant = prefs.integer(forKey: PrefKey.multiSliders.rawValue) == MultiSliders.relevant.rawValue
let combine = prefs.integer(forKey: PrefKey.multiSliders.rawValue) == MultiSliders.combine.rawValue
- let numOfDisplays = displays.count
+ 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 || display == currentDisplay {
+ for display in displays where (!relevant || DisplayManager.resolveEffectiveDisplayID(display.identifier) == DisplayManager.resolveEffectiveDisplayID(currentDisplay!.identifier)) && !display.isDummy {
iterator += 1
if !relevant, !combine, iterator != 1, app.macOS10() {
self.insertItem(NSMenuItem.separator(), at: 0)
diff --git a/MonitorControl/Support/SliderHandler.swift b/MonitorControl/Support/SliderHandler.swift
index 51fcb09..a1c2cc4 100644
--- a/MonitorControl/Support/SliderHandler.swift
+++ b/MonitorControl/Support/SliderHandler.swift
@@ -295,10 +295,10 @@ class SliderHandler {
} else if !otherDisplay.isSw() {
if self.command == Command.audioSpeakerVolume {
if !otherDisplay.readPrefAsBool(key: .enableMuteUnmute) || value != 0 {
- _ = otherDisplay.writeDDCValues(command: self.command, value: otherDisplay.convValueToDDC(for: self.command, from: value))
+ otherDisplay.writeDDCValues(command: self.command, value: otherDisplay.convValueToDDC(for: self.command, from: value))
}
} else {
- _ = otherDisplay.writeDDCValues(command: self.command, value: otherDisplay.convValueToDDC(for: self.command, from: value))
+ otherDisplay.writeDDCValues(command: self.command, value: otherDisplay.convValueToDDC(for: self.command, from: value))
}
otherDisplay.savePref(value, for: self.command)
}
diff --git a/MonitorControl/View Controllers/DisplaysPrefsCellView.swift b/MonitorControl/View Controllers/DisplaysPrefsCellView.swift
index 7bc4e8d..ccb0d9e 100644
--- a/MonitorControl/View Controllers/DisplaysPrefsCellView.swift
+++ b/MonitorControl/View Controllers/DisplaysPrefsCellView.swift
@@ -359,7 +359,7 @@ class DisplaysPrefsCellView: NSTableCellView {
self.ddcButton.state = .on
self.ddcButtonToggled(self.ddcButton)
self.avoidGamma.state = .off
- self.ddcButtonToggled(self.avoidGamma)
+ self.avoidGamma(self.avoidGamma)
self.disableVolumeOSDButton.state = .off
self.disableVolumeOSDButton(self.disableVolumeOSDButton)
self.pollingModeMenu.selectItem(withTag: 2)
diff --git a/MonitorControlHelper/Info.plist b/MonitorControlHelper/Info.plist
index a30473e..955c2f9 100644
--- a/MonitorControlHelper/Info.plist
+++ b/MonitorControlHelper/Info.plist
@@ -19,7 +19,7 @@
CFBundleShortVersionString
$(MARKETING_VERSION)
CFBundleVersion
- 6851
+ 6941
LSApplicationCategoryType
public.app-category.utilities
LSBackgroundOnly
diff --git a/README.md b/README.md
index 8079360..f7d3e01 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
MonitorControl - for Apple Silicon and Intel
-
Controls your external display brightness and volume and shows native OSD.
+
Controls your external display brightness and volume and shows native OSD.
Use menulet sliders or the keyboard, including native Apple keys!
@@ -60,7 +60,7 @@ Go to [Releases](https://github.com/MonitorControl/MonitorControl/releases) and
- Modern, stylish and highly customizable menulet reflecting the design of Control Control introduced in Big Sur.
- Simple, unobstrusive UI to blend in to the general aesthetics of macOS (even the menu icon can be hidden).
- Supports automatic updates for a hassle-free experience.
-- The best app of its kind, completely FREE (donations accepted) with the source code transparently available!
+- The best app of its kind, completely FREE ([donations welcome](https://opencollective.com/monitorcontrol)) with the source code transparently available!
## How to install and use the app
@@ -76,13 +76,13 @@ Go to [Releases](https://github.com/MonitorControl/MonitorControl/releases) and
## Screenshots (Preferences)
-## Compatibility
+## macOS compatibility
| MonitorControl version | macOS version |
| ---------------------- | ----------------- |
@@ -92,25 +92,32 @@ Go to [Releases](https://github.com/MonitorControl/MonitorControl/releases) and
_* With some limitations - full functionality available on macOS 11 Big Sur or newer._
-Note to f.lux users: the app is now compatible with [f.lux](https://justgetflux.com) as well - please activate `Avoid gamma table manipulation` under `Preferences` » `Displays`! This step is not needed if you use Night Shift.
+## Supported displays
-## Supported hardware
+- Most modern LCD displays from all major manufacturers supported implemented DDC/CI protocol via DisplayPort, HDMI, USB-C or VGA to allow for hardware backlight control.
+- Apple (and LG-Apple) displays and built-in displays are supported using native protocol.
+- LCD and LED Televisions usually do not implement DDC, these are supported using software alternatives to dim the image (some higher-end sets are able to translate this into hardware backlight dimming).
+- OLED or mini/micro-LED displays and televisions are fully supported using gamma table manipulation (this is a no-compromise solution for this class of displays).
+- DisplayLink, Airplay and Sidecar are supported using shade (dark overlay) control.
-* Most modern LCD displays from all major manufacturers supported implemented DDC/CI protocol via DisplayPort, HDMI, USB-C or VGA to allow for hardware backlight control.
-* Apple (and LG-Apple) displays and built-in displays are supported using native protocol.
-* LCD and LED Televisions usually do not implement DDC, these are supported using software alternatives to dim the image (some higher-end sets are able to translate this into hardware backlight dimming).
-* OLED or mini/micro-LED displays and televisions are fully supported using gamma table manipulation (this is a no-compromise solution for this class of displays).
-* DisplayLink, Airplay and Sidecar are supported using shade (dark overlay) control.
+Dummy compatibility:
-Notable exceptions for hardware control:
+- The app is compatible with [BetterDummy](https://github.com/waydabber/BetterDummy) mirrored sets.
+- The app is compatible with mirrored sets that include a dummy dongle identifying as `28E850`
-* Some displays (notably EIZO) use MCCS over USB or an entirely custom protocol for control. These displays are supported with software dimming only.
-* The HDMI port of the 2018 Intel Mac mini and 2020 M1 Mac mini prohibit DDC communication. Software control is still available. We recommend connecting the display via the USB-C port (USB-C to HDMI dongles usually work).
-* DisplayLink docks and dongles do not allow for DDC control on Macs, only software dimming is available for these connections.
+Notable exceptions for hardware control compatibility:
+
+- Some displays (notably EIZO) use MCCS over USB or an entirely custom protocol for control. These displays are supported with software dimming only.
+- The HDMI port of the 2018 Intel Mac mini and 2020 M1 Mac mini prohibit DDC communication. Software control is still available. We recommend connecting the display via the USB-C port (USB-C to HDMI dongles usually work).
+- DisplayLink docks and dongles do not allow for DDC control on Macs, only software dimming is available for these connections.
+
+Note to f.lux users - please activate `Avoid gamma table manipulation` under `Preferences` » `Displays`! This step is not needed if you use Night Shift.
## How to help
-Open [issues](https://github.com/MonitorControl/MonitorControl/issues) if you have a question, an enhancement to suggest or a bug you've found. If you want, you can fork the code yourself and submit a pull request to improve the app.
+- You can greatly help out [by financing the project with your donation or by being a Sponsor](https://opencollective.com/monitorcontrol)!
+- Open [issues](https://github.com/MonitorControl/MonitorControl/issues) if you have a question, an enhancement to suggest or a bug you've found.
+- If you want, you can fork the code yourself and submit a pull request to improve the app (Note: accepting a PR is solely in the collective hands of the maintainers).
## Localizations