From 0f66aad47e899eb4ff2f2bc3fa14bac000df0e19 Mon Sep 17 00:00:00 2001 From: Asger Hautop Drewsen Date: Thu, 12 Jan 2017 08:09:00 +0100 Subject: [PATCH] DRY and make sliders snap to multiples of 25 --- MonitorControl.OSX/AppDelegate.swift | 335 +++++++++++---------------- 1 file changed, 137 insertions(+), 198 deletions(-) diff --git a/MonitorControl.OSX/AppDelegate.swift b/MonitorControl.OSX/AppDelegate.swift index b5fffa9..c02673f 100644 --- a/MonitorControl.OSX/AppDelegate.swift +++ b/MonitorControl.OSX/AppDelegate.swift @@ -15,7 +15,42 @@ struct Display { var serial: String } -var app : AppDelegate! = nil +var app: AppDelegate! = nil +let prefs = UserDefaults.standard + +func ddcctl(monitor: CGDirectDisplayID, command: Int32, value: Int) { + var wrcmd = DDCWriteCommand(control_id: UInt8(command), new_value: UInt8(value)) + DDCWrite(monitor, &wrcmd) + print(value) +} + +class SliderHandler : NSObject { + var display : Display + var command : Int32 = 0 + + public init(display: Display, command: Int32) { + self.display = display + self.command = command + } + + 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 + } + + ddcctl(monitor: display.id, command: command, value: value) + + prefs.setValue(value, forKey: "\(command)-\(display.serial)") + prefs.synchronize() + } +} @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { @@ -23,54 +58,15 @@ class AppDelegate: NSObject, NSApplicationDelegate { @IBOutlet weak var statusMenu: NSMenu! @IBOutlet weak var window: NSWindow! - let prefs = UserDefaults.standard - let statusItem = NSStatusBar.system().statusItem(withLength: NSVariableStatusItemLength) - - let keycode = UInt16(0x07) - var monitorItems : [NSMenuItem] = [] - var displays : [Display] = [] + var monitorItems: [NSMenuItem] = [] + var displays: [Display] = [] + var sliderHandlers: [SliderHandler] = [] @IBAction func quitClicked(_ sender: AnyObject) { NSApplication.shared().terminate(self) } - - func setBrightness( slider: NSSlider ){ - let command = "-b" - let value = slider.integerValue - 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 setVolume(slider: NSSlider ){ - let command = "-v" - let value = slider.integerValue - 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 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) { app = self @@ -84,16 +80,62 @@ class AppDelegate: NSObject, NSApplicationDelegate { updateDisplays() } + 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 + } + + func addSliderItem(menu: NSMenu, defaultDisplay: Bool, display: Display, command: Int32, title: String, shortcut: String) -> NSSlider { + let item = NSMenuItem() + + let view = NSView(frame: NSRect(x: 0, y: 5, width: 250, height: 40)) + + let label = makeLabel(text: title, frame: NSRect(x: 20, y: 19, width: 130, height: 20)) + + let labelKeyCode = makeLabel(text: shortcut, frame: NSRect(x: 120, y: 19, width: 100, height: 20)) + labelKeyCode.isHidden = !defaultDisplay + labelKeyCode.alignment = NSTextAlignment.right + + let handler = SliderHandler(display: display, command: command) + sliderHandlers.append(handler) + + let slider = NSSlider(frame: NSRect(x: 20, y: 0, width: 200, height: 19)) + slider.target = handler + slider.minValue = 0 + slider.maxValue = 100 + slider.integerValue = prefs.integer(forKey: "\(command)-\(display.serial)") + slider.action = #selector(SliderHandler.valueChanged) + + view.addSubview(label) + view.addSubview(labelKeyCode) + view.addSubview(slider) + + item.view = view + + menu.addItem(item) + menu.addItem(NSMenuItem.separator()) + + return slider + } + func updateDisplays() { for m in monitorItems { statusMenu.removeItem(m) } monitorItems = [] displays = [] + sliderHandlers = [] sleep(1) - var firstDisplay : Display? = nil + var firstDisplay: Display? = nil + var firstBrightnessSlider: NSSlider! = nil + var firstVolumeSlider: NSSlider! = nil for s in NSScreen.screens()! { let id = s.deviceDescription["NSScreenNumber"] as! CGDirectDisplayID @@ -109,131 +151,43 @@ class AppDelegate: NSObject, NSApplicationDelegate { let name = getDisplayName(edid) let serial = getDisplaySerial(edid) + let defaultDisplay = firstDisplay == nil + 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 brightnessSlider = addSliderItem(menu: monitorSubMenu, defaultDisplay: defaultDisplay, display: d, command: BRIGHTNESS, title: "Brightness", shortcut: "⇧⌘- / ⇧⌘+") + let _ = addSliderItem(menu: monitorSubMenu, defaultDisplay: defaultDisplay, display: d, command: CONTRAST, title: "Contrast", shortcut: "") + let volumeSlider = addSliderItem(menu: monitorSubMenu, defaultDisplay: defaultDisplay, display: d, command: AUDIO_SPEAKER_VOLUME, title: "Volume", shortcut: "⌥⌘- / ⌥⌘+") + + 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-\(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-\(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-\(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 - brightnessLabel.isEditable = false - brightnessLabel.drawsBackground = false - - let brightnessLabelKeyCode = NSTextField(frame: NSRect(x: 120, y: 16, width: 100, height: 20)) - brightnessLabelKeyCode.stringValue = "⇧⌘- / ⇧⌘+" - brightnessLabelKeyCode.isBordered = false - brightnessLabelKeyCode.isBezeled = false - brightnessLabelKeyCode.isEditable = false - brightnessLabelKeyCode.drawsBackground = false - 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 - constrastLabel.isEditable = false - constrastLabel.drawsBackground = false - - let volumeLabel = NSTextField(frame: NSRect(x: 20, y: 19, width: 130, height: 20)) - volumeLabel.stringValue = "Volume" - volumeLabel.isBordered = false - volumeLabel.isBezeled = false - volumeLabel.isEditable = false - volumeLabel.drawsBackground = false - - let volumeLabelKeyCode = NSTextField(frame: NSRect(x: 120, y: 19, width: 100, height: 20)) - volumeLabelKeyCode.stringValue = "⌥⌘- / ⌥⌘+" - volumeLabelKeyCode.isBordered = false - volumeLabelKeyCode.isBezeled = false - volumeLabelKeyCode.isEditable = false - volumeLabelKeyCode.drawsBackground = false - 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) - - brightnessItem.view = brightnesSliderView - contrastItem.view = contrastSliderView - volumeItem.view = volumeSliderView - let defaultMonitorSelectButtom = NSButton(frame: NSRect(x: 25, y: 0, width: 200, height: 25)) - defaultMonitorSelectButtom.title = firstDisplay == nil ? "Default" : "Set as default" + defaultMonitorSelectButtom.title = defaultDisplay ? "Default" : "Set as default" defaultMonitorSelectButtom.bezelStyle = NSRoundRectBezelStyle - defaultMonitorSelectButtom.isEnabled = firstDisplay != nil - defaultMonitorSelectButtom.tag = i + defaultMonitorSelectButtom.isEnabled = !defaultDisplay defaultMonitorView.addSubview(defaultMonitorSelectButtom) defaultMonitorItem.view = defaultMonitorView - monitorSubMenu.addItem(brightnessItem) - monitorSubMenu.addItem(NSMenuItem.separator()) - monitorSubMenu.addItem(contrastItem) - monitorSubMenu.addItem(NSMenuItem.separator()) - monitorSubMenu.addItem(volumeItem) - monitorSubMenu.addItem(NSMenuItem.separator()) monitorSubMenu.addItem(defaultMonitorItem) monitorMenuItem.title = "\(name)" monitorMenuItem.submenu = monitorSubMenu monitorItems.append(monitorMenuItem) - statusMenu.insertItem(monitorMenuItem, at: i) + statusMenu.insertItem(monitorMenuItem, at: displays.count - 1) if firstDisplay == nil { firstDisplay = d + firstBrightnessSlider = brightnessSlider + firstVolumeSlider = volumeSlider } } @@ -245,45 +199,51 @@ class AppDelegate: NSObject, NSApplicationDelegate { NSEvent.addGlobalMonitorForEvents( matching: NSEventMask.keyDown, handler: {(event: NSEvent) in - if (event.keyCode == 27 && - (event.modifierFlags.contains(NSEventModifierFlags.control)) && - (event.modifierFlags.contains(NSEventModifierFlags.command))) { - let value = abs(self.prefs.integer(forKey: "-v-\(d.serial)") - 1) + let modifiers = NSEventModifierFlags.init(rawValue: NSEventModifierFlags.command.rawValue | + NSEventModifierFlags.control.rawValue | + NSEventModifierFlags.option.rawValue | + NSEventModifierFlags.shift.rawValue) + var flags = event.modifierFlags.intersection(modifiers) - 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 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 == 27 && - (event.modifierFlags.contains(NSEventModifierFlags.option)) && - (event.modifierFlags.contains(NSEventModifierFlags.command))) { - let value = abs(self.prefs.integer(forKey: "-b-\(d.serial)") - 1) - - self.prefs.setValue(value, forKey: "-b-\(d.serial))") - - 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 value = abs(self.prefs.integer(forKey: "-b-\(d.serial)") + 1) - - self.prefs.setValue(value, forKey: "-b-\(d.serial)") - - self.ddcctl(monitor: d.id, command: "-b", value: value) + if !flags.contains(NSEventModifierFlags.command) { + return } + flags.subtract(NSEventModifierFlags.command) + + var rel = 0 + if event.keyCode == 27 { + rel = -5 + } else if event.keyCode == 24 { + rel = +5 + } else { + return + } + + var command = Int32() + var slider: NSSlider! = nil + if flags == NSEventModifierFlags.option { + command = AUDIO_SPEAKER_VOLUME + slider = firstVolumeSlider + } else if flags == NSEventModifierFlags.shift { + command = BRIGHTNESS + slider = firstBrightnessSlider + } else { + return + } + + let k = "\(command)-\(d.serial)" + let value = max(0, min(100, prefs.integer(forKey: k) + rel)) + + prefs.setValue(value, forKey: k) + prefs.synchronize() + slider.intValue = Int32(value) + + ddcctl(monitor: d.id, command: command, value: value) }) } func acquirePrivileges() { - let options : NSDictionary = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true] + let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true] let accessibilityEnabled = AXIsProcessTrustedWithOptions(options) if !accessibilityEnabled { @@ -293,27 +253,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { return } - 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 }