mirror of
https://github.com/MonitorControl/MonitorControl.git
synced 2026-05-15 14:15:55 -06:00
* Change icon order * Added option for minimum brightness, fixed text for external display brightness control (+ updated all translations) * Fix cumulative darkening issue upon toggling Disable dimming as fallback when software brightness is not 100% * Further improvement. * New 'Avoid gammatable manipulation' option for coexistence with f.lux in software mode. * Fix some translation errors * Fix signing * Make sure that key repeat speed for custom shortcuts do not go below a certain threshold.
505 lines
24 KiB
Swift
505 lines
24 KiB
Swift
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
|
|
|
|
import AVFoundation
|
|
import Cocoa
|
|
import IOKit
|
|
import os.log
|
|
|
|
class OtherDisplay: Display {
|
|
var volumeSliderHandler: SliderHandler?
|
|
var contrastSliderHandler: SliderHandler?
|
|
var ddc: IntelDDC?
|
|
var arm64ddc: Bool = false
|
|
var arm64avService: IOAVService?
|
|
var isDiscouraged: Bool = false
|
|
let DDC_MAX_DETECT_LIMIT: Int = 100
|
|
private var audioPlayer: AVAudioPlayer?
|
|
var pollingCount: Int {
|
|
get {
|
|
switch self.readPrefAsInt(key: .pollingMode) {
|
|
case PollingMode.none.rawValue: return 0
|
|
case PollingMode.minimal.rawValue: return 1
|
|
case PollingMode.normal.rawValue: return 5
|
|
case PollingMode.heavy.rawValue: return 20
|
|
case PollingMode.custom.rawValue: return prefs.integer(forKey: PrefKey.pollingCount.rawValue + self.prefsId)
|
|
default: return PollingMode.none.rawValue
|
|
}
|
|
}
|
|
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)
|
|
if !isVirtual, !Arm64DDC.isArm64 {
|
|
self.ddc = IntelDDC(for: identifier)
|
|
}
|
|
}
|
|
|
|
func processCurrentDDCValue(read: Bool, command: Command, firstrun: Bool, currentDDCValue: UInt16) {
|
|
if read {
|
|
var currentValue = self.convDDCToValue(for: command, from: currentDDCValue)
|
|
if !prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue), command == .brightness {
|
|
os_log("- Combined brightness mapping on DDC data.", type: .info)
|
|
if currentValue > 0 {
|
|
currentValue = self.combinedBrightnessSwitchingValue() + currentValue * (1 - self.combinedBrightnessSwitchingValue())
|
|
} else if currentValue == 0, firstrun {
|
|
currentValue = self.combinedBrightnessSwitchingValue()
|
|
} else if self.prefExists(for: command), self.readPrefAsFloat(for: command) <= self.combinedBrightnessSwitchingValue() {
|
|
currentValue = self.readPrefAsFloat(for: command)
|
|
} else {
|
|
currentValue = self.combinedBrightnessSwitchingValue()
|
|
}
|
|
}
|
|
self.savePref(currentValue, for: command)
|
|
if command == .brightness {
|
|
self.smoothBrightnessTransient = currentValue
|
|
}
|
|
} else {
|
|
var currentValue: Float = self.readPrefAsFloat(for: command)
|
|
if !prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue), command == .brightness {
|
|
os_log("- Combined brightness mapping on saved data.", type: .info)
|
|
if !self.prefExists(for: command) {
|
|
currentValue = self.combinedBrightnessSwitchingValue() + self.convDDCToValue(for: command, from: currentDDCValue) * (1 - self.combinedBrightnessSwitchingValue())
|
|
} else if firstrun, currentValue < self.combinedBrightnessSwitchingValue() {
|
|
currentValue = self.combinedBrightnessSwitchingValue()
|
|
}
|
|
} else {
|
|
currentValue = self.prefExists(for: command) ? self.readPrefAsFloat(for: command) : self.convDDCToValue(for: command, from: currentDDCValue)
|
|
}
|
|
self.savePref(currentValue, for: command)
|
|
if command == .brightness {
|
|
self.smoothBrightnessTransient = currentValue
|
|
}
|
|
}
|
|
}
|
|
|
|
func setupCurrentAndMaxValues(command: Command, firstrun: Bool = false) {
|
|
var ddcValues: (UInt16, UInt16)?
|
|
var maxDDCValue = UInt16(DDC_MAX_DETECT_LIMIT)
|
|
var currentDDCValue: UInt16
|
|
switch command {
|
|
case .audioSpeakerVolume: currentDDCValue = UInt16(Float(self.DDC_MAX_DETECT_LIMIT) * 0.125)
|
|
case .contrast: currentDDCValue = UInt16(Float(self.DDC_MAX_DETECT_LIMIT) * 0.750)
|
|
default: currentDDCValue = UInt16(Float(self.DDC_MAX_DETECT_LIMIT) * 1.000)
|
|
}
|
|
if command == .audioSpeakerVolume {
|
|
currentDDCValue = UInt16(Float(self.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.bool(forKey: PrefKey.enableDDCDuringStartup.rawValue), prefs.bool(forKey: PrefKey.readDDCInsteadOfRestoreValues.rawValue), self.pollingCount != 0, !app.safeMode {
|
|
os_log("- Reading DDC from display %{public}@ times", type: .info, String(self.pollingCount))
|
|
let delay = self.readPrefAsBool(key: .longerDelay) ? UInt64(40 * kMillisecondScale) : nil
|
|
ddcValues = self.readDDCValues(for: command, tries: UInt(self.pollingCount), minReplyDelay: delay)
|
|
if ddcValues != nil {
|
|
(currentDDCValue, maxDDCValue) = ddcValues ?? (currentDDCValue, maxDDCValue)
|
|
self.processCurrentDDCValue(read: true, command: command, firstrun: firstrun, currentDDCValue: currentDDCValue)
|
|
os_log("- DDC read successful.", type: .info)
|
|
} else {
|
|
os_log("- DDC read failed.", type: .info)
|
|
}
|
|
} else {
|
|
os_log("- DDC read disabled.", type: .info)
|
|
}
|
|
if self.readPrefAsInt(key: .maxDDCOverride, for: command) > self.readPrefAsInt(key: .minDDCOverride, for: command) {
|
|
self.savePref(self.readPrefAsInt(key: .maxDDCOverride, for: command), key: .maxDDC, for: command)
|
|
} else {
|
|
self.savePref(min(Int(maxDDCValue), self.DDC_MAX_DETECT_LIMIT), key: .maxDDC, for: command)
|
|
}
|
|
if ddcValues == nil {
|
|
self.processCurrentDDCValue(read: false, command: command, firstrun: firstrun, currentDDCValue: currentDDCValue)
|
|
currentDDCValue = self.convValueToDDC(for: command, from: (!prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue) && command == .brightness) ? max(0, self.readPrefAsFloat(for: command) - self.combinedBrightnessSwitchingValue()) * (1 / (1 - self.combinedBrightnessSwitchingValue())) : self.readPrefAsFloat(for: command))
|
|
}
|
|
os_log("- Current DDC value: %{public}@", type: .info, String(currentDDCValue))
|
|
os_log("- Minimum DDC value: %{public}@ (overrides 0)", type: .info, String(self.readPrefAsInt(key: .minDDCOverride, for: command)))
|
|
os_log("- Maximum DDC value: %{public}@ (overrides %{public}@)", type: .info, String(self.readPrefAsInt(key: .maxDDC, for: command)), String(maxDDCValue))
|
|
os_log("- Current internal value: %{public}@", type: .info, String(self.readPrefAsFloat(for: command)))
|
|
if prefs.bool(forKey: PrefKey.enableDDCDuringStartup.rawValue), !prefs.bool(forKey: PrefKey.readDDCInsteadOfRestoreValues.rawValue), !app.safeMode {
|
|
os_log("- Writing last saved DDC values.", type: .info, self.name, String(reflecting: command))
|
|
_ = self.writeDDCValues(command: command, value: currentDDCValue)
|
|
}
|
|
} else {
|
|
self.savePref(self.prefExists(for: command) ? self.readPrefAsFloat(for: command) : Float(1), for: command)
|
|
self.savePref(self.readPrefAsFloat(for: command), key: .SwBrightness)
|
|
self.brightnessSyncSourceValue = self.readPrefAsFloat(for: command)
|
|
self.smoothBrightnessTransient = self.readPrefAsFloat(for: command)
|
|
os_log("- Software controlled display current internal value: %{public}@", type: .info, String(self.readPrefAsFloat(for: command)))
|
|
}
|
|
if command == .audioSpeakerVolume {
|
|
self.setupMuteUnMute()
|
|
}
|
|
}
|
|
|
|
func setupMuteUnMute() {
|
|
guard !self.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume) else {
|
|
return
|
|
}
|
|
var currentMuteValue = self.readPrefAsInt(for: .audioMuteScreenBlank)
|
|
currentMuteValue = currentMuteValue == 0 ? 2 : currentMuteValue
|
|
var muteValues: (current: UInt16, max: UInt16)?
|
|
if self.readPrefAsBool(key: .enableMuteUnmute) {
|
|
if self.pollingCount != 0, !app.safeMode, prefs.bool(forKey: PrefKey.enableDDCDuringStartup.rawValue), prefs.bool(forKey: PrefKey.readDDCInsteadOfRestoreValues.rawValue) {
|
|
os_log("Reading DDC from display %{public}@ times for Mute", type: .info, String(self.pollingCount))
|
|
let delay = self.readPrefAsBool(key: .longerDelay) ? UInt64(40 * kMillisecondScale) : nil
|
|
muteValues = self.readDDCValues(for: .audioMuteScreenBlank, tries: UInt(self.pollingCount), minReplyDelay: delay)
|
|
if let muteValues = muteValues {
|
|
os_log("Success, current Mute setting: %{public}@", type: .info, String(muteValues.current))
|
|
currentMuteValue = Int(muteValues.current)
|
|
} else {
|
|
os_log("Mute read failed", type: .info)
|
|
}
|
|
}
|
|
if prefs.bool(forKey: PrefKey.enableDDCDuringStartup.rawValue), !prefs.bool(forKey: PrefKey.readDDCInsteadOfRestoreValues.rawValue), !app.safeMode {
|
|
os_log("Writing last saved DDC value for Mute: %{public}@", type: .info, String(currentMuteValue))
|
|
_ = self.writeDDCValues(command: .audioMuteScreenBlank, value: UInt16(currentMuteValue))
|
|
}
|
|
self.savePref(Int(currentMuteValue), for: .audioMuteScreenBlank)
|
|
}
|
|
}
|
|
|
|
func setupSliderCurrentValue(command: Command) -> Float {
|
|
return (command == .audioSpeakerVolume && self.readPrefAsBool(key: .enableMuteUnmute) && self.readPrefAsInt(for: .audioMuteScreenBlank) == 1) ? 0 : self.readPrefAsFloat(for: command)
|
|
}
|
|
|
|
func stepVolume(isUp: Bool, isSmallIncrement: Bool) {
|
|
guard !self.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume) else {
|
|
OSDUtils.showOsdVolumeDisabled(displayID: self.identifier)
|
|
return
|
|
}
|
|
let currentValue = self.readPrefAsFloat(for: .audioSpeakerVolume)
|
|
var muteValue: Int?
|
|
let volumeOSDValue = self.calcNewValue(currentValue: currentValue, isUp: isUp, isSmallIncrement: isSmallIncrement)
|
|
if self.readPrefAsInt(for: .audioMuteScreenBlank) == 1, volumeOSDValue > 0 {
|
|
muteValue = 2
|
|
} else if self.readPrefAsInt(for: .audioMuteScreenBlank) != 1, volumeOSDValue == 0 {
|
|
muteValue = 1
|
|
}
|
|
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.savePref(muteValue, for: .audioMuteScreenBlank)
|
|
}
|
|
if !self.readPrefAsBool(key: .enableMuteUnmute) || volumeOSDValue != 0 {
|
|
_ = self.writeDDCValues(command: .audioSpeakerVolume, value: self.convValueToDDC(for: .audioSpeakerVolume, from: volumeOSDValue))
|
|
}
|
|
}
|
|
if !self.readPrefAsBool(key: .hideOsd) {
|
|
OSDUtils.showOsd(displayID: self.identifier, command: .audioSpeakerVolume, value: volumeOSDValue, roundChiclet: !isSmallIncrement)
|
|
}
|
|
if !isAlreadySet {
|
|
self.savePref(volumeOSDValue, for: .audioSpeakerVolume)
|
|
if let slider = self.sliderHandler[.audioSpeakerVolume] {
|
|
slider.setValue(volumeOSDValue, displayID: self.identifier)
|
|
}
|
|
}
|
|
}
|
|
|
|
func stepContrast(isUp: Bool, isSmallIncrement: Bool) {
|
|
guard !self.readPrefAsBool(key: .unavailableDDC, for: .contrast), !self.isSw() else {
|
|
return
|
|
}
|
|
let currentValue = self.readPrefAsFloat(for: .contrast)
|
|
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))
|
|
}
|
|
OSDUtils.showOsd(displayID: self.identifier, command: .contrast, value: contrastOSDValue, roundChiclet: !isSmallIncrement)
|
|
if !isAlreadySet {
|
|
self.savePref(contrastOSDValue, for: .contrast)
|
|
if let slider = self.sliderHandler[.contrast] {
|
|
slider.setValue(contrastOSDValue, displayID: self.identifier)
|
|
}
|
|
}
|
|
}
|
|
|
|
func toggleMute(fromVolumeSlider: Bool = false) {
|
|
guard !self.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume) else {
|
|
OSDUtils.showOsdMuteDisabled(displayID: self.identifier)
|
|
return
|
|
}
|
|
var muteValue: Int
|
|
var volumeOSDValue: Float
|
|
if self.readPrefAsInt(for: .audioMuteScreenBlank) != 1 {
|
|
muteValue = 1
|
|
volumeOSDValue = 0
|
|
} else {
|
|
muteValue = 2
|
|
volumeOSDValue = self.readPrefAsFloat(for: .audioSpeakerVolume)
|
|
// The volume that will be set immediately after setting unmute while the old set volume was 0 is unpredictable. Hence, just set it to a single filled chiclet
|
|
if volumeOSDValue == 0 {
|
|
volumeOSDValue = 1 / OSDUtils.chicletCount
|
|
self.savePref(volumeOSDValue, for: .audioSpeakerVolume)
|
|
}
|
|
}
|
|
if self.readPrefAsBool(key: .enableMuteUnmute) {
|
|
guard self.writeDDCValues(command: .audioMuteScreenBlank, value: UInt16(muteValue)) == true else {
|
|
return
|
|
}
|
|
}
|
|
self.savePref(muteValue, for: .audioMuteScreenBlank)
|
|
if !self.readPrefAsBool(key: .enableMuteUnmute) || volumeOSDValue > 0 {
|
|
_ = self.writeDDCValues(command: .audioSpeakerVolume, value: self.convValueToDDC(for: .audioSpeakerVolume, from: volumeOSDValue))
|
|
}
|
|
if !fromVolumeSlider {
|
|
if !self.readPrefAsBool(key: .hideOsd) {
|
|
OSDUtils.showOsd(displayID: self.identifier, command: volumeOSDValue > 0 ? .audioSpeakerVolume : .audioMuteScreenBlank, value: volumeOSDValue, roundChiclet: true)
|
|
}
|
|
if let slider = self.sliderHandler[.audioSpeakerVolume] {
|
|
slider.setValue(volumeOSDValue)
|
|
}
|
|
}
|
|
}
|
|
|
|
func isSwOnly() -> Bool {
|
|
return (!self.arm64ddc && self.ddc == nil) || self.isVirtual
|
|
}
|
|
|
|
func isSw() -> Bool {
|
|
if prefs.bool(forKey: PrefKey.forceSw.rawValue + self.prefsId) || self.isSwOnly() {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
let swAfterOsdAnimationSemaphore = DispatchSemaphore(value: 1)
|
|
var lastAnimationStartedTime: CFTimeInterval = CACurrentMediaTime()
|
|
func doSwAfterOsdAnimation() {
|
|
self.lastAnimationStartedTime = CACurrentMediaTime()
|
|
DispatchQueue.global(qos: .userInteractive).async {
|
|
self.swAfterOsdAnimationSemaphore.wait()
|
|
guard CACurrentMediaTime() < self.lastAnimationStartedTime + 0.05 else {
|
|
self.swAfterOsdAnimationSemaphore.signal()
|
|
return
|
|
}
|
|
for value: Int in stride(from: 1, to: 6, by: 1) {
|
|
guard self.readPrefAsFloat(for: .brightness) <= self.combinedBrightnessSwitchingValue() else {
|
|
self.swAfterOsdAnimationSemaphore.signal()
|
|
return
|
|
}
|
|
OSDUtils.showOsd(displayID: self.identifier, command: .brightness, value: Float(value), maxValue: 100, roundChiclet: false)
|
|
Thread.sleep(forTimeInterval: Double(value * 2) / 300)
|
|
}
|
|
for value: Int in stride(from: 5, to: 0, by: -1) {
|
|
guard self.readPrefAsFloat(for: .brightness) <= self.combinedBrightnessSwitchingValue() else {
|
|
self.swAfterOsdAnimationSemaphore.signal()
|
|
return
|
|
}
|
|
OSDUtils.showOsd(displayID: self.identifier, command: .brightness, value: Float(value), maxValue: 100, roundChiclet: false)
|
|
Thread.sleep(forTimeInterval: Double(value * 2) / 300)
|
|
}
|
|
OSDUtils.showOsd(displayID: self.identifier, command: .brightness, value: 0, roundChiclet: true)
|
|
self.swAfterOsdAnimationSemaphore.signal()
|
|
}
|
|
}
|
|
|
|
override func stepBrightness(isUp: Bool, isSmallIncrement: Bool) {
|
|
if self.isSw() {
|
|
if !prefs.bool(forKey: PrefKey.disableSoftwareFallback.rawValue) {
|
|
super.stepBrightness(isUp: isUp, isSmallIncrement: isSmallIncrement)
|
|
}
|
|
return
|
|
}
|
|
guard !self.readPrefAsBool(key: .unavailableDDC, for: .brightness) else {
|
|
return
|
|
}
|
|
let currentValue = self.readPrefAsFloat(for: .brightness)
|
|
var osdValue: Float = 1
|
|
if !prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue), prefs.bool(forKey: PrefKey.separateCombinedScale.rawValue) {
|
|
osdValue = self.calcNewValue(currentValue: currentValue, isUp: isUp, isSmallIncrement: isSmallIncrement, half: true)
|
|
_ = self.setBrightness(osdValue)
|
|
if osdValue > self.combinedBrightnessSwitchingValue() {
|
|
OSDUtils.showOsd(displayID: self.identifier, command: .brightness, value: osdValue - self.combinedBrightnessSwitchingValue(), maxValue: self.combinedBrightnessSwitchingValue(), roundChiclet: !isSmallIncrement)
|
|
} else {
|
|
self.doSwAfterOsdAnimation()
|
|
}
|
|
} else {
|
|
osdValue = self.calcNewValue(currentValue: currentValue, isUp: isUp, isSmallIncrement: isSmallIncrement)
|
|
_ = self.setBrightness(osdValue)
|
|
OSDUtils.showOsd(displayID: self.identifier, command: .brightness, value: osdValue, roundChiclet: !isSmallIncrement)
|
|
}
|
|
if let slider = self.sliderHandler[.brightness] {
|
|
slider.setValue(osdValue, displayID: self.identifier)
|
|
self.brightnessSyncSourceValue = osdValue
|
|
}
|
|
}
|
|
|
|
override func setDirectBrightness(_ to: Float, transient: Bool = false) -> Bool {
|
|
let value = max(min(to, 1), 0)
|
|
if !self.isSw() {
|
|
if !prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue) {
|
|
var brightnessValue: Float = 0
|
|
var brightnessSwValue: Float = 1
|
|
if value >= self.combinedBrightnessSwitchingValue() {
|
|
brightnessValue = (value - self.combinedBrightnessSwitchingValue()) * (1 / (1 - self.combinedBrightnessSwitchingValue()))
|
|
brightnessSwValue = 1
|
|
} else {
|
|
brightnessValue = 0
|
|
brightnessSwValue = (value / self.combinedBrightnessSwitchingValue())
|
|
}
|
|
_ = self.writeDDCValues(command: .brightness, value: self.convValueToDDC(for: .brightness, from: brightnessValue))
|
|
_ = self.setSwBrightness(brightnessSwValue)
|
|
} else {
|
|
_ = self.writeDDCValues(command: .brightness, value: self.convValueToDDC(for: .brightness, from: value))
|
|
}
|
|
if !transient {
|
|
self.savePref(value, for: .brightness)
|
|
self.smoothBrightnessTransient = value
|
|
}
|
|
} else {
|
|
_ = super.setDirectBrightness(to, transient: transient)
|
|
}
|
|
return true
|
|
}
|
|
|
|
override func getBrightness() -> Float {
|
|
return self.prefExists(for: .brightness) ? self.readPrefAsFloat(for: .brightness) : 1
|
|
}
|
|
|
|
func getRemapControlCodes(command: Command) -> [UInt8] {
|
|
let codes = self.readPrefAsString(key: PrefKey.remapDDC, for: command).components(separatedBy: ",")
|
|
var intCodes: [UInt8] = []
|
|
for code in codes {
|
|
let trimmedCode = code.trimmingCharacters(in: CharacterSet(charactersIn: " "))
|
|
if !trimmedCode.isEmpty, let intCode = UInt8(trimmedCode, radix: 16), intCode != 0 {
|
|
intCodes.append(intCode)
|
|
}
|
|
}
|
|
return intCodes
|
|
}
|
|
|
|
public func writeDDCValues(command: Command, value: UInt16, errorRecoveryWaitTime _: UInt32? = nil) -> Bool? {
|
|
guard app.sleepID == 0, app.reconfigureID == 0, !self.readPrefAsBool(key: .forceSw), !self.readPrefAsBool(key: .unavailableDDC, for: command) else {
|
|
return false
|
|
}
|
|
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
|
|
}
|
|
}
|
|
}
|
|
return success
|
|
}
|
|
|
|
func readDDCValues(for command: Command, tries: UInt, minReplyDelay delay: UInt64?) -> (current: UInt16, max: UInt16)? {
|
|
var values: (UInt16, UInt16)?
|
|
guard app.sleepID == 0, app.reconfigureID == 0, !self.readPrefAsBool(key: .forceSw), !self.readPrefAsBool(key: .unavailableDDC, for: command) else {
|
|
return values
|
|
}
|
|
let controlCodes = self.getRemapControlCodes(command: command)
|
|
let controlCode = controlCodes.count == 0 ? command.rawValue : controlCodes[0]
|
|
if Arm64DDC.isArm64 {
|
|
guard self.arm64ddc else {
|
|
return nil
|
|
}
|
|
DisplayManager.shared.ddcQueue.sync {
|
|
if let unwrappedDelay = delay {
|
|
values = Arm64DDC.read(service: self.arm64avService, command: controlCode, tries: UInt8(min(tries, 255)), minReplyDelay: UInt32(unwrappedDelay / 1000))
|
|
} else {
|
|
values = Arm64DDC.read(service: self.arm64avService, command: controlCode, tries: UInt8(min(tries, 255)))
|
|
}
|
|
}
|
|
} else {
|
|
DisplayManager.shared.ddcQueue.sync {
|
|
values = self.ddc?.read(command: controlCode, tries: tries, minReplyDelay: delay)
|
|
}
|
|
}
|
|
return values
|
|
}
|
|
|
|
func calcNewValue(currentValue: Float, isUp: Bool, isSmallIncrement: Bool, half: Bool = false) -> Float {
|
|
let nextValue: Float
|
|
if isSmallIncrement {
|
|
nextValue = currentValue + (isUp ? 0.01 : -0.01)
|
|
} else {
|
|
let osdChicletFromValue = OSDUtils.chiclet(fromValue: currentValue, maxValue: 1, half: half)
|
|
let distance = OSDUtils.getDistance(fromNearestChiclet: osdChicletFromValue)
|
|
var nextFilledChiclet = isUp ? ceil(osdChicletFromValue) : floor(osdChicletFromValue)
|
|
let distanceThreshold: Float = 0.25 // 25% of the distance between the edges of an osd box
|
|
if distance == 0 {
|
|
nextFilledChiclet += (isUp ? 1 : -1)
|
|
} else if !isUp, distance < distanceThreshold {
|
|
nextFilledChiclet -= 1
|
|
} else if isUp, distance > (1 - distanceThreshold) {
|
|
nextFilledChiclet += 1
|
|
}
|
|
nextValue = OSDUtils.value(fromChiclet: nextFilledChiclet, maxValue: 1, half: half)
|
|
}
|
|
return max(0, min(1, nextValue))
|
|
}
|
|
|
|
func getCurveMultiplier(_ curveDDC: Int) -> Float {
|
|
switch curveDDC {
|
|
case 1: return 0.6
|
|
case 2: return 0.7
|
|
case 3: return 0.8
|
|
case 4: return 0.9
|
|
case 6: return 1.3
|
|
case 7: return 1.5
|
|
case 8: return 1.7
|
|
case 9: return 1.88
|
|
default: return 1.0
|
|
}
|
|
}
|
|
|
|
func convValueToDDC(for command: Command, from: Float) -> UInt16 {
|
|
var value = from
|
|
if self.readPrefAsBool(key: .invertDDC, for: command) {
|
|
value = 1 - value
|
|
}
|
|
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))
|
|
let curvedValue = pow(max(min(value, 1), 0), curveMultiplier)
|
|
let deNormalizedValue = (maxDDCValue - minDDCValue) * curvedValue + minDDCValue
|
|
var intDDCValue = UInt16(min(max(deNormalizedValue, minDDCValue), maxDDCValue))
|
|
if from > 0, command == Command.audioSpeakerVolume {
|
|
intDDCValue = max(1, intDDCValue) // Never let sound to mute accidentally, keep it digitally to at digital 1 if needed as muting breaks some displays
|
|
}
|
|
return intDDCValue
|
|
}
|
|
|
|
func convDDCToValue(for command: Command, from: UInt16) -> Float {
|
|
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))
|
|
let normalizedValue = ((min(max(Float(from), minDDCValue), maxDDCValue) - minDDCValue) / (maxDDCValue - minDDCValue))
|
|
let deCurvedValue = pow(normalizedValue, 1.0 / curveMultiplier)
|
|
var value = deCurvedValue
|
|
if self.readPrefAsBool(key: .invertDDC, for: command) {
|
|
value = 1 - value
|
|
}
|
|
return max(min(value, 1), 0)
|
|
}
|
|
|
|
func playVolumeChangedSound() {
|
|
guard let preferences = app.getSystemPreferences(), let hasSoundEnabled = preferences["com.apple.sound.beep.feedback"] as? Int, hasSoundEnabled == 1 else {
|
|
return
|
|
}
|
|
do {
|
|
self.audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: "/System/Library/LoginPlugins/BezelServices.loginPlugin/Contents/Resources/volume.aiff"))
|
|
self.audioPlayer?.volume = 1
|
|
self.audioPlayer?.play()
|
|
} catch {
|
|
os_log("%{public}@", type: .error, error.localizedDescription)
|
|
}
|
|
}
|
|
|
|
func combinedBrightnessSwitchingValue() -> Float {
|
|
return Float(self.readPrefAsInt(key: .combinedBrightnessSwitchingPoint) + 8) / 16
|
|
}
|
|
}
|