Skip to content

Commit 5cacd25

Browse files
Fix non-standard volume/brightness scales not working properly (MonitorControl#245)
1 parent 9ff4a93 commit 5cacd25

File tree

6 files changed

+78
-30
lines changed

6 files changed

+78
-30
lines changed

MonitorControl.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
F0445D40200259C10025AE82 /* DisplayPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0445D3F200259C10025AE82 /* DisplayPrefsViewController.swift */; };
5252
F06792EA200A73460066C438 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = F06792E9200A73460066C438 /* main.swift */; };
5353
F06792F6200A745F0066C438 /* MonitorControlHelper.app in [Login] Copy Helper to start at Login */ = {isa = PBXBuildFile; fileRef = F06792E7200A73460066C438 /* MonitorControlHelper.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
54+
FE4E0896249D584C003A50BB /* OSDUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE4E0895249D584C003A50BB /* OSDUtils.swift */; };
5455
/* End PBXBuildFile section */
5556

5657
/* Begin PBXCopyFilesBuildPhase section */
@@ -163,6 +164,7 @@
163164
F06792F0200A73470066C438 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
164165
F06792F1200A73470066C438 /* MonitorControlHelper.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MonitorControlHelper.entitlements; sourceTree = "<group>"; };
165166
F0A987D61F77B290009B603D /* OSD.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OSD.framework; sourceTree = "<group>"; };
167+
FE4E0895249D584C003A50BB /* OSDUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSDUtils.swift; sourceTree = "<group>"; };
166168
/* End PBXFileReference section */
167169

168170
/* Begin PBXFrameworksBuildPhase section */
@@ -291,6 +293,7 @@
291293
F01B0683228221B6008E64DB /* Utils.swift */,
292294
6C2EA1CC228F644B00060E3F /* OnlyIntegerValueFormatter.swift */,
293295
6C2EA1CE228F7DFB00060E3F /* PollingMode.swift */,
296+
FE4E0895249D584C003A50BB /* OSDUtils.swift */,
294297
);
295298
path = Support;
296299
sourceTree = "<group>";
@@ -531,6 +534,7 @@
531534
F03FE4C0228DF62B001F59A4 /* FriendlyNameCellView.swift in Sources */,
532535
6C2EA1CF228F7DFB00060E3F /* PollingMode.swift in Sources */,
533536
6CBFE27C23DB27A200D1BC41 /* InternalDisplay.swift in Sources */,
537+
FE4E0896249D584C003A50BB /* OSDUtils.swift in Sources */,
534538
6CBFE27A23DB266000D1BC41 /* Display.swift in Sources */,
535539
F03A8DF21FFBAA6F0034DC27 /* ExternalDisplay.swift in Sources */,
536540
F0445D40200259C10025AE82 /* DisplayPrefsViewController.swift in Sources */,

MonitorControl/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<key>CFBundleShortVersionString</key>
2020
<string>$(MARKETING_VERSION)</string>
2121
<key>CFBundleVersion</key>
22-
<string>647</string>
22+
<string>719</string>
2323
<key>LSApplicationCategoryType</key>
2424
<string>public.app-category.utilities</string>
2525
<key>LSMinimumSystemVersion</key>

MonitorControl/Model/Display.swift

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import DDC
1010
import Foundation
11+
import os.log
1112

