Only show attached monitors and use ddcctl directly

This commit is contained in:
Asger Hautop Drewsen 2017-01-12 05:46:25 +01:00
parent 8de9597139
commit d4efada906
5 changed files with 191 additions and 72 deletions

3
.gitmodules vendored Normal file
View file

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

View file

@ -7,12 +7,23 @@
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 */; };
55359E3C1E2737EC002671BC /* Makefile in Sources */ = {isa = PBXBuildFile; fileRef = 55359E371E2737EC002671BC /* Makefile */; };
55359E3D1E2737EC002671BC /* README.md in Sources */ = {isa = PBXBuildFile; fileRef = 55359E381E2737EC002671BC /* README.md */; };
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 */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
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>"; };
55359E3E1E27380B002671BC /* Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = "<group>"; };
56754EAB1D9A4016007BCDC5 /* MonitorControl.OSX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MonitorControl.OSX.app; sourceTree = BUILT_PRODUCTS_DIR; };
56754EAE1D9A4016007BCDC5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
56754EB01D9A4016007BCDC5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@ -31,10 +42,24 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
55359E321E2737EC002671BC /* ddcctl */ = {
isa = PBXGroup;
children = (
55359E331E2737EC002671BC /* DDC.c */,
55359E341E2737EC002671BC /* DDC.h */,
55359E351E2737EC002671BC /* ddcctl.m */,
55359E361E2737EC002671BC /* ddcctl.sh */,
55359E371E2737EC002671BC /* Makefile */,
55359E381E2737EC002671BC /* README.md */,
);
path = ddcctl;
sourceTree = "<group>";
};
56754EA21D9A4016007BCDC5 = {
isa = PBXGroup;
children = (
56754EAD1D9A4016007BCDC5 /* MonitorControl.OSX */,
55359E321E2737EC002671BC /* ddcctl */,
56754EAC1D9A4016007BCDC5 /* Products */,
);
sourceTree = "<group>";
@ -54,6 +79,7 @@
56754EB01D9A4016007BCDC5 /* Assets.xcassets */,
56754EB21D9A4016007BCDC5 /* MainMenu.xib */,
56754EB51D9A4016007BCDC5 /* Info.plist */,
55359E3E1E27380B002671BC /* Bridging-Header.h */,
);
path = MonitorControl.OSX;
sourceTree = "<group>";
@ -118,6 +144,7 @@
buildActionMask = 2147483647;
files = (
56754EB11D9A4016007BCDC5 /* Assets.xcassets in Resources */,
55359E3B1E2737EC002671BC /* ddcctl.sh in Resources */,
56754EB41D9A4016007BCDC5 /* MainMenu.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -130,6 +157,9 @@
buildActionMask = 2147483647;
files = (
56754EAF1D9A4016007BCDC5 /* AppDelegate.swift in Sources */,
55359E3D1E2737EC002671BC /* README.md in Sources */,
55359E3C1E2737EC002671BC /* Makefile in Sources */,
55359E391E2737EC002671BC /* DDC.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -246,6 +276,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "bluejamesbond.MonitorControl-OSX";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "MonitorControl.OSX/Bridging-Header.h";
SWIFT_VERSION = 3.0;
};
name = Debug;
@ -259,6 +290,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "bluejamesbond.MonitorControl-OSX";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "MonitorControl.OSX/Bridging-Header.h";
SWIFT_VERSION = 3.0;
};
name = Release;
@ -282,6 +314,7 @@
56754EBA1D9A4016007BCDC5 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};

View file

@ -9,6 +9,12 @@
import Cocoa
import Foundation
struct Display {
var id: CGDirectDisplayID
var name: String
var serial: String
}
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
@ -20,6 +26,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
let statusItem = NSStatusBar.system().statusItem(withLength: NSVariableStatusItemLength)
let keycode = UInt16(0x07)
var displays : [Display] = []
@IBAction func quitClicked(_ sender: AnyObject) {
NSApplication.shared().terminate(self);
@ -28,117 +36,140 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func setBrightness( slider: NSSlider ){
let command = "-b";
let value = slider.integerValue;
let monitor = slider.tag;
let i = slider.tag;
let d = displays[i]
ddcctl(monitor: String(monitor), command: command, value: String(value));
ddcctl(monitor: d.id, command: command, value: value);
prefs.setValue(value, forKey: "\(command)-\(monitor)");
prefs.setValue(value, forKey: "\(command)-\(d.serial)");
prefs.synchronize();
}
func setVolume(slider: NSSlider ){
let command = "-v";
let value = slider.integerValue;
let monitor = slider.tag;
ddcctl(monitor: String(monitor), command: command, value: String(value));
prefs.setValue(value, forKey: "\(command)-\(monitor)");
let i = slider.tag;
let d = displays[i]
ddcctl(monitor: d.id, command: command, value: value);
prefs.setValue(value, forKey: "\(command)-\(d.serial)");
prefs.synchronize();
}
func setContrast(slider: NSSlider ){
let command = "-c";
let value = slider.integerValue;
let monitor = slider.tag;
ddcctl(monitor: String(monitor), command: command, value: String(value));
prefs.setValue(value, forKey: "\(command)-\(monitor)");
let i = slider.tag;
let d = displays[i]
ddcctl(monitor: d.id, command: command, value: value);
prefs.setValue(value, forKey: "\(command)-\(d.serial)");
prefs.synchronize();
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
statusItem.title = ""
statusItem.menu = statusMenu;
for i in (1...4).reversed() {
statusItem.menu = statusMenu
var firstDisplay : Display? = nil
for s in NSScreen.screens()! {
let id = s.deviceDescription["NSScreenNumber"] as! CGDirectDisplayID
if CGDisplayIsBuiltin(id) != 0 {
continue
}
var edid = EDID()
if !EDIDTest(id, &edid) {
continue
}
let name = getDisplayName(edid)
let serial = getDisplaySerial(edid)
let d = Display(id: id, name: name, serial: serial)
displays.append(d)
let i = displays.count - 1
let monitorMenuItem = NSMenuItem();
let monitorSubMenu = NSMenu();
let brightnessItem = NSMenuItem();
let contrastItem = NSMenuItem();
let volumeItem = NSMenuItem();
let defaultMonitorItem = NSMenuItem();
let brightnessSlider = NSSlider(frame: NSRect(x: 20, y: 0, width: 200, height: 19));
brightnessSlider.target = self;
brightnessSlider.minValue = 0;
brightnessSlider.maxValue = 100;
brightnessSlider.integerValue = prefs.integer(forKey: "-b-\(i)")
brightnessSlider.integerValue = prefs.integer(forKey: "-b-\(serial)")
brightnessSlider.action = #selector(AppDelegate.setBrightness);
brightnessSlider.tag = i;
let contrastSlider = NSSlider(frame: NSRect(x: 20, y: 0, width: 200, height: 19));
contrastSlider.target = self;
contrastSlider.minValue = 0;
contrastSlider.maxValue = 100;
contrastSlider.integerValue = prefs.integer(forKey: "-c-\(i)")
contrastSlider.integerValue = prefs.integer(forKey: "-c-\(serial)")
contrastSlider.action = #selector(AppDelegate.setContrast);
contrastSlider.tag = i;
let volumeSlider = NSSlider(frame: NSRect(x: 20, y: 3, width: 200, height: 19));
volumeSlider.target = self;
volumeSlider.minValue = 0;
volumeSlider.maxValue = 100;
volumeSlider.integerValue = prefs.integer(forKey: "-v-\(i)")
volumeSlider.integerValue = prefs.integer(forKey: "-v-\(serial)")
volumeSlider.action = #selector(AppDelegate.setVolume);
volumeSlider.tag = i;
let brightnesSliderView = NSView(frame: NSRect(x: 0, y: 5, width: 250, height: 40));
let contrastSliderView = NSView(frame: NSRect(x: 0, y: 5, width: 250, height: 40));
let volumeSliderView = NSView(frame: NSRect(x: 0, y: 5, width: 250, height: 40));
let defaultMonitorView = NSView(frame: NSRect(x: 0, y: 5, width: 250, height: 25));
let brightnessLabel = NSTextField(frame: NSRect(x: 20, y: 16, width: 130, height: 20))
brightnessLabel.stringValue = "Brightness";
brightnessLabel.isBordered = false;
brightnessLabel.isBezeled = false;
let brightnessLabelKeyCode = NSTextField(frame: NSRect(x: 120, y: 16, width: 100, height: 20))
brightnessLabelKeyCode.stringValue = "⇧⌘- / ⇧⌘+"
brightnessLabelKeyCode.isBordered = false;
brightnessLabelKeyCode.isBezeled = false;
brightnessLabelKeyCode.isHidden = i != 1;
brightnessLabelKeyCode.isHidden = firstDisplay == nil;
brightnessLabelKeyCode.alignment = NSTextAlignment.right
let constrastLabel = NSTextField(frame: NSRect(x: 20, y: 16, width: 130, height: 20))
constrastLabel.stringValue = "Contrast"
constrastLabel.isBordered = false;
constrastLabel.isBezeled = false;
let volumeLabel = NSTextField(frame: NSRect(x: 20, y: 19, width: 130, height: 20))
volumeLabel.stringValue = "Volume"
volumeLabel.isBordered = false;
volumeLabel.isBezeled = false;
let volumeLabelKeyCode = NSTextField(frame: NSRect(x: 120, y: 19, width: 100, height: 20))
volumeLabelKeyCode.stringValue = "⌥⌘- / ⌥⌘+"
volumeLabelKeyCode.isBordered = false;
volumeLabelKeyCode.isBezeled = false;
volumeLabelKeyCode.isHidden = i != 1;
volumeLabelKeyCode.isHidden = firstDisplay == nil;
volumeLabelKeyCode.alignment = NSTextAlignment.right
brightnesSliderView.addSubview(brightnessLabel)
brightnesSliderView.addSubview(brightnessLabelKeyCode)
brightnesSliderView.addSubview(brightnessSlider)
contrastSliderView.addSubview(constrastLabel)
contrastSliderView.addSubview(contrastSlider)
volumeSliderView.addSubview(volumeLabel)
volumeSliderView.addSubview(volumeLabelKeyCode)
volumeSliderView.addSubview(volumeSlider)
@ -146,17 +177,17 @@ class AppDelegate: NSObject, NSApplicationDelegate {
brightnessItem.view = brightnesSliderView;
contrastItem.view = contrastSliderView;
volumeItem.view = volumeSliderView;
let defaultMonitorSelectButtom = NSButton(frame: NSRect(x: 25, y: 0, width: 200, height: 25));
defaultMonitorSelectButtom.title = i == 1 ? "Default" : "Set as default";
defaultMonitorSelectButtom.title = firstDisplay == nil ? "Default" : "Set as default";
defaultMonitorSelectButtom.bezelStyle = NSRoundRectBezelStyle;
defaultMonitorSelectButtom.isEnabled = i != 1;
defaultMonitorSelectButtom.isEnabled = firstDisplay == nil;
defaultMonitorSelectButtom.tag = i;
defaultMonitorView.addSubview(defaultMonitorSelectButtom);
defaultMonitorItem.view = defaultMonitorView;
monitorSubMenu.addItem(brightnessItem);
monitorSubMenu.addItem(NSMenuItem.separator());
monitorSubMenu.addItem(contrastItem);
@ -165,53 +196,59 @@ class AppDelegate: NSObject, NSApplicationDelegate {
monitorSubMenu.addItem(NSMenuItem.separator());
monitorSubMenu.addItem(defaultMonitorItem);
monitorMenuItem.title = "Monitor \(i)";
monitorMenuItem.title = "\(name)";
monitorMenuItem.submenu = monitorSubMenu;
statusMenu.insertItem(monitorMenuItem, at: 0)
if firstDisplay == nil {
firstDisplay = d
}
}
acquirePrivileges();
if firstDisplay == nil {
return
}
let d = firstDisplay!
NSEvent.addGlobalMonitorForEvents(
matching: NSEventMask.keyDown, handler: {(event: NSEvent) in
if (event.keyCode == 27 &&
(event.modifierFlags.contains(NSEventModifierFlags.control)) &&
(event.modifierFlags.contains(NSEventModifierFlags.command))) {
let monitor = 1;
let value = abs(self.prefs.integer(forKey: "-v-\(monitor)") - 1);
self.prefs.setValue(value, forKey: "-v-\(monitor)");
self.ddcctl(monitor: String(monitor), command: "-v", value: String(value));
let value = abs(self.prefs.integer(forKey: "-v-\(d.serial)") - 1);
self.prefs.setValue(value, forKey: "-v-\(d.serial)");
self.ddcctl(monitor: d.id, command: "-v", value: value);
} else if (event.keyCode == 24 &&
(event.modifierFlags.contains(NSEventModifierFlags.control)) &&
(event.modifierFlags.contains(NSEventModifierFlags.command))) {
let monitor = 1;
let value = abs(self.prefs.integer(forKey: "-v-\(monitor)") + 1);
let value = abs(self.prefs.integer(forKey: "-v-\(d.serial)") + 1);
self.prefs.setValue(value, forKey: "-v-\(monitor)");
self.prefs.setValue(value, forKey: "-v-\(d.serial)");
self.ddcctl(monitor: String(monitor), command: "-v", value: String(value));
self.ddcctl(monitor: d.id, command: "-v", value: value);
} else if (event.keyCode == 27 &&
(event.modifierFlags.contains(NSEventModifierFlags.option)) &&
(event.modifierFlags.contains(NSEventModifierFlags.command))) {
let monitor = 1;
let value = abs(self.prefs.integer(forKey: "-b-\(monitor)") - 1);
let value = abs(self.prefs.integer(forKey: "-b-\(d.serial)") - 1);
self.prefs.setValue(value, forKey: "-b-\(monitor)");
self.prefs.setValue(value, forKey: "-b-\(d.serial))");
self.ddcctl(monitor: String(monitor), command: "-b", value: String(value));
self.ddcctl(monitor: d.id, command: "-b", value: value);
} else if (event.keyCode == 24 &&
(event.modifierFlags.contains(NSEventModifierFlags.option)) &&
(event.modifierFlags.contains(NSEventModifierFlags.command))) {
let monitor = 1;
let value = abs(self.prefs.integer(forKey: "-b-\(monitor)") + 1);
let value = abs(self.prefs.integer(forKey: "-b-\(d.serial)") + 1);
self.prefs.setValue(value, forKey: "-b-\(monitor)");
self.prefs.setValue(value, forKey: "-b-\(d.serial)");
self.ddcctl(monitor: String(monitor), command: "-b", value: String(value));
self.ddcctl(monitor: d.id, command: "-b", value: value);
}
});
}
@ -227,16 +264,59 @@ class AppDelegate: NSObject, NSApplicationDelegate {
return;
}
func ddcctl(monitor: String, command: String, value: String) {
let task = Process()
task.launchPath = "/usr/local/bin/ddcctl"
task.arguments = ["-d", monitor, command, value]
task.launch()
func ddcctl(monitor: CGDirectDisplayID, command: String, value: Int) {
var cmd : Int32! = nil
switch command {
case "-b":
cmd = BRIGHTNESS
break
case "-v":
cmd = AUDIO_SPEAKER_VOLUME
break
case "-c":
cmd = CONTRAST
break
default:
precondition(false, "Unknown command: \(command)")
}
var wrcmd = DDCWriteCommand(control_id: UInt8(cmd), new_value: UInt8(value))
DDCWrite(monitor, &wrcmd)
print(value)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func edidString(_ d: descriptor) -> String {
var s = ""
for (_, b) in Mirror(reflecting: d.text.data).children {
let b = b as! Int8
let c = Character(UnicodeScalar(UInt8(bitPattern: b)))
if c == "\0" || c == "\n" {
break
}
s.append(c)
}
return s
}
func getDescriptorString(_ edid: EDID, _ type: UInt8) -> String? {
for d in [edid.descriptor1, edid.descriptor2, edid.descriptor3, edid.descriptor4] {
if d.text.type == UInt8(type) {
return edidString(d)
}
}
return nil
}
func getDisplayName(_ edid: EDID) -> String {
return getDescriptorString(edid, 0xFC) ?? "Display"
}
func getDisplaySerial(_ edid: EDID) -> String {
return getDescriptorString(edid, 0xFF) ?? "Unknown"
}
}

View file

@ -0,0 +1,2 @@
#import <Foundation/Foundation.h>
#include "../ddcctl/DDC.h"

1
ddcctl Submodule

@ -0,0 +1 @@
Subproject commit dda64ce43cdf0c16b2bedcf744033911a2bb88c5