🔧 Refactoring + preparing v1.2

- Refactoring of the way the slider are created
- Read the current value of ddcctl monitor after launching the app, so no more value at 0 for the sliders
- When there's only one monitor, display it directly, not in a submenu, closes #10
- Added a "start at login" helper (still need testing) for v1.2

Signed-off-by: Guillaume Broder <iamnotheoneyouseek@gmail.com>
This commit is contained in:
Guillaume Broder 2018-01-13 22:49:10 +01:00
parent 1112380361
commit 087bb132c6
No known key found for this signature in database
GPG key ID: 66FB02D063D9E08F
14 changed files with 340 additions and 213 deletions

120
.github/rules.json vendored
View file

@ -1,120 +0,0 @@
{
"title": "MonitorControl (@the0neyouseek)",
"rules": [
{
"description": "External Screen brightness [-] (using MonitorControl)",
"manipulators": [{
"type": "basic",
"from": {
"key_code": "f1",
"modifiers": {
"optional": [
"any"
]
}
},
"to": [{
"key_code": "down_arrow",
"modifiers": [
"left_shift",
"left_control",
"left_option",
"left_command"
]
}]
}]
},
{
"description": "External Screen brightness [+] (using MonitorControl)",
"manipulators": [{
"type": "basic",
"from": {
"key_code": "f2",
"modifiers": {
"optional": [
"any"
]
}
},
"to": [{
"key_code": "up_arrow",
"modifiers": [
"left_shift",
"left_control",
"left_option",
"left_command"
]
}]
}]
},
{
"description": "External Screen volume [-] (using MonitorControl)",
"manipulators": [{
"type": "basic",
"from": {
"key_code": "f11",
"modifiers": {
"optional": [
"any"
]
}
},
"to": [{
"key_code": "left_arrow",
"modifiers": [
"left_shift",
"left_control",
"left_option",
"left_command"
]
}]
}]
},
{
"description": "External Screen volume [+] (using MonitorControl)",
"manipulators": [{
"type": "basic",
"from": {
"key_code": "f12",
"modifiers": {
"optional": [
"any"
]
}
},
"to": [{
"key_code": "right_arrow",
"modifiers": [
"left_shift",
"left_control",
"left_option",
"left_command"
]
}]
}]
},
{
"description": "External Screen volume mute (using MonitorControl)",
"manipulators": [{
"type": "basic",
"from": {
"key_code": "f10",
"modifiers": {
"optional": [
"any"
]
}
},
"to": [{
"key_code": "equal_sign",
"modifiers": [
"left_shift",
"left_control",
"left_option",
"left_command"
]
}]
}]
}
]
}

View file

@ -19,12 +19,28 @@
F0445D40200259C10025AE82 /* DisplayPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0445D3F200259C10025AE82 /* DisplayPrefsViewController.swift */; };
F0445D41200282E60025AE82 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F0445D43200282E60025AE82 /* Main.storyboard */; };
F0445D4D200294AB0025AE82 /* ButtonCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0445D4C200294AB0025AE82 /* ButtonCellView.swift */; };
F06792EA200A73460066C438 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F06792E9200A73460066C438 /* AppDelegate.swift */; };
F06792F6200A745F0066C438 /* MonitorControlHelper.app in [Login] Copy Helper to start at Login */ = {isa = PBXBuildFile; fileRef = F06792E7200A73460066C438 /* MonitorControlHelper.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
F091C9B31F6EA6110096FD65 /* SliderHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = F091C9B21F6EA6110096FD65 /* SliderHandler.swift */; };
F091C9B81F6EA79B0096FD65 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = F091C9B71F6EA79B0096FD65 /* Utils.swift */; };
F0A987E81F77B40E009B603D /* OSD.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F0A987D61F77B290009B603D /* OSD.framework */; };
F0EB972F1F6ED7C800686D2A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F091C9C11F6EB8660096FD65 /* Localizable.strings */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
F06792F5200A73FA0066C438 /* [Login] Copy Helper to start at Login */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = Contents/Library/LoginItems;
dstSubfolderSpec = 1;
files = (
F06792F6200A745F0066C438 /* MonitorControlHelper.app in [Login] Copy Helper to start at Login */,
);
name = "[Login] Copy Helper to start at Login";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
31E16D90527EBD3F8A12BE0B /* Pods-MonitorControl.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MonitorControl.release.xcconfig"; path = "Pods/Target Support Files/Pods-MonitorControl/Pods-MonitorControl.release.xcconfig"; sourceTree = "<group>"; };
398F482D5C8816B29F16AAEB /* Pods_MonitorControl.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MonitorControl.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -50,6 +66,10 @@
F0445D49200285690025AE82 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/MainMenu.strings; sourceTree = "<group>"; };
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>"; };
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>"; };
F091C9B71F6EA79B0096FD65 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
F091C9C21F6EB8660096FD65 /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; lineEnding = 0; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -79,6 +99,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
F06792E4200A73460066C438 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@ -101,6 +128,7 @@
56754EAD1D9A4016007BCDC5 /* MonitorControl */,
F0A987D61F77B290009B603D /* OSD.framework */,
55359E321E2737EC002671BC /* ddcctl */,
F06792E8200A73460066C438 /* MonitorControlHelper */,
56754EAC1D9A4016007BCDC5 /* Products */,
F0A987D71F77B404009B603D /* Frameworks */,
EFFC2F3E35BEC9ACFA754137 /* Pods */,
@ -111,6 +139,7 @@
isa = PBXGroup;
children = (
56754EAB1D9A4016007BCDC5 /* MonitorControl.app */,
F06792E7200A73460066C438 /* MonitorControlHelper.app */,
);
name = Products;
sourceTree = "<group>";
@ -151,6 +180,16 @@
path = Prefs;
sourceTree = "<group>";
};
F06792E8200A73460066C438 /* MonitorControlHelper */ = {
isa = PBXGroup;
children = (
F06792E9200A73460066C438 /* AppDelegate.swift */,
F06792F0200A73470066C438 /* Info.plist */,
F06792F1200A73470066C438 /* MonitorControlHelper.entitlements */,
);
path = MonitorControlHelper;
sourceTree = "<group>";
};
F091C9B41F6EA6180096FD65 /* Objects */ = {
isa = PBXGroup;
children = (
@ -208,6 +247,7 @@
56754EA91D9A4016007BCDC5 /* Resources */,
9DD5968596EFAF0E2EB56496 /* [CP] Embed Pods Frameworks */,
3ACE91333FC1E781FB2E44E5 /* [CP] Copy Pods Resources */,
F06792F5200A73FA0066C438 /* [Login] Copy Helper to start at Login */,
);
buildRules = (
);
@ -218,13 +258,30 @@
productReference = 56754EAB1D9A4016007BCDC5 /* MonitorControl.app */;
productType = "com.apple.product-type.application";
};
F06792E6200A73460066C438 /* MonitorControlHelper */ = {
isa = PBXNativeTarget;
buildConfigurationList = F06792F4200A73470066C438 /* Build configuration list for PBXNativeTarget "MonitorControlHelper" */;
buildPhases = (
F06792E3200A73460066C438 /* Sources */,
F06792E4200A73460066C438 /* Frameworks */,
F06792E5200A73460066C438 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = MonitorControlHelper;
productName = MonitorControlHelper;
productReference = F06792E7200A73460066C438 /* MonitorControlHelper.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
56754EA31D9A4016007BCDC5 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0800;
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 0900;
ORGANIZATIONNAME = "Mathew Kurian";
TargetAttributes = {
@ -234,6 +291,11 @@
LastSwiftMigration = 0900;
ProvisioningStyle = Automatic;
};
F06792E6200A73460066C438 = {
CreatedOnToolsVersion = 9.2;
DevelopmentTeam = KGY56RWR9A;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 56754EA61D9A4016007BCDC5 /* Build configuration list for PBXProject "MonitorControl" */;
@ -251,6 +313,7 @@
projectRoot = "";
targets = (
56754EAA1D9A4016007BCDC5 /* MonitorControl */,
F06792E6200A73460066C438 /* MonitorControlHelper */,
);
};
/* End PBXProject section */
@ -268,6 +331,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
F06792E5200A73460066C438 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@ -293,11 +363,13 @@
);
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-MonitorControl/Pods-MonitorControl-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/HotKey/HotKey.framework",
"${BUILT_PRODUCTS_DIR}/MASPreferences/MASPreferences.framework",
"${BUILT_PRODUCTS_DIR}/MediaKeyTap/MediaKeyTap.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/HotKey.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MASPreferences.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MediaKeyTap.framework",
);
@ -357,6 +429,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
F06792E3200A73460066C438 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
F06792EA200A73460066C438 /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
@ -564,6 +644,52 @@
};
name = Release;
};
F06792F2200A73470066C438 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = MonitorControlHelper/MonitorControlHelper.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = KGY56RWR9A;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = MonitorControlHelper/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.13;
PRODUCT_BUNDLE_IDENTIFIER = me.guillaumeb.MonitorControlHelper;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 4.0;
};
name = Debug;
};
F06792F3200A73470066C438 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = MonitorControlHelper/MonitorControlHelper.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = KGY56RWR9A;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = MonitorControlHelper/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.13;
PRODUCT_BUNDLE_IDENTIFIER = me.guillaumeb.MonitorControlHelper;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 4.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@ -585,6 +711,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
F06792F4200A73470066C438 /* Build configuration list for PBXNativeTarget "MonitorControlHelper" */ = {
isa = XCConfigurationList;
buildConfigurations = (
F06792F2200A73470066C438 /* Debug */,
F06792F3200A73470066C438 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 56754EA31D9A4016007BCDC5 /* Project object */;

View file

@ -35,13 +35,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, MediaKeyTapDelegate {
app = self
mediaKeyTap = MediaKeyTap.init(delegate: self, forKeys: [.brightnessUp, .brightnessDown, .mute, .volumeUp, .volumeDown], observeBuiltIn: false)
let storyboard: NSStoryboard = NSStoryboard.init(name: NSStoryboard.Name(rawValue: "Main"), bundle: Bundle.main)
prefsController = MASPreferencesWindowController(viewControllers:
[
storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "MainPrefsVC")),
storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "KeysPrefsVC")),
storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "DisplayPrefsVC"))
],
title: NSLocalizedString("Preferences", comment: "Shown in Preferences window"))
let views = [
storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "MainPrefsVC")),
storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "KeysPrefsVC")),
storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "DisplayPrefsVC"))
]
prefsController = MASPreferencesWindowController(viewControllers: views, title: NSLocalizedString("Preferences", comment: "Shown in Preferences window"))
statusItem.image = NSImage.init(named: NSImage.Name(rawValue: "status"))
statusItem.menu = statusMenu
@ -96,47 +95,33 @@ class AppDelegate: NSObject, NSApplicationDelegate, MediaKeyTapDelegate {
clearDisplays()
sleep(1)
for screen in NSScreen.screens {
var filteredScreens = NSScreen.screens.filter { screen -> Bool in
if let id = screen.deviceDescription[NSDeviceDescriptionKey.init("NSScreenNumber")] as? CGDirectDisplayID {
// Is Built In Screen (e.g. MBP/iMac Screen)
if CGDisplayIsBuiltin(id) != 0 {
continue
return false
}
// Does screen support EDID ?
var edid = EDID()
if !EDIDTest(id, &edid) {
continue
return false
}
let name = Utils.getDisplayName(forEdid: edid)
let serial = Utils.getDisplaySerial(forEdid: edid)
let display = Display.init(id, name: name, serial: serial)
let monitorSubMenu = NSMenu()
let brightnessSliderHandler = Utils.addSliderMenuItem(toMenu: monitorSubMenu,
forDisplay: display,
command: BRIGHTNESS,
title: NSLocalizedString("Brightness", comment: "Shown in menu"))
let volumeSliderHandler = Utils.addSliderMenuItem(toMenu: monitorSubMenu,
forDisplay: display,
command: AUDIO_SPEAKER_VOLUME,
title: NSLocalizedString("Volume", comment: "Shown in menu"))
display.brightnessSliderHandler = brightnessSliderHandler
display.volumeSliderHandler = volumeSliderHandler
displays.append(display)
let monitorMenuItem = NSMenuItem()
monitorMenuItem.title = "\(name)"
monitorMenuItem.submenu = monitorSubMenu
monitorItems.append(monitorMenuItem)
statusMenu.insertItem(monitorMenuItem, at: displays.count - 1)
return true
}
}
return false
}
if displays.count == 0 {
if filteredScreens.count == 1 {
self.addScreenToMenu(screen: filteredScreens[0], asSubMenu: false)
} else {
for screen in filteredScreens {
self.addScreenToMenu(screen: screen, asSubMenu: true)
}
}
if filteredScreens.count == 0 {
// If no DDC capable display was detected
let item = NSMenuItem()
item.title = NSLocalizedString("No supported display found", comment: "Shown in menu")
@ -146,6 +131,46 @@ class AppDelegate: NSObject, NSApplicationDelegate, MediaKeyTapDelegate {
}
}
/// Add a screen to the menu
///
/// - Parameters:
/// - screen: The screen to add
/// - asSubMenu: Display in a sub menu or directly in menu
private func addScreenToMenu(screen: NSScreen, asSubMenu: Bool) {
if let id = screen.deviceDescription[NSDeviceDescriptionKey.init("NSScreenNumber")] as? CGDirectDisplayID {
var edid = EDID()
if EDIDTest(id, &edid) {
let name = Utils.getDisplayName(forEdid: edid)
let serial = Utils.getDisplaySerial(forEdid: edid)
let display = Display.init(id, name: name, serial: serial)
let monitorSubMenu: NSMenu = asSubMenu ? NSMenu() : statusMenu
let volumeSliderHandler = Utils.addSliderMenuItem(toMenu: monitorSubMenu,
forDisplay: display,
command: AUDIO_SPEAKER_VOLUME,
title: NSLocalizedString("Volume", comment: "Shown in menu"))
let brightnessSliderHandler = Utils.addSliderMenuItem(toMenu: monitorSubMenu,
forDisplay: display,
command: BRIGHTNESS,
title: NSLocalizedString("Brightness", comment: "Shown in menu"))
display.volumeSliderHandler = volumeSliderHandler
display.brightnessSliderHandler = brightnessSliderHandler
displays.append(display)
let monitorMenuItem = NSMenuItem()
monitorMenuItem.title = "\(name)"
if asSubMenu {
monitorMenuItem.submenu = monitorSubMenu
}
monitorItems.append(monitorMenuItem)
statusMenu.insertItem(monitorMenuItem, at: 0)
}
}
}
// MARK: - Media Key Tap delegate
func handle(mediaKey: MediaKey, event: KeyEvent) {

View file

@ -11,11 +11,11 @@
<objects>
<viewController storyboardIdentifier="MainPrefsVC" id="HNb-aq-vnV" customClass="MainPrefsViewController" customModule="MonitorControl" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="iuQ-z6-BGz">
<rect key="frame" x="0.0" y="0.0" width="400" height="117"/>
<rect key="frame" x="0.0" y="0.0" width="400" height="158"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8KQ-j9-ddv">
<rect key="frame" x="18" y="68" width="364" height="29"/>
<rect key="frame" x="18" y="109" width="92" height="29"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="General" id="ocE-Cc-2bi">
<font key="font" metaFont="systemBold" size="24"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -23,7 +23,7 @@
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dZG-M6-fsQ">
<rect key="frame" x="18" y="42" width="364" height="18"/>
<rect key="frame" x="18" y="83" width="199" height="18"/>
<buttonCell key="cell" type="check" title="Start MonitorControl at Login" bezelStyle="regularSquare" imagePosition="left" inset="2" id="UTh-SV-vAQ">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@ -32,67 +32,55 @@
<action selector="startAtLoginClicked:" target="HNb-aq-vnV" id="OrA-9Y-N8S"/>
</connections>
</button>
<button hidden="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nnZ-LS-lOW">
<rect key="frame" x="18" y="-16" width="364" height="18"/>
<buttonCell key="cell" type="check" title="Check for updates automatically" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="SCJ-AY-phi">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button hidden="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="K3V-Ub-wAA">
<rect key="frame" x="14" y="-52" width="152" height="32"/>
<rect key="frame" x="14" y="13" width="152" height="32"/>
<buttonCell key="cell" type="push" title="Check for updates" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Vi8-Ye-W28">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fds-MD-5OJ">
<rect key="frame" x="168" y="-43" width="78" height="17"/>
<rect key="frame" x="168" y="22" width="78" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Last check: " id="I3I-EP-Ev8">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="71u-hm-uYt">
<rect key="frame" x="18" y="18" width="364" height="18"/>
<buttonCell key="cell" type="check" title="Start when plugged to an external monitor" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="WJp-aA-2Af">
<button hidden="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nnZ-LS-lOW">
<rect key="frame" x="18" y="49" width="218" height="18"/>
<buttonCell key="cell" type="check" title="Check for updates automatically" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="SCJ-AY-phi">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="startWhenExternalClicked:" target="HNb-aq-vnV" id="BT0-Ff-bx9"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="fds-MD-5OJ" firstAttribute="centerY" secondItem="K3V-Ub-wAA" secondAttribute="centerY" id="2RB-zz-fOU"/>
<constraint firstItem="nnZ-LS-lOW" firstAttribute="top" secondItem="71u-hm-uYt" secondAttribute="bottom" constant="20" id="5IO-Jv-9ZU"/>
<constraint firstItem="dZG-M6-fsQ" firstAttribute="leading" secondItem="iuQ-z6-BGz" secondAttribute="leading" constant="20" id="8QT-EO-uBJ"/>
<constraint firstItem="K3V-Ub-wAA" firstAttribute="top" secondItem="nnZ-LS-lOW" secondAttribute="bottom" constant="10" id="9DT-6C-dQo"/>
<constraint firstItem="8KQ-j9-ddv" firstAttribute="top" secondItem="iuQ-z6-BGz" secondAttribute="top" constant="20" id="Fwh-JQ-1Cx"/>
<constraint firstItem="K3V-Ub-wAA" firstAttribute="leading" secondItem="iuQ-z6-BGz" secondAttribute="leading" constant="20" id="RWW-67-dGr"/>
<constraint firstItem="71u-hm-uYt" firstAttribute="top" secondItem="dZG-M6-fsQ" secondAttribute="bottom" constant="10" id="Xmr-XF-7lE"/>
<constraint firstAttribute="trailing" secondItem="dZG-M6-fsQ" secondAttribute="trailing" constant="20" id="c5h-vI-6Dg"/>
<constraint firstItem="nnZ-LS-lOW" firstAttribute="top" secondItem="dZG-M6-fsQ" secondAttribute="bottom" constant="20" id="V4k-4m-OkH"/>
<constraint firstAttribute="bottom" secondItem="K3V-Ub-wAA" secondAttribute="bottom" constant="20" id="bFh-Xa-GoP"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="dZG-M6-fsQ" secondAttribute="trailing" constant="20" id="c5h-vI-6Dg"/>
<constraint firstItem="dZG-M6-fsQ" firstAttribute="top" secondItem="8KQ-j9-ddv" secondAttribute="bottom" constant="10" id="dPJ-ev-Y3O"/>
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="dZG-M6-fsQ" secondAttribute="bottom" constant="20" id="gVa-4Q-fPP"/>
<constraint firstItem="8KQ-j9-ddv" firstAttribute="leading" secondItem="iuQ-z6-BGz" secondAttribute="leading" constant="20" id="ii0-uV-Ylg"/>
<constraint firstItem="71u-hm-uYt" firstAttribute="leading" secondItem="iuQ-z6-BGz" secondAttribute="leading" constant="20" id="jsn-2a-Hse"/>
<constraint firstAttribute="trailing" secondItem="nnZ-LS-lOW" secondAttribute="trailing" constant="20" id="kgN-1K-yxh"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="nnZ-LS-lOW" secondAttribute="trailing" constant="20" id="kgN-1K-yxh"/>
<constraint firstItem="nnZ-LS-lOW" firstAttribute="leading" secondItem="iuQ-z6-BGz" secondAttribute="leading" constant="20" id="kqS-g2-YV5"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="fds-MD-5OJ" secondAttribute="trailing" constant="20" id="mfJ-HC-8ie"/>
<constraint firstAttribute="trailing" secondItem="8KQ-j9-ddv" secondAttribute="trailing" constant="20" id="qVn-hz-dRV"/>
<constraint firstAttribute="trailing" secondItem="71u-hm-uYt" secondAttribute="trailing" constant="20" id="sIC-PJ-9gQ"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="8KQ-j9-ddv" secondAttribute="trailing" constant="20" id="qVn-hz-dRV"/>
<constraint firstItem="fds-MD-5OJ" firstAttribute="leading" secondItem="K3V-Ub-wAA" secondAttribute="trailing" constant="10" id="xuV-jB-am4"/>
</constraints>
</view>
<connections>
<outlet property="startAtLogin" destination="dZG-M6-fsQ" id="Rwg-dp-vIj"/>
<outlet property="startWhenExternal" destination="71u-hm-uYt" id="l02-Xi-74S"/>
</connections>
</viewController>
<customObject id="ALN-AB-CU5" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="116" y="172.5"/>
<point key="canvasLocation" x="116" y="285"/>
</scene>
<!--Keys Prefs View Controller-->
<scene sceneID="DmR-ia-4qL">
@ -162,7 +150,7 @@
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Uti-Vm-YwB">
<rect key="frame" x="18" y="80" width="364" height="29"/>
<rect key="frame" x="18" y="80" 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"/>
@ -179,8 +167,9 @@
<constraint firstItem="Uti-Vm-YwB" firstAttribute="leading" secondItem="nfK-n0-xN4" secondAttribute="leading" constant="20" id="Mfl-NR-mVk"/>
<constraint firstItem="Uti-Vm-YwB" firstAttribute="top" secondItem="nfK-n0-xN4" secondAttribute="top" constant="20" id="ReX-zz-skV"/>
<constraint firstItem="fUf-UM-yW3" firstAttribute="leading" secondItem="jFY-ug-0KQ" secondAttribute="trailing" constant="10" id="Tic-nC-Oax"/>
<constraint firstAttribute="bottom" secondItem="AeW-b4-XNc" secondAttribute="bottom" constant="20" id="ULA-RM-25S"/>
<constraint firstItem="AeW-b4-XNc" firstAttribute="top" secondItem="fUf-UM-yW3" secondAttribute="bottom" constant="10" id="Zd8-nU-IPw"/>
<constraint firstAttribute="trailing" secondItem="Uti-Vm-YwB" secondAttribute="trailing" constant="20" id="ctI-eo-jEw"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Uti-Vm-YwB" secondAttribute="trailing" constant="20" id="ctI-eo-jEw"/>
<constraint firstItem="AeW-b4-XNc" firstAttribute="leading" secondItem="VNO-n0-6Y1" secondAttribute="trailing" constant="10" id="dkl-ho-D2N"/>
<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="leading" secondItem="nfK-n0-xN4" secondAttribute="leading" constant="20" id="xCB-fT-tMo"/>
@ -193,7 +182,7 @@
</viewController>
<customObject id="hVj-0D-6XC" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="618" y="178.5"/>
<point key="canvasLocation" x="624" y="179"/>
</scene>
<!--Display Prefs View Controller-->
<scene sceneID="kdt-oj-Qke">
@ -204,7 +193,7 @@
<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="364" height="29"/>
<rect key="frame" x="18" y="191" 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"/>
@ -214,7 +203,7 @@
<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="23" width="358" height="113"/>
<rect key="frame" x="1" y="0.0" width="358" height="136"/>
<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">
@ -343,7 +332,7 @@
</tableHeaderView>
</scrollView>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Y48-gQ-uJi">
<rect key="frame" x="18" y="165" width="364" height="18"/>
<rect key="frame" x="18" y="165" 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"/>
@ -354,9 +343,9 @@
</button>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="Y48-gQ-uJi" secondAttribute="trailing" constant="20" id="24u-jX-VqD"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Y48-gQ-uJi" secondAttribute="trailing" constant="20" id="24u-jX-VqD"/>
<constraint firstItem="Y48-gQ-uJi" firstAttribute="leading" secondItem="EBf-qN-KaN" secondAttribute="leading" constant="20" id="Ct2-ah-W0a"/>
<constraint firstAttribute="trailing" secondItem="uXD-ZY-N3H" secondAttribute="trailing" constant="20" id="Qwk-xy-tL4"/>
<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"/>

View file

@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.1.1</string>
<string>1.2.0</string>
<key>CFBundleVersion</key>
<string>32</string>
<string>40</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>

View file

@ -29,7 +29,7 @@ class ButtonCellView: NSTableCellView {
break
}
// TODO: Toggle enabled display state
print("Toggle enabled display state -> \(sender.state)")
print("Toggle enabled display state -> \(sender.state == .on ? "on" : "off")")
}
}
}

