From 5cacd25d1115d20bd22940dd288505222eb4c3fc Mon Sep 17 00:00:00 2001 From: Victor Chabbert Date: Sun, 20 Sep 2020 19:35:26 +0200 Subject: [PATCH] Fix non-standard volume/brightness scales not working properly (#245) --- MonitorControl.xcodeproj/project.pbxproj | 4 +++ MonitorControl/Info.plist | 2 +- MonitorControl/Model/Display.swift | 30 +++++++++++----- MonitorControl/Model/ExternalDisplay.swift | 41 ++++++++++++---------- MonitorControl/Support/OSDUtils.swift | 25 +++++++++++++ MonitorControlHelper/Info.plist | 2 +- 6 files changed, 76 insertions(+), 28 deletions(-) create mode 100644 MonitorControl/Support/OSDUtils.swift diff --git a/MonitorControl.xcodeproj/project.pbxproj b/MonitorControl.xcodeproj/project.pbxproj index 6583f34..ed6e39e 100644 --- a/MonitorControl.xcodeproj/project.pbxproj +++ b/MonitorControl.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ F0445D40200259C10025AE82 /* DisplayPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0445D3F200259C10025AE82 /* DisplayPrefsViewController.swift */; }; F06792EA200A73460066C438 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = F06792E9200A73460066C438 /* main.swift */; }; F06792F6200A745F0066C438 /* MonitorControlHelper.app in [Login] Copy Helper to start at Login */ = {isa = PBXBuildFile; fileRef = F06792E7200A73460066C438 /* MonitorControlHelper.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + FE4E0896249D584C003A50BB /* OSDUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE4E0895249D584C003A50BB /* OSDUtils.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -163,6 +164,7 @@ F06792F0200A73470066C438 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; F06792F1200A73470066C438 /* MonitorControlHelper.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MonitorControlHelper.entitlements; sourceTree = ""; }; F0A987D61F77B290009B603D /* OSD.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OSD.framework; sourceTree = ""; }; + FE4E0895249D584C003A50BB /* OSDUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSDUtils.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -291,6 +293,7 @@ F01B0683228221B6008E64DB /* Utils.swift */, 6C2EA1CC228F644B00060E3F /* OnlyIntegerValueFormatter.swift */, 6C2EA1CE228F7DFB00060E3F /* PollingMode.swift */, + FE4E0895249D584C003A50BB /* OSDUtils.swift */, ); path = Support; sourceTree = ""; @@ -531,6 +534,7 @@ F03FE4C0228DF62B001F59A4 /* FriendlyNameCellView.swift in Sources */, 6C2EA1CF228F7DFB00060E3F /* PollingMode.swift in Sources */, 6CBFE27C23DB27A200D1BC41 /* InternalDisplay.swift in Sources */, + FE4E0896249D584C003A50BB /* OSDUtils.swift in Sources */, 6CBFE27A23DB266000D1BC41 /* Display.swift in Sources */, F03A8DF21FFBAA6F0034DC27 /* ExternalDisplay.swift in Sources */, F0445D40200259C10025AE82 /* DisplayPrefsViewController.swift in Sources */, diff --git a/MonitorControl/Info.plist b/MonitorControl/Info.plist index 67248cf..d263750 100644 --- a/MonitorControl/Info.plist +++ b/MonitorControl/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 647 + 719 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/MonitorControl/Model/Display.swift b/MonitorControl/Model/Display.swift index 05af8bb..ba449e4 100644 --- a/MonitorControl/Model/Display.swift +++ b/MonitorControl/Model/Display.swift @@ -8,6 +8,7 @@ import DDC import Foundation +import os.log class Display { internal let identifier: CGDirectDisplayID @@ -42,7 +43,7 @@ class Display { return self.prefs.string(forKey: "friendlyName-\(self.identifier)") ?? self.name } - func showOsd(command: DDC.Command, value: Int, maxValue: Int = 100) { + func showOsd(command: DDC.Command, value: Int, maxValue: Int = 100, roundChiclet: Bool = false) { guard let manager = OSDManager.sharedManager() as? OSDManager else { return } @@ -59,12 +60,25 @@ class Display { osdImage = 1 } - manager.showImage(osdImage, - onDisplayID: self.identifier, - priority: 0x1F4, - msecUntilFade: 1000, - filledChiclets: UInt32(value), - totalChiclets: UInt32(maxValue), - locked: false) + if roundChiclet { + let osdChiclet = OSDUtils.chiclet(fromValue: Float(value), maxValue: Float(maxValue)) + let filledChiclets = round(osdChiclet) + + manager.showImage(osdImage, + onDisplayID: self.identifier, + priority: 0x1F4, + msecUntilFade: 1000, + filledChiclets: UInt32(filledChiclets), + totalChiclets: UInt32(16), + locked: false) + } else { + manager.showImage(osdImage, + onDisplayID: self.identifier, + priority: 0x1F4, + msecUntilFade: 1000, + filledChiclets: UInt32(value), + totalChiclets: UInt32(maxValue), + locked: false) + } } } diff --git a/MonitorControl/Model/ExternalDisplay.swift b/MonitorControl/Model/ExternalDisplay.swift index 279b906..b04fb8d 100644 --- a/MonitorControl/Model/ExternalDisplay.swift +++ b/MonitorControl/Model/ExternalDisplay.swift @@ -32,7 +32,6 @@ class ExternalDisplay: Display { } private var audioPlayer: AVAudioPlayer? - private let osdChicletBoxes: Float = 16 override init(_ identifier: CGDirectDisplayID, name: String, vendorNumber: UInt32?, modelNumber: UInt32?) { super.init(identifier, name: name, vendorNumber: vendorNumber, modelNumber: modelNumber) @@ -90,7 +89,7 @@ class ExternalDisplay: Display { if !fromVolumeSlider { self.hideDisplayOsd() - self.showOsd(command: volumeOSDValue > 0 ? .audioSpeakerVolume : .audioMuteScreenBlank, value: volumeOSDValue) + self.showOsd(command: volumeOSDValue > 0 ? .audioSpeakerVolume : .audioMuteScreenBlank, value: volumeOSDValue, roundChiclet: true) if volumeOSDValue > 0 { self.playVolumeChangedSound() @@ -106,7 +105,6 @@ class ExternalDisplay: Display { var muteValue: Int? let volumeOSDValue = self.calcNewValue(for: .audioSpeakerVolume, isUp: isUp, isSmallIncrement: isSmallIncrement) let volumeDDCValue = UInt16(volumeOSDValue) - if self.isMuted(), volumeOSDValue > 0 { muteValue = 2 } else if !self.isMuted(), volumeOSDValue == 0 { @@ -132,7 +130,7 @@ class ExternalDisplay: Display { } self.hideDisplayOsd() - self.showOsd(command: .audioSpeakerVolume, value: volumeOSDValue) + self.showOsd(command: .audioSpeakerVolume, value: volumeOSDValue, roundChiclet: !isSmallIncrement) if !isAlreadySet { self.saveValue(volumeOSDValue, for: .audioSpeakerVolume) @@ -163,7 +161,7 @@ class ExternalDisplay: Display { } } - self.showOsd(command: .brightness, value: osdValue) + self.showOsd(command: .brightness, value: osdValue, roundChiclet: !isSmallIncrement) if !isAlreadySet { if let slider = self.brightnessSliderHandler?.slider { @@ -221,25 +219,32 @@ class ExternalDisplay: Display { func calcNewValue(for command: DDC.Command, isUp: Bool, isSmallIncrement: Bool) -> Int { let currentValue = self.getValue(for: command) let nextValue: Int + let maxValue = Float(self.getMaxValue(for: command)) if isSmallIncrement { nextValue = currentValue + (isUp ? 1 : -1) } else { - let filledChicletBoxes = self.osdChicletBoxes * (Float(currentValue) / Float(self.getMaxValue(for: command))) + let osdChicletFromValue = OSDUtils.chiclet(fromValue: Float(currentValue), maxValue: maxValue) - var nextFilledChicletBoxes: Float - var filledChicletBoxesRel: Float = isUp ? 1 : -1 + let distance = OSDUtils.getDistance(fromNearestChiclet: osdChicletFromValue) + // get the next rounded chiclet + var nextFilledChiclet = isUp ? ceil(osdChicletFromValue) : floor(osdChicletFromValue) - // This is a workaround to ensure that if the user has set the value using a small step (that is, the current chiclet box isn't completely filled, - // the next regular up or down step will only fill or empty that chiclet, and not the next one as well - it only really works because the max value is 100 - if (isUp && ceil(filledChicletBoxes) - filledChicletBoxes > 0.15) || (!isUp && filledChicletBoxes - floor(filledChicletBoxes) > 0.15) { - filledChicletBoxesRel = 0 + // Depending on the direction, if the chiclet is above or below a certain threshold, we go to the next whole chiclet + 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 } - nextFilledChicletBoxes = isUp ? ceil(filledChicletBoxes + filledChicletBoxesRel) : floor(filledChicletBoxes + filledChicletBoxesRel) - nextValue = Int(Float(self.getMaxValue(for: command)) * (nextFilledChicletBoxes / self.osdChicletBoxes)) + nextValue = Int(round(OSDUtils.value(fromChiclet: nextFilledChiclet, maxValue: maxValue))) + + os_log("next: .value %{public}@/%{public}@, .osd %{public}@/%{public}@", type: .debug, String(nextValue), String(maxValue), String(nextFilledChiclet), String(OSDUtils.chicletCount)) } - return max(0, min(self.getMaxValue(for: command), Int(nextValue))) + return max(0, min(self.getMaxValue(for: command), nextValue)) } func getValue(for command: DDC.Command) -> Int { @@ -308,11 +313,11 @@ class ExternalDisplay: Display { } private func stepSize(for command: DDC.Command, isSmallIncrement: Bool) -> Int { - return isSmallIncrement ? 1 : Int(floor(Float(self.getMaxValue(for: command)) / self.osdChicletBoxes)) + return isSmallIncrement ? 1 : Int(floor(Float(self.getMaxValue(for: command)) / OSDUtils.chicletCount)) } - override func showOsd(command: DDC.Command, value: Int, maxValue _: Int = 100) { - super.showOsd(command: command, value: value, maxValue: self.getMaxValue(for: command)) + override func showOsd(command: DDC.Command, value: Int, maxValue _: Int = 100, roundChiclet: Bool = false) { + super.showOsd(command: command, value: value, maxValue: self.getMaxValue(for: command), roundChiclet: roundChiclet) } private func supportsMuteCommand() -> Bool { diff --git a/MonitorControl/Support/OSDUtils.swift b/MonitorControl/Support/OSDUtils.swift new file mode 100644 index 0000000..07ed83d --- /dev/null +++ b/MonitorControl/Support/OSDUtils.swift @@ -0,0 +1,25 @@ +// +// OSDUtils.swift +// MonitorControl +// +// Created by Victor Chabbert on 19/06/2020. +// Copyright © 2020 Guillaume Broder. All rights reserved. +// + +import Cocoa + +class OSDUtils: NSObject { + static let chicletCount: Float = 16 + + static func chiclet(fromValue value: Float, maxValue: Float) -> Float { + return (value * self.chicletCount) / maxValue + } + + static func value(fromChiclet chiclet: Float, maxValue: Float) -> Float { + return (chiclet * maxValue) / self.chicletCount + } + + static func getDistance(fromNearestChiclet chiclet: Float) -> Float { + return abs(chiclet.rounded(.towardZero) - chiclet) + } +} diff --git a/MonitorControlHelper/Info.plist b/MonitorControlHelper/Info.plist index ac901a1..02a73c7 100644 --- a/MonitorControlHelper/Info.plist +++ b/MonitorControlHelper/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 647 + 719 LSApplicationCategoryType public.app-category.utilities LSBackgroundOnly