mirror of
https://github.com/MonitorControl/MonitorControl.git
synced 2026-05-15 14:15:55 -06:00
Many changes in commit… - Contrast slider is back ! - Added a Preferences Window - Function keys (should) work - If (like me) your screen backlight is still too bright even at 0, you can now set the contrast to 0 when the brightness hit 0 too - App can now be started at login (Needs to be tested) Signed-off-by: Guillaume Broder <iamnotheoneyouseek@gmail.com>
220 lines
6.4 KiB
Swift
220 lines
6.4 KiB
Swift
//
|
|
// Utils.swift
|
|
// MonitorControl
|
|
//
|
|
// Created by Guillaume BRODER on 9/17/2017.
|
|
// MIT Licensed.
|
|
//
|
|
|
|
import Cocoa
|
|
|
|
class Utils: NSObject {
|
|
|
|
// MARK: - DDCCTL
|
|
|
|
/// 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)
|
|
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
|
|
|
|
/// 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
|
|
}
|
|
|
|
/// 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.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.insertItem(item, at: 0)
|
|
menu.insertItem(NSMenuItem.separator(), at: 1)
|
|
|
|
return handler
|
|
}
|
|
|
|
// MARK: - Utilities
|
|
|
|
/// Acquire Privileges (Necessary to listen to keyboard event globally)
|
|
static func acquirePrivileges() {
|
|
let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: true]
|
|
let accessibilityEnabled = AXIsProcessTrustedWithOptions(options)
|
|
|
|
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()
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// MARK: - Display Infos
|
|
|
|
/// 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
|
|
}
|
|
|
|
/// 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)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
/// 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: "")
|
|
}
|
|
|
|
/// 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: "")
|
|
}
|
|
|
|
/// 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
|
|
}
|
|
}
|
|
|
|
// MARK: - Enums
|
|
|
|
/// UserDefault Keys for the app prefs
|
|
enum PrefKeys: String {
|
|
/// Was the app launched once
|
|
case appAlreadyLaunched
|
|
|
|
/// 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
|
|
}
|
|
|
|
}
|