Skip to content

Commit 0b39f50

Browse files
authored
Fix/betaissues (MonitorControl#675)
- Reorganized PrefKey list to be less confusing. - Disengage custom shortcut keyboard after 100 key repeat to prevent possibly endless loop if keyUp event never arrives due to any circumstance. - Don't relinquish control over brightness keys with no external display connected if fine brightness OSD scale is active - Register DDC command touched status. When write on startup enabled, apply only touched command values. - Fixed custom key shortcuts going runaway when menu was opened during a keyrepeat streak.
1 parent 08c41c1 commit 0b39f50

9 files changed

Lines changed: 103 additions & 87 deletions

File tree

.github/screenshot.png

275 KB
Loading

MonitorControl/Enums/PrefKey.swift

Lines changed: 68 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,14 @@
11
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
22

33
enum PrefKey: String {
4-
// Enable mute DDC for display
5-
case enableMuteUnmute
4+
/* -- App-wide settings -- */
65

76
// Sparkle automatic checks
87
case SUEnableAutomaticChecks
98

109
// Receive beta updates?
1110
case isBetaChannel // This is not added to Preferences yet as it will be needed in the future only.
1211

13-
// Hide OSD for display
14-
case hideOsd
15-
16-
// Longer delay DDC for display
17-
case longerDelay
18-
19-
// DDC polling mode for display
20-
case pollingMode
21-
22-
// DDC polling count for display
23-
case pollingCount
24-
25-
// Display should avoid gamma table manipulation and use shades instead (to coexist with other apps doing gamma manipulation)
26-
case avoidGamma
27-
28-
// Command value display
29-
case value
30-
31-
// Min command value display
32-
case minDDCOverride
33-
34-
// Max command value display
35-
case maxDDC
36-
37-
// Max user override command value display
38-
case maxDDCOverride
39-
40-
// Max command value display
41-
case curveDDC
42-
43-
// Is the specific control is set as unavailable for display?
44-
case unavailableDDC
45-
46-
// Invert DDC scale?
47-
case invertDDC
48-
49-
// Override DDC control command code
50-
case remapDDC
51-
52-
// User assigned audio device name for display
53-
case audioDeviceNameOverride
54-
55-
// Display disabled for keyboard control
56-
case isDisabled
57-
58-
// Force software mode for display
59-
case forceSw
60-
61-
// Software brightness for display
62-
case SwBrightness
63-
6412
// Build number
6513
case buildNumber
6614

@@ -94,9 +42,6 @@ enum PrefKey: String {
9442
// Lower via software after brightness
9543
case disableCombinedBrightness
9644

97-
// Lower via software after brightness
98-
case combinedBrightnessSwitchingPoint
99-
10045
// Use separated OSD scale for combined brightness
10146
case separateCombinedScale
10247

@@ -115,9 +60,6 @@ enum PrefKey: String {
11560
// Show tick marks for sliders
11661
case showTickMarks
11762

118-
// Friendly name changed
119-
case friendlyName
120-
12163
// Instead of assuming default values, enable read or write upon startup (according to readDDCInsteadOfRestoreValues)
12264
case enableDDCDuringStartup
12365

@@ -159,6 +101,73 @@ enum PrefKey: String {
159101

160102
// Combine sliders for all displays
161103
case slidersCombine
104+
105+
/* -- Display specific settings */
106+
107+
// Enable mute DDC for display
108+
case enableMuteUnmute
109+
110+
// Hide OSD for display
111+
case hideOsd
112+
113+
// Longer delay DDC for display
114+
case longerDelay
115+
116+
// DDC polling mode for display
117+
case pollingMode
118+
119+
// DDC polling count for display
120+
case pollingCount
121+
122+
// Display should avoid gamma table manipulation and use shades instead (to coexist with other apps doing gamma manipulation)
123+
case avoidGamma
124+
125+
// User assigned audio device name for display
126+
case audioDeviceNameOverride
127+
128+
// Display disabled for keyboard control
129+
case isDisabled
130+
131+
// Force software mode for display
132+
case forceSw
133+
134+
// Software brightness for display
135+
case SwBrightness
136+
137+
// Combined brightness switching point
138+
case combinedBrightnessSwitchingPoint
139+
140+
// Friendly name
141+
case friendlyName
142+
143+
/* -- Display+Command specific settings -- */
144+
145+
// Command value display
146+
case value
147+
148+
// Was the setting ever changed by the user?
149+
case isTouched
150+
151+
// Min command value display
152+
case minDDCOverride
153+
154+
// Max command value display
155+
case maxDDC
156+
157+
// Max user override command value display
158+
case maxDDCOverride
159+
160+
// Max command value display
161+
case curveDDC
162+
163+
// Is the specific control is set as unavailable for display?
164+
case unavailableDDC
165+
166+
// Invert DDC scale?
167+
case invertDDC
168+
169+
// Override DDC control command code
170+
case remapDDC
162171
}
163172

164173
enum PollingMode: Int {

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>6421</string>
22+
<string>6440</string>
2323
<key>LSApplicationCategoryType</key>
2424
<string>public.app-category.utilities</string>
2525
<key>LSMinimumSystemVersion</key>

MonitorControl/Model/OtherDisplay.swift

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
22

3-
import AVFoundation
43
import Cocoa
54
import IOKit
65
import os.log
@@ -13,7 +12,6 @@ class OtherDisplay: Display {
1312
var arm64avService: IOAVService?
1413
var isDiscouraged: Bool = false
1514
let DDC_MAX_DETECT_LIMIT: Int = 100
16-
private var audioPlayer: AVAudioPlayer?
1715
var pollingCount: Int {
1816
get {
1917
switch self.readPrefAsInt(key: .pollingMode) {
@@ -114,7 +112,8 @@ class OtherDisplay: Display {
114112
os_log("- Minimum DDC value: %{public}@ (overrides 0)", type: .info, String(self.readPrefAsInt(key: .minDDCOverride, for: command)))
115113
os_log("- Maximum DDC value: %{public}@ (overrides %{public}@)", type: .info, String(self.readPrefAsInt(key: .maxDDC, for: command)), String(maxDDCValue))
116114
os_log("- Current internal value: %{public}@", type: .info, String(self.readPrefAsFloat(for: command)))
117-
if prefs.bool(forKey: PrefKey.enableDDCDuringStartup.rawValue), !prefs.bool(forKey: PrefKey.readDDCInsteadOfRestoreValues.rawValue), !app.safeMode {
115+
os_log("- Command status: %{public}@", type: .info, self.readPrefAsBool(key: .isTouched, for: command) ? "Touched" : "Untouched")
116+
if self.readPrefAsBool(key: .isTouched, for: command), prefs.bool(forKey: PrefKey.enableDDCDuringStartup.rawValue), !prefs.bool(forKey: PrefKey.readDDCInsteadOfRestoreValues.rawValue), !app.safeMode {
118117
os_log("- Writing last saved DDC values.", type: .info, self.name, String(reflecting: command))
119118
_ = self.writeDDCValues(command: command, value: currentDDCValue)
120119
}
@@ -390,6 +389,7 @@ class OtherDisplay: Display {
390389
} else {
391390
success = self.ddc?.write(command: command.rawValue, value: value, errorRecoveryWaitTime: 2000) ?? false
392391
}
392+
self.savePref(true, key: PrefKey.isTouched, for: command) // We deliberatly consider the value tuched no matter if the call succeeded
393393
}
394394
}
395395
return success
@@ -486,19 +486,6 @@ class OtherDisplay: Display {
486486
return max(min(value, 1), 0)
487487
}
488488

489-
func playVolumeChangedSound() {
490-
guard let preferences = app.getSystemPreferences(), let hasSoundEnabled = preferences["com.apple.sound.beep.feedback"] as? Int, hasSoundEnabled == 1 else {
491-
return
492-
}
493-
do {
494-
self.audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: "/System/Library/LoginPlugins/BezelServices.loginPlugin/Contents/Resources/volume.aiff"))
495-
self.audioPlayer?.volume = 1
496-
self.audioPlayer?.play()
497-
} catch {
498-
os_log("%{public}@", type: .error, error.localizedDescription)
499-
}
500-
}
501-
502489
func combinedBrightnessSwitchingValue() -> Float {
503490
return Float(self.readPrefAsInt(key: .combinedBrightnessSwitchingPoint) + 8) / 16
504491
}

MonitorControl/Support/AppDelegate.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
22

3+
import AVFoundation
34
import Cocoa
45
import Foundation
56
import MediaKeyTap
@@ -19,6 +20,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
1920
var sleepID: Int = 0 // sleep event ID
2021
var safeMode = false
2122
var jobRunning = false
23+
var audioPlayer: AVAudioPlayer?
2224
let updaterController = SPUStandardUpdaterController(startingUpdater: false, updaterDelegate: UpdaterDelegate(), userDriverDelegate: nil)
2325

2426
var preferencePaneStyle: Preferences.Style {
@@ -299,4 +301,17 @@ class AppDelegate: NSObject, NSApplicationDelegate {
299301
return true
300302
}
301303
}
304+
305+
func playVolumeChangedSound() {
306+
guard let preferences = app.getSystemPreferences(), let hasSoundEnabled = preferences["com.apple.sound.beep.feedback"] as? Int, hasSoundEnabled == 1 else {
307+
return
308+
}
309+
do {
310+
self.audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: "/System/Library/LoginPlugins/BezelServices.loginPlugin/Contents/Resources/volume.aiff"))
311+
self.audioPlayer?.volume = 1
312+
self.audioPlayer?.play()
313+
} catch {
314+
os_log("%{public}@", type: .error, error.localizedDescription)
315+
}
316+
}
302317
}

MonitorControl/Support/KeyboardShortcutsManager.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import KeyboardShortcuts
55
import os.log
66

77
class KeyboardShortcutsManager {
8-
var initialKeyRepeat = 0.24 // This should come from UserDefaults instead, but it's ok for now.
9-
var keyRepeat = 0.032 // This should come from UserDefaults instead, but it's ok for now.
8+
var initialKeyRepeat = 0.21
9+
var keyRepeat = 0.028
10+
var keyRepeatCount = 0
1011

1112
var currentCommand = KeyboardShortcuts.Name.none
1213
var isFirstKeypress = false
@@ -62,17 +63,19 @@ class KeyboardShortcutsManager {
6263
self.isFirstKeypress = true
6364
self.isHold = true
6465
self.currentEventId += 1
66+
self.keyRepeatCount = 0
6567
self.apply(shortcut, eventId: self.currentEventId)
6668
}
6769

6870
func disengage() {
6971
self.isHold = false
7072
self.isFirstKeypress = false
7173
self.currentCommand = KeyboardShortcuts.Name.none
74+
self.keyRepeatCount = 0
7275
}
7376

7477
func apply(_ shortcut: KeyboardShortcuts.Name, eventId: Int) {
75-
guard app.sleepID == 0, app.reconfigureID == 0 else {
78+
guard app.sleepID == 0, app.reconfigureID == 0, self.keyRepeatCount <= 100 else {
7679
self.disengage()
7780
return
7881
}
@@ -92,6 +95,7 @@ class KeyboardShortcutsManager {
9295
self.apply(shortcut, eventId: eventId)
9396
}
9497
}
98+
self.keyRepeatCount += 1
9599
switch shortcut {
96100
case KeyboardShortcuts.Name.brightnessUp: self.brightness(isUp: true)
97101
case KeyboardShortcuts.Name.brightnessDown: self.brightness(isUp: false)
@@ -142,7 +146,7 @@ class KeyboardShortcutsManager {
142146
if isPressed {
143147
display.stepVolume(isUp: isUp, isSmallIncrement: prefs.bool(forKey: PrefKey.useFineScaleVolume.rawValue))
144148
} else if !wasNotIsPressedVolumeSentAlready, !display.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume) {
145-
display.playVolumeChangedSound()
149+
app.playVolumeChangedSound()
146150
wasNotIsPressedVolumeSentAlready = true
147151
}
148152
}
@@ -158,7 +162,7 @@ class KeyboardShortcutsManager {
158162
if let display = display as? OtherDisplay {
159163
display.toggleMute()
160164
if !wasNotIsPressedVolumeSentAlready, display.readPrefAsInt(for: .audioMuteScreenBlank) != 1, !display.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume) {
161-
display.playVolumeChangedSound()
165+
app.playVolumeChangedSound()
162166
wasNotIsPressedVolumeSentAlready = true
163167
}
164168
}

MonitorControl/Support/MediaKeyTapManager.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ class MediaKeyTapManager: MediaKeyTapDelegate {
9494
if !isRepeat, isPressed, let display = display as? OtherDisplay {
9595
display.toggleMute()
9696
if !wasNotIsPressedVolumeSentAlready, display.readPrefAsInt(for: .audioMuteScreenBlank) != 1, !display.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume) {
97-
display.playVolumeChangedSound()
97+
app.playVolumeChangedSound()
9898
wasNotIsPressedVolumeSentAlready = true
9999
}
100100
}
@@ -104,7 +104,7 @@ class MediaKeyTapManager: MediaKeyTapDelegate {
104104
if isPressed {
105105
display.stepVolume(isUp: mediaKey == .volumeUp, isSmallIncrement: isSmallIncrement)
106106
} else if !wasNotIsPressedVolumeSentAlready, !display.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume) {
107-
display.playVolumeChangedSound()
107+
app.playVolumeChangedSound()
108108
wasNotIsPressedVolumeSentAlready = true
109109
}
110110
}
@@ -163,13 +163,13 @@ class MediaKeyTapManager: MediaKeyTapDelegate {
163163
if [KeyboardVolume.media.rawValue, KeyboardVolume.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardVolume.rawValue)) {
164164
keys.append(contentsOf: [.mute, .volumeUp, .volumeDown])
165165
}
166-
// Remove keys if no external displays are connected
166+
// Remove brightness keys if no external displays are connected, but only if brightness fine control is not active
167167
var isInternalDisplayOnly = true
168168
for display in DisplayManager.shared.getAllDisplays() where !display.isBuiltIn() {
169169
isInternalDisplayOnly = false
170170
}
171-
if isInternalDisplayOnly {
172-
let keysToDelete: [MediaKey] = [.volumeUp, .volumeDown, .mute, .brightnessUp, .brightnessDown]
171+
if isInternalDisplayOnly, !prefs.bool(forKey: PrefKey.useFineScaleBrightness.rawValue) {
172+
let keysToDelete: [MediaKey] = [.brightnessUp, .brightnessDown]
173173
keys.removeAll { keysToDelete.contains($0) }
174174
}
175175
// Remove volume related keys if audio device is controllable

MonitorControl/Support/MenuHandler.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class MenuHandler: NSMenu, NSMenuDelegate {
2121

2222
func menuWillOpen(_: NSMenu) {
2323
self.updateMenuRelevantDisplay()
24+
app.keyboardShortcuts.disengage()
2425
}
2526

2627
func updateMenus(dontClose: Bool = false) {

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>6421</string>
22+
<string>6440</string>
2323
<key>LSApplicationCategoryType</key>
2424
<string>public.app-category.utilities</string>
2525
<key>LSBackgroundOnly</key>

0 commit comments

Comments
 (0)