1213
class Display {
1314
internal let identifier: CGDirectDisplayID
@@ -42,7 +43,7 @@ class Display {
4243
return self.prefs.string(forKey: "friendlyName-\(self.identifier)") ?? self.name
4344
}
4445

45-
func showOsd(command: DDC.Command, value: Int, maxValue: Int = 100) {
46+
func showOsd(command: DDC.Command, value: Int, maxValue: Int = 100, roundChiclet: Bool = false) {
4647
guard let manager = OSDManager.sharedManager() as? OSDManager else {
4748
return
4849
}
@@ -59,12 +60,25 @@ class Display {
5960
osdImage = 1
6061
}
6162

62-
manager.showImage(osdImage,
63-
onDisplayID: self.identifier,
64-
priority: 0x1F4,
65-
msecUntilFade: 1000,
66-
filledChiclets: UInt32(value),
67-
totalChiclets: UInt32(maxValue),
68-
locked: false)
63+
if roundChiclet {
64+
let osdChiclet = OSDUtils.chiclet(fromValue: Float(value), maxValue: Float(maxValue))
65+
let filledChiclets = round(osdChiclet)
66+
67+
manager.showImage(osdImage,
68+
onDisplayID: self.identifier,
69+
priority: 0x1F4,
70+
msecUntilFade: 1000,
71+
filledChiclets: UInt32(filledChiclets),
72+
totalChiclets: UInt32(16),
73+
locked: false)
74+
} else {
75+
manager.showImage(osdImage,
76+
onDisplayID: self.identifier,
77+
priority: 0x1F4,
78+
msecUntilFade: 1000,
79+
filledChiclets: UInt32(value),
80+
totalChiclets: UInt32(maxValue),
81+
locked: false)
82+
}
6983
}
7084
}

MonitorControl/Model/ExternalDisplay.swift

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ class ExternalDisplay: Display {
3232
}
3333

3434
private var audioPlayer: AVAudioPlayer?
35-
private let osdChicletBoxes: Float = 16
3635

3736
override init(_ identifier: CGDirectDisplayID, name: String, vendorNumber: UInt32?, modelNumber: UInt32?) {
3837
super.init(identifier, name: name, vendorNumber: vendorNumber, modelNumber: modelNumber)
@@ -90,7 +89,7 @@ class ExternalDisplay: Display {
9089

9190
if !fromVolumeSlider {
9291
self.hideDisplayOsd()
93-
self.showOsd(command: volumeOSDValue > 0 ? .audioSpeakerVolume : .audioMuteScreenBlank, value: volumeOSDValue)
92+
self.showOsd(command: volumeOSDValue > 0 ? .audioSpeakerVolume : .audioMuteScreenBlank, value: volumeOSDValue, roundChiclet: true)
9493

9594
if volumeOSDValue > 0 {
9695
self.playVolumeChangedSound()
@@ -106,7 +105,6 @@ class ExternalDisplay: Display {
106105
var muteValue: Int?
107106
let volumeOSDValue = self.calcNewValue(for: .audioSpeakerVolume, isUp: isUp, isSmallIncrement: isSmallIncrement)
108107
let volumeDDCValue = UInt16(volumeOSDValue)
109-
110108
if self.isMuted(), volumeOSDValue > 0 {
111109
muteValue = 2
112110
} else if !self.isMuted(), volumeOSDValue == 0 {
@@ -132,7 +130,7 @@ class ExternalDisplay: Display {
132130
}
133131

134132
self.hideDisplayOsd()
135-
self.showOsd(command: .audioSpeakerVolume, value: volumeOSDValue)
133+
self.showOsd(command: .audioSpeakerVolume, value: volumeOSDValue, roundChiclet: !isSmallIncrement)
136134

137135
if !isAlreadySet {
138136
self.saveValue(volumeOSDValue, for: .audioSpeakerVolume)
@@ -163,7 +161,7 @@ class ExternalDisplay: Display {
163161
}
164162
}
165163

166-
self.showOsd(command: .brightness, value: osdValue)
164+
self.showOsd(command: .brightness, value: osdValue, roundChiclet: !isSmallIncrement)
167165

168166
if !isAlreadySet {
169167
if let slider = self.brightnessSliderHandler?.slider {
@@ -221,25 +219,32 @@ class ExternalDisplay: Display {
221219
func calcNewValue(for command: DDC.Command, isUp: Bool, isSmallIncrement: Bool) -> Int {
222220
let currentValue = self.getValue(for: command)
223221
let nextValue: Int
222+
let maxValue = Float(self.getMaxValue(for: command))
224223

225224
if isSmallIncrement {
226225
nextValue = currentValue + (isUp ? 1 : -1)
227226
} else {
228-
let filledChicletBoxes = self.osdChicletBoxes * (Float(currentValue) / Float(self.getMaxValue(for: command)))
229-
230-
var nextFilledChicletBoxes: Float
231-
var filledChicletBoxesRel: Float = isUp ? 1 : -1
232-
233-
// 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,
234-
// 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
235-
if (isUp && ceil(filledChicletBoxes) - filledChicletBoxes > 0.15) || (!isUp && filledChicletBoxes - floor(filledChicletBoxes) > 0.15) {
236-
filledChicletBoxesRel = 0
227+
let osdChicletFromValue = OSDUtils.chiclet(fromValue: Float(currentValue), maxValue: maxValue)
228+
229+
let distance = OSDUtils.getDistance(fromNearestChiclet: osdChicletFromValue)
230+
// get the next rounded chiclet
231+
var nextFilledChiclet = isUp ? ceil(osdChicletFromValue) : floor(osdChicletFromValue)
232+
233+
// Depending on the direction, if the chiclet is above or below a certain threshold, we go to the next whole chiclet
234+
let distanceThreshold = Float(0.25) // 25% of the distance between the edges of an osd box
235+
if distance == 0 {
236+
nextFilledChiclet += isUp ? 1 : -1
237+
} else if !isUp, distance < distanceThreshold {
238+
nextFilledChiclet -= 1
239+
} else if isUp, distance > (1 - distanceThreshold) {
240+
nextFilledChiclet += 1
237241
}
238242

239-
nextFilledChicletBoxes = isUp ? ceil(filledChicletBoxes + filledChicletBoxesRel) : floor(filledChicletBoxes + filledChicletBoxesRel)
240-
nextValue = Int(Float(self.getMaxValue(for: command)) * (nextFilledChicletBoxes / self.osdChicletBoxes))
243+
nextValue = Int(round(OSDUtils.value(fromChiclet: nextFilledChiclet, maxValue: maxValue)))
244+
245+
os_log("next: .value %{public}@/%{public}@, .osd %{public}@/%{public}@", type: .debug, String(nextValue), String(maxValue), String(nextFilledChiclet), String(OSDUtils.chicletCount))
241246
}
242-
return max(0, min(self.getMaxValue(for: command), Int(nextValue)))
247+
return max(0, min(self.getMaxValue(for: command), nextValue))
243248
}
244249

245250
func getValue(for command: DDC.Command) -> Int {
@@ -308,11 +313,11 @@ class ExternalDisplay: Display {
308313
}
309314

310315
private func stepSize(for command: DDC.Command, isSmallIncrement: Bool) -> Int {
311-
return isSmallIncrement ? 1 : Int(floor(Float(self.getMaxValue(for: command)) / self.osdChicletBoxes))
316+
return isSmallIncrement ? 1 : Int(floor(Float(self.getMaxValue(for: command)) / OSDUtils.chicletCount))
312317
}
313318

314-
override func showOsd(command: DDC.Command, value: Int, maxValue _: Int = 100) {
315-
super.showOsd(command: command, value: value, maxValue: self.getMaxValue(for: command))
319+
override func showOsd(command: DDC.Command, value: Int, maxValue _: Int = 100, roundChiclet: Bool = false) {
320+
super.showOsd(command: command, value: value, maxValue: self.getMaxValue(for: command), roundChiclet: roundChiclet)
316321
}
317322

318323
private func supportsMuteCommand() -> Bool {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// OSDUtils.swift
3+
// MonitorControl
4+
//
5+
// Created by Victor Chabbert on 19/06/2020.
6+
// Copyright © 2020 Guillaume Broder. All rights reserved.
7+
//
8+
9+
import Cocoa
10+
11+
class OSDUtils: NSObject {
12+
static let chicletCount: Float = 16
13+
14+
static func chiclet(fromValue value: Float, maxValue: Float) -> Float {
15+
return (value * self.chicletCount) / maxValue
16+
}
17+
18+
static func value(fromChiclet chiclet: Float, maxValue: Float) -> Float {
19+
return (chiclet * maxValue) / self.chicletCount
20+
}
21+
22+
static func getDistance(fromNearestChiclet chiclet: Float) -> Float {
23+
return abs(chiclet.rounded(.towardZero) - chiclet)
24+
}
25+
}

MonitorControlHelper/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<key>CFBundleShortVersionString</key>
2020
<string>$(MARKETING_VERSION)</string>
2121
<key>CFBundleVersion</key>
22-
<string>647</string>
22+
<string>719</string>
2323
<key>LSApplicationCategoryType</key>
2424
<string>public.app-category.utilities</string>
2525
<key>LSBackgroundOnly</key>

0 commit comments

Comments
 (0)