diff --git a/.github/rules.json b/.github/rules.json deleted file mode 100644 index a3082f1..0000000 --- a/.github/rules.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "title": "MonitorControl (@the0neyouseek)", - "rules": [ - { - "description": "External Screen brightness [-] (using MonitorControl)", - "manipulators": [{ - "type": "basic", - "from": { - "key_code": "f1", - "modifiers": { - "optional": [ - "any" - ] - } - }, - "to": [{ - "key_code": "down_arrow", - "modifiers": [ - "left_shift", - "left_control", - "left_option", - "left_command" - ] - }] - }] - }, - { - "description": "External Screen brightness [+] (using MonitorControl)", - "manipulators": [{ - "type": "basic", - "from": { - "key_code": "f2", - "modifiers": { - "optional": [ - "any" - ] - } - }, - "to": [{ - "key_code": "up_arrow", - "modifiers": [ - "left_shift", - "left_control", - "left_option", - "left_command" - ] - }] - }] - }, - { - "description": "External Screen volume [-] (using MonitorControl)", - "manipulators": [{ - "type": "basic", - "from": { - "key_code": "f11", - "modifiers": { - "optional": [ - "any" - ] - } - }, - "to": [{ - "key_code": "left_arrow", - "modifiers": [ - "left_shift", - "left_control", - "left_option", - "left_command" - ] - }] - }] - }, - { - "description": "External Screen volume [+] (using MonitorControl)", - "manipulators": [{ - "type": "basic", - "from": { - "key_code": "f12", - "modifiers": { - "optional": [ - "any" - ] - } - }, - "to": [{ - "key_code": "right_arrow", - "modifiers": [ - "left_shift", - "left_control", - "left_option", - "left_command" - ] - }] - }] - }, - { - "description": "External Screen volume mute (using MonitorControl)", - "manipulators": [{ - "type": "basic", - "from": { - "key_code": "f10", - "modifiers": { - "optional": [ - "any" - ] - } - }, - "to": [{ - "key_code": "equal_sign", - "modifiers": [ - "left_shift", - "left_control", - "left_option", - "left_command" - ] - }] - }] - } - ] -} \ No newline at end of file diff --git a/MonitorControl.xcodeproj/project.pbxproj b/MonitorControl.xcodeproj/project.pbxproj index 7c3fb04..cb54334 100644 --- a/MonitorControl.xcodeproj/project.pbxproj +++ b/MonitorControl.xcodeproj/project.pbxproj @@ -19,12 +19,28 @@ F0445D40200259C10025AE82 /* DisplayPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0445D3F200259C10025AE82 /* DisplayPrefsViewController.swift */; }; F0445D41200282E60025AE82 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F0445D43200282E60025AE82 /* Main.storyboard */; }; F0445D4D200294AB0025AE82 /* ButtonCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0445D4C200294AB0025AE82 /* ButtonCellView.swift */; }; + F06792EA200A73460066C438 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F06792E9200A73460066C438 /* AppDelegate.swift */; }; + F06792F6200A745F0066C438 /* MonitorControlHelper.app in [Login] Copy Helper to start at Login */ = {isa = PBXBuildFile; fileRef = F06792E7200A73460066C438 /* MonitorControlHelper.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; F091C9B31F6EA6110096FD65 /* SliderHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F091C9B21F6EA6110096FD65 /* SliderHandler.swift */; }; F091C9B81F6EA79B0096FD65 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = F091C9B71F6EA79B0096FD65 /* Utils.swift */; }; F0A987E81F77B40E009B603D /* OSD.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F0A987D61F77B290009B603D /* OSD.framework */; }; F0EB972F1F6ED7C800686D2A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F091C9C11F6EB8660096FD65 /* Localizable.strings */; }; /* End PBXBuildFile section */ +/* Begin PBXCopyFilesBuildPhase section */ + F06792F5200A73FA0066C438 /* [Login] Copy Helper to start at Login */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = Contents/Library/LoginItems; + dstSubfolderSpec = 1; + files = ( + F06792F6200A745F0066C438 /* MonitorControlHelper.app in [Login] Copy Helper to start at Login */, + ); + name = "[Login] Copy Helper to start at Login"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 31E16D90527EBD3F8A12BE0B /* Pods-MonitorControl.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MonitorControl.release.xcconfig"; path = "Pods/Target Support Files/Pods-MonitorControl/Pods-MonitorControl.release.xcconfig"; sourceTree = ""; }; 398F482D5C8816B29F16AAEB /* Pods_MonitorControl.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MonitorControl.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -50,6 +66,10 @@ F0445D49200285690025AE82 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/MainMenu.strings; sourceTree = ""; }; F0445D4B2002856C0025AE82 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/MainMenu.strings; sourceTree = ""; }; F0445D4C200294AB0025AE82 /* ButtonCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonCellView.swift; sourceTree = ""; }; + F06792E7200A73460066C438 /* MonitorControlHelper.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MonitorControlHelper.app; sourceTree = BUILT_PRODUCTS_DIR; }; + F06792E9200A73460066C438 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + F06792F0200A73470066C438 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F06792F1200A73470066C438 /* MonitorControlHelper.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MonitorControlHelper.entitlements; sourceTree = ""; }; F091C9B21F6EA6110096FD65 /* SliderHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderHandler.swift; sourceTree = ""; }; F091C9B71F6EA79B0096FD65 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; F091C9C21F6EB8660096FD65 /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; lineEnding = 0; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; @@ -79,6 +99,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F06792E4200A73460066C438 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -101,6 +128,7 @@ 56754EAD1D9A4016007BCDC5 /* MonitorControl */, F0A987D61F77B290009B603D /* OSD.framework */, 55359E321E2737EC002671BC /* ddcctl */, + F06792E8200A73460066C438 /* MonitorControlHelper */, 56754EAC1D9A4016007BCDC5 /* Products */, F0A987D71F77B404009B603D /* Frameworks */, EFFC2F3E35BEC9ACFA754137 /* Pods */, @@ -111,6 +139,7 @@ isa = PBXGroup; children = ( 56754EAB1D9A4016007BCDC5 /* MonitorControl.app */, + F06792E7200A73460066C438 /* MonitorControlHelper.app */, ); name = Products; sourceTree = ""; @@ -151,6 +180,16 @@ path = Prefs; sourceTree = ""; }; + F06792E8200A73460066C438 /* MonitorControlHelper */ = { + isa = PBXGroup; + children = ( + F06792E9200A73460066C438 /* AppDelegate.swift */, + F06792F0200A73470066C438 /* Info.plist */, + F06792F1200A73470066C438 /* MonitorControlHelper.entitlements */, + ); + path = MonitorControlHelper; + sourceTree = ""; + }; F091C9B41F6EA6180096FD65 /* Objects */ = { isa = PBXGroup; children = ( @@ -208,6 +247,7 @@ 56754EA91D9A4016007BCDC5 /* Resources */, 9DD5968596EFAF0E2EB56496 /* [CP] Embed Pods Frameworks */, 3ACE91333FC1E781FB2E44E5 /* [CP] Copy Pods Resources */, + F06792F5200A73FA0066C438 /* [Login] Copy Helper to start at Login */, ); buildRules = ( ); @@ -218,13 +258,30 @@ productReference = 56754EAB1D9A4016007BCDC5 /* MonitorControl.app */; productType = "com.apple.product-type.application"; }; + F06792E6200A73460066C438 /* MonitorControlHelper */ = { + isa = PBXNativeTarget; + buildConfigurationList = F06792F4200A73470066C438 /* Build configuration list for PBXNativeTarget "MonitorControlHelper" */; + buildPhases = ( + F06792E3200A73460066C438 /* Sources */, + F06792E4200A73460066C438 /* Frameworks */, + F06792E5200A73460066C438 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = MonitorControlHelper; + productName = MonitorControlHelper; + productReference = F06792E7200A73460066C438 /* MonitorControlHelper.app */; + productType = "com.apple.product-type.application"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 56754EA31D9A4016007BCDC5 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0800; + LastSwiftUpdateCheck = 0920; LastUpgradeCheck = 0900; ORGANIZATIONNAME = "Mathew Kurian"; TargetAttributes = { @@ -234,6 +291,11 @@ LastSwiftMigration = 0900; ProvisioningStyle = Automatic; }; + F06792E6200A73460066C438 = { + CreatedOnToolsVersion = 9.2; + DevelopmentTeam = KGY56RWR9A; + ProvisioningStyle = Automatic; + }; }; }; buildConfigurationList = 56754EA61D9A4016007BCDC5 /* Build configuration list for PBXProject "MonitorControl" */; @@ -251,6 +313,7 @@ projectRoot = ""; targets = ( 56754EAA1D9A4016007BCDC5 /* MonitorControl */, + F06792E6200A73460066C438 /* MonitorControlHelper */, ); }; /* End PBXProject section */ @@ -268,6 +331,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F06792E5200A73460066C438 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -293,11 +363,13 @@ ); inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-MonitorControl/Pods-MonitorControl-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/HotKey/HotKey.framework", "${BUILT_PRODUCTS_DIR}/MASPreferences/MASPreferences.framework", "${BUILT_PRODUCTS_DIR}/MediaKeyTap/MediaKeyTap.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/HotKey.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MASPreferences.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MediaKeyTap.framework", ); @@ -357,6 +429,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F06792E3200A73460066C438 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F06792EA200A73460066C438 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ @@ -564,6 +644,52 @@ }; name = Release; }; + F06792F2200A73470066C438 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = MonitorControlHelper/MonitorControlHelper.entitlements; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = KGY56RWR9A; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = MonitorControlHelper/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.13; + PRODUCT_BUNDLE_IDENTIFIER = me.guillaumeb.MonitorControlHelper; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; + }; + name = Debug; + }; + F06792F3200A73470066C438 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = MonitorControlHelper/MonitorControlHelper.entitlements; + CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = KGY56RWR9A; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = MonitorControlHelper/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.13; + PRODUCT_BUNDLE_IDENTIFIER = me.guillaumeb.MonitorControlHelper; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -585,6 +711,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + F06792F4200A73470066C438 /* Build configuration list for PBXNativeTarget "MonitorControlHelper" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F06792F2200A73470066C438 /* Debug */, + F06792F3200A73470066C438 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 56754EA31D9A4016007BCDC5 /* Project object */; diff --git a/MonitorControl/AppDelegate.swift b/MonitorControl/AppDelegate.swift index 2f3ba47..2ffed10 100644 --- a/MonitorControl/AppDelegate.swift +++ b/MonitorControl/AppDelegate.swift @@ -35,13 +35,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, MediaKeyTapDelegate { app = self mediaKeyTap = MediaKeyTap.init(delegate: self, forKeys: [.brightnessUp, .brightnessDown, .mute, .volumeUp, .volumeDown], observeBuiltIn: false) let storyboard: NSStoryboard = NSStoryboard.init(name: NSStoryboard.Name(rawValue: "Main"), bundle: Bundle.main) - prefsController = MASPreferencesWindowController(viewControllers: - [ - storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "MainPrefsVC")), - storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "KeysPrefsVC")), - storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "DisplayPrefsVC")) - ], - title: NSLocalizedString("Preferences", comment: "Shown in Preferences window")) + let views = [ + storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "MainPrefsVC")), + storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "KeysPrefsVC")), + storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "DisplayPrefsVC")) + ] + prefsController = MASPreferencesWindowController(viewControllers: views, title: NSLocalizedString("Preferences", comment: "Shown in Preferences window")) statusItem.image = NSImage.init(named: NSImage.Name(rawValue: "status")) statusItem.menu = statusMenu @@ -96,47 +95,33 @@ class AppDelegate: NSObject, NSApplicationDelegate, MediaKeyTapDelegate { clearDisplays() sleep(1) - for screen in NSScreen.screens { + var filteredScreens = NSScreen.screens.filter { screen -> Bool in if let id = screen.deviceDescription[NSDeviceDescriptionKey.init("NSScreenNumber")] as? CGDirectDisplayID { // Is Built In Screen (e.g. MBP/iMac Screen) if CGDisplayIsBuiltin(id) != 0 { - continue + return false } // Does screen support EDID ? var edid = EDID() if !EDIDTest(id, &edid) { - continue + return false } - let name = Utils.getDisplayName(forEdid: edid) - let serial = Utils.getDisplaySerial(forEdid: edid) - - let display = Display.init(id, name: name, serial: serial) - - let monitorSubMenu = NSMenu() - let brightnessSliderHandler = Utils.addSliderMenuItem(toMenu: monitorSubMenu, - forDisplay: display, - command: BRIGHTNESS, - title: NSLocalizedString("Brightness", comment: "Shown in menu")) - let volumeSliderHandler = Utils.addSliderMenuItem(toMenu: monitorSubMenu, - forDisplay: display, - command: AUDIO_SPEAKER_VOLUME, - title: NSLocalizedString("Volume", comment: "Shown in menu")) - display.brightnessSliderHandler = brightnessSliderHandler - display.volumeSliderHandler = volumeSliderHandler - displays.append(display) - - let monitorMenuItem = NSMenuItem() - monitorMenuItem.title = "\(name)" - monitorMenuItem.submenu = monitorSubMenu - - monitorItems.append(monitorMenuItem) - statusMenu.insertItem(monitorMenuItem, at: displays.count - 1) + return true } - } + return false + } - if displays.count == 0 { + if filteredScreens.count == 1 { + self.addScreenToMenu(screen: filteredScreens[0], asSubMenu: false) + } else { + for screen in filteredScreens { + self.addScreenToMenu(screen: screen, asSubMenu: true) + } + } + + if filteredScreens.count == 0 { // If no DDC capable display was detected let item = NSMenuItem() item.title = NSLocalizedString("No supported display found", comment: "Shown in menu") @@ -146,6 +131,46 @@ class AppDelegate: NSObject, NSApplicationDelegate, MediaKeyTapDelegate { } } + /// Add a screen to the menu + /// + /// - Parameters: + /// - screen: The screen to add + /// - asSubMenu: Display in a sub menu or directly in menu + private func addScreenToMenu(screen: NSScreen, asSubMenu: Bool) { + if let id = screen.deviceDescription[NSDeviceDescriptionKey.init("NSScreenNumber")] as? CGDirectDisplayID { + + var edid = EDID() + if EDIDTest(id, &edid) { + let name = Utils.getDisplayName(forEdid: edid) + let serial = Utils.getDisplaySerial(forEdid: edid) + + let display = Display.init(id, name: name, serial: serial) + + let monitorSubMenu: NSMenu = asSubMenu ? NSMenu() : statusMenu + let volumeSliderHandler = Utils.addSliderMenuItem(toMenu: monitorSubMenu, + forDisplay: display, + command: AUDIO_SPEAKER_VOLUME, + title: NSLocalizedString("Volume", comment: "Shown in menu")) + let brightnessSliderHandler = Utils.addSliderMenuItem(toMenu: monitorSubMenu, + forDisplay: display, + command: BRIGHTNESS, + title: NSLocalizedString("Brightness", comment: "Shown in menu")) + display.volumeSliderHandler = volumeSliderHandler + display.brightnessSliderHandler = brightnessSliderHandler + displays.append(display) + + let monitorMenuItem = NSMenuItem() + monitorMenuItem.title = "\(name)" + if asSubMenu { + monitorMenuItem.submenu = monitorSubMenu + } + + monitorItems.append(monitorMenuItem) + statusMenu.insertItem(monitorMenuItem, at: 0) + } + } + } + // MARK: - Media Key Tap delegate func handle(mediaKey: MediaKey, event: KeyEvent) { diff --git a/MonitorControl/Base.lproj/Main.storyboard b/MonitorControl/Base.lproj/Main.storyboard index 8385f84..f06a856 100644 --- a/MonitorControl/Base.lproj/Main.storyboard +++ b/MonitorControl/Base.lproj/Main.storyboard @@ -11,11 +11,11 @@ - + - + @@ -23,7 +23,7 @@ - - - - - + + + + - - + - - + - - + @@ -162,7 +150,7 @@ - + @@ -179,8 +167,9 @@ + - + @@ -193,7 +182,7 @@ - + @@ -204,7 +193,7 @@ - + @@ -214,7 +203,7 @@ - + @@ -343,7 +332,7 @@ - + - + diff --git a/MonitorControl/Info.plist b/MonitorControl/Info.plist index b8f87f2..3b6aaf6 100644 --- a/MonitorControl/Info.plist +++ b/MonitorControl/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.1.1 + 1.2.0 CFBundleVersion - 32 + 40 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/MonitorControl/Objects/ButtonCellView.swift b/MonitorControl/Objects/ButtonCellView.swift index 8f82322..806b2f0 100644 --- a/MonitorControl/Objects/ButtonCellView.swift +++ b/MonitorControl/Objects/ButtonCellView.swift @@ -29,7 +29,7 @@ class ButtonCellView: NSTableCellView { break } // TODO: Toggle enabled display state - print("Toggle enabled display state -> \(sender.state)") + print("Toggle enabled display state -> \(sender.state == .on ? "on" : "off")") } } } diff --git a/MonitorControl/Objects/Display.swift b/MonitorControl/Objects/Display.swift index 8d772af..dd47962 100644 --- a/MonitorControl/Objects/Display.swift +++ b/MonitorControl/Objects/Display.swift @@ -18,6 +18,8 @@ class Display { var brightnessSliderHandler: SliderHandler? var volumeSliderHandler: SliderHandler? + private let prefs = UserDefaults.standard + init(_ identifier: CGDirectDisplayID, name: String, serial: String, isEnabled: Bool = true) { self.identifier = identifier self.name = name @@ -28,13 +30,13 @@ class Display { func mute() { var value = 0 if isMuted { - value = UserDefaults.standard.integer(forKey: "\(AUDIO_SPEAKER_VOLUME)-\(identifier)") + value = prefs.integer(forKey: "\(AUDIO_SPEAKER_VOLUME)-\(identifier)") isMuted = false } else { isMuted = true } - Utils.ddcctl(monitor: identifier, command: AUDIO_SPEAKER_VOLUME, value: value) + Utils.sendCommand(AUDIO_SPEAKER_VOLUME, toMonitor: identifier, withValue: value) if let slider = volumeSliderHandler?.slider { slider.intValue = Int32(value) } @@ -46,7 +48,7 @@ class Display { isMuted = false } - Utils.ddcctl(monitor: identifier, command: AUDIO_SPEAKER_VOLUME, value: value) + Utils.sendCommand(AUDIO_SPEAKER_VOLUME, toMonitor: identifier, withValue: value) if let slider = volumeSliderHandler?.slider { slider.intValue = Int32(value) } @@ -55,7 +57,7 @@ class Display { } func setBrightness(to value: Int) { - Utils.ddcctl(monitor: identifier, command: BRIGHTNESS, value: value) + Utils.sendCommand(BRIGHTNESS, toMonitor: identifier, withValue: value) if let slider = brightnessSliderHandler?.slider { slider.intValue = Int32(value) } @@ -64,12 +66,12 @@ class Display { } func calcNewValue(for command: Int32, withRel rel: Int) -> Int { - let currentValue = UserDefaults.standard.integer(forKey: "\(command)-\(identifier)") + let currentValue = prefs.integer(forKey: "\(command)-\(identifier)") return max(0, min(100, currentValue + rel)) } func saveValue(_ value: Int, for command: Int32) { - UserDefaults.standard.set(value, forKey: "\(command)-\(identifier)") + prefs.set(value, forKey: "\(command)-\(identifier)") } private func showOsd(command: Int32, value: Int) { diff --git a/MonitorControl/Objects/SliderHandler.swift b/MonitorControl/Objects/SliderHandler.swift index fefc97d..6cddebc 100644 --- a/MonitorControl/Objects/SliderHandler.swift +++ b/MonitorControl/Objects/SliderHandler.swift @@ -31,7 +31,7 @@ class SliderHandler { slider.integerValue = value } - Utils.ddcctl(monitor: display.identifier, command: command, value: value) - prefs.setValue(value, forKey: "\(command)-\(display.identifier)") + Utils.sendCommand(command, toMonitor: display.identifier, withValue: value) + display.saveValue(value, for: command) } } diff --git a/MonitorControl/Prefs/DisplayPrefsViewController.swift b/MonitorControl/Prefs/DisplayPrefsViewController.swift index 01dd539..9d880da 100644 --- a/MonitorControl/Prefs/DisplayPrefsViewController.swift +++ b/MonitorControl/Prefs/DisplayPrefsViewController.swift @@ -43,7 +43,7 @@ class DisplayPrefsViewController: NSViewController, MASPreferencesViewController default: break } // TODO: Toggle allScreens state - print("Toggle allScreens state -> \(sender.state)") + print("Toggle allScreens state -> \(sender.state == .on ? "on" : "off")") } // MARK: - Table datasource diff --git a/MonitorControl/Prefs/MainPrefsViewController.swift b/MonitorControl/Prefs/MainPrefsViewController.swift index 536a70c..941eaba 100644 --- a/MonitorControl/Prefs/MainPrefsViewController.swift +++ b/MonitorControl/Prefs/MainPrefsViewController.swift @@ -8,6 +8,7 @@ import Cocoa import MASPreferences +import ServiceManagement class MainPrefsViewController: NSViewController, MASPreferencesViewController { @@ -27,15 +28,17 @@ class MainPrefsViewController: NSViewController, MASPreferencesViewController { } @IBAction func startAtLoginClicked(_ sender: NSButton) { + let identifier = "me.guillaumeb.MonitorControlHelper" as CFString switch sender.state { case .on: prefs.set(true, forKey: Utils.PrefKeys.startAtLogin.rawValue) + SMLoginItemSetEnabled(identifier, true) case .off: prefs.set(false, forKey: Utils.PrefKeys.startAtLogin.rawValue) + SMLoginItemSetEnabled(identifier, false) default: break } - // TODO: Toggle start at login state - print("Toggle start at login state -> \(sender.state)") + print("Toggle start at login state -> \(sender.state == .on ? "on" : "off")") } @IBAction func startWhenExternalClicked(_ sender: NSButton) { @@ -47,6 +50,6 @@ class MainPrefsViewController: NSViewController, MASPreferencesViewController { default: break } // TODO: Toggle start when external plugged in state - print("Toggle start when external plugged in state -> \(sender.state)") + print("Toggle start when external plugged in state -> \(sender.state == .on ? "on" : "off")") } } diff --git a/MonitorControl/Utils.swift b/MonitorControl/Utils.swift index 1e81bee..3ca0618 100644 --- a/MonitorControl/Utils.swift +++ b/MonitorControl/Utils.swift @@ -15,13 +15,29 @@ class Utils: NSObject { /// Send command to ddcctl /// /// - Parameters: - /// - monitor: The id of the Monitor to send the command to /// - command: The command to send + /// - monitor: The id of the Monitor to send the command to /// - value: the value of the command - static func ddcctl(monitor: CGDirectDisplayID, command: Int32, value: Int) { + 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) - print(value) + print("\(command == BRIGHTNESS ? "Brightness" : "Volume") value : \(value)") + } + + /// 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) + print("\(command == BRIGHTNESS ? "Brightness" : "Volume") value : \(readCmd.current_value)") + return Int(readCmd.current_value) } // MARK: - Menu @@ -59,17 +75,18 @@ class Utils: NSObject { slider.target = handler slider.minValue = 0 slider.maxValue = 100 - slider.integerValue = prefs.integer(forKey: "\(command)-\(display.serial)") + slider.integerValue = getCommand(command, fromMonitor: display.identifier) slider.action = #selector(SliderHandler.valueChanged) handler.slider = slider + display.saveValue(slider.integerValue, for: command) view.addSubview(label) view.addSubview(slider) item.view = view - menu.addItem(item) - menu.addItem(NSMenuItem.separator()) + menu.insertItem(item, at: 0) + menu.insertItem(NSMenuItem.separator(), at: 1) return handler } diff --git a/MonitorControlHelper/AppDelegate.swift b/MonitorControlHelper/AppDelegate.swift new file mode 100644 index 0000000..af74872 --- /dev/null +++ b/MonitorControlHelper/AppDelegate.swift @@ -0,0 +1,32 @@ +// +// AppDelegate.swift +// MonitorControlHelper +// +// Created by Guillaume BRODER on 13/01/2018. +// Copyright © 2018 Mathew Kurian. All rights reserved. +// + +import Cocoa + +@NSApplicationMain +class AppDelegate: NSObject, NSApplicationDelegate { + + @IBOutlet weak var window: NSWindow! + + func applicationDidFinishLaunching(_ aNotification: Notification) { + let bundlePath = Bundle.main.bundlePath as NSString + var pathComponents = bundlePath.pathComponents + for _ in 0...4 { + pathComponents.removeLast() + } + + let path = NSString.path(withComponents: pathComponents) + NSWorkspace.shared.launchApplication(path) + NSApp.terminate(nil) + } + + func applicationWillTerminate(_ aNotification: Notification) { + // Insert code here to tear down your application + } + +} diff --git a/MonitorControlHelper/Info.plist b/MonitorControlHelper/Info.plist new file mode 100644 index 0000000..e560b18 --- /dev/null +++ b/MonitorControlHelper/Info.plist @@ -0,0 +1,34 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSBackgroundOnly + + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + MIT Licensed. 2018. + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/MonitorControlHelper/MonitorControlHelper.entitlements b/MonitorControlHelper/MonitorControlHelper.entitlements new file mode 100644 index 0000000..f2ef3ae --- /dev/null +++ b/MonitorControlHelper/MonitorControlHelper.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + +