mirror of
https://github.com/MonitorControl/MonitorControl.git
synced 2026-05-16 14:15:55 -06:00
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:
parent
830878ee1d
commit
6e91c71353
45 changed files with 1242 additions and 1334 deletions
70
.gitignore
vendored
70
.gitignore
vendored
|
|
@ -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
3
.gitmodules
vendored
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "ddcctl"]
|
||||
path = ddcctl
|
||||
url = https://github.com/kfix/ddcctl
|
||||
1
.swift-version
Normal file
1
.swift-version
Normal file
|
|
@ -0,0 +1 @@
|
|||
5.0
|
||||
5
.swiftformat
Normal file
5
.swiftformat
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
--indent 2
|
||||
--ranges no-space
|
||||
--self insert
|
||||
--exponentcase lowercase
|
||||
--exclude Carthage
|
||||
|
|
@ -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
4
Cartfile
Normal 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
4
Cartfile.resolved
Normal 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"
|
||||
15
Extensions/CGDirectDisplayID+Extension.swift
Normal file
15
Extensions/CGDirectDisplayID+Extension.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
33
Extensions/EDID+Extension.swift
Normal file
33
Extensions/EDID+Extension.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
70
Extensions/NSScreen+Extension.swift
Normal file
70
Extensions/NSScreen+Extension.swift
Normal 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 }
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1010"
|
||||
LastUpgradeVersion = "1020"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
|||
10
MonitorControl.xcworkspace/contents.xcworkspacedata
generated
10
MonitorControl.xcworkspace/contents.xcworkspacedata
generated
|
|
@ -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>
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
29
MonitorControl/ButtonCellView.swift
Normal file
29
MonitorControl/ButtonCellView.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
26
MonitorControl/Display+Whitelist.swift
Normal file
26
MonitorControl/Display+Whitelist.swift
Normal 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
|
||||
}
|
||||
}
|
||||
132
MonitorControl/Display.swift
Normal file
132
MonitorControl/Display.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)"
|
||||
}
|
||||
}
|
||||
29
MonitorControl/SliderHandler.swift
Normal file
29
MonitorControl/SliderHandler.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
115
MonitorControl/View Controllers/DisplayPrefsViewController.swift
Normal file
115
MonitorControl/View Controllers/DisplayPrefsViewController.swift
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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))"
|
||||
}
|
||||
}
|
||||
41
MonitorControl/de.lproj/Localizable.strings
Normal file
41
MonitorControl/de.lproj/Localizable.strings
Normal 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";
|
||||
48
MonitorControl/de.lproj/Main.strings
Normal file
48
MonitorControl/de.lproj/Main.strings
Normal 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";
|
||||
6
MonitorControl/de.lproj/MainMenu.strings
Normal file
6
MonitorControl/de.lproj/MainMenu.strings
Normal 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 …";
|
||||
|
|
@ -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";
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
26
MonitorControlHelper/main.swift
Normal file
26
MonitorControlHelper/main.swift
Normal 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
11
Podfile
|
|
@ -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
|
||||
32
Podfile.lock
32
Podfile.lock
|
|
@ -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
|
||||
|
|
@ -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
ddcctl
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 3d38860d5b1de15199a42d9c5a34756738ca9b2a
|
||||
Loading…
Add table
Add a link
Reference in a new issue