Various Enhancements (#66)

* Update to Swift 5.0 and run `swiftformat`.

* Refactor `MediaKeyTap` start/restart.

* Remove useless comments.

* Reorder files.

* Add German translation.

* Switch to Carthage.

* Use `DDC.swift`.

* Add `NSScreen` extension.

* Simplify menu layout.

* Hide the display’s built-in OSD.

* Fix launch at login helper.

* Fix `quitClicked` connection.

* Refactor build phases.

* Use `os_log` instead of `print`.

* Use more specific check for `minReplyDelay`.

* Add whitelist.
This commit is contained in:
Markus Reiter 2019-05-07 15:01:26 +02:00 committed by Guillaume B
parent 830878ee1d
commit 6e91c71353
45 changed files with 1242 additions and 1334 deletions

70
.gitignore vendored
View file

@ -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

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "ddcctl"]
path = ddcctl
url = https://github.com/kfix/ddcctl

1
.swift-version Normal file
View file

@ -0,0 +1 @@
5.0

5
.swiftformat Normal file
View file

@ -0,0 +1,5 @@
--indent 2
--ranges no-space
--self insert
--exponentcase lowercase
--exclude Carthage

View file

@ -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

4
Cartfile Normal file
View file

@ -0,0 +1,4 @@
github "the0neyouseek/MediaKeyTap" "master"
github "reitermarkus/DDC.swift" "master"
github "rnine/AMCoreAudio"
github "shpakovski/MASPreferences"

4
Cartfile.resolved Normal file
View file

@ -0,0 +1,4 @@
github "reitermarkus/DDC.swift" "9a6a249b4b222e67b1393eecd7ac9eb35dff34a9"
github "rnine/AMCoreAudio" "3.2.1"
github "shpakovski/MASPreferences" "1.3"
github "the0neyouseek/MediaKeyTap" "abfe09f53ccabb1aa14a0f4ab99cfb8812f41919"

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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 }
}
}

View file

@ -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 = "<group>"; };
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 = "<group>"; };
55359E331E2737EC002671BC /* DDC.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = DDC.c; sourceTree = "<group>"; };
55359E341E2737EC002671BC /* DDC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDC.h; sourceTree = "<group>"; };
55359E351E2737EC002671BC /* ddcctl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ddcctl.m; sourceTree = "<group>"; };
55359E361E2737EC002671BC /* ddcctl.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = ddcctl.sh; sourceTree = "<group>"; };
55359E371E2737EC002671BC /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = "<group>"; };
55359E381E2737EC002671BC /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
2894D9B72280B30500DF58DA /* CGDirectDisplayID+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGDirectDisplayID+Extension.swift"; sourceTree = "<group>"; };
28D1DD79227FA927004CB494 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Main.strings; sourceTree = "<group>"; };
28D1DD7A227FA927004CB494 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/MainMenu.strings; sourceTree = "<group>"; };
28D1DD7C227FA939004CB494 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
28D1DDE2227FB7D0004CB494 /* MediaKeyTap.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaKeyTap.framework; path = Carthage/Build/Mac/MediaKeyTap.framework; sourceTree = "<group>"; };
28D1DDE3227FB7D0004CB494 /* AMCoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AMCoreAudio.framework; path = Carthage/Build/Mac/AMCoreAudio.framework; sourceTree = "<group>"; };
28D1DDE4227FB7D0004CB494 /* MASPreferences.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MASPreferences.framework; path = Carthage/Build/Mac/MASPreferences.framework; sourceTree = "<group>"; };
28D1DDEC227FB8F2004CB494 /* EDID+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EDID+Extension.swift"; sourceTree = "<group>"; };
28D1DDED227FB944004CB494 /* DDC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DDC.framework; path = Carthage/Build/Mac/DDC.framework; sourceTree = "<group>"; };
28D1DDF1227FBE71004CB494 /* NSScreen+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSScreen+Extension.swift"; sourceTree = "<group>"; };
28D1DE0E227FD005004CB494 /* MASPreferences.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = MASPreferences.framework.dSYM; path = Carthage/Build/Mac/MASPreferences.framework.dSYM; sourceTree = "<group>"; };
28D1DE0F227FD006004CB494 /* MediaKeyTap.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = MediaKeyTap.framework.dSYM; path = Carthage/Build/Mac/MediaKeyTap.framework.dSYM; sourceTree = "<group>"; };
28D1DE10227FD006004CB494 /* AMCoreAudio.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = AMCoreAudio.framework.dSYM; path = Carthage/Build/Mac/AMCoreAudio.framework.dSYM; sourceTree = "<group>"; };
28D1DE11227FD006004CB494 /* DDC.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = DDC.framework.dSYM; path = Carthage/Build/Mac/DDC.framework.dSYM; sourceTree = "<group>"; };
28D363802280EA6000CB8A99 /* Display+Whitelist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Display+Whitelist.swift"; sourceTree = "<group>"; };
56754EAB1D9A4016007BCDC5 /* MonitorControl.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MonitorControl.app; sourceTree = BUILT_PRODUCTS_DIR; };
56754EAE1D9A4016007BCDC5 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AppDelegate.swift; sourceTree = "<group>"; };
56754EB01D9A4016007BCDC5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@ -67,7 +115,7 @@
F0445D4B2002856C0025AE82 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/MainMenu.strings; sourceTree = "<group>"; };
F0445D4C200294AB0025AE82 /* ButtonCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonCellView.swift; sourceTree = "<group>"; };
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 = "<group>"; };
F06792E9200A73460066C438 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
F06792F0200A73470066C438 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
F06792F1200A73470066C438 /* MonitorControlHelper.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MonitorControlHelper.entitlements; sourceTree = "<group>"; };
F091C9B21F6EA6110096FD65 /* SliderHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderHandler.swift; sourceTree = "<group>"; };
@ -76,17 +124,6 @@
F091C9C31F6EB8720096FD65 /* fr */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; lineEnding = 0; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
F091C9C41F6EBA5A0096FD65 /* Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = "<group>"; };
F0A987D61F77B290009B603D /* OSD.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OSD.framework; sourceTree = "<group>"; };
F0A987DA1F77B404009B603D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/MainMenu.strings; sourceTree = "<group>"; };
F0A987DC1F77B404009B603D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
F0A987DD1F77B404009B603D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
F0A987DF1F77B404009B603D /* SliderHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SliderHandler.swift; sourceTree = "<group>"; };
F0A987E11F77B404009B603D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
F0A987E21F77B404009B603D /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
F0A987E31F77B404009B603D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/MainMenu.strings; sourceTree = "<group>"; };
F0A987E41F77B404009B603D /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
F0A987E51F77B404009B603D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
F0A987E61F77B404009B603D /* Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = "<group>"; };
F0A987E71F77B404009B603D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* 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 */,
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 */,
);
path = ddcctl;
name = Frameworks;
sourceTree = "<group>";
};
28D1DDEB227FB8E9004CB494 /* Extensions */ = {
isa = PBXGroup;
children = (
2894D9B72280B30500DF58DA /* CGDirectDisplayID+Extension.swift */,
28D1DDEC227FB8F2004CB494 /* EDID+Extension.swift */,
28D1DDF1227FBE71004CB494 /* NSScreen+Extension.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
56754EA21D9A4016007BCDC5 = {
isa = PBXGroup;
children = (
28D1DDEB227FB8E9004CB494 /* Extensions */,
28D1DDD1227FB759004CB494 /* Frameworks */,
56754EAD1D9A4016007BCDC5 /* MonitorControl */,
F0A987D61F77B290009B603D /* OSD.framework */,
55359E321E2737EC002671BC /* ddcctl */,
F06792E8200A73460066C438 /* MonitorControlHelper */,
56754EAC1D9A4016007BCDC5 /* Products */,
F0A987D71F77B404009B603D /* Frameworks */,
EFFC2F3E35BEC9ACFA754137 /* Pods */,
);
sourceTree = "<group>";
};
@ -148,91 +199,42 @@
isa = PBXGroup;
children = (
56754EAE1D9A4016007BCDC5 /* AppDelegate.swift */,
F091C9B71F6EA79B0096FD65 /* Utils.swift */,
56754EB01D9A4016007BCDC5 /* Assets.xcassets */,
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 */,
F0445D3620023D5B0025AE82 /* Prefs */,
F091C9B41F6EA6180096FD65 /* Objects */,
F091C9C41F6EBA5A0096FD65 /* Bridging-Header.h */,
56754EB01D9A4016007BCDC5 /* Assets.xcassets */,
F091C9C11F6EB8660096FD65 /* Localizable.strings */,
56754EB51D9A4016007BCDC5 /* Info.plist */,
F091C9B21F6EA6110096FD65 /* SliderHandler.swift */,
F091C9B71F6EA79B0096FD65 /* Utils.swift */,
F0445D3620023D5B0025AE82 /* View Controllers */,
);
path = MonitorControl;
sourceTree = "<group>";
};
EFFC2F3E35BEC9ACFA754137 /* Pods */ = {
F0445D3620023D5B0025AE82 /* View Controllers */ = {
isa = PBXGroup;
children = (
42B61ABC1D7907131330228A /* Pods-MonitorControl.debug.xcconfig */,
31E16D90527EBD3F8A12BE0B /* Pods-MonitorControl.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
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 = "<group>";
};
F06792E8200A73460066C438 /* MonitorControlHelper */ = {
isa = PBXGroup;
children = (
F06792E9200A73460066C438 /* AppDelegate.swift */,
F06792F0200A73470066C438 /* Info.plist */,
F06792E9200A73460066C438 /* main.swift */,
F06792F1200A73470066C438 /* MonitorControlHelper.entitlements */,
);
path = MonitorControlHelper;
sourceTree = "<group>";
};
F091C9B41F6EA6180096FD65 /* Objects */ = {
isa = PBXGroup;
children = (
F0445D4C200294AB0025AE82 /* ButtonCellView.swift */,
F091C9B21F6EA6110096FD65 /* SliderHandler.swift */,
F03A8DF11FFBAA6F0034DC27 /* Display.swift */,
);
path = Objects;
sourceTree = "<group>";
};
F0A987D71F77B404009B603D /* Frameworks */ = {
isa = PBXGroup;
children = (
F0A987D81F77B404009B603D /* MonitorControl */,
398F482D5C8816B29F16AAEB /* Pods_MonitorControl.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
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 = "<group>";
};
F0A987DE1F77B404009B603D /* Objects */ = {
isa = PBXGroup;
children = (
F0A987DF1F77B404009B603D /* SliderHandler.swift */,
);
path = Objects;
sourceTree = "<group>";
};
/* 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 = (
);
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",
inputFileListPaths = (
);
inputPaths = (
);
name = "Increase Build Number";
outputFileListPaths = (
);
name = "[CP] Embed Pods Frameworks";
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 = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
inputFileListPaths = (
);
inputPaths = (
);
name = "Sync Version Numbers";
outputFileListPaths = (
);
name = "[CP] Check Pods Manifest.lock";
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 */,
F0445D3D200254FA0025AE82 /* KeysPrefsViewController.swift in Sources */,
F091C9B81F6EA79B0096FD65 /* Utils.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 */,
);
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 = "<group>";
};
F0A987D91F77B404009B603D /* MainMenu.strings */ = {
isa = PBXVariantGroup;
children = (
F0A987DA1F77B404009B603D /* en */,
F0A987E31F77B404009B603D /* fr */,
);
name = MainMenu.strings;
sourceTree = "<group>";
};
F0A987DB1F77B404009B603D /* Localizable.strings */ = {
isa = PBXVariantGroup;
children = (
F0A987DC1F77B404009B603D /* en */,
F0A987E41F77B404009B603D /* fr */,
);
name = Localizable.strings;
sourceTree = "<group>";
};
F0A987E01F77B404009B603D /* MainMenu.xib */ = {
isa = PBXVariantGroup;
children = (
F0A987E11F77B404009B603D /* Base */,
);
name = MainMenu.xib;
sourceTree = "<group>";
};
/* 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;
};

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1010"
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:MonitorControl.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View file

@ -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 Cocoa
import Foundation
import MediaKeyTap
import MASPreferences
import AMCoreAudio
import Cocoa
import DDC
import Foundation
import MASPreferences
import MediaKeyTap
import os.log
var app: AppDelegate! = nil
var app: AppDelegate!
let prefs = UserDefaults.standard
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet var statusMenu: NSMenu!
@IBOutlet var window: NSWindow!
@IBOutlet weak var statusMenu: NSMenu!
@IBOutlet weak var window: NSWindow!
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
var monitorItems: [NSMenuItem] = []
var displays: [Display] = []
var monitorItems: [NSMenuItem] = []
var displays: [Display] = []
let step = 100 / 16
let step = 100/16
var mediaKeyTap: MediaKeyTap?
var prefsController: NSWindowController?
var mediaKeyTap: MediaKeyTap?
var prefsController: NSWindowController?
func applicationDidFinishLaunching(_: Notification) {
app = self
var keysListenedFor: [MediaKey] = [.brightnessUp, .brightnessDown, .mute, .volumeUp, .volumeDown]
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 applicationDidFinishLaunching(_ aNotification: Notification) {
app = self
func applicationWillTerminate(_: Notification) {
AMCoreAudio.NotificationCenter.defaultCenter.unsubscribe(self, eventType: AudioHardwareEvent.self)
}
setupLayout()
subscribeEventListeners()
setVolumeKeysMode()
statusItem.image = NSImage.init(named: "status")
statusItem.menu = statusMenu
setDefaultPrefs()
Utils.acquirePrivileges()
CGDisplayRegisterReconfigurationCallback({_, _, _ in app.updateDisplays()}, nil)
updateDisplays()
@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)
}
}
/// 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)
prefs.set(false, forKey: Utils.PrefKeys.showContrast.rawValue)
prefs.set(false, forKey: Utils.PrefKeys.lowerContrast.rawValue)
}
}
// MARK: - Menu
func clearDisplays() {
if self.statusMenu.items.count > 2 {
var items: [NSMenuItem] = []
for i in 0..<self.statusMenu.items.count - 2 {
items.append(self.statusMenu.items[i])
}
for item in items {
self.statusMenu.removeItem(item)
}
}
@IBAction func quitClicked(_ sender: AnyObject) {
NSApplication.shared.terminate(self)
self.monitorItems = []
self.displays = []
}
func updateDisplays() {
self.clearDisplays()
let filteredScreens = NSScreen.screens.filter { screen -> Bool in
// Skip built-in displays.
if screen.isBuiltin {
return false
}
return DDC(for: screen.displayID)?.edid() != nil
}
@IBAction func prefsClicked(_ sender: AnyObject) {
if let prefsController = prefsController {
prefsController.showWindow(sender)
NSApp.activate(ignoringOtherApps: true)
prefsController.window?.makeKeyAndOrderFront(sender)
}
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)
}
}
}
/// 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)
/// 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)
prefs.set(false, forKey: Utils.PrefKeys.startAtLogin.rawValue)
if let edid = ddc?.edid() {
let name = Utils.getDisplayName(forEdid: edid)
prefs.set(false, forKey: Utils.PrefKeys.showContrast.rawValue)
prefs.set(false, forKey: Utils.PrefKeys.lowerContrast.rawValue)
}
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)
}
}
// MARK: - Menu
func clearDisplays() {
if statusMenu.items.count > 2 {
var items: [NSMenuItem] = []
for i in 0..<statusMenu.items.count - 2 {
items.append(statusMenu.items[i])
}
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"))
}
for item in items {
statusMenu.removeItem(item)
}
}
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)
monitorItems = []
displays = []
}
func updateDisplays() {
clearDisplays()
var filteredScreens = NSScreen.screens.filter { screen -> Bool in
if let id = screen.deviceDescription[NSDeviceDescriptionKey.init("NSScreenNumber")] as? CGDirectDisplayID {
// Is Built In Screen (e.g. MBP/iMac Screen)
if CGDisplayIsBuiltin(id) != 0 {
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)
}
}
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)
}
}
/// 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)
}
}
}
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)
}
// 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 }
func handle(mediaKey: MediaKey, event: KeyEvent?) {
let allDisplays = prefs.bool(forKey: Utils.PrefKeys.allScreens.rawValue) ? self.displays : [currentDisplay]
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: +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)
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
}
}
default:
return
}
}
}
}
// MARK: - Prefs notification
@objc func handleListenForChanged() {
self.startOrRestartMediaKeyTap()
}
@objc func handleShowContrastChanged() {
self.updateDisplays()
}
private func startOrRestartMediaKeyTap() {
var keys: [MediaKey]
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]
}
// MARK: - Prefs notification
@objc func handleListenForChanged() {
readKeyListenPreferences()
setKeysToListenFor()
if let audioDevice = AudioDevice.defaultOutputDevice(), audioDevice.canSetVirtualMasterVolume(direction: .playback) {
// Remove volume related keys.
let keysToDelete: [MediaKey] = [.volumeUp, .volumeDown, .mute]
keys.removeAll { keysToDelete.contains($0) }
}
@objc func handleShowContrastChanged() {
self.updateDisplays()
}
private func setKeysToListenFor() {
mediaKeyTap?.stop()
mediaKeyTap = MediaKeyTap.init(delegate: self, for: keysListenedFor, observeBuiltIn: false)
mediaKeyTap?.start()
}
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)
}
}
self.mediaKeyTap?.stop()
self.mediaKeyTap = MediaKeyTap(delegate: self, for: keys, observeBuiltIn: false)
self.mediaKeyTap?.start()
}
}
extension AppDelegate: EventSubscriber {
/**
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
/**
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()
self.startOrRestartMediaKeyTap()
}
}
}

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@ -11,7 +11,7 @@
<objects>
<viewController storyboardIdentifier="KeysPrefsVC" id="MHy-Dv-i5A" customClass="KeysPrefsViewController" customModule="MonitorControl" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="nfK-n0-xN4">
<rect key="frame" x="0.0" y="0.0" width="400" height="98"/>
<rect key="frame" x="0.0" y="0.0" width="400" height="108"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fUf-UM-yW3">
@ -46,7 +46,7 @@
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Uti-Vm-YwB">
<rect key="frame" x="18" y="49" width="59" height="29"/>
<rect key="frame" x="18" y="59" width="59" height="29"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Keys" id="Dcz-GG-1li">
<font key="font" metaFont="systemBold" size="24"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -62,7 +62,7 @@
<constraint firstAttribute="bottom" secondItem="fUf-UM-yW3" secondAttribute="bottom" constant="20" id="Rys-Fj-Tne"/>
<constraint firstItem="fUf-UM-yW3" firstAttribute="leading" secondItem="jFY-ug-0KQ" secondAttribute="trailing" constant="10" id="Tic-nC-Oax"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Uti-Vm-YwB" secondAttribute="trailing" constant="20" id="ctI-eo-jEw"/>
<constraint firstItem="jFY-ug-0KQ" firstAttribute="top" secondItem="Uti-Vm-YwB" secondAttribute="bottom" constant="10" id="t7Y-Pv-HlI"/>
<constraint firstItem="jFY-ug-0KQ" firstAttribute="top" secondItem="Uti-Vm-YwB" secondAttribute="bottom" constant="20" id="t7Y-Pv-HlI"/>
<constraint firstItem="jFY-ug-0KQ" firstAttribute="leading" secondItem="nfK-n0-xN4" secondAttribute="leading" constant="20" id="xCB-fT-tMo"/>
</constraints>
</view>
@ -79,11 +79,11 @@
<objects>
<viewController storyboardIdentifier="DisplayPrefsVC" id="NAx-6T-ZPc" customClass="DisplayPrefsViewController" customModule="MonitorControl" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="EBf-qN-KaN">
<rect key="frame" x="0.0" y="0.0" width="400" height="240"/>
<rect key="frame" x="0.0" y="0.0" width="400" height="223"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="uXD-ZY-N3H">
<rect key="frame" x="18" y="191" width="88" height="29"/>
<rect key="frame" x="18" y="174" width="88" height="29"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Display" id="ExD-7P-6XI">
<font key="font" metaFont="systemBold" size="24"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -91,13 +91,13 @@
</textFieldCell>
</textField>
<scrollView autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="B5k-we-kuP">
<rect key="frame" x="20" y="20" width="360" height="137"/>
<clipView key="contentView" id="4ot-Jo-X5O">
<rect key="frame" x="1" y="0.0" width="358" height="136"/>
<rect key="frame" x="20" y="20" width="360" height="100"/>
<clipView key="contentView" drawsBackground="NO" id="4ot-Jo-X5O">
<rect key="frame" x="1" y="0.0" width="358" height="99"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" rowSizeStyle="automatic" headerView="ckY-Px-mJn" viewBased="YES" id="dyo-uY-pMe">
<rect key="frame" x="0.0" y="0.0" width="358" height="113"/>
<rect key="frame" x="0.0" y="0.0" width="358" height="76"/>
<autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
@ -207,6 +207,7 @@
</connections>
</tableView>
</subviews>
<nil key="backgroundColor"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="UHx-W7-xSv">
<rect key="frame" x="-100" y="-100" width="358" height="16"/>
@ -222,7 +223,7 @@
</tableHeaderView>
</scrollView>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Y48-gQ-uJi">
<rect key="frame" x="18" y="165" width="275" height="18"/>
<rect key="frame" x="18" y="138" width="275" height="18"/>
<buttonCell key="cell" type="check" title="Change Brightness/Volume for all screens" bezelStyle="regularSquare" imagePosition="left" inset="2" id="0Z7-PQ-Bl8">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@ -234,11 +235,11 @@
</subviews>
<constraints>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Y48-gQ-uJi" secondAttribute="trailing" constant="20" id="24u-jX-VqD"/>
<constraint firstItem="B5k-we-kuP" firstAttribute="top" secondItem="Y48-gQ-uJi" secondAttribute="bottom" constant="20" id="4l9-vD-MBp"/>
<constraint firstItem="Y48-gQ-uJi" firstAttribute="leading" secondItem="EBf-qN-KaN" secondAttribute="leading" constant="20" id="Ct2-ah-W0a"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="uXD-ZY-N3H" secondAttribute="trailing" constant="20" id="Qwk-xy-tL4"/>
<constraint firstAttribute="trailing" secondItem="B5k-we-kuP" secondAttribute="trailing" constant="20" id="SgW-60-zGH"/>
<constraint firstItem="Y48-gQ-uJi" firstAttribute="top" secondItem="uXD-ZY-N3H" secondAttribute="bottom" constant="10" id="WSd-oq-oD7"/>
<constraint firstItem="B5k-we-kuP" firstAttribute="top" secondItem="Y48-gQ-uJi" secondAttribute="bottom" constant="10" id="YIT-MK-KKw"/>
<constraint firstItem="Y48-gQ-uJi" firstAttribute="top" secondItem="uXD-ZY-N3H" secondAttribute="bottom" constant="20" id="WSd-oq-oD7"/>
<constraint firstAttribute="bottom" secondItem="B5k-we-kuP" secondAttribute="bottom" constant="20" id="dfj-cp-Rh1"/>
<constraint firstItem="uXD-ZY-N3H" firstAttribute="top" secondItem="EBf-qN-KaN" secondAttribute="top" constant="20" id="jAo-pS-Y7d"/>
<constraint firstItem="B5k-we-kuP" firstAttribute="leading" secondItem="EBf-qN-KaN" secondAttribute="leading" constant="20" id="uZk-3q-LOM"/>
@ -252,7 +253,7 @@
</viewController>
<customObject id="34q-0u-YTQ" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="1116" y="234"/>
<point key="canvasLocation" x="1116" y="224"/>
</scene>
<!--Main Prefs View Controller-->
<scene sceneID="zAg-r8-WQ5">
@ -301,8 +302,8 @@
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="tMg-qE-zTW">
<rect key="frame" x="18" y="122" width="99" height="17"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="MonitorControl " id="mBs-6m-13Q">
<rect key="frame" x="18" y="122" width="138" height="17"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Version 0.0.0 (Build 0)" id="mBs-6m-13Q">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
@ -318,14 +319,14 @@
<constraint firstItem="xGa-qG-9ut" firstAttribute="leading" secondItem="COE-Oc-gZs" secondAttribute="leading" constant="20" id="NgS-ga-iNo"/>
<constraint firstItem="i5K-Cd-wxm" firstAttribute="top" secondItem="xGa-qG-9ut" secondAttribute="bottom" constant="20" id="R2I-Ko-ZJ9"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="siR-fL-v2a" secondAttribute="trailing" constant="20" id="Srs-vY-IQB"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="tMg-qE-zTW" secondAttribute="trailing" constant="20" id="cf5-i9-1VX"/>
<constraint firstItem="9Gu-aU-Td2" firstAttribute="top" secondItem="tMg-qE-zTW" secondAttribute="bottom" constant="20" id="caN-Et-n47"/>
<constraint firstItem="siR-fL-v2a" firstAttribute="leading" secondItem="COE-Oc-gZs" secondAttribute="leading" constant="20" id="eXs-op-WJj"/>
<constraint firstItem="tMg-qE-zTW" firstAttribute="top" secondItem="siR-fL-v2a" secondAttribute="bottom" constant="20" id="gY5-7g-9Zb"/>
<constraint firstItem="siR-fL-v2a" firstAttribute="top" secondItem="COE-Oc-gZs" secondAttribute="top" constant="20" id="iTz-r7-NFb"/>
<constraint firstItem="i5K-Cd-wxm" firstAttribute="leading" secondItem="COE-Oc-gZs" secondAttribute="leading" constant="20" id="jWI-DQ-WdT"/>
<constraint firstItem="tMg-qE-zTW" firstAttribute="leading" secondItem="COE-Oc-gZs" secondAttribute="leading" constant="20" id="l4b-at-imk"/>
<constraint firstItem="9Gu-aU-Td2" firstAttribute="top" secondItem="tMg-qE-zTW" secondAttribute="bottom" constant="20" id="mre-Oy-gJw"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="tMg-qE-zTW" secondAttribute="trailing" constant="20" id="jzw-7W-4cW"/>
<constraint firstItem="tMg-qE-zTW" firstAttribute="leading" secondItem="COE-Oc-gZs" secondAttribute="leading" constant="20" id="qhb-hw-Gwg"/>
<constraint firstAttribute="bottom" secondItem="i5K-Cd-wxm" secondAttribute="bottom" constant="20" id="xxz-YT-Uma"/>
<constraint firstItem="tMg-qE-zTW" firstAttribute="top" secondItem="siR-fL-v2a" secondAttribute="bottom" constant="20" id="zis-v1-6aZ"/>
</constraints>
</view>
<connections>

View file

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@ -13,7 +14,6 @@
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="MonitorControl" customModuleProvider="target">
<connections>
<outlet property="quitClicked" destination="JTa-2I-AsI" id="pCf-S9-cfs"/>
<outlet property="statusMenu" destination="lCi-vw-mwp" id="iBf-iw-RcA"/>
</connections>
</customObject>
@ -30,11 +30,11 @@
<menuItem title="Quit" id="JTa-2I-AsI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="quitClicked:" target="Voe-Tx-rLC" id="quI-C7-yby"/>
<action selector="quitClicked:" target="Voe-Tx-rLC" id="6Y0-el-CJx"/>
</connections>
</menuItem>
</items>
<point key="canvasLocation" x="-348" y="-499"/>
<point key="canvasLocation" x="-1180" y="-1189"/>
</menu>
</objects>
</document>

View file

@ -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 <Foundation/Foundation.h>
#include "../ddcctl/DDC.h"
#import <OSD/OSDManager.h>
#endif /* Bridging_Header_h */

View file

@ -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
}
}
}

