From 59620bbba324d89629d5cea5116d1eee4f9aa578 Mon Sep 17 00:00:00 2001 From: JoniVR Date: Fri, 22 Feb 2019 21:26:37 +0100 Subject: [PATCH] implement #3, add fallback for initial slider values - added a fallback when no initial value could be read from display. Instead we will read the last known value from prefs. - implement better way to handle lowering contrast after brightness is 0, this way, contrast will slowly go down as the user keeps pressing brightness down until a default contrast value has been reached. --- .swiftlint.yml | 1 + MonitorControl.xcodeproj/project.pbxproj | 4 +- MonitorControl/AppDelegate.swift | 25 +- MonitorControl/Info.plist | 2 +- MonitorControl/Objects/Display.swift | 29 +- MonitorControl/Util/Extensions.swift | 1 + MonitorControl/Util/Utils.swift | 473 +++++++++++------------ Podfile.lock | 6 +- 8 files changed, 276 insertions(+), 265 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index b156b01b..b508d7b9 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -6,3 +6,4 @@ excluded: - Pods type_body_length: 500 file_length: 500 +cyclomatic_complexity: 11 diff --git a/MonitorControl.xcodeproj/project.pbxproj b/MonitorControl.xcodeproj/project.pbxproj index c5d301b0..783e44f6 100644 --- a/MonitorControl.xcodeproj/project.pbxproj +++ b/MonitorControl.xcodeproj/project.pbxproj @@ -359,7 +359,7 @@ files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-MonitorControl/Pods-MonitorControl-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-MonitorControl/Pods-MonitorControl-frameworks.sh", "${BUILT_PRODUCTS_DIR}/AMCoreAudio/AMCoreAudio.framework", "${BUILT_PRODUCTS_DIR}/MASPreferences/MASPreferences.framework", "${BUILT_PRODUCTS_DIR}/MediaKeyTap/MediaKeyTap.framework", @@ -372,7 +372,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MonitorControl/Pods-MonitorControl-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MonitorControl/Pods-MonitorControl-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; C0EF20D28FC7408CBE89A686 /* [CP] Check Pods Manifest.lock */ = { diff --git a/MonitorControl/AppDelegate.swift b/MonitorControl/AppDelegate.swift index dffcda88..7a2fecd6 100644 --- a/MonitorControl/AppDelegate.swift +++ b/MonitorControl/AppDelegate.swift @@ -212,11 +212,26 @@ extension AppDelegate: MediaKeyTapDelegate { if (prefs.object(forKey: "\(display.identifier)-state") as? Bool) ?? true { switch mediaKey { case .brightnessUp: - let value = display.calcNewValue(for: BRIGHTNESS, withRel: +step) - display.setBrightness(to: value) + var brightnessValue = display.readValue(for: BRIGHTNESS) + var contrastValue = display.readValue(for: CONTRAST) + // increase brightness after contrast + if contrastValue < 70 && brightnessValue == 0 && prefs.bool(forKey: Utils.PrefKeys.lowerContrast.rawValue) { + contrastValue = display.calcNewValue(for: CONTRAST, withRel: +step) + display.setContrast(to: contrastValue) + } else { + brightnessValue = display.calcNewValue(for: BRIGHTNESS, withRel: +step) + } + display.setBrightness(to: brightnessValue) case .brightnessDown: - let value = currentDisplay.calcNewValue(for: BRIGHTNESS, withRel: -step) - display.setBrightness(to: value) + var brightnessValue = display.readValue(for: BRIGHTNESS) + // lower contrast after brightness + if brightnessValue <= 0 && prefs.bool(forKey: Utils.PrefKeys.lowerContrast.rawValue) { + let contrastValue = display.calcNewValue(for: CONTRAST, withRel: -step) + display.setContrast(to: contrastValue) + } else { + brightnessValue = display.calcNewValue(for: BRIGHTNESS, withRel: -step) + } + display.setBrightness(to: brightnessValue) case .mute: display.mute() case .volumeUp: @@ -225,13 +240,11 @@ extension AppDelegate: MediaKeyTapDelegate { case .volumeDown: let value = display.calcNewValue(for: AUDIO_SPEAKER_VOLUME, withRel: -step) display.setVolume(to: value) - default: return } } } - } // MARK: - Prefs notification diff --git a/MonitorControl/Info.plist b/MonitorControl/Info.plist index 6a0afd04..69678096 100644 --- a/MonitorControl/Info.plist +++ b/MonitorControl/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.3.1 CFBundleVersion - 99 + 105 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/MonitorControl/Objects/Display.swift b/MonitorControl/Objects/Display.swift index cf0f9eb6..7fd2888d 100644 --- a/MonitorControl/Objects/Display.swift +++ b/MonitorControl/Objects/Display.swift @@ -58,18 +58,6 @@ class Display { } func setBrightness(to value: Int) { - if prefs.bool(forKey: Utils.PrefKeys.lowerContrast.rawValue) { - if value == 0 { - Utils.sendCommand(CONTRAST, toMonitor: identifier, withValue: value) - if let slider = contrastSliderHandler?.slider { - slider.intValue = Int32(value) - } - } else if prefs.integer(forKey: "\(BRIGHTNESS)-\(identifier)") == 0 { - let contrastValue = prefs.integer(forKey: "\(CONTRAST)-\(identifier)") - Utils.sendCommand(CONTRAST, toMonitor: identifier, withValue: contrastValue) - } - } - Utils.sendCommand(BRIGHTNESS, toMonitor: identifier, withValue: value) if let slider = brightnessSliderHandler?.slider { slider.intValue = Int32(value) @@ -78,15 +66,28 @@ class Display { saveValue(value, for: BRIGHTNESS) } - func calcNewValue(for command: Int32, withRel rel: Int) -> Int { + func setContrast(to value: Int) { + Utils.sendCommand(CONTRAST, toMonitor: identifier, withValue: value) + if let slider = contrastSliderHandler?.slider { + slider.intValue = Int32(value) + } + saveValue(value, for: CONTRAST) + } + + func calcNewValue(for command: Int32, withRel rel: Int) -> Int { + let maxValue = command == CONTRAST ? 70 : 100 let currentValue = prefs.integer(forKey: "\(command)-\(identifier)") - return max(0, min(100, currentValue + rel)) + return max(0, min(maxValue, currentValue + rel)) } func saveValue(_ value: Int, for command: Int32) { prefs.set(value, forKey: "\(command)-\(identifier)") } + func readValue(for command: Int32) -> Int { + return prefs.integer(forKey: "\(command)-\(identifier)") + } + private func showOsd(command: Int32, value: Int) { if let manager = OSDManager.sharedManager() as? OSDManager { var osdImage: Int64 = 1 // Brightness Image diff --git a/MonitorControl/Util/Extensions.swift b/MonitorControl/Util/Extensions.swift index 65cc85cd..18787295 100644 --- a/MonitorControl/Util/Extensions.swift +++ b/MonitorControl/Util/Extensions.swift @@ -4,6 +4,7 @@ // // Created by Joni Van Roost on 15/01/2019. // Copyright © 2019 Mathew Kurian. All rights reserved. +// MIT Licensed. // import AppKit diff --git a/MonitorControl/Util/Utils.swift b/MonitorControl/Util/Utils.swift index 9a62ffe1..95664055 100644 --- a/MonitorControl/Util/Utils.swift +++ b/MonitorControl/Util/Utils.swift @@ -9,59 +9,59 @@ import Cocoa class Utils: NSObject { - private static func printCommandValue(_ command: Int32, _ value: Int) { - let cmdString: (Int32) -> String? = { - switch $0 { - case BRIGHTNESS: - return "Brightness" - case CONTRAST: - return "Contrast" - case AUDIO_SPEAKER_VOLUME: - return "Volume" - default: - return nil - } - } - - print("\(cmdString(command) ?? "N/A") value: \(value)") - } - - // MARK: - DDCCTL - - /// Send command to ddcctl - /// - /// - Parameters: - /// - command: The command to send - /// - monitor: The id of the Monitor to send the command to - /// - value: the value of the command - static func sendCommand(_ command: Int32, toMonitor monitor: CGDirectDisplayID, withValue value: Int) { - var wrcmd = DDCWriteCommand(control_id: UInt8(command), new_value: UInt8(value)) - DDCWrite(monitor, &wrcmd) - - #if DEBUG - printCommandValue(command, value) - #endif - } - - /// Get current value of ddcctl command - /// - /// - Parameters: - /// - command: The command to send - /// - monitor: The id of the monitor to send the command to - /// - Returns: the value of the command - static func getCommand(_ command: Int32, fromMonitor monitor: CGDirectDisplayID) -> Int? { - var readCmd = DDCReadCommand() - readCmd.control_id = UInt8(command) - readCmd.max_value = 0 - readCmd.current_value = 0 - DDCRead(monitor, &readCmd) - - #if DEBUG - printCommandValue(command, Int(readCmd.current_value)) - #endif - - return readCmd.success ? Int(readCmd.current_value) : nil - } + private static func printCommandValue(_ command: Int32, _ value: Int) { + let cmdString: (Int32) -> String? = { + switch $0 { + case BRIGHTNESS: + return "Brightness" + case CONTRAST: + return "Contrast" + case AUDIO_SPEAKER_VOLUME: + return "Volume" + default: + return nil + } + } + + print("\(cmdString(command) ?? "N/A") value: \(value)") + } + + // MARK: - DDCCTL + + /// Send command to ddcctl + /// + /// - Parameters: + /// - command: The command to send + /// - monitor: The id of the Monitor to send the command to + /// - value: the value of the command + static func sendCommand(_ command: Int32, toMonitor monitor: CGDirectDisplayID, withValue value: Int) { + var wrcmd = DDCWriteCommand(control_id: UInt8(command), new_value: UInt8(value)) + DDCWrite(monitor, &wrcmd) + + #if DEBUG + printCommandValue(command, value) + #endif + } + + /// Get current value of ddcctl command + /// + /// - Parameters: + /// - command: The command to send + /// - monitor: The id of the monitor to send the command to + /// - Returns: the value of the command + static func getCommand(_ command: Int32, fromMonitor monitor: CGDirectDisplayID) -> Int? { + var readCmd = DDCReadCommand() + readCmd.control_id = UInt8(command) + readCmd.max_value = 0 + readCmd.current_value = 0 + DDCRead(monitor, &readCmd) + + #if DEBUG + printCommandValue(command, Int(readCmd.current_value)) + #endif + + return readCmd.success ? Int(readCmd.current_value) : nil + } /// MARK - General @@ -103,7 +103,6 @@ class Utils: NSObject { static func updateDockAutohide() { if let currentDockAutohideState = shell("defaults read com.apple.dock autohide") { let externalScreenCount = NSScreen.externalScreens().count - if externalScreenCount == 0 && currentDockAutohideState == "0" { #if DEBUG print("Enable Dock autohide") @@ -118,200 +117,196 @@ class Utils: NSObject { } } - // MARK: - Menu - - /// Create a label - /// - /// - Parameters: - /// - text: The text of the label - /// - frame: The frame of the label - /// - Returns: An `NSTextField` label - static func makeLabel(text: String, frame: NSRect) -> NSTextField { - let label = NSTextField(frame: frame) - label.stringValue = text - label.isBordered = false - label.isBezeled = false - label.isEditable = false - label.drawsBackground = false - return label - } - - /// Create a slider and add it to the menu - /// - /// - Parameters: - /// - menu: Menu containing the slider - /// - display: Display to control - /// - command: Command (Brightness/Volume/...) - /// - title: Title of the slider - /// - Returns: An `NSSlider` slider - static func addSliderMenuItem(toMenu menu: NSMenu, forDisplay display: Display, command: Int32, title: String) -> SliderHandler { - let item = NSMenuItem() - let view = NSView(frame: NSRect(x: 0, y: 5, width: 250, height: 40)) - let label = Utils.makeLabel(text: title, frame: NSRect(x: 20, y: 19, width: 130, height: 20)) - let handler = SliderHandler(display: display, command: command) - let slider = NSSlider(frame: NSRect(x: 20, y: 0, width: 200, height: 19)) - slider.target = handler - slider.minValue = 0 - slider.maxValue = 100 - slider.action = #selector(SliderHandler.valueChanged) - handler.slider = slider - - view.addSubview(label) - view.addSubview(slider) - - item.view = view - - menu.insertItem(item, at: 0) - menu.insertItem(NSMenuItem.separator(), at: 1) - - DispatchQueue.global(qos: .background).async { - var val: Int? - - if let res = getCommand(command, fromMonitor: display.identifier) { - val = res + // MARK: - Menu + + /// Create a label + /// + /// - Parameters: + /// - text: The text of the label + /// - frame: The frame of the label + /// - Returns: An `NSTextField` label + static func makeLabel(text: String, frame: NSRect) -> NSTextField { + let label = NSTextField(frame: frame) + label.stringValue = text + label.isBordered = false + label.isBezeled = false + label.isEditable = false + label.drawsBackground = false + return label + } + + /// Create a slider and add it to the menu + /// + /// - Parameters: + /// - menu: Menu containing the slider + /// - display: Display to control + /// - command: Command (Brightness/Volume/...) + /// - title: Title of the slider + /// - Returns: An `NSSlider` slider + static func addSliderMenuItem(toMenu menu: NSMenu, forDisplay display: Display, command: Int32, title: String) -> SliderHandler { + let item = NSMenuItem() + let view = NSView(frame: NSRect(x: 0, y: 5, width: 250, height: 40)) + let label = Utils.makeLabel(text: title, frame: NSRect(x: 20, y: 19, width: 130, height: 20)) + let handler = SliderHandler(display: display, command: command) + let slider = NSSlider(frame: NSRect(x: 20, y: 0, width: 200, height: 19)) + slider.target = handler + slider.minValue = 0 + slider.maxValue = 100 + slider.action = #selector(SliderHandler.valueChanged) + handler.slider = slider + + view.addSubview(label) + view.addSubview(slider) + + item.view = view + + menu.insertItem(item, at: 0) + menu.insertItem(NSMenuItem.separator(), at: 1) + + var val: Int? + + if let res = getCommand(command, fromMonitor: display.identifier) { + val = res + } + + if let val = val { + display.saveValue(val, for: command) + slider.integerValue = val + } else { + // if unable to read value from display, use last known value + slider.integerValue = prefs.integer(forKey: "\(command)-\(display.identifier)") + } + return handler + } + + // MARK: - Utilities + + /// Acquire Privileges (Necessary to listen to keyboard event globally) + static func acquirePrivileges() { + let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true] + let accessibilityEnabled = AXIsProcessTrustedWithOptions(options) + + if !accessibilityEnabled { + let alert = NSAlert() + alert.addButton(withTitle: NSLocalizedString("Ok", comment: "Shown in the alert dialog")) + alert.messageText = NSLocalizedString("Shortcuts not available", comment: "Shown in the alert dialog") + alert.informativeText = NSLocalizedString("You need to enable MonitorControl in System Preferences > Security and Privacy > Accessibility for the keyboard shortcuts to work", comment: "Shown in the alert dialog") + alert.alertStyle = .warning + alert.runModal() + } + + return + } + + // MARK: - Display Infos + + /// Get the descriptor text + /// + /// - Parameter descriptor: the descriptor + /// - Returns: a string + static func getEdidString(_ descriptor: descriptor) -> String { + var result = "" + for (_, bitChar) in Mirror(reflecting: descriptor.text.data).children { + if let bitChar = bitChar as? Int8 { + let char = Character(UnicodeScalar(UInt8(bitPattern: bitChar))) + if char == "\0" || char == "\n" { + break + } + result.append(char) + } + } + return result + } + + /// Get the descriptors of a display from the Edid + /// + /// - Parameters: + /// - edid: the EDID of a display + /// - type: the type of descriptor + /// - Returns: a string if type of descriptor is found + static func getDescriptorString(_ edid: EDID, _ type: UInt8) -> String? { + for (_, descriptor) in Mirror(reflecting: edid.descriptors).children { + if let descriptor = descriptor as? descriptor { + if descriptor.text.type == UInt8(type) { + return getEdidString(descriptor) + } } + } + return nil + } + + /// Get the name of a display + /// + /// - Parameter edid: the EDID of a display + /// - Returns: a string + static func getDisplayName(forEdid edid: EDID) -> String { + return getDescriptorString(edid, 0xFC) ?? NSLocalizedString("Display", comment: "") + } - if let val = val { - display.saveValue(val, for: command) - - DispatchQueue.main.async { - slider.integerValue = val - } - } - } - - return handler - } - - // MARK: - Utilities - - /// Acquire Privileges (Necessary to listen to keyboard event globally) - static func acquirePrivileges() { - let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true] - let accessibilityEnabled = AXIsProcessTrustedWithOptions(options) - - if !accessibilityEnabled { - let alert = NSAlert() - alert.addButton(withTitle: NSLocalizedString("Ok", comment: "Shown in the alert dialog")) - alert.messageText = NSLocalizedString("Shortcuts not available", comment: "Shown in the alert dialog") - alert.informativeText = NSLocalizedString("You need to enable MonitorControl in System Preferences > Security and Privacy > Accessibility for the keyboard shortcuts to work", comment: "Shown in the alert dialog") - alert.alertStyle = .warning - alert.runModal() - } - - return - } - - // MARK: - Display Infos - - /// Get the descriptor text - /// - /// - Parameter descriptor: the descriptor - /// - Returns: a string - static func getEdidString(_ descriptor: descriptor) -> String { - var result = "" - for (_, bitChar) in Mirror(reflecting: descriptor.text.data).children { - if let bitChar = bitChar as? Int8 { - let char = Character(UnicodeScalar(UInt8(bitPattern: bitChar))) - if char == "\0" || char == "\n" { - break - } - result.append(char) - } - } - return result - } - - /// Get the descriptors of a display from the Edid - /// - /// - Parameters: - /// - edid: the EDID of a display - /// - type: the type of descriptor - /// - Returns: a string if type of descriptor is found - static func getDescriptorString(_ edid: EDID, _ type: UInt8) -> String? { - for (_, descriptor) in Mirror(reflecting: edid.descriptors).children { - if let descriptor = descriptor as? descriptor { - if descriptor.text.type == UInt8(type) { - return getEdidString(descriptor) - } - } - } - - return nil - } - - /// Get the name of a display - /// - /// - Parameter edid: the EDID of a display - /// - Returns: a string - static func getDisplayName(forEdid edid: EDID) -> String { - return getDescriptorString(edid, 0xFC) ?? NSLocalizedString("Display", comment: "") - } - - /// Get the serial of a display - /// - /// - Parameter edid: the EDID of a display - /// - Returns: a string - static func getDisplaySerial(forEdid edid: EDID) -> String { - return getDescriptorString(edid, 0xFF) ?? NSLocalizedString("Unknown", comment: "") - } - - /// Get the main display from a list of display - /// - /// - Parameter displays: List of Display - /// - Returns: the main display or nil if not found - static func getCurrentDisplay(from displays: [Display]) -> Display? { - return displays.first { display -> Bool in - if let main = NSScreen.main { - if let id = main.deviceDescription[NSDeviceDescriptionKey.init("NSScreenNumber")] as? CGDirectDisplayID { - return display.identifier == id - } - } - return false - } - } - - // MARK: - Enums - - /// UserDefault Keys for the app prefs - enum PrefKeys: String { - /// Was the app launched once - case appAlreadyLaunched - - /// Does the app start at Login - case startAtLogin - - /// Does the app start when plugged to an external monitor - case startWhenExternal - - /// Keys listened for (Brightness/Volume) - case listenFor - - /// Show contrast sliders - case showContrast - - /// Lower contrast after brightness - case lowerContrast - - /// Change Brightness/Volume for all screens - case allScreens + /// Get the serial of a display + /// + /// - Parameter edid: the EDID of a display + /// - Returns: a string + static func getDisplaySerial(forEdid edid: EDID) -> String { + return getDescriptorString(edid, 0xFF) ?? NSLocalizedString("Unknown", comment: "") + } + + /// Get the main display from a list of display + /// + /// - Parameter displays: List of Display + /// - Returns: the main display or nil if not found + static func getCurrentDisplay(from displays: [Display]) -> Display? { + return displays.first { display -> Bool in + if let main = NSScreen.main { + if let id = main.deviceDescription[NSDeviceDescriptionKey.init("NSScreenNumber")] as? CGDirectDisplayID { + return display.identifier == id + } + } + return false + } + } + + // MARK: - Enums + + /// UserDefault Keys for the app prefs + enum PrefKeys: String { + /// Was the app launched once + case appAlreadyLaunched + + /// Does the app start at Login + case startAtLogin + + /// Does the app start when plugged to an external monitor + case startWhenExternal + + /// Keys listened for (Brightness/Volume) + case listenFor + + /// Show contrast sliders + case showContrast + + /// Lower contrast after brightness + case lowerContrast + + /// Change Brightness/Volume for all screens + case allScreens /// Automatically turn on/off autohiding of the Dock if external monitor is connected. /// external monitor -> Dock autohide disabled /// internal monitor -> Dock autohide enabled case intelliDock - } + } - /// Keys for the value of listenFor option - enum ListenForKeys: Int { - /// Listen for Brightness and Volume keys - case brightnessAndVolumeKeys = 0 + /// Keys for the value of listenFor option + enum ListenForKeys: Int { + /// Listen for Brightness and Volume keys + case brightnessAndVolumeKeys = 0 - /// Listen for Brightness keys only - case brightnessOnlyKeys = 1 + /// Listen for Brightness keys only + case brightnessOnlyKeys = 1 - /// Listen for Volume keys only - case volumeOnlyKeys = 2 - } + /// Listen for Volume keys only + case volumeOnlyKeys = 2 + } } diff --git a/Podfile.lock b/Podfile.lock index 1daf432e..cdd0b485 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -20,10 +20,10 @@ EXTERNAL SOURCES: CHECKOUT OPTIONS: MASPreferences: - :commit: 52b15d34b7509436c73d6e89d2b02bf29507df50 + :commit: aa9cc4ebf8599382a239b96b000112a36c2554d3 :git: https://github.com/JoniVR/MASPreferences.git MediaKeyTap: - :commit: a7afe10951d2800d94c04091c2684ec9e978fb3e + :commit: ca246d59e8bedeca24440fad67e6a5750ff71ef5 :git: https://github.com/JoniVR/MediaKeyTap.git SPEC CHECKSUMS: @@ -33,4 +33,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 947d8968124719d712379b2dbc0a24b1595515fe -COCOAPODS: 1.5.3 +COCOAPODS: 1.6.0