diff --git a/.gitignore b/.gitignore index 4b28e730..8c58b49e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,69 +1,3 @@ +Carthage -# Created by https://www.gitignore.io/api/macos,xcode,cocoapods - -### CocoaPods ### -## CocoaPods GitIgnore Template - -# CocoaPods - Only use to conserve bandwidth / Save time on Pushing -# - Also handy if you have a large number of dependant pods -# - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE -Pods/ - -### macOS ### -*.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### Xcode ### -# Xcode -# -# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore - -## Build generated -build/ -DerivedData/ - -## Various settings -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata/ - -## Other -*.moved-aside -*.xccheckout -*.xcscmblueprint - -### Xcode Patch ### -*.xcodeproj/* -!*.xcodeproj/project.pbxproj -!*.xcodeproj/xcshareddata/ -!*.xcworkspace/contents.xcworkspacedata -/*.gcno +*.xcuserdatad diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 8bfdbcfc..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "ddcctl"] - path = ddcctl - url = https://github.com/kfix/ddcctl diff --git a/.swift-version b/.swift-version new file mode 100644 index 00000000..819e07a2 --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +5.0 diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 00000000..e9c0c81b --- /dev/null +++ b/.swiftformat @@ -0,0 +1,5 @@ +--indent 2 +--ranges no-space +--self insert +--exponentcase lowercase +--exclude Carthage diff --git a/.swiftlint.yml b/.swiftlint.yml index b156b01b..056c607b 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -2,7 +2,8 @@ disabled_rules: - line_length - function_body_length - identifier_name + - trailing_comma excluded: - - Pods + - Carthage type_body_length: 500 file_length: 500 diff --git a/Cartfile b/Cartfile new file mode 100644 index 00000000..6d10a3f5 --- /dev/null +++ b/Cartfile @@ -0,0 +1,4 @@ +github "the0neyouseek/MediaKeyTap" "master" +github "reitermarkus/DDC.swift" "master" +github "rnine/AMCoreAudio" +github "shpakovski/MASPreferences" diff --git a/Cartfile.resolved b/Cartfile.resolved new file mode 100644 index 00000000..3d641403 --- /dev/null +++ b/Cartfile.resolved @@ -0,0 +1,4 @@ +github "reitermarkus/DDC.swift" "9a6a249b4b222e67b1393eecd7ac9eb35dff34a9" +github "rnine/AMCoreAudio" "3.2.1" +github "shpakovski/MASPreferences" "1.3" +github "the0neyouseek/MediaKeyTap" "abfe09f53ccabb1aa14a0f4ab99cfb8812f41919" diff --git a/Extensions/CGDirectDisplayID+Extension.swift b/Extensions/CGDirectDisplayID+Extension.swift new file mode 100644 index 00000000..dd1e6ad2 --- /dev/null +++ b/Extensions/CGDirectDisplayID+Extension.swift @@ -0,0 +1,15 @@ +import Cocoa + +extension CGDirectDisplayID { + public var vendorNumber: UInt32? { + return CGDisplayVendorNumber(self) + } + + public var modelNumber: UInt32? { + return CGDisplayModelNumber(self) + } + + public var serialNumber: UInt32? { + return CGDisplaySerialNumber(self) + } +} diff --git a/Extensions/EDID+Extension.swift b/Extensions/EDID+Extension.swift new file mode 100644 index 00000000..1801e7c7 --- /dev/null +++ b/Extensions/EDID+Extension.swift @@ -0,0 +1,33 @@ +import DDC + +extension EDID { + public func displayName() -> String? { + let descriptors = [self.descriptors.0, self.descriptors.1, self.descriptors.2, self.descriptors.3] + + for descriptor in descriptors { + switch descriptor { + case let .displayName(name): + return name + default: + continue + } + } + + return nil + } + + public func serialNumber() -> String? { + let descriptors = [self.descriptors.0, self.descriptors.1, self.descriptors.2, self.descriptors.3] + + for descriptor in descriptors { + switch descriptor { + case let .serialNumber(number): + return number + default: + continue + } + } + + return String(self.serialNumber) + } +} diff --git a/Extensions/NSScreen+Extension.swift b/Extensions/NSScreen+Extension.swift new file mode 100644 index 00000000..d9ab7d77 --- /dev/null +++ b/Extensions/NSScreen+Extension.swift @@ -0,0 +1,70 @@ +import Cocoa + +extension NSScreen { + public var displayID: CGDirectDisplayID { + return (self.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? CGDirectDisplayID)! + } + + public var vendorNumber: UInt32? { + switch self.displayID.vendorNumber { + case 0xFFFF_FFFF: + return nil + case let vendorNumber: + return vendorNumber + } + } + + public var modelNumber: UInt32? { + switch self.displayID.modelNumber { + case 0xFFFF_FFFF: + return nil + case let modelNumber: + return modelNumber + } + } + + public var serialNumber: UInt32? { + switch self.displayID.serialNumber { + case 0x0000_0000: + return nil + case let serialNumber: + return serialNumber + } + } + + public var displayName: String? { + var servicePortIterator = io_iterator_t() + + let status = IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("IODisplayConnect"), &servicePortIterator) + guard status == KERN_SUCCESS else { + return nil + } + + defer { + assert(IOObjectRelease(servicePortIterator) == KERN_SUCCESS) + } + + while case let object = IOIteratorNext(servicePortIterator), object != 0 { + let dict = (IODisplayCreateInfoDictionary(object, UInt32(kIODisplayOnlyPreferredName)).takeRetainedValue() as NSDictionary as? [String: AnyObject])! + + if dict[kDisplayVendorID] as? UInt32 == self.vendorNumber, + dict[kDisplayProductID] as? UInt32 == self.modelNumber, + dict[kDisplaySerialNumber] as? UInt32 == self.serialNumber { + if let productName = dict["DisplayProductName"] as? [String: String], + let firstKey = Array(productName.keys).first { + return productName[firstKey]! + } + } + } + + return nil + } + + public var isBuiltin: Bool { + return CGDisplayIsBuiltin(self.displayID) != 0 + } + + public static func getByDisplayID(displayID: CGDirectDisplayID) -> NSScreen? { + return NSScreen.screens.first { $0.displayID == displayID } + } +} diff --git a/MonitorControl.xcodeproj/project.pbxproj b/MonitorControl.xcodeproj/project.pbxproj index 7fe33595..f19e12d2 100644 --- a/MonitorControl.xcodeproj/project.pbxproj +++ b/MonitorControl.xcodeproj/project.pbxproj @@ -7,27 +7,69 @@ objects = { /* Begin PBXBuildFile section */ - 55359E391E2737EC002671BC /* DDC.c in Sources */ = {isa = PBXBuildFile; fileRef = 55359E331E2737EC002671BC /* DDC.c */; }; - 55359E3B1E2737EC002671BC /* ddcctl.sh in Resources */ = {isa = PBXBuildFile; fileRef = 55359E361E2737EC002671BC /* ddcctl.sh */; }; + 2894D9B82280B30500DF58DA /* CGDirectDisplayID+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2894D9B72280B30500DF58DA /* CGDirectDisplayID+Extension.swift */; }; + 28D1DDD8227FB7A4004CB494 /* OSD.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F0A987D61F77B290009B603D /* OSD.framework */; }; + 28D1DDE5227FB7D0004CB494 /* MediaKeyTap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28D1DDE2227FB7D0004CB494 /* MediaKeyTap.framework */; }; + 28D1DDE6227FB7D0004CB494 /* MediaKeyTap.framework in [Carthage] Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 28D1DDE2227FB7D0004CB494 /* MediaKeyTap.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 28D1DDE7227FB7D0004CB494 /* AMCoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28D1DDE3227FB7D0004CB494 /* AMCoreAudio.framework */; }; + 28D1DDE8227FB7D0004CB494 /* AMCoreAudio.framework in [Carthage] Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 28D1DDE3227FB7D0004CB494 /* AMCoreAudio.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 28D1DDE9227FB7D0004CB494 /* MASPreferences.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28D1DDE4227FB7D0004CB494 /* MASPreferences.framework */; }; + 28D1DDEA227FB7D0004CB494 /* MASPreferences.framework in [Carthage] Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 28D1DDE4227FB7D0004CB494 /* MASPreferences.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 28D1DDEE227FB944004CB494 /* DDC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28D1DDED227FB944004CB494 /* DDC.framework */; }; + 28D1DDEF227FB944004CB494 /* DDC.framework in [Carthage] Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 28D1DDED227FB944004CB494 /* DDC.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 28D1DDF0227FBD99004CB494 /* EDID+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D1DDEC227FB8F2004CB494 /* EDID+Extension.swift */; }; + 28D1DDF2227FBE71004CB494 /* NSScreen+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D1DDF1227FBE71004CB494 /* NSScreen+Extension.swift */; }; + 28D1DDF3227FC8C6004CB494 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 56754EB01D9A4016007BCDC5 /* Assets.xcassets */; }; + 28D1DE12227FD006004CB494 /* MASPreferences.framework.dSYM in [Carthage] Copy Framework Debug Symbols */ = {isa = PBXBuildFile; fileRef = 28D1DE0E227FD005004CB494 /* MASPreferences.framework.dSYM */; }; + 28D1DE13227FD006004CB494 /* MediaKeyTap.framework.dSYM in [Carthage] Copy Framework Debug Symbols */ = {isa = PBXBuildFile; fileRef = 28D1DE0F227FD006004CB494 /* MediaKeyTap.framework.dSYM */; }; + 28D1DE14227FD006004CB494 /* AMCoreAudio.framework.dSYM in [Carthage] Copy Framework Debug Symbols */ = {isa = PBXBuildFile; fileRef = 28D1DE10227FD006004CB494 /* AMCoreAudio.framework.dSYM */; }; + 28D1DE15227FD006004CB494 /* DDC.framework.dSYM in [Carthage] Copy Framework Debug Symbols */ = {isa = PBXBuildFile; fileRef = 28D1DE11227FD006004CB494 /* DDC.framework.dSYM */; }; + 28D363812280EA6000CB8A99 /* Display+Whitelist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28D363802280EA6000CB8A99 /* Display+Whitelist.swift */; }; 56754EAF1D9A4016007BCDC5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56754EAE1D9A4016007BCDC5 /* AppDelegate.swift */; }; 56754EB11D9A4016007BCDC5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 56754EB01D9A4016007BCDC5 /* Assets.xcassets */; }; 56754EB41D9A4016007BCDC5 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 56754EB21D9A4016007BCDC5 /* MainMenu.xib */; }; 6C778D5A21E91060000A4D5F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6C778D5821E91060000A4D5F /* Main.storyboard */; }; - 9A19D3B73485870616B6D4E0 /* Pods_MonitorControl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 398F482D5C8816B29F16AAEB /* Pods_MonitorControl.framework */; }; F03A8DF21FFBAA6F0034DC27 /* Display.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03A8DF11FFBAA6F0034DC27 /* Display.swift */; }; F0445D3820023E710025AE82 /* MainPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0445D3720023E710025AE82 /* MainPrefsViewController.swift */; }; F0445D3D200254FA0025AE82 /* KeysPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0445D3B200254FA0025AE82 /* KeysPrefsViewController.swift */; }; F0445D40200259C10025AE82 /* DisplayPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0445D3F200259C10025AE82 /* DisplayPrefsViewController.swift */; }; F0445D4D200294AB0025AE82 /* ButtonCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0445D4C200294AB0025AE82 /* ButtonCellView.swift */; }; - F06792EA200A73460066C438 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F06792E9200A73460066C438 /* AppDelegate.swift */; }; + F06792EA200A73460066C438 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = F06792E9200A73460066C438 /* main.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 */ + 28D1DDBE227FB668004CB494 /* [Carthage] Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 28D1DDE8227FB7D0004CB494 /* AMCoreAudio.framework in [Carthage] Embed Frameworks */, + 28D1DDEF227FB944004CB494 /* DDC.framework in [Carthage] Embed Frameworks */, + 28D1DDEA227FB7D0004CB494 /* MASPreferences.framework in [Carthage] Embed Frameworks */, + 28D1DDE6227FB7D0004CB494 /* MediaKeyTap.framework in [Carthage] Embed Frameworks */, + ); + name = "[Carthage] Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 28D1DE0B227FCF99004CB494 /* [Carthage] Copy Framework Debug Symbols */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 16; + files = ( + 28D1DE14227FD006004CB494 /* AMCoreAudio.framework.dSYM in [Carthage] Copy Framework Debug Symbols */, + 28D1DE15227FD006004CB494 /* DDC.framework.dSYM in [Carthage] Copy Framework Debug Symbols */, + 28D1DE12227FD006004CB494 /* MASPreferences.framework.dSYM in [Carthage] Copy Framework Debug Symbols */, + 28D1DE13227FD006004CB494 /* MediaKeyTap.framework.dSYM in [Carthage] Copy Framework Debug Symbols */, + ); + name = "[Carthage] Copy Framework Debug Symbols"; + runOnlyForDeploymentPostprocessing = 0; + }; F06792F5200A73FA0066C438 /* [Login] Copy Helper to start at Login */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -42,15 +84,21 @@ /* 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; }; - 42B61ABC1D7907131330228A /* Pods-MonitorControl.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MonitorControl.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MonitorControl/Pods-MonitorControl.debug.xcconfig"; sourceTree = ""; }; - 55359E331E2737EC002671BC /* DDC.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = DDC.c; sourceTree = ""; }; - 55359E341E2737EC002671BC /* DDC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDC.h; sourceTree = ""; }; - 55359E351E2737EC002671BC /* ddcctl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ddcctl.m; sourceTree = ""; }; - 55359E361E2737EC002671BC /* ddcctl.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = ddcctl.sh; sourceTree = ""; }; - 55359E371E2737EC002671BC /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; - 55359E381E2737EC002671BC /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 2894D9B72280B30500DF58DA /* CGDirectDisplayID+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGDirectDisplayID+Extension.swift"; sourceTree = ""; }; + 28D1DD79227FA927004CB494 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Main.strings; sourceTree = ""; }; + 28D1DD7A227FA927004CB494 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/MainMenu.strings; sourceTree = ""; }; + 28D1DD7C227FA939004CB494 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + 28D1DDE2227FB7D0004CB494 /* MediaKeyTap.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaKeyTap.framework; path = Carthage/Build/Mac/MediaKeyTap.framework; sourceTree = ""; }; + 28D1DDE3227FB7D0004CB494 /* AMCoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AMCoreAudio.framework; path = Carthage/Build/Mac/AMCoreAudio.framework; sourceTree = ""; }; + 28D1DDE4227FB7D0004CB494 /* MASPreferences.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MASPreferences.framework; path = Carthage/Build/Mac/MASPreferences.framework; sourceTree = ""; }; + 28D1DDEC227FB8F2004CB494 /* EDID+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EDID+Extension.swift"; sourceTree = ""; }; + 28D1DDED227FB944004CB494 /* DDC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DDC.framework; path = Carthage/Build/Mac/DDC.framework; sourceTree = ""; }; + 28D1DDF1227FBE71004CB494 /* NSScreen+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSScreen+Extension.swift"; sourceTree = ""; }; + 28D1DE0E227FD005004CB494 /* MASPreferences.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = MASPreferences.framework.dSYM; path = Carthage/Build/Mac/MASPreferences.framework.dSYM; sourceTree = ""; }; + 28D1DE0F227FD006004CB494 /* MediaKeyTap.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = MediaKeyTap.framework.dSYM; path = Carthage/Build/Mac/MediaKeyTap.framework.dSYM; sourceTree = ""; }; + 28D1DE10227FD006004CB494 /* AMCoreAudio.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = AMCoreAudio.framework.dSYM; path = Carthage/Build/Mac/AMCoreAudio.framework.dSYM; sourceTree = ""; }; + 28D1DE11227FD006004CB494 /* DDC.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = DDC.framework.dSYM; path = Carthage/Build/Mac/DDC.framework.dSYM; sourceTree = ""; }; + 28D363802280EA6000CB8A99 /* Display+Whitelist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Display+Whitelist.swift"; sourceTree = ""; }; 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 = ""; }; 56754EB01D9A4016007BCDC5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -67,7 +115,7 @@ 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 = ""; }; + F06792E9200A73460066C438 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.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 = ""; }; @@ -76,17 +124,6 @@ F091C9C31F6EB8720096FD65 /* fr */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; lineEnding = 0; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; F091C9C41F6EBA5A0096FD65 /* Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = ""; }; F0A987D61F77B290009B603D /* OSD.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OSD.framework; sourceTree = ""; }; - F0A987DA1F77B404009B603D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/MainMenu.strings; sourceTree = ""; }; - F0A987DC1F77B404009B603D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; - F0A987DD1F77B404009B603D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - F0A987DF1F77B404009B603D /* SliderHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderHandler.swift; sourceTree = ""; }; - F0A987E11F77B404009B603D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; - F0A987E21F77B404009B603D /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; - F0A987E31F77B404009B603D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/MainMenu.strings; sourceTree = ""; }; - F0A987E41F77B404009B603D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; - F0A987E51F77B404009B603D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - F0A987E61F77B404009B603D /* Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = ""; }; - F0A987E71F77B404009B603D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -94,8 +131,11 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F0A987E81F77B40E009B603D /* OSD.framework in Frameworks */, - 9A19D3B73485870616B6D4E0 /* Pods_MonitorControl.framework in Frameworks */, + 28D1DDE7227FB7D0004CB494 /* AMCoreAudio.framework in Frameworks */, + 28D1DDEE227FB944004CB494 /* DDC.framework in Frameworks */, + 28D1DDE9227FB7D0004CB494 /* MASPreferences.framework in Frameworks */, + 28D1DDE5227FB7D0004CB494 /* MediaKeyTap.framework in Frameworks */, + 28D1DDD8227FB7A4004CB494 /* OSD.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -109,29 +149,40 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 55359E321E2737EC002671BC /* ddcctl */ = { + 28D1DDD1227FB759004CB494 /* Frameworks */ = { isa = PBXGroup; children = ( - 55359E331E2737EC002671BC /* DDC.c */, - 55359E341E2737EC002671BC /* DDC.h */, - 55359E351E2737EC002671BC /* ddcctl.m */, - 55359E361E2737EC002671BC /* ddcctl.sh */, - 55359E371E2737EC002671BC /* Makefile */, - 55359E381E2737EC002671BC /* README.md */, - ); - path = ddcctl; + 28D1DDE3227FB7D0004CB494 /* AMCoreAudio.framework */, + 28D1DE10227FD006004CB494 /* AMCoreAudio.framework.dSYM */, + 28D1DDED227FB944004CB494 /* DDC.framework */, + 28D1DE11227FD006004CB494 /* DDC.framework.dSYM */, + 28D1DDE4227FB7D0004CB494 /* MASPreferences.framework */, + 28D1DE0E227FD005004CB494 /* MASPreferences.framework.dSYM */, + 28D1DDE2227FB7D0004CB494 /* MediaKeyTap.framework */, + 28D1DE0F227FD006004CB494 /* MediaKeyTap.framework.dSYM */, + F0A987D61F77B290009B603D /* OSD.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 28D1DDEB227FB8E9004CB494 /* Extensions */ = { + isa = PBXGroup; + children = ( + 2894D9B72280B30500DF58DA /* CGDirectDisplayID+Extension.swift */, + 28D1DDEC227FB8F2004CB494 /* EDID+Extension.swift */, + 28D1DDF1227FBE71004CB494 /* NSScreen+Extension.swift */, + ); + path = Extensions; sourceTree = ""; }; 56754EA21D9A4016007BCDC5 = { isa = PBXGroup; children = ( + 28D1DDEB227FB8E9004CB494 /* Extensions */, + 28D1DDD1227FB759004CB494 /* Frameworks */, 56754EAD1D9A4016007BCDC5 /* MonitorControl */, - F0A987D61F77B290009B603D /* OSD.framework */, - 55359E321E2737EC002671BC /* ddcctl */, F06792E8200A73460066C438 /* MonitorControlHelper */, 56754EAC1D9A4016007BCDC5 /* Products */, - F0A987D71F77B404009B603D /* Frameworks */, - EFFC2F3E35BEC9ACFA754137 /* Pods */, ); sourceTree = ""; }; @@ -148,91 +199,42 @@ isa = PBXGroup; children = ( 56754EAE1D9A4016007BCDC5 /* AppDelegate.swift */, - F091C9B71F6EA79B0096FD65 /* Utils.swift */, - 6C778D5821E91060000A4D5F /* Main.storyboard */, - 56754EB21D9A4016007BCDC5 /* MainMenu.xib */, - F0445D3620023D5B0025AE82 /* Prefs */, - F091C9B41F6EA6180096FD65 /* Objects */, - F091C9C41F6EBA5A0096FD65 /* Bridging-Header.h */, 56754EB01D9A4016007BCDC5 /* Assets.xcassets */, - F091C9C11F6EB8660096FD65 /* Localizable.strings */, + F091C9C41F6EBA5A0096FD65 /* Bridging-Header.h */, + F0445D4C200294AB0025AE82 /* ButtonCellView.swift */, + F03A8DF11FFBAA6F0034DC27 /* Display.swift */, + 28D363802280EA6000CB8A99 /* Display+Whitelist.swift */, 56754EB51D9A4016007BCDC5 /* Info.plist */, + F091C9C11F6EB8660096FD65 /* Localizable.strings */, + 6C778D5821E91060000A4D5F /* Main.storyboard */, + 56754EB21D9A4016007BCDC5 /* MainMenu.xib */, + F091C9B21F6EA6110096FD65 /* SliderHandler.swift */, + F091C9B71F6EA79B0096FD65 /* Utils.swift */, + F0445D3620023D5B0025AE82 /* View Controllers */, ); path = MonitorControl; sourceTree = ""; }; - EFFC2F3E35BEC9ACFA754137 /* Pods */ = { + F0445D3620023D5B0025AE82 /* View Controllers */ = { isa = PBXGroup; children = ( - 42B61ABC1D7907131330228A /* Pods-MonitorControl.debug.xcconfig */, - 31E16D90527EBD3F8A12BE0B /* Pods-MonitorControl.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; - F0445D3620023D5B0025AE82 /* Prefs */ = { - isa = PBXGroup; - children = ( - F0445D3720023E710025AE82 /* MainPrefsViewController.swift */, - F0445D3B200254FA0025AE82 /* KeysPrefsViewController.swift */, F0445D3F200259C10025AE82 /* DisplayPrefsViewController.swift */, + F0445D3B200254FA0025AE82 /* KeysPrefsViewController.swift */, + F0445D3720023E710025AE82 /* MainPrefsViewController.swift */, ); - path = Prefs; + path = "View Controllers"; sourceTree = ""; }; F06792E8200A73460066C438 /* MonitorControlHelper */ = { isa = PBXGroup; children = ( - F06792E9200A73460066C438 /* AppDelegate.swift */, F06792F0200A73470066C438 /* Info.plist */, + F06792E9200A73460066C438 /* main.swift */, F06792F1200A73470066C438 /* MonitorControlHelper.entitlements */, ); path = MonitorControlHelper; sourceTree = ""; }; - F091C9B41F6EA6180096FD65 /* Objects */ = { - isa = PBXGroup; - children = ( - F0445D4C200294AB0025AE82 /* ButtonCellView.swift */, - F091C9B21F6EA6110096FD65 /* SliderHandler.swift */, - F03A8DF11FFBAA6F0034DC27 /* Display.swift */, - ); - path = Objects; - sourceTree = ""; - }; - F0A987D71F77B404009B603D /* Frameworks */ = { - isa = PBXGroup; - children = ( - F0A987D81F77B404009B603D /* MonitorControl */, - 398F482D5C8816B29F16AAEB /* Pods_MonitorControl.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - F0A987D81F77B404009B603D /* MonitorControl */ = { - isa = PBXGroup; - children = ( - F0A987D91F77B404009B603D /* MainMenu.strings */, - F0A987DB1F77B404009B603D /* Localizable.strings */, - F0A987DD1F77B404009B603D /* Assets.xcassets */, - F0A987DE1F77B404009B603D /* Objects */, - F0A987E01F77B404009B603D /* MainMenu.xib */, - F0A987E21F77B404009B603D /* Utils.swift */, - F0A987E51F77B404009B603D /* AppDelegate.swift */, - F0A987E61F77B404009B603D /* Bridging-Header.h */, - F0A987E71F77B404009B603D /* Info.plist */, - ); - path = MonitorControl; - sourceTree = ""; - }; - F0A987DE1F77B404009B603D /* Objects */ = { - isa = PBXGroup; - children = ( - F0A987DF1F77B404009B603D /* SliderHandler.swift */, - ); - path = Objects; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -240,14 +242,16 @@ isa = PBXNativeTarget; buildConfigurationList = 56754EB81D9A4016007BCDC5 /* Build configuration list for PBXNativeTarget "MonitorControl" */; buildPhases = ( - C0EF20D28FC7408CBE89A686 /* [CP] Check Pods Manifest.lock */, + 28D1DE0C227FCFAF004CB494 /* [Format] Run SwiftFormat */, F03A8DF01FFB9D4C0034DC27 /* [Lint] Run SwiftLint */, 56754EA71D9A4016007BCDC5 /* Sources */, 56754EA81D9A4016007BCDC5 /* Frameworks */, 56754EA91D9A4016007BCDC5 /* Resources */, - 9DD5968596EFAF0E2EB56496 /* [CP] Embed Pods Frameworks */, F06792F5200A73FA0066C438 /* [Login] Copy Helper to start at Login */, - 6C778D4D21E90DA1000A4D5F /* ShellScript */, + 28D1DDBE227FB668004CB494 /* [Carthage] Embed Frameworks */, + 28D1DE0B227FCF99004CB494 /* [Carthage] Copy Framework Debug Symbols */, + 28D1DE19227FD375004CB494 /* Increase Build Number */, + 28D1DE1A227FD39F004CB494 /* Sync Version Numbers */, ); buildRules = ( ); @@ -282,31 +286,32 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1020; ORGANIZATIONNAME = "Mathew Kurian"; TargetAttributes = { 56754EAA1D9A4016007BCDC5 = { CreatedOnToolsVersion = 8.0; - DevelopmentTeam = CYC8C8R4K9; - LastSwiftMigration = 1010; + DevelopmentTeam = KJ5F4KTDFH; + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; }; F06792E6200A73460066C438 = { CreatedOnToolsVersion = 9.2; - DevelopmentTeam = CYC8C8R4K9; - LastSwiftMigration = 1010; + DevelopmentTeam = KJ5F4KTDFH; + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; }; }; }; buildConfigurationList = 56754EA61D9A4016007BCDC5 /* Build configuration list for PBXProject "MonitorControl" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, fr, + de, ); mainGroup = 56754EA21D9A4016007BCDC5; productRefGroup = 56754EAC1D9A4016007BCDC5 /* Products */; @@ -327,7 +332,6 @@ 56754EB11D9A4016007BCDC5 /* Assets.xcassets in Resources */, F0EB972F1F6ED7C800686D2A /* Localizable.strings in Resources */, 6C778D5A21E91060000A4D5F /* Main.storyboard in Resources */, - 55359E3B1E2737EC002671BC /* ddcctl.sh in Resources */, 56754EB41D9A4016007BCDC5 /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -336,13 +340,14 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 28D1DDF3227FC8C6004CB494 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 6C778D4D21E90DA1000A4D5F /* ShellScript */ = { + 28D1DE0C227FCFAF004CB494 /* [Format] Run SwiftFormat */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -351,53 +356,50 @@ ); inputPaths = ( ); + name = "[Format] Run SwiftFormat"; outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nbuildNumber=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"${INFOPLIST_FILE}\")\nbuildNumber=$(($buildNumber + 1))\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"${INFOPLIST_FILE}\"\n\n"; + shellScript = "if which swiftformat >/dev/null; then\n swiftformat .\nelse\n echo \"warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat\" >&2\nfi\n"; }; - 9DD5968596EFAF0E2EB56496 /* [CP] Embed Pods Frameworks */ = { + 28D1DE19227FD375004CB494 /* Increase Build Number */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${SRCROOT}/Pods/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", ); - name = "[CP] Embed Pods Frameworks"; + name = "Increase Build Number"; + outputFileListPaths = ( + ); outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AMCoreAudio.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MASPreferences.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MediaKeyTap.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MonitorControl/Pods-MonitorControl-frameworks.sh\"\n"; - showEnvVarsInLog = 0; + shellScript = "set -e\nset -u\nset -o pipefail\n\nfiles_have_changed() {\n test -n \"$(find \"${1}\" ! -path '*xcuserdata*' ! -path '*.git' ! -path '*.git/*' -newer \"${2}\")\"\n}\n\nif files_have_changed \"${PROJECT_DIR}\" \"${INFOPLIST_FILE}\"; then\n agvtool next-version -all\nfi\n"; }; - C0EF20D28FC7408CBE89A686 /* [CP] Check Pods Manifest.lock */ = { + 28D1DE1A227FD39F004CB494 /* Sync Version Numbers */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", ); - name = "[CP] Check Pods Manifest.lock"; + name = "Sync Version Numbers"; + outputFileListPaths = ( + ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-MonitorControl-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; + shellScript = "set -e\nset -u\nset -o pipefail\n\nagvtool new-marketing-version \"$(agvtool what-marketing-version -terse1)\"\n"; }; F03A8DF01FFB9D4C0034DC27 /* [Lint] Run SwiftLint */ = { isa = PBXShellScriptBuildPhase; @@ -420,15 +422,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F0445D40200259C10025AE82 /* DisplayPrefsViewController.swift in Sources */, - F03A8DF21FFBAA6F0034DC27 /* Display.swift in Sources */, - F091C9B31F6EA6110096FD65 /* SliderHandler.swift in Sources */, 56754EAF1D9A4016007BCDC5 /* AppDelegate.swift in Sources */, - 55359E391E2737EC002671BC /* DDC.c in Sources */, - F0445D3820023E710025AE82 /* MainPrefsViewController.swift in Sources */, + F0445D4D200294AB0025AE82 /* ButtonCellView.swift in Sources */, + 2894D9B82280B30500DF58DA /* CGDirectDisplayID+Extension.swift in Sources */, + 28D363812280EA6000CB8A99 /* Display+Whitelist.swift in Sources */, + F03A8DF21FFBAA6F0034DC27 /* Display.swift in Sources */, + F0445D40200259C10025AE82 /* DisplayPrefsViewController.swift in Sources */, + 28D1DDF0227FBD99004CB494 /* EDID+Extension.swift in Sources */, F0445D3D200254FA0025AE82 /* KeysPrefsViewController.swift in Sources */, + F0445D3820023E710025AE82 /* MainPrefsViewController.swift in Sources */, + 28D1DDF2227FBE71004CB494 /* NSScreen+Extension.swift in Sources */, + F091C9B31F6EA6110096FD65 /* SliderHandler.swift in Sources */, F091C9B81F6EA79B0096FD65 /* Utils.swift in Sources */, - F0445D4D200294AB0025AE82 /* ButtonCellView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -436,7 +441,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F06792EA200A73460066C438 /* AppDelegate.swift in Sources */, + F06792EA200A73460066C438 /* main.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -447,6 +452,7 @@ isa = PBXVariantGroup; children = ( 56754EB31D9A4016007BCDC5 /* Base */, + 28D1DD7A227FA927004CB494 /* de */, F0445D49200285690025AE82 /* en */, F0445D4B2002856C0025AE82 /* fr */, ); @@ -457,6 +463,7 @@ isa = PBXVariantGroup; children = ( 6C778D5921E91060000A4D5F /* Base */, + 28D1DD79227FA927004CB494 /* de */, 6C778D5E21E910A2000A4D5F /* en */, 6C778D5F21E910A6000A4D5F /* fr */, ); @@ -466,38 +473,13 @@ F091C9C11F6EB8660096FD65 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( + 28D1DD7C227FA939004CB494 /* de */, F091C9C21F6EB8660096FD65 /* en */, F091C9C31F6EB8720096FD65 /* fr */, ); name = Localizable.strings; sourceTree = ""; }; - F0A987D91F77B404009B603D /* MainMenu.strings */ = { - isa = PBXVariantGroup; - children = ( - F0A987DA1F77B404009B603D /* en */, - F0A987E31F77B404009B603D /* fr */, - ); - name = MainMenu.strings; - sourceTree = ""; - }; - F0A987DB1F77B404009B603D /* Localizable.strings */ = { - isa = PBXVariantGroup; - children = ( - F0A987DC1F77B404009B603D /* en */, - F0A987E41F77B404009B603D /* fr */, - ); - name = Localizable.strings; - sourceTree = ""; - }; - F0A987E01F77B404009B603D /* MainMenu.xib */ = { - isa = PBXVariantGroup; - children = ( - F0A987E11F77B404009B603D /* Base */, - ); - name = MainMenu.xib; - sourceTree = ""; - }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ @@ -552,7 +534,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.12; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -606,7 +588,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.12; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; @@ -615,39 +597,53 @@ }; 56754EB91D9A4016007BCDC5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 42B61ABC1D7907131330228A /* Pods-MonitorControl.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = CYC8C8R4K9; - INFOPLIST_FILE = "$(SRCROOT)/MonitorControl/Info.plist"; + CURRENT_PROJECT_VERSION = 179; + DEVELOPMENT_TEAM = KJ5F4KTDFH; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/Mac", + "$(PROJECT_DIR)/Carthage/Build", + "$(PROJECT_DIR)", + ); + INFOPLIST_FILE = MonitorControl/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = me.guillaumeb.MonitorControl; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "MonitorControl/Bridging-Header.h"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 56754EBA1D9A4016007BCDC5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 31E16D90527EBD3F8A12BE0B /* Pods-MonitorControl.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = CYC8C8R4K9; - INFOPLIST_FILE = "$(SRCROOT)/MonitorControl/Info.plist"; + CURRENT_PROJECT_VERSION = 179; + DEVELOPMENT_TEAM = KJ5F4KTDFH; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/Mac", + "$(PROJECT_DIR)/Carthage/Build", + "$(PROJECT_DIR)", + ); + INFOPLIST_FILE = MonitorControl/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = me.guillaumeb.MonitorControl; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "MonitorControl/Bridging-Header.h"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; @@ -662,15 +658,16 @@ CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = CYC8C8R4K9; + CURRENT_PROJECT_VERSION = 179; + DEVELOPMENT_TEAM = KJ5F4KTDFH; 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.2; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; @@ -685,15 +682,16 @@ CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = CYC8C8R4K9; + CURRENT_PROJECT_VERSION = 179; + DEVELOPMENT_TEAM = KJ5F4KTDFH; 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.2; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; diff --git a/MonitorControl.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/MonitorControl.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from MonitorControl.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to MonitorControl.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/MonitorControl.xcodeproj/xcshareddata/xcschemes/MonitorControl.xcscheme b/MonitorControl.xcodeproj/xcshareddata/xcschemes/MonitorControl.xcscheme index eaaeab23..657c698f 100644 --- a/MonitorControl.xcodeproj/xcshareddata/xcschemes/MonitorControl.xcscheme +++ b/MonitorControl.xcodeproj/xcshareddata/xcschemes/MonitorControl.xcscheme @@ -1,6 +1,6 @@ - - - - - - diff --git a/MonitorControl/AppDelegate.swift b/MonitorControl/AppDelegate.swift index e4b7de53..d336e70f 100644 --- a/MonitorControl/AppDelegate.swift +++ b/MonitorControl/AppDelegate.swift @@ -1,303 +1,267 @@ -// -// AppDelegate.swift -// MonitorControl -// -// Created by Mathew Kurian on 9/26/16. -// Last edited by Guillaume Broder on 9/17/2017 -// MIT Licensed. 2017. -// - +import AMCoreAudio import Cocoa +import DDC import Foundation -import MediaKeyTap import MASPreferences -import AMCoreAudio +import MediaKeyTap +import os.log -var app: AppDelegate! = nil +var app: AppDelegate! let prefs = UserDefaults.standard @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { - - @IBOutlet weak var statusMenu: NSMenu! - @IBOutlet weak var window: NSWindow! - - let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) - - var monitorItems: [NSMenuItem] = [] - var displays: [Display] = [] - - let step = 100/16 - - var mediaKeyTap: MediaKeyTap? - var prefsController: NSWindowController? - - var keysListenedFor: [MediaKey] = [.brightnessUp, .brightnessDown, .mute, .volumeUp, .volumeDown] - - func applicationDidFinishLaunching(_ aNotification: Notification) { - app = self - - setupLayout() - subscribeEventListeners() - setVolumeKeysMode() - statusItem.image = NSImage.init(named: "status") - statusItem.menu = statusMenu - setDefaultPrefs() - Utils.acquirePrivileges() - CGDisplayRegisterReconfigurationCallback({_, _, _ in app.updateDisplays()}, nil) - updateDisplays() + @IBOutlet var statusMenu: NSMenu! + @IBOutlet var window: NSWindow! + + let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) + + var monitorItems: [NSMenuItem] = [] + var displays: [Display] = [] + + let step = 100 / 16 + + var mediaKeyTap: MediaKeyTap? + var prefsController: NSWindowController? + + func applicationDidFinishLaunching(_: Notification) { + app = self + + self.setupLayout() + self.subscribeEventListeners() + self.startOrRestartMediaKeyTap() + self.statusItem.image = NSImage(named: "status") + self.statusItem.menu = self.statusMenu + self.setDefaultPrefs() + Utils.acquirePrivileges() + CGDisplayRegisterReconfigurationCallback({ _, _, _ in app.updateDisplays() }, nil) + self.updateDisplays() + } + + func applicationWillTerminate(_: Notification) { + AMCoreAudio.NotificationCenter.defaultCenter.unsubscribe(self, eventType: AudioHardwareEvent.self) + } + + @IBAction func quitClicked(_: AnyObject) { + NSApplication.shared.terminate(self) + } + + @IBAction func prefsClicked(_ sender: AnyObject) { + if let prefsController = prefsController { + prefsController.showWindow(sender) + NSApp.activate(ignoringOtherApps: true) + prefsController.window?.makeKeyAndOrderFront(sender) } + } - @IBAction func quitClicked(_ sender: AnyObject) { - NSApplication.shared.terminate(self) - } + /// 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) - @IBAction func prefsClicked(_ sender: AnyObject) { - if let prefsController = prefsController { - prefsController.showWindow(sender) - NSApp.activate(ignoringOtherApps: true) - prefsController.window?.makeKeyAndOrderFront(sender) - } + prefs.set(false, forKey: Utils.PrefKeys.showContrast.rawValue) + prefs.set(false, forKey: Utils.PrefKeys.lowerContrast.rawValue) } + } - /// 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) + // MARK: - Menu - prefs.set(false, forKey: Utils.PrefKeys.startAtLogin.rawValue) + func clearDisplays() { + if self.statusMenu.items.count > 2 { + var items: [NSMenuItem] = [] + for i in 0.. 2 { - var items: [NSMenuItem] = [] - for i in 0.. 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 { - return false - } - - // Does screen support EDID ? - var edid = EDID() - if !EDIDTest(id, &edid) { - return false - } - - return true - } - return false - } - - if filteredScreens.count == 1 { - self.addScreenToMenu(screen: filteredScreens[0], asSubMenu: false) - } else { - for screen in filteredScreens { - self.addScreenToMenu(screen: screen, asSubMenu: true) - } - } + let filteredScreens = NSScreen.screens.filter { screen -> Bool in + // Skip built-in displays. + if screen.isBuiltin { + return false + } - 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") - item.isEnabled = false - monitorItems.append(item) - statusMenu.insertItem(item, at: 0) - } + return DDC(for: screen.displayID)?.edid() != nil } - /// 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")) - if prefs.bool(forKey: Utils.PrefKeys.showContrast.rawValue) { - let contrastSliderHandler = Utils.addSliderMenuItem(toMenu: monitorSubMenu, - forDisplay: display, - command: CONTRAST, - title: NSLocalizedString("Contrast", comment: "Shown in menu")) - display.contrastSliderHandler = contrastSliderHandler - } - - 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) - } - } + switch filteredScreens.count { + case 0: + // If no DDC capable display was detected + let item = NSMenuItem() + item.title = NSLocalizedString("No supported display found", comment: "Shown in menu") + item.isEnabled = false + self.monitorItems.append(item) + self.statusMenu.insertItem(item, at: 0) + self.statusMenu.insertItem(NSMenuItem.separator(), at: 1) + default: + os_log("The following displays were found:", type: .info) + + for screen in filteredScreens { + os_log(" - %@", type: .info, "\(screen.displayName ?? NSLocalizedString("Unknown", comment: "unknown display name")) (Vendor: \(screen.vendorNumber ?? 0), Model: \(screen.modelNumber ?? 0))") + self.addScreenToMenu(screen: screen, asSubMenu: filteredScreens.count > 1) + } } - - private func setupLayout() { - let storyboard: NSStoryboard = NSStoryboard.init(name: "Main", bundle: Bundle.main) - let views = [ - storyboard.instantiateController(withIdentifier: "MainPrefsVC"), - storyboard.instantiateController(withIdentifier: "KeysPrefsVC"), - storyboard.instantiateController(withIdentifier: "DisplayPrefsVC") - ] - prefsController = MASPreferencesWindowController(viewControllers: views, title: NSLocalizedString("Preferences", comment: "Shown in Preferences window")) - } - - private func subscribeEventListeners() { - // subscribe KeyTap event listener - NotificationCenter.default.addObserver(self, selector: #selector(handleListenForChanged), name: NSNotification.Name.init(Utils.PrefKeys.listenFor.rawValue), object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(handleShowContrastChanged), name: NSNotification.Name.init(Utils.PrefKeys.showContrast.rawValue), object: nil) - - // subscribe Audio output detector (AMCoreAudio) - NotificationCenter.defaultCenter.subscribe(self, eventType: AudioHardwareEvent.self, dispatchQueue: DispatchQueue.main) + } + + /// 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) { + let id = screen.displayID + let ddc = DDC(for: id) + + if let edid = ddc?.edid() { + let name = Utils.getDisplayName(forEdid: edid) + + let display = Display(id, name: name) + + let monitorSubMenu: NSMenu = asSubMenu ? NSMenu() : self.statusMenu + + self.statusMenu.insertItem(NSMenuItem.separator(), at: 0) + + let volumeSliderHandler = Utils.addSliderMenuItem(toMenu: monitorSubMenu, + forDisplay: display, + command: .audioSpeakerVolume, + title: NSLocalizedString("Volume", comment: "Shown in menu")) + let brightnessSliderHandler = Utils.addSliderMenuItem(toMenu: monitorSubMenu, + forDisplay: display, + command: .brightness, + title: NSLocalizedString("Brightness", comment: "Shown in menu")) + if prefs.bool(forKey: Utils.PrefKeys.showContrast.rawValue) { + let contrastSliderHandler = Utils.addSliderMenuItem(toMenu: monitorSubMenu, + forDisplay: display, + command: .contrast, + title: NSLocalizedString("Contrast", comment: "Shown in menu")) + display.contrastSliderHandler = contrastSliderHandler + } + + display.volumeSliderHandler = volumeSliderHandler + display.brightnessSliderHandler = brightnessSliderHandler + self.displays.append(display) + + let monitorMenuItem = NSMenuItem() + monitorMenuItem.title = "\(name)" + if asSubMenu { + monitorMenuItem.submenu = monitorSubMenu + } + + self.monitorItems.append(monitorMenuItem) + self.statusMenu.insertItem(monitorMenuItem, at: 0) } + } + + private func setupLayout() { + let storyboard: NSStoryboard = NSStoryboard(name: "Main", bundle: Bundle.main) + let views = [ + storyboard.instantiateController(withIdentifier: "MainPrefsVC"), + storyboard.instantiateController(withIdentifier: "KeysPrefsVC"), + storyboard.instantiateController(withIdentifier: "DisplayPrefsVC"), + ] + prefsController = MASPreferencesWindowController(viewControllers: views, title: NSLocalizedString("Preferences", comment: "Shown in Preferences window")) + } + + 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) + + // subscribe Audio output detector (AMCoreAudio) + AMCoreAudio.NotificationCenter.defaultCenter.subscribe(self, eventType: AudioHardwareEvent.self, dispatchQueue: DispatchQueue.main) + } } // MARK: - Media Key Tap delegate -extension AppDelegate: MediaKeyTapDelegate { - func handle(mediaKey: MediaKey, event: KeyEvent?) { - - guard let currentDisplay = Utils.getCurrentDisplay(from: displays) else { return } - let allDisplays = prefs.bool(forKey: Utils.PrefKeys.allScreens.rawValue) ? displays : [currentDisplay] - - for display in allDisplays { - 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) - case .brightnessDown: - let value = currentDisplay.calcNewValue(for: BRIGHTNESS, withRel: -step) - display.setBrightness(to: value) - case .mute: - display.mute() - case .volumeUp: - let value = display.calcNewValue(for: AUDIO_SPEAKER_VOLUME, withRel: +step) - display.setVolume(to: value) - case .volumeDown: - let value = display.calcNewValue(for: AUDIO_SPEAKER_VOLUME, withRel: -step) - display.setVolume(to: value) - - default: - return - } - } +extension AppDelegate: MediaKeyTapDelegate { + func handle(mediaKey: MediaKey, event _: KeyEvent?) { + guard let currentDisplay = Utils.getCurrentDisplay(from: displays) else { return } + + let allDisplays = prefs.bool(forKey: Utils.PrefKeys.allScreens.rawValue) ? self.displays : [currentDisplay] + + for display in allDisplays { + if (prefs.object(forKey: "\(display.identifier)-state") as? Bool) ?? true { + switch mediaKey { + case .brightnessUp: + let value = display.calcNewValue(for: .brightness, withRel: +self.step) + display.setBrightness(to: value) + case .brightnessDown: + let value = currentDisplay.calcNewValue(for: .brightness, withRel: -self.step) + display.setBrightness(to: value) + case .mute: + display.mute() + case .volumeUp: + let value = display.calcNewValue(for: .audioSpeakerVolume, withRel: +self.step) + display.setVolume(to: value) + case .volumeDown: + let value = display.calcNewValue(for: .audioSpeakerVolume, withRel: -self.step) + display.setVolume(to: value) + + default: + return } - + } } + } - // MARK: - Prefs notification - @objc func handleListenForChanged() { - readKeyListenPreferences() - setKeysToListenFor() - } + // MARK: - Prefs notification - @objc func handleShowContrastChanged() { - self.updateDisplays() - } + @objc func handleListenForChanged() { + self.startOrRestartMediaKeyTap() + } + + @objc func handleShowContrastChanged() { + self.updateDisplays() + } + + private func startOrRestartMediaKeyTap() { + var keys: [MediaKey] - private func setKeysToListenFor() { - mediaKeyTap?.stop() - mediaKeyTap = MediaKeyTap.init(delegate: self, for: keysListenedFor, observeBuiltIn: false) - mediaKeyTap?.start() + switch prefs.integer(forKey: Utils.PrefKeys.listenFor.rawValue) { + case Utils.ListenForKeys.brightnessOnlyKeys.rawValue: + keys = [.brightnessUp, .brightnessDown] + case Utils.ListenForKeys.volumeOnlyKeys.rawValue: + keys = [.mute, .volumeUp, .volumeDown] + default: + keys = [.brightnessUp, .brightnessDown, .mute, .volumeUp, .volumeDown] } - private func readKeyListenPreferences() { - let listenFor = prefs.integer(forKey: Utils.PrefKeys.listenFor.rawValue) - keysListenedFor = [.brightnessUp, .brightnessDown, .mute, .volumeUp, .volumeDown] - if listenFor == Utils.ListenForKeys.brightnessOnlyKeys.rawValue { - keysListenedFor.removeSubrange(2...4) - } else if listenFor == Utils.ListenForKeys.volumeOnlyKeys.rawValue { - keysListenedFor.removeSubrange(0...1) - } + if let audioDevice = AudioDevice.defaultOutputDevice(), audioDevice.canSetVirtualMasterVolume(direction: .playback) { + // Remove volume related keys. + let keysToDelete: [MediaKey] = [.volumeUp, .volumeDown, .mute] + keys.removeAll { keysToDelete.contains($0) } } + + self.mediaKeyTap?.stop() + self.mediaKeyTap = MediaKeyTap(delegate: self, for: keys, observeBuiltIn: false) + self.mediaKeyTap?.start() + } } extension AppDelegate: EventSubscriber { - - /** - Fires off when a change in default audio device is detected. - */ - func eventReceiver(_ event: Event) { - - switch event { - case let event as AudioHardwareEvent: - switch event { - case .defaultOutputDeviceChanged(let audioDevice): - #if DEBUG - print("Default output device changed to \(audioDevice)") - print("Can device set its own volume? \(audioDevice.canSetVirtualMasterVolume(direction: .playback))") - #endif - setVolumeKeysMode() - default: break - } - default: break - } - } - - /** - We check if the current default audio output device can change the volume, - if not, we know for sure that we don't need to interact with it. - */ - func setVolumeKeysMode() { - - readKeyListenPreferences() - - if let defaultOutputDevice = AudioDevice.defaultOutputDevice() { - if defaultOutputDevice.canSetVirtualMasterVolume(direction: .playback) { - // Remove volume related keys - let keysToDelete: [MediaKey] = [.volumeUp, .volumeDown, .mute] - keysListenedFor = keysListenedFor.filter({ !keysToDelete.contains($0) }) - } else { - // load keys to listen to from prefs like normal - readKeyListenPreferences() - } - } - setKeysToListenFor() + /** + Fires off when the default audio device changes. + */ + func eventReceiver(_ event: Event) { + if case let .defaultOutputDeviceChanged(audioDevice)? = event as? AudioHardwareEvent { + #if DEBUG + os_log("Default output device changed to “%@”.", type: .info, audioDevice.name) + os_log("Can device set its own volume? %@", type: .info, audioDevice.canSetVirtualMasterVolume(direction: .playback).description) + #endif + + self.startOrRestartMediaKeyTap() } + } } diff --git a/MonitorControl/Base.lproj/Main.storyboard b/MonitorControl/Base.lproj/Main.storyboard index 8c520638..91e8a70a 100644 --- a/MonitorControl/Base.lproj/Main.storyboard +++ b/MonitorControl/Base.lproj/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -11,7 +11,7 @@ - + @@ -46,7 +46,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -79,11 +79,11 @@ - + - + @@ -91,13 +91,13 @@ - - - + + + - + @@ -207,6 +207,7 @@ + - - + + @@ -318,14 +319,14 @@ - + - - - + + + diff --git a/MonitorControl/Base.lproj/MainMenu.xib b/MonitorControl/Base.lproj/MainMenu.xib index a8339424..ae743590 100644 --- a/MonitorControl/Base.lproj/MainMenu.xib +++ b/MonitorControl/Base.lproj/MainMenu.xib @@ -1,7 +1,8 @@ - + - + + @@ -13,7 +14,6 @@ - @@ -30,11 +30,11 @@ - + - + diff --git a/MonitorControl/Bridging-Header.h b/MonitorControl/Bridging-Header.h index 7d73d33e..f529e5ba 100644 --- a/MonitorControl/Bridging-Header.h +++ b/MonitorControl/Bridging-Header.h @@ -1,16 +1,4 @@ -// -// Bridging-Header.h -// MonitorControl -// -// Created by Guillaume BRODER on 17/09/2017. -// MIT Licensed. 2017. -// - -#ifndef Bridging_Header_h -#define Bridging_Header_h +#pragma once #import -#include "../ddcctl/DDC.h" #import - -#endif /* Bridging_Header_h */ diff --git a/MonitorControl/ButtonCellView.swift b/MonitorControl/ButtonCellView.swift new file mode 100644 index 00000000..4eaad286 --- /dev/null +++ b/MonitorControl/ButtonCellView.swift @@ -0,0 +1,29 @@ +import Cocoa +import os.log + +class ButtonCellView: 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: + self.prefs.set(true, forKey: "\(display.identifier)-state") + case .off: + self.prefs.set(false, forKey: "\(display.identifier)-state") + default: + break + } + + #if DEBUG + os_log("Toggle enabled display state: %@", type: .info, sender.state == .on ? "on" : "off") + #endif + } + } +} diff --git a/MonitorControl/Display+Whitelist.swift b/MonitorControl/Display+Whitelist.swift new file mode 100644 index 00000000..40cb717c --- /dev/null +++ b/MonitorControl/Display+Whitelist.swift @@ -0,0 +1,26 @@ +extension Display { + enum WhitelistReason { + case longerDelay + case hideOsd + } + + static let whitelist: [UInt32: [UInt32: [WhitelistReason]]] = [ + 7789: [30460: [.hideOsd, .longerDelay]], // LG 38UC99-W + ] + + 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 + } +} diff --git a/MonitorControl/Display.swift b/MonitorControl/Display.swift new file mode 100644 index 00000000..9aef176d --- /dev/null +++ b/MonitorControl/Display.swift @@ -0,0 +1,132 @@ +import Cocoa +import DDC + +class Display { + let identifier: CGDirectDisplayID + let name: String + var isEnabled: Bool + var isMuted: Bool = false + var brightnessSliderHandler: SliderHandler? + var volumeSliderHandler: SliderHandler? + var contrastSliderHandler: SliderHandler? + var ddc: DDC? + + private let prefs = UserDefaults.standard + + init(_ identifier: CGDirectDisplayID, name: String, isEnabled: Bool = true) { + self.identifier = identifier + self.name = name + self.isEnabled = isEnabled + self.ddc = DDC(for: identifier) + } + + // On some displays, the display's OSD overlaps the macOS OSD, + // calling the OSD command with 1 seems to hide it. + func hideDisplayOsd() { + guard self.hideOsd else { + return + } + + _ = self.ddc?.write(command: .onScreenDisplay, value: 1) + + DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime.now() + 0.000001) { + _ = self.ddc?.write(command: .onScreenDisplay, value: 1) + } + + DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime.now() + 0.00001) { + _ = self.ddc?.write(command: .onScreenDisplay, value: 1) + } + + DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime.now() + 0.0001) { + _ = self.ddc?.write(command: .onScreenDisplay, value: 1) + } + } + + func mute() { + var value = 0 + if self.isMuted { + value = self.prefs.integer(forKey: "\(DDC.Command.audioSpeakerVolume.value)-\(self.identifier)") + self.isMuted = false + } else { + self.isMuted = true + } + + _ = self.ddc?.write(command: .audioSpeakerVolume, value: UInt8(value)) + self.hideDisplayOsd() + + if let slider = volumeSliderHandler?.slider { + slider.intValue = Int32(value) + } + + self.showOsd(command: .audioSpeakerVolume, value: value) + } + + func setVolume(to value: Int) { + if value > 0 { + self.isMuted = false + } + + _ = self.ddc?.write(command: .audioSpeakerVolume, value: UInt8(value)) + self.hideDisplayOsd() + + if let slider = volumeSliderHandler?.slider { + slider.intValue = Int32(value) + } + + self.showOsd(command: .audioSpeakerVolume, value: value) + self.saveValue(value, for: .audioSpeakerVolume) + } + + func setBrightness(to value: Int) { + if self.prefs.bool(forKey: Utils.PrefKeys.lowerContrast.rawValue) { + if value == 0 { + _ = self.ddc?.write(command: .contrast, value: UInt8(value)) + + if let slider = contrastSliderHandler?.slider { + slider.intValue = Int32(value) + } + } else if self.prefs.integer(forKey: "\(DDC.Command.brightness.value)-\(self.identifier)") == 0 { + let contrastValue = self.prefs.integer(forKey: "\(DDC.Command.contrast.value)-\(self.identifier)") + _ = self.ddc?.write(command: .contrast, value: UInt8(contrastValue)) + } + } + + _ = self.ddc?.write(command: .brightness, value: UInt8(value)) + + if let slider = brightnessSliderHandler?.slider { + slider.intValue = Int32(value) + } + + self.showOsd(command: .brightness, value: value) + self.saveValue(value, for: .brightness) + } + + func calcNewValue(for command: DDC.Command, withRel rel: Int) -> Int { + let currentValue = self.prefs.integer(forKey: "\(command)-\(self.identifier)") + return max(0, min(100, currentValue + rel)) + } + + func saveValue(_ value: Int, for command: DDC.Command) { + self.prefs.set(value, forKey: "\(command)-\(self.identifier)") + } + + private func showOsd(command: DDC.Command, value: Int) { + if let manager = OSDManager.sharedManager() as? OSDManager { + var osdImage: Int64 = 1 // Brightness Image + if command == .audioSpeakerVolume { + osdImage = 3 // Speaker image + if self.isMuted { + osdImage = 4 // Mute speaker + } + } + let step = 100 / 16 + manager.showImage(osdImage, + onDisplayID: self.identifier, + priority: 0x1F4, + msecUntilFade: 2000, + filledChiclets: UInt32(value / step), + totalChiclets: UInt32(100 / step), + locked: false) + } + } +} diff --git a/MonitorControl/Info.plist b/MonitorControl/Info.plist index 874975af..9ee4ba21 100644 --- a/MonitorControl/Info.plist +++ b/MonitorControl/Info.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - en + $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.3.0 CFBundleVersion - 48 + 179 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/MonitorControl/Objects/ButtonCellView.swift b/MonitorControl/Objects/ButtonCellView.swift deleted file mode 100644 index fa6aafc4..00000000 --- a/MonitorControl/Objects/ButtonCellView.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// ButtonCellView.swift -// MonitorControl -// -// Created by Guillaume BRODER on 07/01/2018. -// Copyright © 2018 Mathew Kurian. All rights reserved. -// - -import Cocoa - -class ButtonCellView: 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: - prefs.set(true, forKey: "\(display.identifier)-state") - case .off: - prefs.set(false, forKey: "\(display.identifier)-state") - default: - break - } - - #if DEBUG - print("Toggle enabled display state -> \(sender.state == .on ? "on" : "off")") - #endif - } - } -} diff --git a/MonitorControl/Objects/Display.swift b/MonitorControl/Objects/Display.swift deleted file mode 100644 index cf0f9eb6..00000000 --- a/MonitorControl/Objects/Display.swift +++ /dev/null @@ -1,109 +0,0 @@ -// -// Display.swift -// MonitorControl -// -// Created by Guillaume BRODER on 02/01/2018. -// MIT Licensed. -// - -import Cocoa - -/// A display -class Display { - let identifier: CGDirectDisplayID - let name: String - let serial: String - var isEnabled: Bool - var isMuted: Bool = false - var brightnessSliderHandler: SliderHandler? - var volumeSliderHandler: SliderHandler? - var contrastSliderHandler: SliderHandler? - - private let prefs = UserDefaults.standard - - init(_ identifier: CGDirectDisplayID, name: String, serial: String, isEnabled: Bool = true) { - self.identifier = identifier - self.name = name - self.serial = serial - self.isEnabled = isEnabled - } - - func mute() { - var value = 0 - if isMuted { - value = prefs.integer(forKey: "\(AUDIO_SPEAKER_VOLUME)-\(identifier)") - isMuted = false - } else { - isMuted = true - } - - Utils.sendCommand(AUDIO_SPEAKER_VOLUME, toMonitor: identifier, withValue: value) - if let slider = volumeSliderHandler?.slider { - slider.intValue = Int32(value) - } - showOsd(command: AUDIO_SPEAKER_VOLUME, value: value) - } - - func setVolume(to value: Int) { - if value > 0 { - isMuted = false - } - - Utils.sendCommand(AUDIO_SPEAKER_VOLUME, toMonitor: identifier, withValue: value) - if let slider = volumeSliderHandler?.slider { - slider.intValue = Int32(value) - } - showOsd(command: AUDIO_SPEAKER_VOLUME, value: value) - saveValue(value, for: AUDIO_SPEAKER_VOLUME) - } - - 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) - } - showOsd(command: BRIGHTNESS, value: value) - saveValue(value, for: BRIGHTNESS) - } - - func calcNewValue(for command: Int32, withRel rel: Int) -> Int { - let currentValue = prefs.integer(forKey: "\(command)-\(identifier)") - return max(0, min(100, currentValue + rel)) - } - - func saveValue(_ value: Int, for command: Int32) { - prefs.set(value, forKey: "\(command)-\(identifier)") - } - - private func showOsd(command: Int32, value: Int) { - if let manager = OSDManager.sharedManager() as? OSDManager { - var osdImage: Int64 = 1 // Brightness Image - if command == AUDIO_SPEAKER_VOLUME { - osdImage = 3 // Speaker image - if isMuted { - osdImage = 4 // Mute speaker - } - } - let step = 100/16 - manager.showImage(osdImage, - onDisplayID: identifier, - priority: 0x1f4, - msecUntilFade: 2000, - filledChiclets: UInt32(value/step), - totalChiclets: UInt32(100/step), - locked: false) - } - } -} diff --git a/MonitorControl/Objects/SliderHandler.swift b/MonitorControl/Objects/SliderHandler.swift deleted file mode 100644 index 6cddebcd..00000000 --- a/MonitorControl/Objects/SliderHandler.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// SliderHandler.swift -// MonitorControl -// -// Created by Guillaume BRODER on 9/17/2017. -// MIT Licensed. 2017. -// - -import Cocoa - -/// Handle the slider -class SliderHandler { - var slider: NSSlider? - var display: Display - var command: Int32 = 0 - - public init(display: Display, command: Int32) { - self.display = display - self.command = command - } - - @objc func valueChanged(slider: NSSlider) { - let snapInterval = 25 - let snapThreshold = 3 - - var value = slider.integerValue - - let closest = (value + snapInterval / 2) / snapInterval * snapInterval - if abs(closest - value) <= snapThreshold { - value = closest - slider.integerValue = value - } - - 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 deleted file mode 100644 index 3b933c7d..00000000 --- a/MonitorControl/Prefs/DisplayPrefsViewController.swift +++ /dev/null @@ -1,122 +0,0 @@ -// -// DisplayPrefsViewController.swift -// MonitorControl -// -// Created by Guillaume BRODER on 07/01/2018. -// MIT Licensed. -// - -import Cocoa -import MASPreferences - -class DisplayPrefsViewController: NSViewController, MASPreferencesViewController, NSTableViewDataSource, NSTableViewDelegate { - - var viewIdentifier: String = "Display" - var toolbarItemLabel: String? = NSLocalizedString("Display", comment: "Shown in the main prefs window") - var toolbarItemImage: NSImage? = NSImage.init(named: NSImage.computerName) - let prefs = UserDefaults.standard - - var displays: [Display] = [] - enum DisplayCell: String { - case checkbox - case name - case identifier - } - - @IBOutlet var allScreens: NSButton! - @IBOutlet var displayList: NSTableView! - - override func viewDidLoad() { - super.viewDidLoad() - - allScreens.state = prefs.bool(forKey: Utils.PrefKeys.allScreens.rawValue) ? .on : .off - - loadDisplayList() - } - - @IBAction func allScreensTouched(_ sender: NSButton) { - switch sender.state { - case .on: - prefs.set(true, forKey: Utils.PrefKeys.allScreens.rawValue) - case .off: - prefs.set(false, forKey: Utils.PrefKeys.allScreens.rawValue) - default: break - } - - #if DEBUG - print("Toggle allScreens state -> \(sender.state == .on ? "on" : "off")") - #endif - } - - // MARK: - Table datasource - - func loadDisplayList() { - for screen in NSScreen.screens { - if let id = screen.deviceDescription[NSDeviceDescriptionKey.init("NSScreenNumber")] as? CGDirectDisplayID { - // Is Built In Screen (e.g. MBP/iMac Screen) - if CGDisplayIsBuiltin(id) != 0 { - let display = Display(id, name: "Mac built-in Display", serial: "", isEnabled: false) - displays.append(display) - continue - } - - // Does screen support EDID ? - var edid = EDID() - if !EDIDTest(id, &edid) { - continue - } - - let name = Utils.getDisplayName(forEdid: edid) - let serial = Utils.getDisplaySerial(forEdid: edid) - let isEnabled = (prefs.object(forKey: "\(id)-state") as? Bool) ?? true - - let display = Display(id, name: name, serial: serial, isEnabled: isEnabled) - displays.append(display) - } - } - displayList.reloadData() - } - - func numberOfRows(in tableView: NSTableView) -> Int { - return displays.count - } - - // MARK: - Table delegate - - func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { - var cellType = DisplayCell.checkbox - var checked = false - var text = "" - let display = displays[row] - - if tableColumn == tableView.tableColumns[0] { - // Checkbox - checked = display.isEnabled - } else if tableColumn == tableView.tableColumns[1] { - // Name - text = display.name - cellType = DisplayCell.name - } else if tableColumn == tableView.tableColumns[2] { - // Identifier - text = "\(display.identifier)" - cellType = DisplayCell.identifier - } - if cellType == DisplayCell.checkbox { - if let cell = tableView.makeView(withIdentifier: (tableColumn?.identifier)!, owner: nil) as? ButtonCellView { - cell.button.state = checked ? .on : .off - cell.display = display - if display.name == "Mac built-in Display" { - cell.button.isEnabled = false - } - return cell - } - } else { - if let cell = tableView.makeView(withIdentifier: (tableColumn?.identifier)!, owner: nil) as? NSTableCellView { - cell.textField?.stringValue = text - return cell - } - } - - return nil - } -} diff --git a/MonitorControl/Prefs/KeysPrefsViewController.swift b/MonitorControl/Prefs/KeysPrefsViewController.swift deleted file mode 100644 index 9c74dbfb..00000000 --- a/MonitorControl/Prefs/KeysPrefsViewController.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// KeysPrefsViewController.swift -// MonitorControl -// -// Created by Guillaume BRODER on 07/01/2018. -// MIT Licensed. -// - -import Cocoa -import MASPreferences - -class KeysPrefsViewController: NSViewController, MASPreferencesViewController { - - var viewIdentifier: String = "Keys" - var toolbarItemLabel: String? = NSLocalizedString("Keys", comment: "Shown in the main prefs window") - var toolbarItemImage: NSImage? = NSImage.init(named: "KeyboardPref") - let prefs = UserDefaults.standard - - @IBOutlet var listenFor: NSPopUpButton! - - override func viewDidLoad() { - super.viewDidLoad() - - listenFor.selectItem(at: prefs.integer(forKey: Utils.PrefKeys.listenFor.rawValue)) - } - - @IBAction func listenForChanged(_ sender: NSPopUpButton) { - prefs.set(sender.selectedTag(), forKey: Utils.PrefKeys.listenFor.rawValue) - - #if DEBUG - print("Toggle keys listened for state state -> \(sender.selectedItem?.title ?? "")") - #endif - - NotificationCenter.default.post(name: Notification.Name.init(Utils.PrefKeys.listenFor.rawValue), object: nil) - } - -} diff --git a/MonitorControl/Prefs/MainPrefsViewController.swift b/MonitorControl/Prefs/MainPrefsViewController.swift deleted file mode 100644 index 7805f86b..00000000 --- a/MonitorControl/Prefs/MainPrefsViewController.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// MainPrefsViewController.swift -// MonitorControl -// -// Created by Guillaume BRODER on 07/01/2018. -// MIT Licensed. -// - -import Cocoa -import MASPreferences -import ServiceManagement - -class MainPrefsViewController: NSViewController, MASPreferencesViewController { - - var viewIdentifier: String = "Main" - var toolbarItemLabel: String? = NSLocalizedString("General", comment: "Shown in the main prefs window") - var toolbarItemImage: NSImage? = NSImage.init(named: NSImage.preferencesGeneralName) - let prefs = UserDefaults.standard - - - @IBOutlet weak var versionLabel: NSTextField! - @IBOutlet var startAtLogin: NSButton! - @IBOutlet var showContrastSlider: NSButton! - @IBOutlet var lowerContrast: NSButton! - - override func viewDidLoad() { - super.viewDidLoad() - - startAtLogin.state = prefs.bool(forKey: Utils.PrefKeys.startAtLogin.rawValue) ? .on : .off - showContrastSlider.state = prefs.bool(forKey: Utils.PrefKeys.showContrast.rawValue) ? .on : .off - lowerContrast.state = prefs.bool(forKey: Utils.PrefKeys.lowerContrast.rawValue) ? .on : .off - setVersionNumber() - } - - @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 - } - - #if DEBUG - print("Toggle start at login state -> \(sender.state == .on ? "on" : "off")") - #endif - } - - @IBAction func showContrastSliderClicked(_ sender: NSButton) { - switch sender.state { - case .on: - prefs.set(true, forKey: Utils.PrefKeys.showContrast.rawValue) - case .off: - prefs.set(false, forKey: Utils.PrefKeys.showContrast.rawValue) - default: break - } - - #if DEBUG - print("Toggle show contrast slider state -> \(sender.state == .on ? "on" : "off")") - #endif - - NotificationCenter.default.post(name: Notification.Name.init(Utils.PrefKeys.showContrast.rawValue), object: nil) - } - - @IBAction func lowerContrastClicked(_ sender: NSButton) { - switch sender.state { - case .on: - prefs.set(true, forKey: Utils.PrefKeys.lowerContrast.rawValue) - case .off: - prefs.set(false, forKey: Utils.PrefKeys.lowerContrast.rawValue) - default: break - } - - #if DEBUG - print("Toggle lower contrast after brightness state -> \(sender.state == .on ? "on" : "off")") - #endif - } - - fileprivate func setVersionNumber() { - let versionNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") ?? "unknown" - let buildNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") ?? "unknown" - versionLabel.stringValue = "version \(versionNumber) build \(buildNumber)" - } -} diff --git a/MonitorControl/SliderHandler.swift b/MonitorControl/SliderHandler.swift new file mode 100644 index 00000000..2c1c8456 --- /dev/null +++ b/MonitorControl/SliderHandler.swift @@ -0,0 +1,29 @@ +import Cocoa +import DDC + +class SliderHandler { + var slider: NSSlider? + var display: Display + let cmd: DDC.Command + + public init(display: Display, command: DDC.Command) { + self.display = display + self.cmd = command + } + + @objc func valueChanged(slider: NSSlider) { + let snapInterval = 25 + let snapThreshold = 3 + + var value = slider.integerValue + + let closest = (value + snapInterval / 2) / snapInterval * snapInterval + if abs(closest - value) <= snapThreshold { + value = closest + slider.integerValue = value + } + + _ = self.display.ddc?.write(command: self.cmd, value: UInt8(value)) + self.display.saveValue(value, for: self.cmd) + } +} diff --git a/MonitorControl/Utils.swift b/MonitorControl/Utils.swift index 87bf2cee..1981c706 100644 --- a/MonitorControl/Utils.swift +++ b/MonitorControl/Utils.swift @@ -1,256 +1,138 @@ -// -// Utils.swift -// MonitorControl -// -// Created by Guillaume BRODER on 9/17/2017. -// MIT Licensed. -// - import Cocoa +import DDC 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 - } - - // 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 - } - - 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 - } - - /// 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 Volume keys only - case volumeOnlyKeys = 2 - } + // MARK: - Menu + + /// 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: DDC.Command, title: String) -> SliderHandler { + let item = NSMenuItem() + + let handler = SliderHandler(display: display, command: command) + + let slider = NSSlider(value: 0, minValue: 0, maxValue: 100, target: handler, action: #selector(SliderHandler.valueChanged)) + slider.isEnabled = false + slider.frame.size.width = 180 + slider.frame.origin = NSPoint(x: 20, y: 5) + + handler.slider = slider + + let view = NSView(frame: NSRect(x: 0, y: 0, width: slider.frame.width + 30, height: slider.frame.height + 10)) + view.addSubview(slider) + + item.view = view + + menu.insertItem(item, at: 0) + menu.insertItem(withTitle: title, action: nil, keyEquivalent: "", at: 0) + + DispatchQueue.global(qos: .background).async { + var minReplyDelay = 10 + + if display.needsLongerDelay { + minReplyDelay = 30 * kMillisecondScale + } + + defer { + DispatchQueue.main.async { + slider.isEnabled = true + } + } + + guard let (currentValue, maxValue) = display.ddc?.read(command: command, tries: 1000, minReplyDelay: UInt64(minReplyDelay)) else { + return + } + + let value = Int(currentValue > maxValue ? maxValue : currentValue) + + display.saveValue(value, for: command) + + DispatchQueue.main.async { + slider.integerValue = value + } + } + 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 name of a display + /// + /// - Parameter edid: the EDID of a display + /// - Returns: a string + static func getDisplayName(forEdid edid: EDID) -> String { + return edid.displayName() ?? 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? { + guard let mainDisplayID = NSScreen.main?.displayID else { + return nil + } + + return displays.first { $0.identifier == mainDisplayID } + } + + // MARK: - Enums + + /// UserDefault Keys for the app prefs + enum PrefKeys: String { + /// Was the app launched once + case appAlreadyLaunched + + /// 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 + } + + /// 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 Volume keys only + case volumeOnlyKeys = 2 + } } diff --git a/MonitorControl/View Controllers/DisplayPrefsViewController.swift b/MonitorControl/View Controllers/DisplayPrefsViewController.swift new file mode 100644 index 00000000..2e78c580 --- /dev/null +++ b/MonitorControl/View Controllers/DisplayPrefsViewController.swift @@ -0,0 +1,115 @@ +import Cocoa +import DDC +import MASPreferences +import os.log + +class DisplayPrefsViewController: NSViewController, MASPreferencesViewController, NSTableViewDataSource, NSTableViewDelegate { + var viewIdentifier: String = "Display" + var toolbarItemLabel: String? = NSLocalizedString("Display", comment: "Shown in the main prefs window") + var toolbarItemImage: NSImage? = NSImage(named: NSImage.computerName) + let prefs = UserDefaults.standard + + var displays: [Display] = [] + enum DisplayCell: String { + case checkbox + case name + case identifier + } + + @IBOutlet var allScreens: NSButton! + @IBOutlet var displayList: NSTableView! + + override func viewDidLoad() { + super.viewDidLoad() + + self.allScreens.state = self.prefs.bool(forKey: Utils.PrefKeys.allScreens.rawValue) ? .on : .off + + self.loadDisplayList() + } + + @IBAction func allScreensTouched(_ sender: NSButton) { + switch sender.state { + case .on: + self.prefs.set(true, forKey: Utils.PrefKeys.allScreens.rawValue) + case .off: + self.prefs.set(false, forKey: Utils.PrefKeys.allScreens.rawValue) + default: break + } + + #if DEBUG + os_log("Toggle allScreens state: %@", type: .info, sender.state == .on ? "on" : "off") + #endif + } + + // MARK: - Table datasource + + func loadDisplayList() { + for screen in NSScreen.screens { + let id = screen.displayID + + // Disable built-in displays. + if screen.isBuiltin { + let display = Display(id, name: screen.displayName ?? NSLocalizedString("Unknown", comment: "unknown display name"), isEnabled: false) + self.displays.append(display) + continue + } + + let ddc = DDC(for: id) + + guard let edid = ddc?.edid() else { + continue + } + + let name = Utils.getDisplayName(forEdid: edid) + let isEnabled = (prefs.object(forKey: "\(id)-state") as? Bool) ?? true + + let display = Display(id, name: name, isEnabled: isEnabled) + self.displays.append(display) + } + + self.displayList.reloadData() + } + + func numberOfRows(in _: NSTableView) -> Int { + return self.displays.count + } + + // MARK: - Table delegate + + func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + var cellType = DisplayCell.checkbox + var checked = false + var text = "" + let display = self.displays[row] + + if tableColumn == tableView.tableColumns[0] { + // Checkbox + checked = display.isEnabled + } else if tableColumn == tableView.tableColumns[1] { + // Name + text = display.name + cellType = DisplayCell.name + } else if tableColumn == tableView.tableColumns[2] { + // Identifier + text = "\(display.identifier)" + cellType = DisplayCell.identifier + } + if cellType == DisplayCell.checkbox { + if let cell = tableView.makeView(withIdentifier: (tableColumn?.identifier)!, owner: nil) as? ButtonCellView { + cell.button.state = checked ? .on : .off + cell.display = display + if display.name == "Mac built-in Display" { + cell.button.isEnabled = false + } + return cell + } + } else { + if let cell = tableView.makeView(withIdentifier: (tableColumn?.identifier)!, owner: nil) as? NSTableCellView { + cell.textField?.stringValue = text + return cell + } + } + + return nil + } +} diff --git a/MonitorControl/View Controllers/KeysPrefsViewController.swift b/MonitorControl/View Controllers/KeysPrefsViewController.swift new file mode 100644 index 00000000..3d9e36cc --- /dev/null +++ b/MonitorControl/View Controllers/KeysPrefsViewController.swift @@ -0,0 +1,28 @@ +import Cocoa +import MASPreferences +import os.log + +class KeysPrefsViewController: NSViewController, MASPreferencesViewController { + var viewIdentifier: String = "Keys" + var toolbarItemLabel: String? = NSLocalizedString("Keys", comment: "Shown in the main prefs window") + var toolbarItemImage: NSImage? = NSImage(named: "KeyboardPref") + let prefs = UserDefaults.standard + + @IBOutlet var listenFor: NSPopUpButton! + + override func viewDidLoad() { + super.viewDidLoad() + + self.listenFor.selectItem(at: self.prefs.integer(forKey: Utils.PrefKeys.listenFor.rawValue)) + } + + @IBAction func listenForChanged(_ sender: NSPopUpButton) { + self.prefs.set(sender.selectedTag(), forKey: Utils.PrefKeys.listenFor.rawValue) + + #if DEBUG + os_log("Toggle keys listened for state state: %@", type: .info, sender.selectedItem?.title ?? "") + #endif + + NotificationCenter.default.post(name: Notification.Name(Utils.PrefKeys.listenFor.rawValue), object: nil) + } +} diff --git a/MonitorControl/View Controllers/MainPrefsViewController.swift b/MonitorControl/View Controllers/MainPrefsViewController.swift new file mode 100644 index 00000000..c728c5ae --- /dev/null +++ b/MonitorControl/View Controllers/MainPrefsViewController.swift @@ -0,0 +1,80 @@ +import Cocoa +import MASPreferences +import os.log +import ServiceManagement + +class MainPrefsViewController: NSViewController, MASPreferencesViewController { + var viewIdentifier: String = "Main" + var toolbarItemLabel: String? = NSLocalizedString("General", comment: "Shown in the main prefs window") + var toolbarItemImage: NSImage? = NSImage(named: NSImage.preferencesGeneralName) + let prefs = UserDefaults.standard + + @IBOutlet var versionLabel: NSTextField! + @IBOutlet var startAtLogin: NSButton! + @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 + + switch sender.state { + case .on: + SMLoginItemSetEnabled(identifier, true) + case .off: + SMLoginItemSetEnabled(identifier, false) + default: break + } + + #if DEBUG + os_log("Toggle start at login state: %@", type: .info, sender.state == .on ? "on" : "off") + #endif + } + + @IBAction func showContrastSliderClicked(_ sender: NSButton) { + switch sender.state { + case .on: + self.prefs.set(true, forKey: Utils.PrefKeys.showContrast.rawValue) + case .off: + self.prefs.set(false, forKey: Utils.PrefKeys.showContrast.rawValue) + default: break + } + + #if DEBUG + os_log("Toggle show contrast slider state: %@", type: .info, sender.state == .on ? "on" : "off") + #endif + + NotificationCenter.default.post(name: Notification.Name(Utils.PrefKeys.showContrast.rawValue), object: nil) + } + + @IBAction func lowerContrastClicked(_ sender: NSButton) { + switch sender.state { + case .on: + self.prefs.set(true, forKey: Utils.PrefKeys.lowerContrast.rawValue) + case .off: + self.prefs.set(false, forKey: Utils.PrefKeys.lowerContrast.rawValue) + default: break + } + + #if DEBUG + os_log("Toggle lower contrast after brightness state: %@", type: .info, sender.state == .on ? "on" : "off") + #endif + } + + fileprivate func setVersionNumber() { + let versionNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") ?? "unknown" + let buildNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") ?? "unknown" + self.versionLabel.stringValue = "Version \(versionNumber) (Build \(buildNumber))" + } +} diff --git a/MonitorControl/de.lproj/Localizable.strings b/MonitorControl/de.lproj/Localizable.strings new file mode 100644 index 00000000..7608e0f9 --- /dev/null +++ b/MonitorControl/de.lproj/Localizable.strings @@ -0,0 +1,41 @@ +/* Sown in menu */ +"Brightness" = "Helligkeit"; + +/* Shown in menu */ +"Contrast" = "Kontrast"; + +/* Shown in menu */ +"Default" = "Standard"; + +/* No comment provided by engineer. */ +"Display" = "Monitor"; + +/* Shown in menu */ +"No supported display found" = "Kein unterstützter Monitor angeschlossen"; + +/* Shown in menu */ +"Set as default" = "Als Standard festlegen"; + +/* No comment provided by engineer. */ +"Unknown" = "Unbekannt"; + +/* Shown in menu */ +"Volume" = "Lautstärke"; + +/* Shown in Preferences window */ +"Preferences" = "Einstellungen"; + +/* Shown in the alert dialog */ +"Ok" = "OK"; + +/* Shown in the alert dialog */ +"Shortcuts not available" = "Kurzbefehle nicht verfügbar"; + +/* Shown in the alert dialog */ +"You need to enable MonitorControl in System Preferences > Security and Privacy > Accessibility for the keyboard shortcuts to work" = "Du musst MonitorControl in Systemeinstellungen > Sicherheit > Datenschutz > Bedienungshilfen aktivieren, damit die Kurzbefehle funktionieren."; + +/* Shown in the main prefs window */ +"General" = "Allgemein"; + +/* Shown in the main prefs window */ +"Keys" = "Tasten"; diff --git a/MonitorControl/de.lproj/Main.strings b/MonitorControl/de.lproj/Main.strings new file mode 100644 index 00000000..db4acd9e --- /dev/null +++ b/MonitorControl/de.lproj/Main.strings @@ -0,0 +1,48 @@ + +/* Class = "NSButtonCell"; title = "Change Brightness/Volume for all screens"; ObjectID = "0Z7-PQ-Bl8"; */ +"0Z7-PQ-Bl8.title" = "Helligkeit/Lautstärke für alle Monitore ändern"; + +/* Class = "NSTableColumn"; headerCell.title = "Enabled"; ObjectID = "8U8-ec-Zbv"; */ +"8U8-ec-Zbv.headerCell.title" = "Aktiv"; + +/* Class = "NSTableColumn"; headerCell.title = "Display Name"; ObjectID = "CHc-s5-4MN"; */ +"CHc-s5-4MN.headerCell.title" = "Name"; + +/* Class = "NSTextFieldCell"; title = "Keys"; ObjectID = "Dcz-GG-1li"; */ +"Dcz-GG-1li.title" = "Tasten"; + +/* Class = "NSTextFieldCell"; title = "General"; ObjectID = "ENU-js-huy"; */ +"ENU-js-huy.title" = "Allgemein"; + +/* Class = "NSTextFieldCell"; title = "Display"; ObjectID = "ExD-7P-6XI"; */ +"ExD-7P-6XI.title" = "Monitor"; + +/* Class = "NSMenuItem"; title = "Volume only"; ObjectID = "NLP-dU-Dam"; */ +"NLP-dU-Dam.title" = "Lautstärke"; + +/* Class = "NSTextFieldCell"; title = "Listen for"; ObjectID = "Vh8-06-U3K"; */ +"Vh8-06-U3K.title" = "Hören auf"; + +/* Class = "NSMenuItem"; title = "Both Brightness & Volume"; ObjectID = "Vr4-xb-B4o"; */ +"Vr4-xb-B4o.title" = "Helligkeit & Lautstärke"; + +/* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "aSw-3H-uNa"; */ +"aSw-3H-uNa.title" = "Table View Cell"; + +/* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "cnb-Li-1lE"; */ +"cnb-Li-1lE.title" = "Table View Cell"; + +/* Class = "NSTableColumn"; headerCell.title = "Display Id"; ObjectID = "dgp-q7-cBK"; */ +"dgp-q7-cBK.headerCell.title" = "ID"; + +/* Class = "NSButtonCell"; title = "Lower Contrast after Brightness"; ObjectID = "fhy-Er-0aI"; */ +"fhy-Er-0aI.title" = "Lower Contrast after Brightness"; + +/* Class = "NSMenuItem"; title = "Brightness only"; ObjectID = "hjz-0c-rvK"; */ +"hjz-0c-rvK.title" = "Helligkeit"; + +/* Class = "NSButtonCell"; title = "Start MonitorControl at Login"; ObjectID = "j72-NF-zsW"; */ +"j72-NF-zsW.title" = "MonitorControl beim Login starten"; + +/* Class = "NSButtonCell"; title = "Show a slider for contrast"; ObjectID = "xSI-8W-Xd0"; */ +"xSI-8W-Xd0.title" = "Slider für Kontrast anzeigen"; diff --git a/MonitorControl/de.lproj/MainMenu.strings b/MonitorControl/de.lproj/MainMenu.strings new file mode 100644 index 00000000..bba11843 --- /dev/null +++ b/MonitorControl/de.lproj/MainMenu.strings @@ -0,0 +1,6 @@ + +/* Class = "NSMenuItem"; title = "Quit"; ObjectID = "JTa-2I-AsI"; */ +"JTa-2I-AsI.title" = "MonitorControl beenden"; + +/* Class = "NSMenuItem"; title = "Preferences..."; ObjectID = "SOS-eZ-uU5"; */ +"SOS-eZ-uU5.title" = "Einstellungen …"; diff --git a/MonitorControl/en.lproj/Main.strings b/MonitorControl/en.lproj/Main.strings index b72ba6ec..4e79ac8c 100644 --- a/MonitorControl/en.lproj/Main.strings +++ b/MonitorControl/en.lproj/Main.strings @@ -2,9 +2,6 @@ /* Class = "NSButtonCell"; title = "Change Brightness/Volume for all screens"; ObjectID = "0Z7-PQ-Bl8"; */ "0Z7-PQ-Bl8.title" = "Change Brightness/Volume for all screens"; -/* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "2gr-xG-Byx"; */ -"2gr-xG-Byx.title" = "Text Cell"; - /* Class = "NSTableColumn"; headerCell.title = "Enabled"; ObjectID = "8U8-ec-Zbv"; */ "8U8-ec-Zbv.headerCell.title" = "Enabled"; @@ -17,9 +14,6 @@ /* Class = "NSTextFieldCell"; title = "Display"; ObjectID = "ExD-7P-6XI"; */ "ExD-7P-6XI.title" = "Display"; -/* Class = "NSTextFieldCell"; title = "Last check: "; ObjectID = "I3I-EP-Ev8"; */ -"I3I-EP-Ev8.title" = "Last check: "; - /* Class = "NSMenuItem"; title = "Volume only"; ObjectID = "NLP-dU-Dam"; */ "NLP-dU-Dam.title" = "Volume only"; @@ -41,24 +35,12 @@ /* Class = "NSButtonCell"; title = "Start when plugged to an external monitor"; ObjectID = "WJp-aA-2Af"; */ "WJp-aA-2Af.title" = "Start when plugged to an external monitor"; -/* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "Xje-0J-NNJ"; */ -"Xje-0J-NNJ.title" = "Text Cell"; - -/* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "as0-t3-Aub"; */ -"as0-t3-Aub.title" = "Text Cell"; - -/* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "cUg-j2-8gt"; */ -"cUg-j2-8gt.title" = "Table View Cell"; - /* Class = "NSTableColumn"; headerCell.title = "Display Id"; ObjectID = "dgp-q7-cBK"; */ "dgp-q7-cBK.headerCell.title" = "Display Id"; /* Class = "NSMenuItem"; title = "Brightness only"; ObjectID = "hjz-0c-rvK"; */ "hjz-0c-rvK.title" = "Brightness only"; -/* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "ldv-hu-9Hl"; */ -"ldv-hu-9Hl.title" = "Table View Cell"; - /* Class = "NSTextFieldCell"; title = "General"; ObjectID = "ocE-Cc-2bi"; */ "ocE-Cc-2bi.title" = "General"; diff --git a/MonitorControl/fr.lproj/Main.strings b/MonitorControl/fr.lproj/Main.strings index a2480097..72ff04b4 100644 --- a/MonitorControl/fr.lproj/Main.strings +++ b/MonitorControl/fr.lproj/Main.strings @@ -2,9 +2,6 @@ /* Class = "NSButtonCell"; title = "Change Brightness/Volume for all screens"; ObjectID = "0Z7-PQ-Bl8"; */ "0Z7-PQ-Bl8.title" = "Modifier Luminosité/Volume pour tout les écrans"; -/* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "2gr-xG-Byx"; */ -"2gr-xG-Byx.title" = "Text Cell"; - /* Class = "NSTableColumn"; headerCell.title = "Enabled"; ObjectID = "8U8-ec-Zbv"; */ "8U8-ec-Zbv.headerCell.title" = "Activé"; @@ -17,9 +14,6 @@ /* Class = "NSTextFieldCell"; title = "Display"; ObjectID = "ExD-7P-6XI"; */ "ExD-7P-6XI.title" = "Écrans"; -/* Class = "NSTextFieldCell"; title = "Last check: "; ObjectID = "I3I-EP-Ev8"; */ -"I3I-EP-Ev8.title" = "Dernière vérification: "; - /* Class = "NSMenuItem"; title = "Volume only"; ObjectID = "NLP-dU-Dam"; */ "NLP-dU-Dam.title" = "Volume uniquement"; @@ -41,24 +35,12 @@ /* Class = "NSButtonCell"; title = "Start when plugged to an external monitor"; ObjectID = "WJp-aA-2Af"; */ "WJp-aA-2Af.title" = "Lancer quand branché à un écran externe"; -/* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "Xje-0J-NNJ"; */ -"Xje-0J-NNJ.title" = "Text Cell"; - -/* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "as0-t3-Aub"; */ -"as0-t3-Aub.title" = "Text Cell"; - -/* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "cUg-j2-8gt"; */ -"cUg-j2-8gt.title" = "Table View Cell"; - /* Class = "NSTableColumn"; headerCell.title = "Display Id"; ObjectID = "dgp-q7-cBK"; */ "dgp-q7-cBK.headerCell.title" = "Identifiant"; /* Class = "NSMenuItem"; title = "Brightness only"; ObjectID = "hjz-0c-rvK"; */ "hjz-0c-rvK.title" = "Luminosité uniquement"; -/* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "ldv-hu-9Hl"; */ -"ldv-hu-9Hl.title" = "Table View Cell"; - /* Class = "NSTextFieldCell"; title = "General"; ObjectID = "ocE-Cc-2bi"; */ "ocE-Cc-2bi.title" = "Général"; diff --git a/MonitorControlHelper/AppDelegate.swift b/MonitorControlHelper/AppDelegate.swift deleted file mode 100644 index af74872b..00000000 --- a/MonitorControlHelper/AppDelegate.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// 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 index e560b18d..d35557d7 100644 --- a/MonitorControlHelper/Info.plist +++ b/MonitorControlHelper/Info.plist @@ -17,9 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + 1.3.0 CFBundleVersion - 1 + 179 + LSApplicationCategoryType + public.app-category.utilities LSBackgroundOnly LSMinimumSystemVersion diff --git a/MonitorControlHelper/main.swift b/MonitorControlHelper/main.swift new file mode 100644 index 00000000..2f7efc8d --- /dev/null +++ b/MonitorControlHelper/main.swift @@ -0,0 +1,26 @@ +import Cocoa + +class AppDelegate: NSObject, NSApplicationDelegate { + func applicationDidFinishLaunching(_: Notification) { + let mainBundleID = Bundle.main.bundleIdentifier!.replacingOccurrences(of: "Helper", with: "") + + let bundlePath = Bundle.main.bundlePath as NSString + + guard NSRunningApplication.runningApplications(withBundleIdentifier: mainBundleID).isEmpty else { + return NSApp.terminate(self) + } + + var pathComponents = bundlePath.pathComponents + let path = NSString.path(withComponents: Array(pathComponents[0..<(pathComponents.count - 4)])) + + NSWorkspace.shared.launchApplication(path) + NSApp.terminate(nil) + } + + func applicationWillTerminate(_: Notification) {} +} + +let app = NSApplication.shared +let delegate = AppDelegate() +app.delegate = delegate +app.run() diff --git a/Podfile b/Podfile deleted file mode 100644 index bc304895..00000000 --- a/Podfile +++ /dev/null @@ -1,11 +0,0 @@ -# Podfile - -platform :osx, '10.11' - -target 'MonitorControl' do - use_frameworks! - - pod 'MediaKeyTap', :git => 'https://github.com/the0neyouseek/MediaKeyTap.git' - pod 'MASPreferences' - pod 'AMCoreAudio', '~> 3.2' -end diff --git a/Podfile.lock b/Podfile.lock deleted file mode 100644 index 8cbe4ded..00000000 --- a/Podfile.lock +++ /dev/null @@ -1,32 +0,0 @@ -PODS: - - AMCoreAudio (3.2.1) - - MASPreferences (1.3) - - MediaKeyTap (2.1.0) - -DEPENDENCIES: - - AMCoreAudio (~> 3.2) - - MASPreferences - - MediaKeyTap (from `https://github.com/the0neyouseek/MediaKeyTap.git`) - -SPEC REPOS: - https://github.com/cocoapods/specs.git: - - AMCoreAudio - - MASPreferences - -EXTERNAL SOURCES: - MediaKeyTap: - :git: https://github.com/the0neyouseek/MediaKeyTap.git - -CHECKOUT OPTIONS: - MediaKeyTap: - :commit: 3722ad54585d931977af8152a9555e832f4000f6 - :git: https://github.com/the0neyouseek/MediaKeyTap.git - -SPEC CHECKSUMS: - AMCoreAudio: 7fa6b718dc93acc29f849d60c3ad680ae1bf07b5 - MASPreferences: c08b8622dd17b47da87669e741efd7c92e970e8c - MediaKeyTap: b652877e9ae2d52ca4f5310fa5152945ad3f0798 - -PODFILE CHECKSUM: 1b20d86a04731d82d4e8f346646d2b6b9d2db778 - -COCOAPODS: 1.5.3 diff --git a/README.md b/README.md index 9d75c893..c173e7ff 100644 --- a/README.md +++ b/README.md @@ -54,16 +54,16 @@ Open [issues](https://github.com/the0neyouseek/MonitorControl/issues) if you hav ### Required - Xcode -- [Cocoapods](https://cocoapods.org/) +- [Carthage](https://github.com/Carthage/Carthage) - [Swiftlint](https://github.com/realm/SwiftLint) Clone the project ```sh git clone https://github.com/the0neyouseek/MonitorControl.git --recurse-submodules ``` -Then download the dependencies with Cocoapods +Then download the dependencies with Carthage ```sh -$ pod install +$ carthage update --platform macOS ``` You're all set ! Now open the `MonitorControl.xcworkspace` with Xcode diff --git a/ddcctl b/ddcctl deleted file mode 160000 index 3d38860d..00000000 --- a/ddcctl +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3d38860d5b1de15199a42d9c5a34756738ca9b2a