View file

@ -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
}
}

View file

@ -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)
}
}
}

View file

@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
@ -19,7 +19,7 @@
<key>CFBundleShortVersionString</key>
<string>1.3.0</string>
<key>CFBundleVersion</key>
<string>48</string>
<string>179</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>

View file

@ -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
}
}
}

View file

@ -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)
}
}
}

View file

@ -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)
}
}

View file

@ -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
}
}

View file

@ -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)
}
}

View file

@ -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)"
}
}

View file

@ -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)
}
}

View file

@ -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
}
}
// MARK: - Menu
print("\(cmdString(command) ?? "N/A") value: \(value)")
}
/// 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()
// MARK: - DDCCTL
let handler = SliderHandler(display: display, command: command)
/// 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)
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)
#if DEBUG
printCommandValue(command, value)
#endif
}
handler.slider = slider
/// 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)
let view = NSView(frame: NSRect(x: 0, y: 0, width: slider.frame.width + 30, height: slider.frame.height + 10))
view.addSubview(slider)
#if DEBUG
printCommandValue(command, Int(readCmd.current_value))
#endif
item.view = view
return readCmd.success ? Int(readCmd.current_value) : nil
}
menu.insertItem(item, at: 0)
menu.insertItem(withTitle: title, action: nil, keyEquivalent: "", at: 0)
// MARK: - Menu
DispatchQueue.global(qos: .background).async {
var minReplyDelay = 10
/// 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
}
if display.needsLongerDelay {
minReplyDelay = 30 * kMillisecondScale
}
/// 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
defer {
DispatchQueue.main.async {
slider.isEnabled = true
}
}
view.addSubview(label)
view.addSubview(slider)
guard let (currentValue, maxValue) = display.ddc?.read(command: command, tries: 1000, minReplyDelay: UInt64(minReplyDelay)) else {
return
}
item.view = view
let value = Int(currentValue > maxValue ? maxValue : currentValue)
menu.insertItem(item, at: 0)
menu.insertItem(NSMenuItem.separator(), at: 1)
display.saveValue(value, for: command)
DispatchQueue.global(qos: .background).async {
var val: Int?
DispatchQueue.main.async {
slider.integerValue = value
}
}
return handler
}
if let res = getCommand(command, fromMonitor: display.identifier) {
val = res
}
// MARK: - Utilities
if let val = val {
display.saveValue(val, for: command)
/// Acquire Privileges (Necessary to listen to keyboard event globally)
static func acquirePrivileges() {
let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true]
let accessibilityEnabled = AXIsProcessTrustedWithOptions(options)
DispatchQueue.main.async {
slider.integerValue = val
}
}
}
return handler
}
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()
}
// MARK: - Utilities
return
}
/// Acquire Privileges (Necessary to listen to keyboard event globally)
static func acquirePrivileges() {
let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true]
let accessibilityEnabled = AXIsProcessTrustedWithOptions(options)
// MARK: - Display Infos
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()
}
/// 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: "")
}
return
}
/// 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
}
// MARK: - Display Infos
return displays.first { $0.identifier == mainDisplayID }
}
/// 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
}
// MARK: - Enums
/// 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)
}
}
}
/// UserDefault Keys for the app prefs
enum PrefKeys: String {
/// Was the app launched once
case appAlreadyLaunched
return nil
}
/// Does the app start when plugged to an external monitor
case startWhenExternal
/// 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: "")
}
/// Keys listened for (Brightness/Volume)
case listenFor
/// 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: "")
}
/// Show contrast sliders
case showContrast
/// 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
}
}
/// Lower contrast after brightness
case lowerContrast
// MARK: - Enums
/// Change Brightness/Volume for all screens
case allScreens
}
/// UserDefault Keys for the app prefs
enum PrefKeys: String {
/// Was the app launched once
case appAlreadyLaunched
/// Keys for the value of listenFor option
enum ListenForKeys: Int {
/// Listen for Brightness and Volume keys
case brightnessAndVolumeKeys = 0
/// 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
}
/// Listen for Brightness keys only
case brightnessOnlyKeys = 1
/// Listen for Volume keys only
case volumeOnlyKeys = 2
}
}

View file

@ -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
}
}

View file

@ -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)
}
}

View file

@ -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))"
}
}

View file

@ -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";

View file

@ -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";

View file

@ -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 …";

View file

@ -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";

View file

@ -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";

View file

@ -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
}
}

View file

@ -17,9 +17,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>1.3.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>179</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSBackgroundOnly</key>
<true/>
<key>LSMinimumSystemVersion</key>

View file

@ -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()

11
Podfile
View file

@ -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

View file

@ -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

View file

@ -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

1
ddcctl

@ -1 +0,0 @@
Subproject commit 3d38860d5b1de15199a42d9c5a34756738ca9b2a