diff --git a/.github/screenshot.png b/.github/screenshot.png
index 671762b..700cefd 100644
Binary files a/.github/screenshot.png and b/.github/screenshot.png differ
diff --git a/MonitorControl/Enums/PrefKey.swift b/MonitorControl/Enums/PrefKey.swift
index ec67f81..3d81e74 100644
--- a/MonitorControl/Enums/PrefKey.swift
+++ b/MonitorControl/Enums/PrefKey.swift
@@ -1,8 +1,7 @@
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
enum PrefKey: String {
- // Enable mute DDC for display
- case enableMuteUnmute
+ /* -- App-wide settings -- */
// Sparkle automatic checks
case SUEnableAutomaticChecks
@@ -10,57 +9,6 @@ enum PrefKey: String {
// Receive beta updates?
case isBetaChannel // This is not added to Preferences yet as it will be needed in the future only.
- // Hide OSD for display
- case hideOsd
-
- // Longer delay DDC for display
- case longerDelay
-
- // DDC polling mode for display
- case pollingMode
-
- // DDC polling count for display
- case pollingCount
-
- // Display should avoid gamma table manipulation and use shades instead (to coexist with other apps doing gamma manipulation)
- case avoidGamma
-
- // Command value display
- case value
-
- // Min command value display
- case minDDCOverride
-
- // Max command value display
- case maxDDC
-
- // Max user override command value display
- case maxDDCOverride
-
- // Max command value display
- case curveDDC
-
- // Is the specific control is set as unavailable for display?
- case unavailableDDC
-
- // Invert DDC scale?
- case invertDDC
-
- // Override DDC control command code
- case remapDDC
-
- // User assigned audio device name for display
- case audioDeviceNameOverride
-
- // Display disabled for keyboard control
- case isDisabled
-
- // Force software mode for display
- case forceSw
-
- // Software brightness for display
- case SwBrightness
-
// Build number
case buildNumber
@@ -94,9 +42,6 @@ enum PrefKey: String {
// Lower via software after brightness
case disableCombinedBrightness
- // Lower via software after brightness
- case combinedBrightnessSwitchingPoint
-
// Use separated OSD scale for combined brightness
case separateCombinedScale
@@ -115,9 +60,6 @@ enum PrefKey: String {
// Show tick marks for sliders
case showTickMarks
- // Friendly name changed
- case friendlyName
-
// Instead of assuming default values, enable read or write upon startup (according to readDDCInsteadOfRestoreValues)
case enableDDCDuringStartup
@@ -159,6 +101,73 @@ enum PrefKey: String {
// Combine sliders for all displays
case slidersCombine
+
+ /* -- Display specific settings */
+
+ // Enable mute DDC for display
+ case enableMuteUnmute
+
+ // Hide OSD for display
+ case hideOsd
+
+ // Longer delay DDC for display
+ case longerDelay
+
+ // DDC polling mode for display
+ case pollingMode
+
+ // DDC polling count for display
+ case pollingCount
+
+ // Display should avoid gamma table manipulation and use shades instead (to coexist with other apps doing gamma manipulation)
+ case avoidGamma
+
+ // User assigned audio device name for display
+ case audioDeviceNameOverride
+
+ // Display disabled for keyboard control
+ case isDisabled
+
+ // Force software mode for display
+ case forceSw
+
+ // Software brightness for display
+ case SwBrightness
+
+ // Combined brightness switching point
+ case combinedBrightnessSwitchingPoint
+
+ // Friendly name
+ case friendlyName
+
+ /* -- Display+Command specific settings -- */
+
+ // Command value display
+ case value
+
+ // Was the setting ever changed by the user?
+ case isTouched
+
+ // Min command value display
+ case minDDCOverride
+
+ // Max command value display
+ case maxDDC
+
+ // Max user override command value display
+ case maxDDCOverride
+
+ // Max command value display
+ case curveDDC
+
+ // Is the specific control is set as unavailable for display?
+ case unavailableDDC
+
+ // Invert DDC scale?
+ case invertDDC
+
+ // Override DDC control command code
+ case remapDDC
}
enum PollingMode: Int {
diff --git a/MonitorControl/Info.plist b/MonitorControl/Info.plist
index 637c02d..c158f20 100644
--- a/MonitorControl/Info.plist
+++ b/MonitorControl/Info.plist
@@ -19,7 +19,7 @@
CFBundleShortVersionString
$(MARKETING_VERSION)
CFBundleVersion
- 6421
+ 6440
LSApplicationCategoryType
public.app-category.utilities
LSMinimumSystemVersion
diff --git a/MonitorControl/Model/OtherDisplay.swift b/MonitorControl/Model/OtherDisplay.swift
index 1222c30..5120e21 100644
--- a/MonitorControl/Model/OtherDisplay.swift
+++ b/MonitorControl/Model/OtherDisplay.swift
@@ -1,6 +1,5 @@
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
-import AVFoundation
import Cocoa
import IOKit
import os.log
@@ -13,7 +12,6 @@ class OtherDisplay: Display {
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) {
@@ -114,7 +112,8 @@ class OtherDisplay: Display {
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("- Command status: %{public}@", type: .info, self.readPrefAsBool(key: .isTouched, for: command) ? "Touched" : "Untouched")
+ if self.readPrefAsBool(key: .isTouched, for: command), 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)
}
@@ -390,6 +389,7 @@ class OtherDisplay: Display {
} else {
success = self.ddc?.write(command: command.rawValue, value: value, errorRecoveryWaitTime: 2000) ?? false
}
+ self.savePref(true, key: PrefKey.isTouched, for: command) // We deliberatly consider the value tuched no matter if the call succeeded
}
}
return success
@@ -486,19 +486,6 @@ class OtherDisplay: Display {
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
}
diff --git a/MonitorControl/Support/AppDelegate.swift b/MonitorControl/Support/AppDelegate.swift
index 92c075b..fcb8665 100644
--- a/MonitorControl/Support/AppDelegate.swift
+++ b/MonitorControl/Support/AppDelegate.swift
@@ -1,5 +1,6 @@
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
+import AVFoundation
import Cocoa
import Foundation
import MediaKeyTap
@@ -19,6 +20,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
var sleepID: Int = 0 // sleep event ID
var safeMode = false
var jobRunning = false
+ var audioPlayer: AVAudioPlayer?
let updaterController = SPUStandardUpdaterController(startingUpdater: false, updaterDelegate: UpdaterDelegate(), userDriverDelegate: nil)
var preferencePaneStyle: Preferences.Style {
@@ -299,4 +301,17 @@ class AppDelegate: NSObject, NSApplicationDelegate {
return true
}
}
+
+ 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)
+ }
+ }
}
diff --git a/MonitorControl/Support/KeyboardShortcutsManager.swift b/MonitorControl/Support/KeyboardShortcutsManager.swift
index 1c10a67..8de4ac8 100644
--- a/MonitorControl/Support/KeyboardShortcutsManager.swift
+++ b/MonitorControl/Support/KeyboardShortcutsManager.swift
@@ -5,8 +5,9 @@ import KeyboardShortcuts
import os.log
class KeyboardShortcutsManager {
- var initialKeyRepeat = 0.24 // This should come from UserDefaults instead, but it's ok for now.
- var keyRepeat = 0.032 // This should come from UserDefaults instead, but it's ok for now.
+ var initialKeyRepeat = 0.21
+ var keyRepeat = 0.028
+ var keyRepeatCount = 0
var currentCommand = KeyboardShortcuts.Name.none
var isFirstKeypress = false
@@ -62,6 +63,7 @@ class KeyboardShortcutsManager {
self.isFirstKeypress = true
self.isHold = true
self.currentEventId += 1
+ self.keyRepeatCount = 0
self.apply(shortcut, eventId: self.currentEventId)
}
@@ -69,10 +71,11 @@ class KeyboardShortcutsManager {
self.isHold = false
self.isFirstKeypress = false
self.currentCommand = KeyboardShortcuts.Name.none
+ self.keyRepeatCount = 0
}
func apply(_ shortcut: KeyboardShortcuts.Name, eventId: Int) {
- guard app.sleepID == 0, app.reconfigureID == 0 else {
+ guard app.sleepID == 0, app.reconfigureID == 0, self.keyRepeatCount <= 100 else {
self.disengage()
return
}
@@ -92,6 +95,7 @@ class KeyboardShortcutsManager {
self.apply(shortcut, eventId: eventId)
}
}
+ self.keyRepeatCount += 1
switch shortcut {
case KeyboardShortcuts.Name.brightnessUp: self.brightness(isUp: true)
case KeyboardShortcuts.Name.brightnessDown: self.brightness(isUp: false)
@@ -142,7 +146,7 @@ class KeyboardShortcutsManager {
if isPressed {
display.stepVolume(isUp: isUp, isSmallIncrement: prefs.bool(forKey: PrefKey.useFineScaleVolume.rawValue))
} else if !wasNotIsPressedVolumeSentAlready, !display.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume) {
- display.playVolumeChangedSound()
+ app.playVolumeChangedSound()
wasNotIsPressedVolumeSentAlready = true
}
}
@@ -158,7 +162,7 @@ class KeyboardShortcutsManager {
if let display = display as? OtherDisplay {
display.toggleMute()
if !wasNotIsPressedVolumeSentAlready, display.readPrefAsInt(for: .audioMuteScreenBlank) != 1, !display.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume) {
- display.playVolumeChangedSound()
+ app.playVolumeChangedSound()
wasNotIsPressedVolumeSentAlready = true
}
}
diff --git a/MonitorControl/Support/MediaKeyTapManager.swift b/MonitorControl/Support/MediaKeyTapManager.swift
index 2a5f223..f6a446a 100644
--- a/MonitorControl/Support/MediaKeyTapManager.swift
+++ b/MonitorControl/Support/MediaKeyTapManager.swift
@@ -94,7 +94,7 @@ class MediaKeyTapManager: MediaKeyTapDelegate {
if !isRepeat, isPressed, let display = display as? OtherDisplay {
display.toggleMute()
if !wasNotIsPressedVolumeSentAlready, display.readPrefAsInt(for: .audioMuteScreenBlank) != 1, !display.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume) {
- display.playVolumeChangedSound()
+ app.playVolumeChangedSound()
wasNotIsPressedVolumeSentAlready = true
}
}
@@ -104,7 +104,7 @@ class MediaKeyTapManager: MediaKeyTapDelegate {
if isPressed {
display.stepVolume(isUp: mediaKey == .volumeUp, isSmallIncrement: isSmallIncrement)
} else if !wasNotIsPressedVolumeSentAlready, !display.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume) {
- display.playVolumeChangedSound()
+ app.playVolumeChangedSound()
wasNotIsPressedVolumeSentAlready = true
}
}
@@ -163,13 +163,13 @@ class MediaKeyTapManager: MediaKeyTapDelegate {
if [KeyboardVolume.media.rawValue, KeyboardVolume.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardVolume.rawValue)) {
keys.append(contentsOf: [.mute, .volumeUp, .volumeDown])
}
- // Remove keys if no external displays are connected
+ // Remove brightness keys if no external displays are connected, but only if brightness fine control is not active
var isInternalDisplayOnly = true
for display in DisplayManager.shared.getAllDisplays() where !display.isBuiltIn() {
isInternalDisplayOnly = false
}
- if isInternalDisplayOnly {
- let keysToDelete: [MediaKey] = [.volumeUp, .volumeDown, .mute, .brightnessUp, .brightnessDown]
+ if isInternalDisplayOnly, !prefs.bool(forKey: PrefKey.useFineScaleBrightness.rawValue) {
+ let keysToDelete: [MediaKey] = [.brightnessUp, .brightnessDown]
keys.removeAll { keysToDelete.contains($0) }
}
// Remove volume related keys if audio device is controllable
diff --git a/MonitorControl/Support/MenuHandler.swift b/MonitorControl/Support/MenuHandler.swift
index 61b10f2..7a53f09 100644
--- a/MonitorControl/Support/MenuHandler.swift
+++ b/MonitorControl/Support/MenuHandler.swift
@@ -21,6 +21,7 @@ class MenuHandler: NSMenu, NSMenuDelegate {
func menuWillOpen(_: NSMenu) {
self.updateMenuRelevantDisplay()
+ app.keyboardShortcuts.disengage()
}
func updateMenus(dontClose: Bool = false) {
diff --git a/MonitorControlHelper/Info.plist b/MonitorControlHelper/Info.plist
index 89a3013..c1793f6 100644
--- a/MonitorControlHelper/Info.plist
+++ b/MonitorControlHelper/Info.plist
@@ -19,7 +19,7 @@
CFBundleShortVersionString
$(MARKETING_VERSION)
CFBundleVersion
- 6421
+ 6440
LSApplicationCategoryType
public.app-category.utilities
LSBackgroundOnly