View file

@ -18,6 +18,8 @@ class Display {
var brightnessSliderHandler: SliderHandler?
var volumeSliderHandler: SliderHandler?
private let prefs = UserDefaults.standard
init(_ identifier: CGDirectDisplayID, name: String, serial: String, isEnabled: Bool = true) {
self.identifier = identifier
self.name = name
@ -28,13 +30,13 @@ class Display {
func mute() {
var value = 0
if isMuted {
value = UserDefaults.standard.integer(forKey: "\(AUDIO_SPEAKER_VOLUME)-\(identifier)")
value = prefs.integer(forKey: "\(AUDIO_SPEAKER_VOLUME)-\(identifier)")
isMuted = false
} else {
isMuted = true
}
Utils.ddcctl(monitor: identifier, command: AUDIO_SPEAKER_VOLUME, value: value)
Utils.sendCommand(AUDIO_SPEAKER_VOLUME, toMonitor: identifier, withValue: value)
if let slider = volumeSliderHandler?.slider {
slider.intValue = Int32(value)
}
@ -46,7 +48,7 @@ class Display {
isMuted = false
}
Utils.ddcctl(monitor: identifier, command: AUDIO_SPEAKER_VOLUME, value: value)
Utils.sendCommand(AUDIO_SPEAKER_VOLUME, toMonitor: identifier, withValue: value)
if let slider = volumeSliderHandler?.slider {
slider.intValue = Int32(value)
}
@ -55,7 +57,7 @@ class Display {
}
func setBrightness(to value: Int) {
Utils.ddcctl(monitor: identifier, command: BRIGHTNESS, value: value)
Utils.sendCommand(BRIGHTNESS, toMonitor: identifier, withValue: value)
if let slider = brightnessSliderHandler?.slider {
slider.intValue = Int32(value)
}
@ -64,12 +66,12 @@ class Display {
}
func calcNewValue(for command: Int32, withRel rel: Int) -> Int {
let currentValue = UserDefaults.standard.integer(forKey: "\(command)-\(identifier)")
let currentValue = prefs.integer(forKey: "\(command)-\(identifier)")
return max(0, min(100, currentValue + rel))
}
func saveValue(_ value: Int, for command: Int32) {
UserDefaults.standard.set(value, forKey: "\(command)-\(identifier)")
prefs.set(value, forKey: "\(command)-\(identifier)")
}
private func showOsd(command: Int32, value: Int) {

View file

@ -31,7 +31,7 @@ class SliderHandler {
slider.integerValue = value
}
Utils.ddcctl(monitor: display.identifier, command: command, value: value)
prefs.setValue(value, forKey: "\(command)-\(display.identifier)")
Utils.sendCommand(command, toMonitor: display.identifier, withValue: value)
display.saveValue(value, for: command)
}
}

View file

@ -43,7 +43,7 @@ class DisplayPrefsViewController: NSViewController, MASPreferencesViewController
default: break
}
// TODO: Toggle allScreens state
print("Toggle allScreens state -> \(sender.state)")
print("Toggle allScreens state -> \(sender.state == .on ? "on" : "off")")
}
// MARK: - Table datasource

View file

@ -8,6 +8,7 @@
import Cocoa
import MASPreferences
import ServiceManagement
class MainPrefsViewController: NSViewController, MASPreferencesViewController {
@ -27,15 +28,17 @@ class MainPrefsViewController: NSViewController, MASPreferencesViewController {
}
@IBAction func startAtLoginClicked(_ sender: NSButton) {
let identifier = "me.guillaumeb.MonitorControlHelper" as CFString
switch sender.state {
case .on:
prefs.set(true, forKey: Utils.PrefKeys.startAtLogin.rawValue)
SMLoginItemSetEnabled(identifier, true)
case .off:
prefs.set(false, forKey: Utils.PrefKeys.startAtLogin.rawValue)
SMLoginItemSetEnabled(identifier, false)
default: break
}
// TODO: Toggle start at login state
print("Toggle start at login state -> \(sender.state)")
print("Toggle start at login state -> \(sender.state == .on ? "on" : "off")")
}
@IBAction func startWhenExternalClicked(_ sender: NSButton) {
@ -47,6 +50,6 @@ class MainPrefsViewController: NSViewController, MASPreferencesViewController {
default: break
}
// TODO: Toggle start when external plugged in state
print("Toggle start when external plugged in state -> \(sender.state)")
print("Toggle start when external plugged in state -> \(sender.state == .on ? "on" : "off")")
}
}

View file

@ -15,13 +15,29 @@ class Utils: NSObject {
/// Send command to ddcctl
///
/// - Parameters:
/// - monitor: The id of the Monitor to send the command to
/// - command: The command to send
/// - monitor: The id of the Monitor to send the command to
/// - value: the value of the command
static func ddcctl(monitor: CGDirectDisplayID, command: Int32, value: Int) {
static func sendCommand(_ command: Int32, toMonitor monitor: CGDirectDisplayID, withValue value: Int) {
var wrcmd = DDCWriteCommand(control_id: UInt8(command), new_value: UInt8(value))
DDCWrite(monitor, &wrcmd)
print(value)
print("\(command == BRIGHTNESS ? "Brightness" : "Volume") value : \(value)")
}
/// Get current value of ddcctl command
///
/// - Parameters:
/// - command: The command to send
/// - monitor: The id of the monitor to send the command to
/// - Returns: the value of the command
static func getCommand(_ command: Int32, fromMonitor monitor: CGDirectDisplayID) -> Int {
var readCmd = DDCReadCommand()
readCmd.control_id = UInt8(command)
readCmd.max_value = 0
readCmd.current_value = 0
DDCRead(monitor, &readCmd)
print("\(command == BRIGHTNESS ? "Brightness" : "Volume") value : \(readCmd.current_value)")
return Int(readCmd.current_value)
}
// MARK: - Menu
@ -59,17 +75,18 @@ class Utils: NSObject {
slider.target = handler
slider.minValue = 0
slider.maxValue = 100
slider.integerValue = prefs.integer(forKey: "\(command)-\(display.serial)")
slider.integerValue = getCommand(command, fromMonitor: display.identifier)
slider.action = #selector(SliderHandler.valueChanged)
handler.slider = slider
display.saveValue(slider.integerValue, for: command)
view.addSubview(label)
view.addSubview(slider)
item.view = view
menu.addItem(item)
menu.addItem(NSMenuItem.separator())
menu.insertItem(item, at: 0)
menu.insertItem(NSMenuItem.separator(), at: 1)
return handler
}

View file

@ -0,0 +1,32 @@
//
// AppDelegate.swift
// MonitorControlHelper
//
// Created by Guillaume BRODER on 13/01/2018.
// Copyright © 2018 Mathew Kurian. All rights reserved.
//
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet weak var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let bundlePath = Bundle.main.bundlePath as NSString
var pathComponents = bundlePath.pathComponents
for _ in 0...4 {
pathComponents.removeLast()
}
let path = NSString.path(withComponents: pathComponents)
NSWorkspace.shared.launchApplication(path)
NSApp.terminate(nil)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}

View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSBackgroundOnly</key>
<true/>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>MIT Licensed. 2018.</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>