@@ -8,7 +8,6 @@ class Display {
88 let name : String
99 let isBuiltin : Bool
1010 var isEnabled : Bool
11- var isMuted : Bool = false
1211 var brightnessSliderHandler : SliderHandler ?
1312 var volumeSliderHandler : SliderHandler ?
1413 var contrastSliderHandler : SliderHandler ?
@@ -37,13 +36,14 @@ class Display {
3736 private let prefs = UserDefaults . standard
3837 private var audioPlayer : AVAudioPlayer ?
3938
39+ private let osdChicletBoxes : Float = 16
40+
4041 init ( _ identifier: CGDirectDisplayID , name: String , isBuiltin: Bool , isEnabled: Bool = true ) {
4142 self . identifier = identifier
4243 self . name = name
4344 self . isEnabled = isBuiltin ? false : isEnabled
4445 self . ddc = DDC ( for: identifier)
4546 self . isBuiltin = isBuiltin
46- self . isMuted = self . getValue ( for: . audioMuteScreenBlank) == 1
4747 }
4848
4949 // On some displays, the display's OSD overlaps the macOS OSD,
@@ -58,77 +58,115 @@ class Display {
5858 }
5959 }
6060
61- func mute( forceVolume: Int ? = nil ) {
62- var value = 0
63-
64- if self . isMuted, forceVolume == nil || forceVolume! > 0 {
65- value = forceVolume ?? self . getValue ( for: . audioSpeakerVolume)
66- self . saveValue ( value, for: . audioSpeakerVolume)
61+ func isMuted( ) -> Bool {
62+ return self . getValue ( for: . audioMuteScreenBlank) == 1
63+ }
6764
68- self . isMuted = false
69- } else if !self . isMuted, forceVolume == nil || forceVolume == 0 {
70- self . isMuted = true
65+ func toggleMute( fromVolumeSlider: Bool = false ) {
66+ var muteValue : Int
67+ var volumeOSDValue : Int
68+
69+ if !self . isMuted ( ) {
70+ muteValue = 1
71+ volumeOSDValue = 0
72+ } else {
73+ muteValue = 2
74+ volumeOSDValue = self . getValue ( for: . audioSpeakerVolume)
75+
76+ // The volume that will be set immediately after setting unmute while the old set volume was 0 is unpredictable
77+ // Hence, just set it to a single filled chiclet
78+ if volumeOSDValue == 0 {
79+ volumeOSDValue = self . stepSize ( for: . audioSpeakerVolume, isSmallIncrement: false )
80+ self . saveValue ( volumeOSDValue, for: . audioSpeakerVolume)
81+ }
7182 }
7283
7384 DispatchQueue . global ( qos: . userInitiated) . async {
74- let muteValue = self . isMuted ? 1 : 2
75- guard self . ddc ? . write ( command : . audioMuteScreenBlank , value : UInt16 ( muteValue ) , errorRecoveryWaitTime : self . hideOsd ? 0 : nil ) == true else {
76- self . setVolume ( to : value)
85+ let volumeDDCValue = UInt16 ( volumeOSDValue )
86+
87+ guard self . ddc ? . write ( command : . audioSpeakerVolume , value: volumeDDCValue ) == true else {
7788 return
7889 }
7990
80- if forceVolume == nil || forceVolume == 0 {
81- self . hideDisplayOsd ( )
82- self . showOsd ( command : . audioSpeakerVolume , value : value )
83- self . playVolumeChangedSound ( )
91+ if self . supportsMuteCommand ( ) {
92+ guard self . ddc ? . write ( command : . audioMuteScreenBlank , value : UInt16 ( muteValue ) ) == true else {
93+ return
94+ }
8495 }
8596
8697 self . saveValue ( muteValue, for: . audioMuteScreenBlank)
87- }
8898
89- if let slider = volumeSliderHandler? . slider {
90- DispatchQueue . main. async {
91- slider. intValue = Int32 ( value)
99+ if !fromVolumeSlider {
100+ self . hideDisplayOsd ( )
101+ self . showOsd ( command: . audioSpeakerVolume, value: volumeOSDValue)
102+
103+ if volumeOSDValue > 0 {
104+ self . playVolumeChangedSound ( )
105+ }
106+
107+ if let slider = self . volumeSliderHandler? . slider {
108+ DispatchQueue . main. async {
109+ slider. intValue = Int32 ( volumeDDCValue)
110+ }
111+ }
92112 }
93113 }
94114 }
95115
96- func setVolume( to value: Int , isSmallIncrement: Bool = false ) {
97- if value > 0 , self . isMuted {
98- self . mute ( forceVolume: value)
99- } else if value == 0 {
100- self . mute ( forceVolume: 0 )
101- return
116+ func setVolume( to volumeOSDValue: Int ) {
117+ var muteValue : Int ?
118+ let volumeDDCValue = UInt16 ( volumeOSDValue)
119+
120+ if self . isMuted ( ) , volumeOSDValue > 0 {
121+ muteValue = 2
122+ } else if !self . isMuted ( ) , volumeOSDValue == 0 {
123+ muteValue = 1
102124 }
103125
104126 DispatchQueue . global ( qos: . userInitiated) . async {
105- guard self . ddc? . write ( command: . audioSpeakerVolume, value: UInt16 ( value ) , errorRecoveryWaitTime : self . hideOsd ? 0 : nil ) == true else {
127+ guard self . ddc? . write ( command: . audioSpeakerVolume, value: volumeDDCValue ) == true else {
106128 return
107129 }
108130
131+ if muteValue != nil {
132+ // If the mute command is supported, set its value accordingly
133+ if self . supportsMuteCommand ( ) {
134+ guard self . ddc? . write ( command: . audioMuteScreenBlank, value: UInt16 ( muteValue!) ) == true else {
135+ return
136+ }
137+ }
138+
139+ self . saveValue ( muteValue!, for: . audioMuteScreenBlank)
140+ }
141+
142+ self . saveValue ( volumeOSDValue, for: . audioSpeakerVolume)
143+
109144 self . hideDisplayOsd ( )
110- self . showOsd ( command: . audioSpeakerVolume, value: value, isSmallIncrement: isSmallIncrement)
111- self . playVolumeChangedSound ( )
112- }
145+ self . showOsd ( command: . audioSpeakerVolume, value: volumeOSDValue)
113146
114- if let slider = volumeSliderHandler? . slider {
115- DispatchQueue . main. async {
116- slider. intValue = Int32 ( value)
147+ if volumeOSDValue > 0 {
148+ self . playVolumeChangedSound ( )
117149 }
118- }
119150
120- self . saveValue ( value, for: . audioSpeakerVolume)
151+ if let slider = self . volumeSliderHandler? . slider {
152+ DispatchQueue . main. async {
153+ slider. intValue = Int32 ( volumeDDCValue)
154+ }
155+ }
156+ }
121157 }
122158
123- func setBrightness( to value: Int , isSmallIncrement: Bool = false ) {
159+ func setBrightness( to osdValue: Int ) {
160+ let ddcValue = UInt16 ( osdValue)
161+
124162 if self . prefs. bool ( forKey: Utils . PrefKeys. lowerContrast. rawValue) {
125- if value == 0 {
163+ if ddcValue == 0 {
126164 DispatchQueue . global ( qos: . userInitiated) . async {
127- _ = self . ddc? . write ( command: . contrast, value: UInt16 ( value ) )
165+ _ = self . ddc? . write ( command: . contrast, value: ddcValue )
128166 }
129167
130168 if let slider = contrastSliderHandler? . slider {
131- slider. intValue = Int32 ( value )
169+ slider. intValue = Int32 ( ddcValue )
132170 }
133171 } else if self . getValue ( for: DDC . Command. brightness) == 0 {
134172 let contrastValue = self . getValue ( for: DDC . Command. contrast)
@@ -140,23 +178,67 @@ class Display {
140178 }
141179
142180 DispatchQueue . global ( qos: . userInitiated) . async {
143- guard self . ddc? . write ( command: . brightness, value: UInt16 ( value ) ) == true else {
181+ guard self . ddc? . write ( command: . brightness, value: ddcValue ) == true else {
144182 return
145183 }
146184
147- self . showOsd ( command: . brightness, value: value , isSmallIncrement : isSmallIncrement )
185+ self . showOsd ( command: . brightness, value: osdValue )
148186 }
149187
150188 if let slider = brightnessSliderHandler? . slider {
151- slider. intValue = Int32 ( value )
189+ slider. intValue = Int32 ( ddcValue )
152190 }
153191
154- self . saveValue ( value , for: . brightness)
192+ self . saveValue ( osdValue , for: . brightness)
155193 }
156194
157- func calcNewValue( for command: DDC . Command , withRel rel: Int ) -> Int {
195+ func readDDCValues( for command: DDC . Command , tries: UInt , minReplyDelay delay: UInt64 ? ) -> ( current: UInt16 , max: UInt16 ) ? {
196+ var values : ( UInt16 , UInt16 ) ?
197+
198+ if self . ddc? . supported ( minReplyDelay: delay) == true {
199+ os_log ( " Display supports DDC. " , type: . debug)
200+ } else {
201+ os_log ( " Display does not support DDC. " , type: . debug)
202+ }
203+
204+ if self . ddc? . enableAppReport ( ) == true {
205+ os_log ( " Display supports enabling DDC application report. " , type: . debug)
206+ } else {
207+ os_log ( " Display does not support enabling DDC application report. " , type: . debug)
208+ }
209+
210+ values = self . ddc? . read ( command: command, tries: tries, minReplyDelay: delay)
211+
212+ if values != nil {
213+ return values!
214+ }
215+
216+ return nil
217+ }
218+
219+ func calcNewValue( for command: DDC . Command , isUp: Bool , isSmallIncrement: Bool ) -> Int {
158220 let currentValue = self . getValue ( for: command)
159- return max ( 0 , min ( self . getMaxValue ( for: command) , currentValue + rel) )
221+ let nextValue : Int
222+
223+ if isSmallIncrement {
224+ nextValue = currentValue + ( isUp ? 1 : - 1 )
225+ } else {
226+ let filledChicletBoxes = self . osdChicletBoxes * ( Float ( currentValue) / Float( self . getMaxValue ( for: command) ) )
227+
228+ var nextFilledChicletBoxes : Float
229+ var fillecChicletBoxesRel : Float = isUp ? 1 : - 1
230+
231+ // 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,
232+ // 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
233+ if ( isUp && ceil ( filledChicletBoxes) - filledChicletBoxes > 0.15 ) || ( !isUp && filledChicletBoxes - floor( filledChicletBoxes) > 0.15 ) {
234+ fillecChicletBoxesRel = 0
235+ }
236+
237+ nextFilledChicletBoxes = isUp ? ceil ( filledChicletBoxes + fillecChicletBoxesRel) : floor ( filledChicletBoxes + fillecChicletBoxesRel)
238+ nextValue = Int ( Float ( self . getMaxValue ( for: command) ) * ( nextFilledChicletBoxes / self . osdChicletBoxes) )
239+ }
240+
241+ return max ( 0 , min ( self . getMaxValue ( for: command) , Int ( nextValue) ) )
160242 }
161243
162244 func getValue( for command: DDC . Command ) -> Int {
@@ -225,32 +307,37 @@ class Display {
225307 self . prefs. set ( value, forKey: " pollingCount- \( self . identifier) " )
226308 }
227309
228- private func showOsd( command: DDC . Command , value: Int , isSmallIncrement: Bool = false ) {
310+ private func stepSize( for command: DDC . Command , isSmallIncrement: Bool ) -> Int {
311+ return isSmallIncrement ? 1 : Int ( floor ( Float ( self . getMaxValue ( for: command) ) / self . osdChicletBoxes) )
312+ }
313+
314+ private func showOsd( command: DDC . Command , value: Int ) {
229315 guard let manager = OSDManager . sharedManager ( ) as? OSDManager else {
230316 return
231317 }
232318
233- let maxValue = self . getMaxValue ( for: command)
234-
235319 var osdImage : Int64 = 1 // Brightness Image
236320 if command == . audioSpeakerVolume {
237321 osdImage = 3 // Speaker image
238- if self . isMuted {
322+ if self . isMuted ( ) {
239323 osdImage = 4 // Mute speaker
240324 }
241325 }
242326
243- let step = isSmallIncrement ? maxValue / maxValue : maxValue / 16
244-
245327 manager. showImage ( osdImage,
246328 onDisplayID: self . identifier,
247329 priority: 0x1F4 ,
248330 msecUntilFade: 1000 ,
249- filledChiclets: UInt32 ( value / step ) ,
250- totalChiclets: UInt32 ( maxValue / step ) ,
331+ filledChiclets: UInt32 ( value) ,
332+ totalChiclets: UInt32 ( self . getMaxValue ( for : command ) ) ,
251333 locked: false )
252334 }
253335
336+ private func supportsMuteCommand( ) -> Bool {
337+ // Monitors which don't support the mute command - e.g. Dell U3419W - will have a maximum value of 100 for the DDC mute command
338+ return self . getMaxValue ( for: . audioMuteScreenBlank) == 2
339+ }
340+
254341 private func playVolumeChangedSound( ) {
255342 let soundPath = " /System/Library/LoginPlugins/BezelServices.loginPlugin/Contents/Resources/volume.aiff "
256343 let soundUrl = URL ( fileURLWithPath: soundPath)
0 commit comments