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
2 changes: 1 addition & 1 deletion MonitorControl/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>6828</string>
<string>6850</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
Expand Down
16 changes: 14 additions & 2 deletions MonitorControl/Model/AppleDisplay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,34 @@ import os.log
class AppleDisplay: Display {
private var displayQueue: DispatchQueue

override init(_ identifier: CGDirectDisplayID, name: String, vendorNumber: UInt32?, modelNumber: UInt32?, isVirtual: Bool = false) {
override init(_ identifier: CGDirectDisplayID, name: String, vendorNumber: UInt32?, modelNumber: UInt32?, isVirtual: Bool = false, isDummy: Bool = false) {
self.displayQueue = DispatchQueue(label: String("displayQueue-\(identifier)"))
super.init(identifier, name: name, vendorNumber: vendorNumber, modelNumber: modelNumber, isVirtual: isVirtual)
super.init(identifier, name: name, vendorNumber: vendorNumber, modelNumber: modelNumber, isVirtual: isVirtual, isDummy: isDummy)
}

public func getAppleBrightness() -> Float {
guard !self.isDummy else {
return 1
}
var brightness: Float = 0
DisplayServicesGetBrightness(self.identifier, &brightness)
return brightness
}

public func setAppleBrightness(value: Float) {
guard !self.isDummy else {
return
}
self.displayQueue.sync {
DisplayServicesSetBrightness(self.identifier, value)
DisplayServicesBrightnessChanged(self.identifier, Double(value))
}
}

override func setDirectBrightness(_ to: Float, transient: Bool = false) -> Bool {
guard !self.isDummy else {
return false
}
let value = max(min(to, 1), 0)
self.setAppleBrightness(value: value)
if !transient {
Expand All @@ -36,6 +45,9 @@ class AppleDisplay: Display {
}

override func getBrightness() -> Float {
guard !self.isDummy else {
return 1
}
if self.prefExists(for: .brightness) {
return self.readPrefAsFloat(for: .brightness)
} else {
Expand Down
28 changes: 22 additions & 6 deletions MonitorControl/Model/Display.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Display: Equatable {
var sliderHandler: [Command: SliderHandler] = [:]
var brightnessSyncSourceValue: Float = 1
var isVirtual: Bool = false
var isDummy: Bool = false

var defaultGammaTableRed = [CGGammaValue](repeating: 0, count: 256)
var defaultGammaTableGreen = [CGGammaValue](repeating: 0, count: 256)
Expand Down Expand Up @@ -61,21 +62,22 @@ class Display: Equatable {
return (key ?? PrefKey.value).rawValue + (command != nil ? String((command ?? Command.none).rawValue) : "") + self.prefsId
}

internal init(_ identifier: CGDirectDisplayID, name: String, vendorNumber: UInt32?, modelNumber: UInt32?, isVirtual: Bool = false) {
internal init(_ identifier: CGDirectDisplayID, name: String, vendorNumber: UInt32?, modelNumber: UInt32?, isVirtual: Bool = false, isDummy: Bool = false) {
self.identifier = identifier
self.name = name
self.vendorNumber = vendorNumber
self.modelNumber = modelNumber
self.prefsId = "(" + String(name.filter { !$0.isWhitespace }) + String(vendorNumber ?? 0) + String(modelNumber ?? 0) + "@" + String(identifier) + ")"
os_log("Display init with prefsIdentifier %{public}@", type: .info, self.prefsId)
self.isVirtual = DEBUG_VIRTUAL ? true : isVirtual
self.isDummy = isDummy
self.swUpdateDefaultGammaTable()
self.smoothBrightnessTransient = self.getBrightness()
if self.isVirtual {
os_log("Creating or updating shade for virtual display %{public}@", type: .info, String(self.identifier))
if self.isVirtual || self.readPrefAsBool(key: PrefKey.avoidGamma), !self.isDummy {
os_log("Creating or updating shade for display %{public}@", type: .info, String(self.identifier))
_ = DisplayManager.shared.updateShade(displayID: self.identifier)
} else {
os_log("Destroying shade (if exists) for real display %{public}@", type: .info, String(self.identifier))
os_log("Destroying shade (if exists) for display %{public}@", type: .info, String(self.identifier))
_ = DisplayManager.shared.destroyShade(displayID: self.identifier)
}
self.brightnessSyncSourceValue = self.getBrightness()
Expand Down Expand Up @@ -187,6 +189,9 @@ class Display: Equatable {
}

func swUpdateDefaultGammaTable() {
guard !self.isDummy else {
return
}
CGGetDisplayTransferByTable(self.identifier, 256, &self.defaultGammaTableRed, &self.defaultGammaTableGreen, &self.defaultGammaTableBlue, &self.defaultGammaTableSampleCount)
let redPeak = self.defaultGammaTableRed.max() ?? 0
let greenPeak = self.defaultGammaTableGreen.max() ?? 0
Expand All @@ -210,6 +215,10 @@ class Display: Equatable {
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)
Expand Down Expand Up @@ -249,6 +258,13 @@ class Display: Equatable {
}

func getSwBrightness() -> Float {
guard !self.isDummy else {
if self.prefExists(key: .SwBrightness) {
return self.readPrefAsFloat(key: .SwBrightness)
} else {
return 1
}
}
self.swBrightnessSemaphore.wait()
if self.isVirtual || self.readPrefAsBool(key: .avoidGamma) {
let rawBrightnessValue = 1 - (DisplayManager.shared.getShadeAlpha(displayID: self.identifier) ?? 1)
Expand All @@ -274,7 +290,7 @@ class Display: Equatable {

func checkGammaInterference() {
let currentSwBrightness = self.getSwBrightness()
guard !DisplayManager.shared.gammaInterferenceWarningShown, !(prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue)), !self.readPrefAsBool(key: .avoidGamma), !self.isVirtual, !self.smoothBrightnessRunning, self.prefExists(key: .SwBrightness), abs(currentSwBrightness - self.readPrefAsFloat(key: .SwBrightness)) > 0.02 else {
guard !self.isDummy, !DisplayManager.shared.gammaInterferenceWarningShown, !(prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue)), !self.readPrefAsBool(key: .avoidGamma), !self.isVirtual, !self.smoothBrightnessRunning, self.prefExists(key: .SwBrightness), abs(currentSwBrightness - self.readPrefAsFloat(key: .SwBrightness)) > 0.02 else {
return
}
DisplayManager.shared.gammaInterferenceCounter += 1
Expand Down Expand Up @@ -309,7 +325,7 @@ class Display: Equatable {
}

func isSwBrightnessNotDefault() -> Bool {
guard !self.isVirtual else {
guard !self.isVirtual, !self.isDummy else {
return false
}
if self.getSwBrightness() < 1 {
Expand Down
6 changes: 3 additions & 3 deletions MonitorControl/Model/OtherDisplay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ class OtherDisplay: Display {
set { prefs.set(newValue, forKey: PrefKey.pollingCount.rawValue + self.prefsId) }
}

override init(_ identifier: CGDirectDisplayID, name: String, vendorNumber: UInt32?, modelNumber: UInt32?, isVirtual: Bool = false) {
super.init(identifier, name: name, vendorNumber: vendorNumber, modelNumber: modelNumber, isVirtual: isVirtual)
override init(_ identifier: CGDirectDisplayID, name: String, vendorNumber: UInt32?, modelNumber: UInt32?, isVirtual: Bool = false, isDummy: Bool = false) {
super.init(identifier, name: name, vendorNumber: vendorNumber, modelNumber: modelNumber, isVirtual: isVirtual, isDummy: isDummy)
if !isVirtual, !Arm64DDC.isArm64 {
self.ddc = IntelDDC(for: identifier)
}
Expand Down Expand Up @@ -256,7 +256,7 @@ class OtherDisplay: Display {
}

func isSwOnly() -> Bool {
return (!self.arm64ddc && self.ddc == nil) || self.isVirtual
return (!self.arm64ddc && self.ddc == nil) || self.isVirtual || self.isDummy
}

func isSw() -> Bool {
Expand Down
6 changes: 3 additions & 3 deletions MonitorControl/Support/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
NotificationCenter.default.addObserver(self, selector: #selector(self.audioDeviceChanged), name: Notification.Name.defaultOutputDeviceChanged, object: nil) // subscribe Audio output detector (SimplyCoreAudio)
DistributedNotificationCenter.default.addObserver(self, selector: #selector(self.displayReconfigured), 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.wakeNotification), name: NSWorkspace.screensDidWakeNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.sleepNotification), name: NSWorkspace.willSleepNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.wakeNotofication), name: NSWorkspace.didWakeNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.wakeNotification), name: NSWorkspace.didWakeNotification, object: nil)
_ = DistributedNotificationCenter.default().addObserver(forName: NSNotification.Name(rawValue: NSNotification.Name.accessibilityApi.rawValue), object: nil, queue: nil) { _ in DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.updateMediaKeyTap() } } // listen for accessibility status changes
}

Expand All @@ -179,7 +179,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
os_log("Sleeping with sleep %{public}@", type: .info, String(self.sleepID))
}

@objc private func wakeNotofication() {
@objc private func wakeNotification() {
if self.sleepID != 0 {
os_log("Waking up from sleep %{public}@", type: .info, String(self.sleepID))
let dispatchedSleepID = self.sleepID
Expand Down
4 changes: 4 additions & 0 deletions MonitorControl/Support/Arm64DDC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,10 @@ class Arm64DDC: NSObject {
if ioregService.manufacturerID == "AOC", ioregService.productName == "28E850" {
return true
}
// If the display contains the string "Dummy", then it is highly suspicious
if ioregService.productName.contains("Dummy") || ioregService.productName.contains("dummy") {
return true
}
// First service location of Mac Mini HDMI is broken for DDC communication
if ioregService.transportDownstream == "HDMI", ioregService.serviceLocation == 1, modelIdentifier == "Macmini9,1" {
return true
Expand Down
17 changes: 12 additions & 5 deletions MonitorControl/Support/DisplayManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,27 +151,34 @@ 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
}
}
}
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)
os_log("Apple display found - %{public}@", type: .info, "ID: \(appleDisplay.identifier) Name: \(appleDisplay.name) (Vendor: \(appleDisplay.vendorNumber ?? 0), Model: \(appleDisplay.modelNumber ?? 0))")
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))")
self.addDisplay(display: appleDisplay)
} else {
let otherDisplay = OtherDisplay(id, name: name, vendorNumber: vendorNumber, modelNumber: modelNumber, isVirtual: isVirtual)
os_log("Other display found - %{public}@", type: .info, "ID: \(otherDisplay.identifier) Name: \(otherDisplay.name) (Vendor: \(otherDisplay.vendorNumber ?? 0), Model: \(otherDisplay.modelNumber ?? 0))")
let otherDisplay = OtherDisplay(id, name: name, vendorNumber: vendorNumber, modelNumber: modelNumber, isVirtual: isVirtual, isDummy: isDummy)
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)
}
}
Expand Down Expand Up @@ -351,7 +358,7 @@ class DisplayManager {
}
}

func getAffectedDisplays(isBrightness: Bool = false, isVolume: Bool = false, isContrast _: Bool = false) -> [Display]? {
func getAffectedDisplays(isBrightness: Bool = false, isVolume: Bool = false) -> [Display]? {
var affectedDisplays: [Display]
let allDisplays = self.getAllDisplays()
var currentDisplay: Display?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ class DisplaysPrefsViewController: NSViewController, PreferencePane, NSTableView

override func viewDidLoad() {
super.viewDidLoad()
self.showAdvancedDisplays.state = prefs.bool(forKey: PrefKey.showAdvancedSettings.rawValue) ? .on : .off
self.loadDisplayList()
self.displayScrollView.scrollerStyle = .legacy
self.populateSettings()
self.loadDisplayList()
}

func populateSettings() {
self.showAdvancedDisplays.state = prefs.bool(forKey: PrefKey.showAdvancedSettings.rawValue) ? .on : .off
}

override func viewWillAppear() {
Expand Down Expand Up @@ -88,12 +92,12 @@ class DisplaysPrefsViewController: NSViewController, PreferencePane, NSTableView
var displayImage = "display.trianglebadge.exclamationmark"
var controlMethod = NSLocalizedString("No Control", comment: "Shown in the Display Preferences") + " ⚠️"
var controlStatus = NSLocalizedString("This display has an unspecified control status.", comment: "Shown in the Display Preferences")
if display.isVirtual {
if display.isVirtual, !display.isDummy {
displayType = NSLocalizedString("Virtual Display", comment: "Shown in the Display Preferences")
displayImage = "tv.and.mediabox"
controlMethod = NSLocalizedString("Software (shade)", comment: "Shown in the Display Preferences") + " ⚠️"
controlStatus = NSLocalizedString("This is a virtual display (examples: AirPlay, Sidecar, display connected via a DisplayLink Dock or similar) which does not allow hardware or software gammatable control. Shading is used as a substitute but only in non-mirror scenarios. Mouse cursor will be unaffected and artifacts may appear when entering/leaving full screen mode.", comment: "Shown in the Display Preferences")
} else if display is OtherDisplay {
} else if display is OtherDisplay, !display.isDummy {
displayType = NSLocalizedString("External Display", comment: "Shown in the Display Preferences")
displayImage = "display"
if let otherDisplay: OtherDisplay = display as? OtherDisplay {
Expand All @@ -119,7 +123,7 @@ class DisplaysPrefsViewController: NSViewController, PreferencePane, NSTableView
}
}
}
} else if let appleDisplay: AppleDisplay = display as? AppleDisplay {
} else if !display.isDummy, let appleDisplay: AppleDisplay = display as? AppleDisplay {
if appleDisplay.isBuiltIn() {
displayType = NSLocalizedString("Built-in Display", comment: "Shown in the Display Preferences")
if self.isImac() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ class MainPrefsViewController: NSViewController, PreferencePane {
self.populateSettings()
menuslidersPrefsVc?.populateSettings()
keyboardPrefsVc?.populateSettings()
displaysPrefsVc?.populateSettings()
}
}

Expand Down
2 changes: 1 addition & 1 deletion MonitorControlHelper/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>6828</string>
<string>6850</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSBackgroundOnly</key>
Expand Down