Add Advanced Preferences panel (#97)

- Removed whitelist which caused some issues for some people #105 
- Added `hide OSD` option to `AdvancedPrefsViewController`
- Added `Longer Delay` option to `AdvancedPrefsViewController`
- Added `PollingMode` and `PollingCount` to `AdvancedPrefsViewController` (should help #37)
- Added option to reset all preferences to `AdvancedPrefsViewController`
See the wiki for more info: https://github.com/the0neyouseek/MonitorControl/wiki/Advanced-Preferences
This commit is contained in:
Joni Van Roost 2019-08-28 12:17:35 +02:00 committed by GitHub
parent 98d9952d35
commit dd3d026db3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 894 additions and 108 deletions

View file

@ -7,3 +7,5 @@ excluded:
- Carthage
type_body_length: 500
file_length: 500
cyclomatic_complexity:
ignores_case_statements: true

View file

@ -1,4 +1,4 @@
github "reitermarkus/DDC.swift" "5c03666e17a1a850892c08a5db6f4a208f762f26"
github "rnine/AMCoreAudio" "3.3"
github "reitermarkus/DDC.swift" "f8e7dc7f2fea41ec4e27672fdbc7051be218ad02"
github "rnine/AMCoreAudio" "3.3.1"
github "shpakovski/MASPreferences" "1.3"
github "the0neyouseek/MediaKeyTap" "3.1.0"

View file

@ -26,8 +26,16 @@
28D1DE15227FD006004CB494 /* DDC.framework.dSYM in [Carthage] Copy Framework Debug Symbols */ = {isa = PBXBuildFile; fileRef = 28D1DE11227FD006004CB494 /* DDC.framework.dSYM */; };
56754EAF1D9A4016007BCDC5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56754EAE1D9A4016007BCDC5 /* AppDelegate.swift */; };
56754EB11D9A4016007BCDC5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 56754EB01D9A4016007BCDC5 /* Assets.xcassets */; };
8C0E20562296ABBA000CBF15 /* NSNotification+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C0E20552296ABBA000CBF15 /* NSNotification+Extension.swift */; };
F01B067922822141008E64DB /* Display+Whitelist.swift in Sources */ = {isa = PBXBuildFile; fileRef = F01B067822822141008E64DB /* Display+Whitelist.swift */; };
6C0CCB26228F4F720037D2C5 /* AdvancedPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C0CCB25228F4F720037D2C5 /* AdvancedPrefsViewController.swift */; };
6C20466C23153E4F00859767 /* Display+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C20466B23153E4F00859767 /* Display+Extension.swift */; };
6C2EA1CD228F644B00060E3F /* OnlyIntegerValueFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C2EA1CC228F644B00060E3F /* OnlyIntegerValueFormatter.swift */; };
6C2EA1CF228F7DFB00060E3F /* PollingMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C2EA1CE228F7DFB00060E3F /* PollingMode.swift */; };
6C85EFDA22C941B000227EA1 /* DisplayManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C85EFD922C941B000227EA1 /* DisplayManager.swift */; };
6C85EFDD22CBAA8F00227EA1 /* PollingModeCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C85EFDC22CBAA8F00227EA1 /* PollingModeCellView.swift */; };
6C85EFDF22CBB54100227EA1 /* PollingCountCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C85EFDE22CBB54100227EA1 /* PollingCountCellView.swift */; };
6C85EFE122CC00AD00227EA1 /* NSNotification+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C85EFE022CC00AD00227EA1 /* NSNotification+Extension.swift */; };
6CCB278622D5315200619B05 /* HideOsdCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CCB278522D5315200619B05 /* HideOsdCellView.swift */; };
6CD444C322D4FBB8005BFD3D /* LongerDelayCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CD444C222D4FBB8005BFD3D /* LongerDelayCellView.swift */; };
F01B0699228221B7008E64DB /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F01B0680228221B6008E64DB /* Localizable.strings */; };
F01B069A228221B7008E64DB /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = F01B0683228221B6008E64DB /* Utils.swift */; };
F01B069E228221B7008E64DB /* ButtonCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F01B068E228221B6008E64DB /* ButtonCellView.swift */; };
@ -106,8 +114,16 @@
56754EAB1D9A4016007BCDC5 /* MonitorControl.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MonitorControl.app; sourceTree = BUILT_PRODUCTS_DIR; };
56754EAE1D9A4016007BCDC5 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppDelegate.swift; sourceTree = "<group>"; };
56754EB01D9A4016007BCDC5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
8C0E20552296ABBA000CBF15 /* NSNotification+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSNotification+Extension.swift"; sourceTree = "<group>"; };
F01B067822822141008E64DB /* Display+Whitelist.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Display+Whitelist.swift"; sourceTree = "<group>"; };
6C0CCB25228F4F720037D2C5 /* AdvancedPrefsViewController.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = AdvancedPrefsViewController.swift; sourceTree = "<group>"; tabWidth = 4; };
6C20466B23153E4F00859767 /* Display+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Display+Extension.swift"; sourceTree = "<group>"; };
6C2EA1CC228F644B00060E3F /* OnlyIntegerValueFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnlyIntegerValueFormatter.swift; sourceTree = "<group>"; };
6C2EA1CE228F7DFB00060E3F /* PollingMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollingMode.swift; sourceTree = "<group>"; };
6C85EFD922C941B000227EA1 /* DisplayManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayManager.swift; sourceTree = "<group>"; };
6C85EFDC22CBAA8F00227EA1 /* PollingModeCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollingModeCellView.swift; sourceTree = "<group>"; };
6C85EFDE22CBB54100227EA1 /* PollingCountCellView.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = PollingCountCellView.swift; sourceTree = "<group>"; tabWidth = 4; };
6C85EFE022CC00AD00227EA1 /* NSNotification+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSNotification+Extension.swift"; sourceTree = "<group>"; };
6CCB278522D5315200619B05 /* HideOsdCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HideOsdCellView.swift; sourceTree = "<group>"; };
6CD444C222D4FBB8005BFD3D /* LongerDelayCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongerDelayCellView.swift; sourceTree = "<group>"; };
F01B0681228221B6008E64DB /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
F01B0682228221B6008E64DB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
F01B0683228221B6008E64DB /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
@ -179,10 +195,10 @@
isa = PBXGroup;
children = (
2894D9B72280B30500DF58DA /* CGDirectDisplayID+Extension.swift */,
F01B067822822141008E64DB /* Display+Whitelist.swift */,
28D1DDEC227FB8F2004CB494 /* EDID+Extension.swift */,
28D1DDF1227FBE71004CB494 /* NSScreen+Extension.swift */,
8C0E20552296ABBA000CBF15 /* NSNotification+Extension.swift */,
6C85EFE022CC00AD00227EA1 /* NSNotification+Extension.swift */,
6C20466B23153E4F00859767 /* Display+Extension.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -210,6 +226,8 @@
56754EAD1D9A4016007BCDC5 /* MonitorControl */ = {
isa = PBXGroup;
children = (
F01B0686228221B6008E64DB /* Info.plist */,
6C85EFD622C74B0E00227EA1 /* Manager */,
56754EAE1D9A4016007BCDC5 /* AppDelegate.swift */,
56754EB01D9A4016007BCDC5 /* Assets.xcassets */,
F03A8DF11FFBAA6F0034DC27 /* Display.swift */,
@ -221,13 +239,35 @@
path = MonitorControl;
sourceTree = "<group>";
};
6C85EFD622C74B0E00227EA1 /* Manager */ = {
isa = PBXGroup;
children = (
6C85EFD922C941B000227EA1 /* DisplayManager.swift */,
);
path = Manager;
sourceTree = "<group>";
};
6C85EFDB22CBA77600227EA1 /* Cells */ = {
isa = PBXGroup;
children = (
F03FE4BF228DF62A001F59A4 /* FriendlyNameCellView.swift */,
F01B068E228221B6008E64DB /* ButtonCellView.swift */,
6C85EFDC22CBAA8F00227EA1 /* PollingModeCellView.swift */,
6C85EFDE22CBB54100227EA1 /* PollingCountCellView.swift */,
6CD444C222D4FBB8005BFD3D /* LongerDelayCellView.swift */,
6CCB278522D5315200619B05 /* HideOsdCellView.swift */,
);
path = Cells;
sourceTree = "<group>";
};
F01B067F228221B6008E64DB /* Support */ = {
isa = PBXGroup;
children = (
F01B0685228221B6008E64DB /* Bridging-Header.h */,
F01B0686228221B6008E64DB /* Info.plist */,
F01B0680228221B6008E64DB /* Localizable.strings */,
F01B0683228221B6008E64DB /* Utils.swift */,
6C2EA1CC228F644B00060E3F /* OnlyIntegerValueFormatter.swift */,
6C2EA1CE228F7DFB00060E3F /* PollingMode.swift */,
);
path = Support;
sourceTree = "<group>";
@ -235,8 +275,7 @@
F01B0687228221B6008E64DB /* UI */ = {
isa = PBXGroup;
children = (
F03FE4BF228DF62A001F59A4 /* FriendlyNameCellView.swift */,
F01B068E228221B6008E64DB /* ButtonCellView.swift */,
6C85EFDB22CBA77600227EA1 /* Cells */,
F01B0690228221B7008E64DB /* Main.storyboard */,
F01B0692228221B7008E64DB /* MainMenu.xib */,
F01B068F228221B7008E64DB /* SliderHandler.swift */,
@ -250,6 +289,7 @@
F0445D3F200259C10025AE82 /* DisplayPrefsViewController.swift */,
F0445D3B200254FA0025AE82 /* KeysPrefsViewController.swift */,
F0445D3720023E710025AE82 /* MainPrefsViewController.swift */,
6C0CCB25228F4F720037D2C5 /* AdvancedPrefsViewController.swift */,
);
path = "View Controllers";
sourceTree = "<group>";
@ -320,13 +360,13 @@
TargetAttributes = {
56754EAA1D9A4016007BCDC5 = {
CreatedOnToolsVersion = 8.0;
DevelopmentTeam = KGY56RWR9A;
DevelopmentTeam = CYC8C8R4K9;
LastSwiftMigration = 1020;
ProvisioningStyle = Automatic;
};
F06792E6200A73460066C438 = {
CreatedOnToolsVersion = 9.2;
DevelopmentTeam = KGY56RWR9A;
DevelopmentTeam = CYC8C8R4K9;
LastSwiftMigration = 1020;
ProvisioningStyle = Automatic;
};
@ -454,18 +494,26 @@
buildActionMask = 2147483647;
files = (
56754EAF1D9A4016007BCDC5 /* AppDelegate.swift in Sources */,
6C85EFE122CC00AD00227EA1 /* NSNotification+Extension.swift in Sources */,
6C85EFDF22CBB54100227EA1 /* PollingCountCellView.swift in Sources */,
6C0CCB26228F4F720037D2C5 /* AdvancedPrefsViewController.swift in Sources */,
F01B069E228221B7008E64DB /* ButtonCellView.swift in Sources */,
6C2EA1CD228F644B00060E3F /* OnlyIntegerValueFormatter.swift in Sources */,
2894D9B82280B30500DF58DA /* CGDirectDisplayID+Extension.swift in Sources */,
6CCB278622D5315200619B05 /* HideOsdCellView.swift in Sources */,
6CD444C322D4FBB8005BFD3D /* LongerDelayCellView.swift in Sources */,
F03FE4C0228DF62B001F59A4 /* FriendlyNameCellView.swift in Sources */,
F01B067922822141008E64DB /* Display+Whitelist.swift in Sources */,
6C2EA1CF228F7DFB00060E3F /* PollingMode.swift in Sources */,
F03A8DF21FFBAA6F0034DC27 /* Display.swift in Sources */,
F0445D40200259C10025AE82 /* DisplayPrefsViewController.swift in Sources */,
8C0E20562296ABBA000CBF15 /* NSNotification+Extension.swift in Sources */,
28D1DDF0227FBD99004CB494 /* EDID+Extension.swift in Sources */,
6C85EFDD22CBAA8F00227EA1 /* PollingModeCellView.swift in Sources */,
F0445D3D200254FA0025AE82 /* KeysPrefsViewController.swift in Sources */,
6C20466C23153E4F00859767 /* Display+Extension.swift in Sources */,
F0445D3820023E710025AE82 /* MainPrefsViewController.swift in Sources */,
28D1DDF2227FBE71004CB494 /* NSScreen+Extension.swift in Sources */,
F01B069F228221B7008E64DB /* SliderHandler.swift in Sources */,
6C85EFDA22C941B000227EA1 /* DisplayManager.swift in Sources */,
F01B069A228221B7008E64DB /* Utils.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -641,8 +689,8 @@
CODE_SIGN_IDENTITY = "Mac Developer";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 560;
DEVELOPMENT_TEAM = KGY56RWR9A;
CURRENT_PROJECT_VERSION = 570;
DEVELOPMENT_TEAM = CYC8C8R4K9;
ENABLE_HARDENED_RUNTIME = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -668,8 +716,8 @@
CODE_SIGN_IDENTITY = "Mac Developer";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 560;
DEVELOPMENT_TEAM = KGY56RWR9A;
CURRENT_PROJECT_VERSION = 570;
DEVELOPMENT_TEAM = CYC8C8R4K9;
ENABLE_HARDENED_RUNTIME = YES;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -699,8 +747,8 @@
CODE_SIGN_IDENTITY = "Mac Developer";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 560;
DEVELOPMENT_TEAM = KGY56RWR9A;
CURRENT_PROJECT_VERSION = 570;
DEVELOPMENT_TEAM = CYC8C8R4K9;
ENABLE_HARDENED_RUNTIME = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = MonitorControlHelper/Info.plist;
@ -724,8 +772,8 @@
CODE_SIGN_IDENTITY = "Mac Developer";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 560;
DEVELOPMENT_TEAM = KGY56RWR9A;
CURRENT_PROJECT_VERSION = 570;
DEVELOPMENT_TEAM = CYC8C8R4K9;
ENABLE_HARDENED_RUNTIME = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = MonitorControlHelper/Info.plist;

View file

@ -17,10 +17,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
var monitorItems: [NSMenuItem] = []
var displays: [Display] = []
let step = 100 / 16
var displayManager: DisplayManager?
var mediaKeyTap: MediaKeyTap?
var prefsController: NSWindowController?
@ -29,7 +29,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_: Notification) {
app = self
self.setupLayout()
self.displayManager = DisplayManager()
self.setupViewControllers()
self.subscribeEventListeners()
self.startOrRestartMediaKeyTap()
self.statusItem.image = NSImage(named: "status")
@ -40,11 +41,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
self.updateDisplays()
}
func applicationWillTerminate(_: Notification) {
AMCoreAudio.NotificationCenter.defaultCenter.unsubscribe(self, eventType: AudioHardwareEvent.self)
DistributedNotificationCenter.default().removeObserver(self.accessibilityObserver as Any, name: .accessibilityApi, object: nil)
}
@IBAction func quitClicked(_: AnyObject) {
NSApplication.shared.terminate(self)
}
@ -59,7 +55,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
/// Set the default prefs of the app
func setDefaultPrefs() {
let prefs = UserDefaults.standard
if !prefs.bool(forKey: Utils.PrefKeys.appAlreadyLaunched.rawValue) {
prefs.set(true, forKey: Utils.PrefKeys.appAlreadyLaunched.rawValue)
@ -83,7 +78,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
self.monitorItems = []
self.displays = []
self.displayManager?.clearDisplays()
}
func updateDisplays() {
@ -94,7 +89,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
if screen.isBuiltin {
return false
}
return DDC(for: screen.displayID)?.edid() != nil
}
@ -128,8 +122,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
if let edid = ddc?.edid() {
let name = Utils.getDisplayName(forEdid: edid)
let isEnabled = (prefs.object(forKey: "\(id)-state") as? Bool) ?? true
let display = Display(id, name: name, isBuiltin: screen.isBuiltin)
let display = Display(id, name: name, isBuiltin: screen.isBuiltin, isEnabled: isEnabled)
let monitorSubMenu: NSMenu = asSubMenu ? NSMenu() : self.statusMenu
@ -153,7 +148,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
display.volumeSliderHandler = volumeSliderHandler
display.brightnessSliderHandler = brightnessSliderHandler
self.displays.append(display)
self.displayManager?.addDisplay(display: display)
let monitorMenuItem = NSMenuItem()
monitorMenuItem.title = "\(display.getFriendlyName())"
@ -166,27 +161,39 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
}
private func setupLayout() {
private func setupViewControllers() {
let storyboard: NSStoryboard = NSStoryboard(name: "Main", bundle: Bundle.main)
let mainPrefsVc = storyboard.instantiateController(withIdentifier: "MainPrefsVC")
let keyPrefsVc = storyboard.instantiateController(withIdentifier: "KeysPrefsVC")
let displayPrefsVc = storyboard.instantiateController(withIdentifier: "DisplayPrefsVC")
let advancedPrefsVc = storyboard.instantiateController(withIdentifier: "AdvancedPrefsVC")
let views = [
storyboard.instantiateController(withIdentifier: "MainPrefsVC"),
storyboard.instantiateController(withIdentifier: "KeysPrefsVC"),
storyboard.instantiateController(withIdentifier: "DisplayPrefsVC"),
mainPrefsVc,
keyPrefsVc,
displayPrefsVc,
advancedPrefsVc,
]
prefsController = MASPreferencesWindowController(viewControllers: views, title: NSLocalizedString("Preferences", comment: "Shown in Preferences window"))
if let displayPrefs = displayPrefsVc as? DisplayPrefsViewController {
displayPrefs.displayManager = self.displayManager
}
if let advancedPrefs = advancedPrefsVc as? AdvancedPrefsViewController {
advancedPrefs.displayManager = self.displayManager
}
}
private func subscribeEventListeners() {
// subscribe KeyTap event listener
NotificationCenter.default.addObserver(self, selector: #selector(handleListenForChanged), name: NSNotification.Name(Utils.PrefKeys.listenFor.rawValue), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleShowContrastChanged), name: NSNotification.Name(Utils.PrefKeys.showContrast.rawValue), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleFriendlyNameChanged), name: NSNotification.Name(Utils.PrefKeys.friendlyName.rawValue), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleListenForChanged), name: .listenFor, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleShowContrastChanged), name: .showContrast, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleFriendlyNameChanged), name: .friendlyName, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handlePreferenceReset), name: .preferenceReset, object: nil)
// subscribe Audio output detector (AMCoreAudio)
AMCoreAudio.NotificationCenter.defaultCenter.subscribe(self, eventType: AudioHardwareEvent.self, dispatchQueue: DispatchQueue.main)
// listen for accessibility status changes
self.accessibilityObserver = DistributedNotificationCenter.default().addObserver(forName: .accessibilityApi, object: nil, queue: nil) { _ in
_ = DistributedNotificationCenter.default().addObserver(forName: .accessibilityApi, object: nil, queue: nil) { _ in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.startOrRestartMediaKeyTap()
}
@ -198,9 +205,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
extension AppDelegate: MediaKeyTapDelegate {
func handle(mediaKey: MediaKey, event _: KeyEvent?, modifiers: NSEvent.ModifierFlags?) {
let displays = self.displayManager?.getDisplays() ?? [Display]()
guard let currentDisplay = Utils.getCurrentDisplay(from: displays) else { return }
let allDisplays = prefs.bool(forKey: Utils.PrefKeys.allScreens.rawValue) ? self.displays : [currentDisplay]
let allDisplays = prefs.bool(forKey: Utils.PrefKeys.allScreens.rawValue) ? displays : [currentDisplay]
let isSmallIncrement = modifiers?.isSuperset(of: NSEvent.ModifierFlags([.shift, .option])) ?? false
for display in allDisplays {
@ -241,6 +249,12 @@ extension AppDelegate: MediaKeyTapDelegate {
self.updateDisplays()
}
@objc func handlePreferenceReset() {
self.setDefaultPrefs()
self.updateDisplays()
self.startOrRestartMediaKeyTap()
}
private func startOrRestartMediaKeyTap() {
var keys: [MediaKey]

View file

@ -14,6 +14,26 @@ class Display {
var contrastSliderHandler: SliderHandler?
var ddc: DDC?
var hideOsd: Bool {
get {
return self.prefs.bool(forKey: "hideOsd-\(self.identifier)")
}
set {
self.prefs.set(newValue, forKey: "hideOsd-\(self.identifier)")
os_log("Set `hideOsd` to: %{public}@", type: .info, String(newValue))
}
}
var needsLongerDelay: Bool {
get {
return self.prefs.object(forKey: "longerDelay-\(self.identifier)") as? Bool ?? false
}
set {
self.prefs.set(newValue, forKey: "longerDelay-\(self.identifier)")
os_log("Set `needsLongerDisplay` to: %{public}@", type: .info, String(newValue))
}
}
private let prefs = UserDefaults.standard
private var audioPlayer: AVAudioPlayer?
@ -67,7 +87,9 @@ class Display {
}
if let slider = volumeSliderHandler?.slider {
slider.intValue = Int32(value)
DispatchQueue.main.async {
slider.intValue = Int32(value)
}
}
}
@ -90,7 +112,9 @@ class Display {
}
if let slider = volumeSliderHandler?.slider {
slider.intValue = Int32(value)
DispatchQueue.main.async {
slider.intValue = Int32(value)
}
}
self.saveValue(value, for: .audioSpeakerVolume)
@ -161,6 +185,46 @@ class Display {
return self.prefs.string(forKey: "friendlyName-\(self.identifier)") ?? self.name
}
func setPollingMode(_ value: Int) {
self.prefs.set(String(value), forKey: "pollingMode-\(self.identifier)")
}
/*
Polling Modes:
0 -> .none -> 0 tries
1 -> .minimal -> 5 tries
2 -> .normal -> 10 tries
3 -> .heavy -> 100 tries
4 -> .custom -> $pollingCount tries
*/
func getPollingMode() -> Int {
// Reading as string so we don't get "0" as the default value
return Int(self.prefs.string(forKey: "pollingMode-\(self.identifier)") ?? "2") ?? 2
}
func getPollingCount() -> Int {
let selectedMode = self.getPollingMode()
switch selectedMode {
case 0:
return PollingMode.none.value
case 1:
return PollingMode.minimal.value
case 2:
return PollingMode.normal.value
case 3:
return PollingMode.heavy.value
case 4:
let val = self.prefs.integer(forKey: "pollingCount-\(self.identifier)")
return PollingMode.custom(value: val).value
default:
return 0
}
}
func setPollingCount(_ value: Int) {
self.prefs.set(value, forKey: "pollingCount-\(self.identifier)")
}
private func showOsd(command: DDC.Command, value: Int, isSmallIncrement: Bool = false) {
guard let manager = OSDManager.sharedManager() as? OSDManager else {
return

View file

@ -0,0 +1,7 @@
import Foundation
extension Display: Equatable {
static func == (lhs: Display, rhs: Display) -> Bool {
return lhs.identifier == rhs.identifier
}
}

View file

@ -1,29 +0,0 @@
extension Display {
enum WhitelistReason {
case longerDelay
case hideOsd
}
static let whitelist: [UInt32: [UInt32: [WhitelistReason]]] = [
7789: [
30460: [.hideOsd, .longerDelay], // LG 38UC99-W over DisplayPort
30459: [.hideOsd, .longerDelay], // LG 38UC99-W over HDMI
],
]
var hideOsd: Bool {
guard let vendor = self.identifier.vendorNumber, let model = self.identifier.modelNumber else {
return false
}
return Display.whitelist[vendor]?[model]?.contains(.hideOsd) ?? false
}
var needsLongerDelay: Bool {
guard let vendor = self.identifier.vendorNumber, let model = self.identifier.modelNumber else {
return false
}
return Display.whitelist[vendor]?[model]?.contains(.longerDelay) ?? false
}
}

View file

@ -2,4 +2,9 @@ import Cocoa
extension NSNotification.Name {
static let accessibilityApi = NSNotification.Name(rawValue: "com.apple.accessibility.api")
static let listenFor = NSNotification.Name(rawValue: Utils.PrefKeys.listenFor.rawValue)
static let showContrast = NSNotification.Name(rawValue: Utils.PrefKeys.showContrast.rawValue)
static let friendlyName = NSNotification.Name(rawValue: Utils.PrefKeys.friendlyName.rawValue)
static let preferenceReset = NSNotification.Name(rawValue: Utils.PrefKeys.preferenceReset.rawValue)
static let displayListUpdate = NSNotification.Name(rawValue: Utils.PrefKeys.displayListUpdate.rawValue)
}

View file

@ -0,0 +1,35 @@
import Foundation
class DisplayManager {
private var displays: [Display] {
didSet {
NotificationCenter.default.post(name: Notification.Name(Utils.PrefKeys.displayListUpdate.rawValue), object: nil)
}
}
init() {
self.displays = []
}
func updateDisplays(displays: [Display]) {
self.displays = displays
}
func getDisplays() -> [Display] {
return self.displays
}
func addDisplay(display: Display) {
self.displays.append(display)
}
func updateDisplay(display updatedDisplay: Display) {
if let indexToUpdate = self.displays.firstIndex(of: updatedDisplay) {
self.displays[indexToUpdate] = updatedDisplay
}
}
func clearDisplays() {
self.displays = []
}
}

View file

@ -0,0 +1,15 @@
import Cocoa
class OnlyIntegerValueFormatter: NumberFormatter {
override func isPartialStringValid(_ partialString: String, newEditingString _: AutoreleasingUnsafeMutablePointer<NSString?>?, errorDescription _: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
if partialString.isEmpty {
return true
}
if partialString.count > 3 {
return false
}
return Int(partialString) != nil
}
}

View file

@ -0,0 +1,24 @@
import Foundation
enum PollingMode {
case none
case minimal
case normal
case heavy
case custom(value: Int)
var value: Int {
switch self {
case .none:
return 0
case .minimal:
return 5
case .normal:
return 10
case .heavy:
return 100
case let .custom(val):
return val
}
}
}

View file

@ -1,6 +1,7 @@
import Cocoa
import DDC
import os.log
import ServiceManagement
class Utils: NSObject {
// MARK: - Menu
@ -56,7 +57,12 @@ class Utils: NSObject {
os_log("Display does not support enabling DDC application report.", type: .debug)
}
values = display.ddc?.read(command: command, tries: 10, minReplyDelay: delay)
let tries = UInt(display.getPollingCount())
os_log("Polling %{public}@ times", type: .info, String(tries))
if tries != 0 {
values = display.ddc?.read(command: command, tries: tries, minReplyDelay: delay)
}
let (currentValue, maxValue) = values ?? (UInt16(display.getValue(for: command)), UInt16(display.getMaxValue(for: command)))
@ -94,6 +100,12 @@ class Utils: NSObject {
return
}
static func setStartAtLogin(enabled: Bool) {
let identifier = "\(Bundle.main.bundleIdentifier!)Helper" as CFString
SMLoginItemSetEnabled(identifier, enabled)
os_log("Toggle start at login state: %{public}@", type: .info, enabled ? "on" : "off")
}
static func getSystemPreferences() -> [String: AnyObject]? {
var propertyListFormat = PropertyListSerialization.PropertyListFormat.xml
let plistPath = NSString(string: "~/Library/Preferences/.GlobalPreferences.plist").expandingTildeInPath
@ -154,6 +166,12 @@ class Utils: NSObject {
/// Friendly name changed
case friendlyName
/// Prefs Reset
case preferenceReset
/// Used for notification when displays are updated in DisplayManager
case displayListUpdate
}
/// Keys for the value of listenFor option

View file

@ -473,5 +473,339 @@
</objects>
<point key="canvasLocation" x="162" y="218"/>
</scene>
<!--Advanced Prefs View Controller-->
<scene sceneID="tdo-vT-sVy">
<objects>
<viewController storyboardIdentifier="AdvancedPrefsVC" id="xjG-x0-7HO" customClass="AdvancedPrefsViewController" customModule="MonitorControl" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="MqL-7s-HoR">
<rect key="frame" x="0.0" y="0.0" width="628" height="267"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="moz-ZW-I46">
<rect key="frame" x="18" y="218" width="118" height="29"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Advanced" id="5wk-Dy-0fG">
<font key="font" metaFont="systemBold" size="24"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<scrollView autohidesScrollers="YES" horizontalLineScroll="22" horizontalPageScroll="10" verticalLineScroll="22" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VCP-xz-0jI">
<rect key="frame" x="20" y="61" width="588" height="83"/>
<clipView key="contentView" drawsBackground="NO" id="EAu-T4-lFV">
<rect key="frame" x="1" y="0.0" width="586" height="82"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="20" rowSizeStyle="automatic" headerView="qVc-cy-LaW" viewBased="YES" id="t7B-Q7-Ssj">
<rect key="frame" x="0.0" y="0.0" width="586" height="57"/>
<autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn width="122" minWidth="40" maxWidth="1000" id="dNl-I0-hcg">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Display Name">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" title="Text Cell" id="PZ9-0Z-K6J">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="OH7-e2-Xoh" customClass="FriendlyNameCellView" customModule="MonitorControl" customModuleProvider="target">
<rect key="frame" x="1" y="1" width="122" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="aqx-sQ-YMe">
<rect key="frame" x="0.0" y="3" width="122" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" title="Table View Cell" id="Euj-aT-PWW">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="aqx-sQ-YMe" id="3eJ-Tg-zKo"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn width="108" minWidth="10" maxWidth="3.4028234663852886e+38" id="JKW-oY-bSb">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="ID">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" alignment="left" title="Text Cell" id="UBV-gO-AZz">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="YSF-wp-a89">
<rect key="frame" x="126" y="1" width="108" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4mP-DS-UcD">
<rect key="frame" x="0.0" y="3" width="108" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="5xu-ja-W21">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="4mP-DS-UcD" id="Du8-3S-RN7"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn width="95" minWidth="10" maxWidth="3.4028234663852886e+38" id="gxn-NH-Qhb">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Polling Mode">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tableHeaderCell>
<popUpButtonCell key="dataCell" type="bevel" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="bezel" imageScaling="proportionallyDown" inset="2" arrowPosition="arrowAtCenter" preferredEdge="maxY" id="1vY-Fh-0Cn">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="fyQ-11-vzK"/>
</popUpButtonCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="ZCd-vc-3fw" customClass="PollingModeCellView" customModule="MonitorControl" customModuleProvider="target">
<rect key="frame" x="237" y="1" width="95" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="7FH-uw-jcn">
<rect key="frame" x="6" y="-3" width="85" height="24"/>
<constraints>
<constraint firstAttribute="height" constant="20" id="2Bp-6e-RcJ"/>
</constraints>
<popUpButtonCell key="cell" type="push" title="Normal" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="2" imageScaling="proportionallyDown" inset="2" selectedItem="Riq-uM-bTs" id="M5p-a2-UEs">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="Nil-kM-Hvj">
<items>
<menuItem title="None" id="FoA-yh-Yx3"/>
<menuItem title="Minimal" tag="1" id="Eq3-z9-yIo"/>
<menuItem title="Normal" state="on" tag="2" id="Riq-uM-bTs"/>
<menuItem title="Heavy" tag="3" id="vik-vN-bJe"/>
<menuItem title="Custom" tag="4" id="Cle-DD-vR7"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="valueChanged:" target="ZCd-vc-3fw" id="7w0-4K-RjT"/>
</connections>
</popUpButton>
</subviews>
<constraints>
<constraint firstItem="7FH-uw-jcn" firstAttribute="centerY" secondItem="ZCd-vc-3fw" secondAttribute="centerY" id="7vv-CC-KvO"/>
<constraint firstItem="7FH-uw-jcn" firstAttribute="centerX" secondItem="ZCd-vc-3fw" secondAttribute="centerX" id="WoT-d3-OhY"/>
</constraints>
<connections>
<outlet property="pollingModeMenu" destination="M5p-a2-UEs" id="qXA-qI-8fN"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn width="99" minWidth="10" maxWidth="3.4028234663852886e+38" id="ytT-up-Dhs">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Polling Count">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="H4a-c9-LcB">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="dEe-qK-55M" customClass="PollingCountCellView" customModule="MonitorControl" customModuleProvider="target">
<rect key="frame" x="335" y="1" width="99" height="20"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2ys-EO-duG">
<rect key="frame" x="0.0" y="3" width="99" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" enabled="NO" title="Table View Cell" id="LZ8-Tj-mCs">
<numberFormatter key="formatter" formatterBehavior="default10_4" usesGroupingSeparator="NO" groupingSize="0" minimumIntegerDigits="0" maximumIntegerDigits="42" id="Sfn-Gs-w6w" customClass="OnlyIntegerValueFormatter" customModule="MonitorControl" customModuleProvider="target"/>
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<action selector="valueChanged:" target="dEe-qK-55M" id="YkW-av-OBC"/>
</connections>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="2ys-EO-duG" id="elf-GU-vnP"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn width="79" minWidth="40" maxWidth="1000" id="grO-Kr-l4d">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Longer Delay">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<customCell key="dataCell" alignment="left" id="fNO-9H-lO8"/>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="cOJ-Ph-muT" customClass="LongerDelayCellView" customModule="MonitorControl" customModuleProvider="target">
<rect key="frame" x="437" y="1" width="79" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cmY-eY-p4P">
<rect key="frame" x="0.0" y="0.0" width="49" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="overlaps" inset="2" id="XFk-DD-r8N">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="buttonToggled:" target="cOJ-Ph-muT" id="zaI-dE-Adg"/>
</connections>
</button>
</subviews>
<connections>
<outlet property="button" destination="cmY-eY-p4P" id="2qv-ZX-ifA"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
<tableColumn width="65" minWidth="40" maxWidth="1000" id="MPF-Mr-zVU">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Hide OSD">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<customCell key="dataCell" alignment="left" id="LXT-s7-Hac"/>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="iWx-Xs-DXV" customClass="HideOsdCellView" customModule="MonitorControl" customModuleProvider="target">
<rect key="frame" x="519" y="1" width="65" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="rW9-Zy-n6z">
<rect key="frame" x="0.0" y="0.0" width="49" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" bezelStyle="regularSquare" imagePosition="overlaps" inset="2" id="5mh-qn-QcE">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="buttonToggled:" target="iWx-Xs-DXV" id="liH-GI-qNw"/>
</connections>
</button>
</subviews>
<connections>
<outlet property="button" destination="rW9-Zy-n6z" id="9JR-jX-ogx"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
</tableColumns>
<connections>
<outlet property="dataSource" destination="xjG-x0-7HO" id="yPI-ND-kmF"/>
<outlet property="delegate" destination="xjG-x0-7HO" id="o1o-wb-TQA"/>
</connections>
</tableView>
</subviews>
<nil key="backgroundColor"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="7ew-dx-qzM">
<rect key="frame" x="-100" y="-100" width="358" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="9J2-D1-8Yx">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<tableHeaderView key="headerView" id="qVc-cy-LaW">
<rect key="frame" x="0.0" y="0.0" width="586" height="25"/>
<autoresizingMask key="autoresizingMask"/>
</tableHeaderView>
</scrollView>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="bct-zW-zKf">
<rect key="frame" x="14" y="13" width="151" height="32"/>
<buttonCell key="cell" type="push" title="Reset Preferences" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="4Pj-3t-PJr">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="resetPrefsClicked:" target="xjG-x0-7HO" id="rET-YD-lIt"/>
</connections>
</button>
<stackView distribution="fillProportionally" orientation="horizontal" alignment="centerY" spacing="0.0" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VYW-sX-LN2">
<rect key="frame" x="20" y="164" width="586" height="34"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="Z6T-PG-PcF">
<rect key="frame" x="-2" y="0.0" width="567" height="34"/>
<textFieldCell key="cell" selectable="YES" id="frw-j2-tE1">
<font key="font" metaFont="system"/>
<string key="title">Warning ⚠️
Changing some of these setting may cause system freezes or unexpected behaviour. </string>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button toolTip="More Info" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="kqn-gU-mZX">
<rect key="frame" x="561" y="3" width="27" height="25"/>
<buttonCell key="cell" type="help" bezelStyle="helpButton" imagePosition="overlaps" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="H6v-Sv-5MG">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="helpClicked:" target="xjG-x0-7HO" id="37V-Cf-oZ0"/>
</connections>
</button>
</subviews>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
</subviews>
<constraints>
<constraint firstItem="VYW-sX-LN2" firstAttribute="leading" secondItem="MqL-7s-HoR" secondAttribute="leading" constant="20" id="CUY-YM-yrE"/>
<constraint firstItem="VCP-xz-0jI" firstAttribute="leading" secondItem="MqL-7s-HoR" secondAttribute="leading" constant="20" id="EXX-P9-jE2"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="bct-zW-zKf" secondAttribute="trailing" constant="20" id="FI6-Dj-LYL"/>
<constraint firstItem="VYW-sX-LN2" firstAttribute="width" secondItem="t7B-Q7-Ssj" secondAttribute="width" id="HDw-Th-5gb"/>
<constraint firstItem="moz-ZW-I46" firstAttribute="leading" secondItem="MqL-7s-HoR" secondAttribute="leading" constant="20" id="Kh2-TM-Ugz"/>
<constraint firstAttribute="bottom" secondItem="bct-zW-zKf" secondAttribute="bottom" constant="20" id="Kr9-Xc-ADk"/>
<constraint firstItem="moz-ZW-I46" firstAttribute="top" secondItem="MqL-7s-HoR" secondAttribute="top" constant="20" id="MfN-q8-NmM"/>
<constraint firstItem="VYW-sX-LN2" firstAttribute="top" secondItem="moz-ZW-I46" secondAttribute="bottom" constant="20" id="P9l-74-III"/>
<constraint firstAttribute="trailing" secondItem="VCP-xz-0jI" secondAttribute="trailing" constant="20" id="Vxd-7K-b1F"/>
<constraint firstItem="VCP-xz-0jI" firstAttribute="top" secondItem="VYW-sX-LN2" secondAttribute="bottom" constant="20" id="ccu-p4-dcq"/>
<constraint firstItem="bct-zW-zKf" firstAttribute="top" secondItem="VCP-xz-0jI" secondAttribute="bottom" constant="20" id="dgi-9A-uCh"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="moz-ZW-I46" secondAttribute="trailing" constant="20" id="nUH-DD-Xsg"/>
<constraint firstItem="bct-zW-zKf" firstAttribute="leading" secondItem="MqL-7s-HoR" secondAttribute="leading" constant="20" id="qvo-d9-w65"/>
</constraints>
</view>
<connections>
<outlet property="displayList" destination="t7B-Q7-Ssj" id="ZaZ-6Q-b35"/>
</connections>
</viewController>
<customObject id="lVu-28-yuM" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="380" y="544.5"/>
</scene>
</scenes>
</document>

View file

@ -3,7 +3,6 @@ import os.log
class FriendlyNameCellView: NSTableCellView {
var display: Display?
let prefs = UserDefaults.standard
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)

View file

@ -0,0 +1,25 @@
import Cocoa
import os.log
class HideOsdCellView: NSTableCellView {
@IBOutlet var button: NSButton!
var display: Display?
let prefs = UserDefaults.standard
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
}
@IBAction func buttonToggled(_ sender: NSButton) {
if let display = display {
switch sender.state {
case .on:
display.hideOsd = true
case .off:
display.hideOsd = false
default:
break
}
}
}
}

View file

@ -0,0 +1,41 @@
import Cocoa
import os.log
class LongerDelayCellView: NSTableCellView {
@IBOutlet var button: NSButton!
var display: Display?
let prefs = UserDefaults.standard
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
}
@IBAction func buttonToggled(_ sender: NSButton) {
if let display = self.display {
switch sender.state {
case .on:
let alert: NSAlert = NSAlert()
alert.messageText = NSLocalizedString("Enable Longer Delay?", comment: "Shown in the alert dialog")
alert.informativeText = NSLocalizedString("Are you sure you want to enable a longer delay? Doing so may freeze your system and require a restart. Start at login will be disabled as a safety measure.", comment: "Shown in the alert dialog")
alert.addButton(withTitle: NSLocalizedString("Yes", comment: "Shown in the alert dialog"))
alert.addButton(withTitle: NSLocalizedString("No", comment: "Shown in the alert dialog"))
alert.alertStyle = NSAlert.Style.critical
if let window = self.window {
alert.beginSheetModal(for: window, completionHandler: { modalResponse in
if modalResponse == NSApplication.ModalResponse.alertFirstButtonReturn {
Utils.setStartAtLogin(enabled: false)
display.needsLongerDelay = true
} else {
sender.state = .off
}
})
}
case .off:
display.needsLongerDelay = false
default:
break
}
}
}
}

View file

@ -0,0 +1,24 @@
import Cocoa
import os.log
class PollingCountCellView: NSTableCellView {
var display: Display?
@IBAction func valueChanged(_ sender: NSTextField) {
if let display = display {
let newValue = sender.stringValue
let originalValue = "\(display.getPollingCount())"
if newValue.isEmpty {
self.textField?.stringValue = originalValue
}
if newValue != originalValue,
!newValue.isEmpty,
let newValue = Int(newValue) {
display.setPollingCount(newValue)
os_log("Value changed for polling count: %{public}@", type: .info, "from `\(originalValue)` to `\(newValue)`")
}
}
}
}

View file

@ -0,0 +1,31 @@
import Cocoa
import os.log
/*
menu tags:
0: none
1: minimal
2: normal
3: heavy
4: custom
We use these tags as a way to mark selection
*/
class PollingModeCellView: NSTableCellView {
var display: Display?
@IBOutlet var pollingModeMenu: NSPopUpButtonCell!
var didChangePollingMode: ((_ pollingModeInt: Int) -> Void)?
@IBAction func valueChanged(_ sender: NSPopUpButton) {
if let display = display {
let newValue = sender.selectedTag()
let originalValue = display.getPollingMode()
if newValue != originalValue {
display.setPollingMode(newValue)
self.didChangePollingMode?(newValue)
os_log("Value changed for polling count: %{public}@", type: .info, "from `\(originalValue)` to `\(newValue)`")
}
}
}
}

View file

@ -0,0 +1,132 @@
import Cocoa
import DDC
import MASPreferences
import os.log
class AdvancedPrefsViewController: NSViewController, MASPreferencesViewController, NSTableViewDataSource, NSTableViewDelegate {
var viewIdentifier: String = "Advanced"
var toolbarItemLabel: String? = NSLocalizedString("Advanced", comment: "Shown in the main prefs window")
var toolbarItemImage: NSImage? = NSImage(named: NSImage.advancedName)
let prefs = UserDefaults.standard
var displays: [Display] = []
var displayManager: DisplayManager?
enum DisplayColumn: Int {
case friendlyName
case identifier
case pollingMode
case pollingCount
case longerDelay
case hideOsd
}
@IBOutlet var displayList: NSTableView!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.loadDisplayList), name: .displayListUpdate, object: nil)
self.loadDisplayList()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@IBAction func resetPrefsClicked(_: NSButton) {
let alert: NSAlert = NSAlert()
alert.messageText = NSLocalizedString("Reset Preferences?", comment: "Shown in the alert dialog")
alert.informativeText = NSLocalizedString("Are you sure you want to reset all preferences?", comment: "Shown in the alert dialog")
alert.addButton(withTitle: NSLocalizedString("Yes", comment: "Shown in the alert dialog"))
alert.addButton(withTitle: NSLocalizedString("No", comment: "Shown in the alert dialog"))
alert.alertStyle = NSAlert.Style.warning
if let window = self.view.window {
alert.beginSheetModal(for: window, completionHandler: { modalResponse in
if modalResponse == NSApplication.ModalResponse.alertFirstButtonReturn {
if let bundleID = Bundle.main.bundleIdentifier {
UserDefaults.standard.removePersistentDomain(forName: bundleID)
NotificationCenter.default.post(name: Notification.Name(Utils.PrefKeys.preferenceReset.rawValue), object: nil)
os_log("Resetting all preferences.")
}
}
})
}
}
@IBAction func helpClicked(_: NSButton) {
if let url = URL(string: "https://github.com/the0neyouseek/MonitorControl/wiki/Advanced-Preferences") {
NSWorkspace.shared.open(url)
}
}
@objc func loadDisplayList() {
if let displays = displayManager?.getDisplays() {
self.displays = displays
self.displayList.reloadData()
}
}
func numberOfRows(in _: NSTableView) -> Int {
return self.displays.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
guard let tableColumn = tableColumn,
let columnIndex = tableView.tableColumns.firstIndex(of: tableColumn),
let column = DisplayColumn(rawValue: columnIndex) else {
return nil
}
let display = self.displays[row]
let pollingMode = display.getPollingMode()
switch column {
case .pollingMode:
if let cell = tableView.makeView(withIdentifier: tableColumn.identifier, owner: nil) as? PollingModeCellView {
cell.display = display
cell.pollingModeMenu.selectItem(withTag: pollingMode)
cell.didChangePollingMode = { _ in
// if the polling mode changed, reload the row so we can enable/disable the PollingCount field
tableView.reloadData(forRowIndexes: [row], columnIndexes: [DisplayColumn.pollingCount.rawValue])
}
return cell
}
case .pollingCount:
if let cell = tableView.makeView(withIdentifier: tableColumn.identifier, owner: nil) as? PollingCountCellView {
cell.textField?.stringValue = "\(display.getPollingCount())"
cell.display = display
cell.textField?.isEnabled = pollingMode == 4
return cell
}
case .longerDelay:
if let cell = tableView.makeView(withIdentifier: tableColumn.identifier, owner: nil) as? LongerDelayCellView {
cell.button.state = display.needsLongerDelay ? .on : .off
cell.display = display
return cell
}
case .hideOsd:
if let cell = tableView.makeView(withIdentifier: tableColumn.identifier, owner: nil) as? HideOsdCellView {
cell.button.state = display.hideOsd ? .on : .off
cell.display = display
return cell
}
default:
if let cell = tableView.makeView(withIdentifier: tableColumn.identifier, owner: nil) as? NSTableCellView {
cell.textField?.stringValue = self.getText(for: column, with: display)
return cell
}
}
return nil
}
private func getText(for column: DisplayColumn, with display: Display) -> String {
switch column {
case .friendlyName:
return display.getFriendlyName()
case .identifier:
return "\(display.identifier)"
default:
return ""
}
}
}

View file

@ -10,6 +10,8 @@ class DisplayPrefsViewController: NSViewController, MASPreferencesViewController
let prefs = UserDefaults.standard
var displays: [Display] = []
var displayManager: DisplayManager?
enum DisplayColumn: Int {
case checkbox
case ddc
@ -25,12 +27,19 @@ class DisplayPrefsViewController: NSViewController, MASPreferencesViewController
override func viewDidLoad() {
super.viewDidLoad()
self.allScreens.state = self.prefs.bool(forKey: Utils.PrefKeys.allScreens.rawValue) ? .on : .off
NotificationCenter.default.addObserver(self, selector: #selector(self.loadDisplayList), name: .displayListUpdate, object: nil)
self.loadDisplayList()
}
override func viewWillAppear() {
super.viewWillAppear()
self.allScreens.state = self.prefs.bool(forKey: Utils.PrefKeys.allScreens.rawValue) ? .on : .off
}
deinit {
NotificationCenter.default.removeObserver(self)
}
@IBAction func allScreensTouched(_ sender: NSButton) {
switch sender.state {
case .on:
@ -47,17 +56,10 @@ class DisplayPrefsViewController: NSViewController, MASPreferencesViewController
// MARK: - Table datasource
func loadDisplayList() {
for screen in NSScreen.screens {
let id = screen.displayID
let name = screen.displayName ?? NSLocalizedString("Unknown", comment: "Unknown display name")
let isEnabled = (prefs.object(forKey: "\(id)-state") as? Bool) ?? true
let display = Display(id, name: name, isBuiltin: screen.isBuiltin, isEnabled: isEnabled)
self.displays.append(display)
@objc func loadDisplayList() {
if let displays = self.displayManager?.getDisplays() {
self.displays = displays
}
self.displayList.reloadData()
}

View file

@ -10,9 +10,8 @@ class KeysPrefsViewController: NSViewController, MASPreferencesViewController {
@IBOutlet var listenFor: NSPopUpButton!
override func viewDidLoad() {
super.viewDidLoad()
override func viewWillAppear() {
super.viewWillAppear()
self.listenFor.selectItem(at: self.prefs.integer(forKey: Utils.PrefKeys.listenFor.rawValue))
}

View file

@ -14,32 +14,28 @@ class MainPrefsViewController: NSViewController, MASPreferencesViewController {
@IBOutlet var showContrastSlider: NSButton!
@IBOutlet var lowerContrast: NSButton!
@available(macOS, deprecated: 10.10)
override func viewDidLoad() {
super.viewDidLoad()
let startAtLogin = (SMCopyAllJobDictionaries(kSMDomainUserLaunchd).takeRetainedValue() as? [[String: AnyObject]])?.first { $0["Label"] as? String == "\(Bundle.main.bundleIdentifier!)Helper" }?["OnDemand"] as? Bool ?? false
self.startAtLogin.state = startAtLogin ? .on : .off
self.showContrastSlider.state = self.prefs.bool(forKey: Utils.PrefKeys.showContrast.rawValue) ? .on : .off
self.lowerContrast.state = self.prefs.bool(forKey: Utils.PrefKeys.lowerContrast.rawValue) ? .on : .off
self.setVersionNumber()
}
@IBAction func startAtLoginClicked(_ sender: NSButton) {
let identifier = "\(Bundle.main.bundleIdentifier!)Helper" as CFString
@available(macOS, deprecated: 10.10)
override func viewWillAppear() {
super.viewWillAppear()
let startAtLogin = (SMCopyAllJobDictionaries(kSMDomainUserLaunchd).takeRetainedValue() as? [[String: AnyObject]])?.first { $0["Label"] as? String == "\(Bundle.main.bundleIdentifier!)Helper" }?["OnDemand"] as? Bool ?? false
self.startAtLogin.state = startAtLogin ? .on : .off
self.showContrastSlider.state = self.prefs.bool(forKey: Utils.PrefKeys.showContrast.rawValue) ? .on : .off
self.lowerContrast.state = self.prefs.bool(forKey: Utils.PrefKeys.lowerContrast.rawValue) ? .on : .off
}
@IBAction func startAtLoginClicked(_ sender: NSButton) {
switch sender.state {
case .on:
SMLoginItemSetEnabled(identifier, true)
Utils.setStartAtLogin(enabled: true)
case .off:
SMLoginItemSetEnabled(identifier, false)
Utils.setStartAtLogin(enabled: false)
default: break
}
#if DEBUG
os_log("Toggle start at login state: %{public}@", type: .info, sender.state == .on ? "on" : "off")
#endif
}
@IBAction func showContrastSliderClicked(_ sender: NSButton) {