mirror of
https://github.com/MonitorControl/MonitorControl.git
synced 2026-05-15 14:15:55 -06:00
### Added/improved functionality - Added proper support for controlling Apple displays. - Added option to show/hide brightness slider. - Added option to show brightness slider for internal display and apple displays (enabled by default). - Replication of built-in and Apple display brightness to corresponding brightness slider. - Added suffix to similarly named displays for better differentiation. - Option to disable slider snapping for finer control + disable slider snapping by default. - Added option to show slider tick marks for better accuracy. - Added option to use window focus instead of mouse to determine which display to control. - Ctrl+Command+Brightness now controls external displays only (Ctrl+Brightness continues to control internal display only) - Added separate tab for menu options. - Added option to restore last saved values upon startup. - Added option for audio device name matching for display volume control selection. - Separated option to change all screens for brightness and volume. - Added option for keyboard fine scale for brightness. - Added option for keyboard fine scale for volume. - Added version check upon startup for mandatory preferences reset upon downgrade or incompatible previous version + notification about this. - Added implementation for Command+F1 macOS shortcut to enable/disable mirroring. - Added safer 'Assume last saved settings are valid' option as default instead of startup DDC read (or restore). - Streamlined preference panes, 'Show advanced settings' now affect all tabs. This leads to a better and safer first timer experience (especially because of the influx of many new features). - Added a Quit button to Preferences if menu is hidden (it was not passible to quit the application until this time in this mode only by re-enabling the menu). - Lowered default first-run volume DDC default from 75% to 15% if read is not possible or disabled to prevent unexpectedly loud sound. - Added slider skew setting on a per control basis to have the ability to manipulate DDC slider balance and OSD scale if display control is not linear. - Added the ability to set min. and max. DDC bounds on a per display, per control basis. - Audio device name override option for a display (manually assign a specific audio device to a display). - Advanced setting to invert DDC control range (some displays have the scale reversed). - Advanced setting to remap DDC control code (some displays have contrast and brightness mixed up). - Ability to mark a DDC control as available or unavailable in advanced settings under Displays. - Ability to automatically hide menu icon if there is no slider present in the menu. - Option to show slider percentage for more precision. - Option to set combined or separate OSD scale when combined hardware+software brightness is used. - Apple like smooth brightness change (both for software, hardware, mixed and DisplayServices). - Added support for DisplayLink, AirPlay, Sidecar, screen sharing etc. using window shades (this is an inferior technique to the existing software implementation - gamma control - but still better than nothing). Disabled for any kind of mirroring setups. _(Only on Big Sur and above)_ - Brightness change synchronisation from Built-In and Apple displays to other displays. This makes Touch Bar, Ambient light sensor, Control Center and System Preferences induced changes affect all displays. Synchronisation uses a sophisticated indirect delta method + the user can intervene and adjust individual screen brightness at any time to easily compensate mismatching native brightness levels. - Preferences pane tab selector has a simpler look on Catalina. - All menu sliders are now scrollable using a magic mouse/trackpad swipes or mouse wheel. - Added option for menu to show only items that are relevant to display which shows the menu currently. - Added option to enable combined sliders (note: this option combined with enabled Apple/built-in display syncing and enabled 'change all' keyboard settings finally provides full synchronised control of all displays). - Combined sliders can now display multiple displays when keyboard and brightness syncing is not enabled. _(Only on Big Sur and above)_ - Redesigned sliders to look like Big Sur/Monterey Control Center's sliders. _(Only on Big Sur and above)_ - Quit and Preferences... are now icons for a much cleaner look. _(Only on Big Sur and above)_ - Added option to change additional menu options style or hide them. _(Only on Big Sur and above)_ - Multiple displays are now in nice Big Sur styled blocks - no more ugly separators. _(Only on Big Sur and above)_ - Added customisable gamma/ddc switchover point for combined brightness in the advanced section of Displays. - Added comma separated list for control code override to enable edge cases like controlling Brightness and Contrast at the same time (use VCP list entry `10, 12` for that) - Contrast can now be controlled from keyboard via <kbd>control</kbd> + <kbd>option</kbd> + <kbd>command</kbd> + brightness up/down. - Custom keyboard shortcuts for brightness, contrast, volume and mute - Added automatic update. ### Other under the hood changes and bug fixes - Standardised internal scale among various displays and DDC ranges for ranged controls. - Uses the new internal scale for combined hardware-software brightness mode. - Migrated scales to internal float representation to prevent loss of fine detail on transformations. - Fixed double sound when muting multiple external displays at the same time. - Fixed lack of initial volume configuration if slider is not shown in menu. - Fixed wrong settings being applied to a display when replaced on Apple Silicon (UserDefaults preferences are now tied to specific display strings instead of CGDirectDisplayID - which is no longer semi-unique on arm64). - A lot of refactoring, streamlining and general optimisations.
277 lines
11 KiB
Swift
277 lines
11 KiB
Swift
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
|
|
|
|
import Cocoa
|
|
import KeyboardShortcuts
|
|
import Preferences
|
|
import ServiceManagement
|
|
|
|
class KeyboardPrefsViewController: NSViewController, PreferencePane {
|
|
let preferencePaneIdentifier = Preferences.PaneIdentifier.keyboard
|
|
let preferencePaneTitle: String = NSLocalizedString("Keyboard", comment: "Shown in the main prefs window")
|
|
|
|
var toolbarItemIcon: NSImage {
|
|
if !DEBUG_MACOS10, #available(macOS 11.0, *) {
|
|
return NSImage(systemSymbolName: "keyboard", accessibilityDescription: "Keyboard")!
|
|
} else {
|
|
return NSImage(named: NSImage.infoName)!
|
|
}
|
|
}
|
|
|
|
@IBOutlet var customBrightnessUp: NSView!
|
|
@IBOutlet var customBrightnessDown: NSView!
|
|
@IBOutlet var customContrastUp: NSView!
|
|
@IBOutlet var customContrastDown: NSView!
|
|
@IBOutlet var customVolumeUp: NSView!
|
|
@IBOutlet var customVolumeDown: NSView!
|
|
@IBOutlet var customMute: NSView!
|
|
|
|
@IBOutlet var keyboardBrightness: NSPopUpButton!
|
|
@IBOutlet var keyboardVolume: NSPopUpButton!
|
|
@IBOutlet var disableAltBrightnessKeys: NSButton!
|
|
|
|
@IBOutlet var allScreens: NSButton!
|
|
@IBOutlet var useFocusInsteadOfMouse: NSButton!
|
|
@IBOutlet var allScreensVolume: NSButton!
|
|
@IBOutlet var useAudioDeviceNameMatching: NSButton!
|
|
@IBOutlet var useFineScale: NSButton!
|
|
@IBOutlet var useFineScaleVolume: NSButton!
|
|
@IBOutlet var separateCombinedScale: NSButton!
|
|
|
|
@IBOutlet var rowKeyboardBrightnessPopUp: NSGridRow!
|
|
@IBOutlet var rowKeyboardBrightnessText: NSGridRow!
|
|
@IBOutlet var rowDisableAltBrightnessKeysCheck: NSGridRow!
|
|
@IBOutlet var rowDisableAltBrightnessKeysText: NSGridRow!
|
|
@IBOutlet var rowCustomBrightnessShortcuts: NSGridRow!
|
|
@IBOutlet var rowUseFocusCheck: NSGridRow!
|
|
@IBOutlet var rowUseFocusText: NSGridRow!
|
|
@IBOutlet var rowCustomAudioShortcuts: NSGridRow!
|
|
@IBOutlet var rowUseAudioNameCheck: NSGridRow!
|
|
@IBOutlet var rowUseAudioNameText: NSGridRow!
|
|
@IBOutlet var rowUseFineScaleCheck: NSGridRow!
|
|
@IBOutlet var rowUseFineScaleText: NSGridRow!
|
|
@IBOutlet var rowSeparateCombinedScaleCheck: NSGridRow!
|
|
@IBOutlet var rowSeparateCombinedScaleText: NSGridRow!
|
|
|
|
func showAdvanced() -> Bool {
|
|
let hide = !prefs.bool(forKey: PrefKey.showAdvancedSettings.rawValue)
|
|
|
|
if self.keyboardBrightness.selectedTag() == KeyboardBrightness.media.rawValue {
|
|
self.rowKeyboardBrightnessPopUp.bottomPadding = hide ? -6 : -13
|
|
self.rowKeyboardBrightnessText.isHidden = hide
|
|
if self.disableAltBrightnessKeys.state == .on {
|
|
self.rowDisableAltBrightnessKeysCheck.isHidden = false
|
|
self.rowDisableAltBrightnessKeysText.isHidden = false
|
|
} else {
|
|
self.rowDisableAltBrightnessKeysCheck.isHidden = hide
|
|
self.rowDisableAltBrightnessKeysText.isHidden = hide
|
|
}
|
|
self.rowCustomBrightnessShortcuts.isHidden = true
|
|
} else if self.keyboardBrightness.selectedTag() == KeyboardBrightness.custom.rawValue {
|
|
self.rowKeyboardBrightnessPopUp.bottomPadding = -6
|
|
self.rowKeyboardBrightnessText.isHidden = true
|
|
self.rowDisableAltBrightnessKeysCheck.isHidden = true
|
|
self.rowDisableAltBrightnessKeysText.isHidden = true
|
|
self.rowCustomBrightnessShortcuts.isHidden = false
|
|
} else if self.keyboardBrightness.selectedTag() == KeyboardBrightness.both.rawValue {
|
|
self.rowKeyboardBrightnessPopUp.bottomPadding = hide ? -6 : -13
|
|
self.rowKeyboardBrightnessText.isHidden = hide
|
|
if self.disableAltBrightnessKeys.state == .on {
|
|
self.rowDisableAltBrightnessKeysCheck.isHidden = false
|
|
self.rowDisableAltBrightnessKeysText.isHidden = false
|
|
} else {
|
|
self.rowDisableAltBrightnessKeysCheck.isHidden = hide
|
|
self.rowDisableAltBrightnessKeysText.isHidden = hide
|
|
}
|
|
self.rowCustomBrightnessShortcuts.isHidden = false
|
|
} else {
|
|
self.rowKeyboardBrightnessPopUp.bottomPadding = -6
|
|
self.rowKeyboardBrightnessText.isHidden = true
|
|
self.rowDisableAltBrightnessKeysCheck.isHidden = true
|
|
self.rowDisableAltBrightnessKeysText.isHidden = true
|
|
self.rowCustomBrightnessShortcuts.isHidden = true
|
|
}
|
|
|
|
if [KeyboardVolume.custom.rawValue, KeyboardVolume.both.rawValue].contains(self.keyboardVolume.selectedTag()) {
|
|
self.rowCustomAudioShortcuts.isHidden = false
|
|
} else {
|
|
self.rowCustomAudioShortcuts.isHidden = true
|
|
}
|
|
|
|
if self.useFocusInsteadOfMouse.state == .on {
|
|
self.rowUseFocusCheck.isHidden = false
|
|
self.rowUseFocusText.isHidden = false
|
|
} else {
|
|
self.rowUseFocusCheck.isHidden = hide
|
|
self.rowUseFocusText.isHidden = hide
|
|
}
|
|
if self.useAudioDeviceNameMatching.state == .on {
|
|
self.rowUseAudioNameCheck.isHidden = false
|
|
self.rowUseAudioNameText.isHidden = false
|
|
} else {
|
|
self.rowUseAudioNameCheck.isHidden = hide
|
|
self.rowUseAudioNameText.isHidden = hide
|
|
}
|
|
|
|
if self.useFineScale.state == .on {
|
|
self.rowUseFineScaleCheck.isHidden = false
|
|
self.rowUseFineScaleText.isHidden = false
|
|
} else {
|
|
self.rowUseFineScaleCheck.isHidden = hide
|
|
self.rowUseFineScaleText.isHidden = hide
|
|
}
|
|
|
|
if self.separateCombinedScale.state == .on {
|
|
self.rowSeparateCombinedScaleCheck.isHidden = false
|
|
self.rowSeparateCombinedScaleText.isHidden = false
|
|
} else {
|
|
self.rowSeparateCombinedScaleCheck.isHidden = hide
|
|
self.rowSeparateCombinedScaleText.isHidden = hide
|
|
}
|
|
|
|
return !hide
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
let customBrightnessUpRecorder = KeyboardShortcuts.RecorderCocoa(for: .brightnessUp)
|
|
self.customBrightnessUp.addSubview(customBrightnessUpRecorder)
|
|
let customBrightnessDownRecorder = KeyboardShortcuts.RecorderCocoa(for: .brightnessDown)
|
|
self.customBrightnessDown.addSubview(customBrightnessDownRecorder)
|
|
let customContrastUpRecorder = KeyboardShortcuts.RecorderCocoa(for: .contrastUp)
|
|
self.customContrastUp.addSubview(customContrastUpRecorder)
|
|
let customContrastDownRecorder = KeyboardShortcuts.RecorderCocoa(for: .contrastDown)
|
|
self.customContrastDown.addSubview(customContrastDownRecorder)
|
|
let customVolumeUpRecorder = KeyboardShortcuts.RecorderCocoa(for: .volumeUp)
|
|
self.customVolumeUp.addSubview(customVolumeUpRecorder)
|
|
let customVolumeDownRecorder = KeyboardShortcuts.RecorderCocoa(for: .volumeDown)
|
|
self.customVolumeDown.addSubview(customVolumeDownRecorder)
|
|
let customMuteRecorder = KeyboardShortcuts.RecorderCocoa(for: .mute)
|
|
self.customMute.addSubview(customMuteRecorder)
|
|
self.populateSettings()
|
|
}
|
|
|
|
func populateSettings() {
|
|
self.keyboardBrightness.selectItem(withTag: prefs.integer(forKey: PrefKey.keyboardBrightness.rawValue))
|
|
self.keyboardVolume.selectItem(withTag: prefs.integer(forKey: PrefKey.keyboardVolume.rawValue))
|
|
self.disableAltBrightnessKeys.state = prefs.bool(forKey: PrefKey.disableAltBrightnessKeys.rawValue) ? .on : .off
|
|
self.allScreens.state = prefs.bool(forKey: PrefKey.allScreensBrightness.rawValue) ? .on : .off
|
|
self.useFocusInsteadOfMouse.state = prefs.bool(forKey: PrefKey.useFocusInsteadOfMouse.rawValue) ? .on : .off
|
|
self.allScreensVolume.state = prefs.bool(forKey: PrefKey.allScreensVolume.rawValue) ? .on : .off
|
|
self.useAudioDeviceNameMatching.state = prefs.bool(forKey: PrefKey.useAudioDeviceNameMatching.rawValue) ? .on : .off
|
|
self.useFineScale.state = prefs.bool(forKey: PrefKey.useFineScaleBrightness.rawValue) ? .on : .off
|
|
self.useFineScaleVolume.state = prefs.bool(forKey: PrefKey.useFineScaleVolume.rawValue) ? .on : .off
|
|
self.separateCombinedScale.state = prefs.bool(forKey: PrefKey.separateCombinedScale.rawValue) ? .on : .off
|
|
self.allScreensClicked(self.allScreens)
|
|
self.allScreensVolumeClicked(self.allScreensVolume)
|
|
_ = self.showAdvanced()
|
|
}
|
|
|
|
@IBAction func allScreensClicked(_ sender: NSButton) {
|
|
switch sender.state {
|
|
case .on:
|
|
prefs.set(true, forKey: PrefKey.allScreensBrightness.rawValue)
|
|
self.useFocusInsteadOfMouse.state = .off
|
|
self.useFocusInsteadOfMouse.isEnabled = false
|
|
case .off:
|
|
prefs.set(false, forKey: PrefKey.allScreensBrightness.rawValue)
|
|
self.useFocusInsteadOfMouse.isEnabled = true
|
|
self.useFocusInsteadOfMouse.state = prefs.bool(forKey: PrefKey.useFocusInsteadOfMouse.rawValue) ? .on : .off
|
|
default: break
|
|
}
|
|
}
|
|
|
|
@IBAction func useFocusInsteadOfMouseClicked(_ sender: NSButton) {
|
|
switch sender.state {
|
|
case .on:
|
|
prefs.set(true, forKey: PrefKey.useFocusInsteadOfMouse.rawValue)
|
|
case .off:
|
|
prefs.set(false, forKey: PrefKey.useFocusInsteadOfMouse.rawValue)
|
|
default: break
|
|
}
|
|
_ = self.showAdvanced()
|
|
}
|
|
|
|
@IBAction func allScreensVolumeClicked(_ sender: NSButton) {
|
|
switch sender.state {
|
|
case .on:
|
|
prefs.set(true, forKey: PrefKey.allScreensVolume.rawValue)
|
|
self.useAudioDeviceNameMatching.state = .off
|
|
self.useAudioDeviceNameMatching.isEnabled = false
|
|
case .off:
|
|
prefs.set(false, forKey: PrefKey.allScreensVolume.rawValue)
|
|
self.useAudioDeviceNameMatching.isEnabled = true
|
|
self.useAudioDeviceNameMatching.state = prefs.bool(forKey: PrefKey.useAudioDeviceNameMatching.rawValue) ? .on : .off
|
|
default: break
|
|
}
|
|
app.updateMediaKeyTap()
|
|
}
|
|
|
|
@IBAction func useAudioDeviceNameMatchingClicked(_ sender: NSButton) {
|
|
switch sender.state {
|
|
case .on:
|
|
prefs.set(true, forKey: PrefKey.useAudioDeviceNameMatching.rawValue)
|
|
case .off:
|
|
prefs.set(false, forKey: PrefKey.useAudioDeviceNameMatching.rawValue)
|
|
default: break
|
|
}
|
|
app.updateMediaKeyTap()
|
|
_ = self.showAdvanced()
|
|
}
|
|
|
|
@IBAction func useFineScaleClicked(_ sender: NSButton) {
|
|
switch sender.state {
|
|
case .on:
|
|
prefs.set(true, forKey: PrefKey.useFineScaleBrightness.rawValue)
|
|
case .off:
|
|
prefs.set(false, forKey: PrefKey.useFineScaleBrightness.rawValue)
|
|
default: break
|
|
}
|
|
_ = self.showAdvanced()
|
|
}
|
|
|
|
@IBAction func useFineScaleVolumeClicked(_ sender: NSButton) {
|
|
switch sender.state {
|
|
case .on:
|
|
prefs.set(true, forKey: PrefKey.useFineScaleVolume.rawValue)
|
|
case .off:
|
|
prefs.set(false, forKey: PrefKey.useFineScaleVolume.rawValue)
|
|
default: break
|
|
}
|
|
}
|
|
|
|
@IBAction func separateCombinedScale(_ sender: NSButton) {
|
|
switch sender.state {
|
|
case .on:
|
|
prefs.set(true, forKey: PrefKey.separateCombinedScale.rawValue)
|
|
case .off:
|
|
prefs.set(false, forKey: PrefKey.separateCombinedScale.rawValue)
|
|
default: break
|
|
}
|
|
_ = self.showAdvanced()
|
|
}
|
|
|
|
@IBAction func disableAltBrightnessKeys(_ sender: NSButton) {
|
|
switch sender.state {
|
|
case .on:
|
|
prefs.set(true, forKey: PrefKey.disableAltBrightnessKeys.rawValue)
|
|
case .off:
|
|
prefs.set(false, forKey: PrefKey.disableAltBrightnessKeys.rawValue)
|
|
default: break
|
|
}
|
|
_ = self.showAdvanced()
|
|
app.updateMediaKeyTap()
|
|
}
|
|
|
|
@IBAction func keyboardBrightness(_ sender: NSPopUpButton) {
|
|
prefs.set(sender.selectedTag(), forKey: PrefKey.keyboardBrightness.rawValue)
|
|
app.updateMenusAndKeys()
|
|
_ = self.showAdvanced()
|
|
}
|
|
|
|
@IBAction func keyboardVolume(_ sender: NSPopUpButton) {
|
|
prefs.set(sender.selectedTag(), forKey: PrefKey.keyboardVolume.rawValue)
|
|
app.updateMenusAndKeys()
|
|
_ = self.showAdvanced()
|
|
}
|
|
}
|