MonitorControl
可以在 Mac 上控制显示器的亮度和音量,就好像它是原生 Apple 显示器一样。
对于在任务栏上的设计,该软件也怎么做了如下的出来,自绘了相关的关键部件。
override func drawBar(inside aRect: NSRect, flipped: Bool) {
guard !DEBUG_MACOS10, #available(macOS 11.0, *) else {
super.drawBar(inside: aRect, flipped: flipped)
return
}
var maxValue: Float = self.floatValue
var minValue: Float = self.floatValue
if self.isHighlightDisplayItems {
maxValue = max(self.displayHighlightItems.values.max() ?? 0, maxValue)
minValue = min(self.displayHighlightItems.values.min() ?? 1, minValue)
}
let barRadius = aRect.height * 0.5 * (self.numOfTickmarks == 0 ? 1 : self.tickMarkKnobExtraRadiusMultiplier)
let bar = NSBezierPath(roundedRect: aRect, xRadius: barRadius, yRadius: barRadius)
self.barFillColor.setFill()
bar.fill()
let barFilledWidth = (aRect.width - aRect.height) * CGFloat(maxValue) + aRect.height
let barFilledRect = NSRect(x: aRect.origin.x, y: aRect.origin.y, width: barFilledWidth, height: aRect.height)
let barFilled = NSBezierPath(roundedRect: barFilledRect, xRadius: barRadius, yRadius: barRadius)
self.barFilledFillColor.setFill()
barFilled.fill()
let knobMinX = aRect.origin.x + (aRect.width - aRect.height) * CGFloat(minValue)
let knobMaxX = aRect.origin.x + (aRect.width - aRect.height) * CGFloat(maxValue)
let knobRect = NSRect(x: knobMinX + (self.numOfTickmarks == 0 ? CGFloat(0) : self.tickMarkKnobExtraInset), y: aRect.origin.y, width: aRect.height + CGFloat(knobMaxX - knobMinX), height: aRect.height).insetBy(dx: self.numOfTickmarks == 0 ? CGFloat(0) : self.tickMarkKnobExtraInset, dy: 0)
let knobRadius = knobRect.height * 0.5 * (self.numOfTickmarks == 0 ? 1 : self.tickMarkKnobExtraRadiusMultiplier)
if self.numOfTickmarks > 0 {
for i in 1 ... self.numOfTickmarks - 2 {
let currentMarkLocation = CGFloat((Float(1) / Float(self.numOfTickmarks - 1)) * Float(i))
let tickMarkBounds = NSRect(x: aRect.origin.x + aRect.height + self.tickMarkKnobExtraInset - knobRect.height + self.tickMarkKnobExtraInset * 2 + CGFloat(Float((aRect.width - self.tickMarkKnobExtraInset * 5) * currentMarkLocation)), y: aRect.origin.y + aRect.height * (1 / 3), width: 4, height: aRect.height / 3)
let tickmark = NSBezierPath(roundedRect: tickMarkBounds, xRadius: 1, yRadius: 1)
self.tickMarkColor.setFill()
tickmark.fill()
}
}
let knobAlpha = CGFloat(max(0, min(1, (minValue - 0.08) * 5)))
for i in 1 ... 3 {
let knobShadow = NSBezierPath(roundedRect: knobRect.offsetBy(dx: CGFloat(-1 * 2 * i), dy: 0), xRadius: knobRadius, yRadius: knobRadius)
self.knobShadowColor.withAlphaComponent(self.knobShadowColor.alphaComponent * knobAlpha).setFill()
knobShadow.fill()
}
let knob = NSBezierPath(roundedRect: knobRect, xRadius: knobRadius, yRadius: knobRadius)
(self.isTracking ? self.knobFillColorTracking : self.knobFillColor).withAlphaComponent(knobAlpha).setFill()
knob.fill()
if self.isHighlightDisplayItems, self.displayHighlightItems.count > 2 {
for currentMarkLocation in self.displayHighlightItems.values {
let highlightKnobX = aRect.origin.x + (aRect.width - aRect.height) * CGFloat(currentMarkLocation)
let highlightKnobRect = NSRect(x: highlightKnobX + (self.numOfTickmarks == 0 ? CGFloat(0) : self.tickMarkKnobExtraInset), y: aRect.origin.y, width: aRect.height, height: aRect.height).insetBy(dx: (self.numOfTickmarks == 0 ? CGFloat(0) : self.tickMarkKnobExtraInset) + CGFloat(self.numOfTickmarks == 0 ? 6 : 3), dy: CGFloat(self.numOfTickmarks == 0 ? 6 : 6))
let highlightKnobRadius = highlightKnobRect.height * 0.5 * (self.numOfTickmarks == 0 ? 1 : self.tickMarkKnobExtraRadiusMultiplier)
let highlightKnob = NSBezierPath(roundedRect: highlightKnobRect, xRadius: highlightKnobRadius, yRadius: highlightKnobRadius)
let highlightDisplayIndicatorAlpha = CGFloat(max(0, min(1, (currentMarkLocation - 0.08) * 5)))
self.highlightDisplayIndicatorColor.withAlphaComponent(self.highlightDisplayIndicatorColor.alphaComponent * highlightDisplayIndicatorAlpha).setFill()
highlightKnob.fill()
}
}
self.knobStrokeColor.withAlphaComponent(self.knobStrokeColor.alphaComponent * knobAlpha).setStroke()
knob.stroke()
self.barStrokeColor.setStroke()
bar.stroke()
}
}
对于屏幕亮度的控制。
func setSmoothBrightness(_ to: Float = -1, slow: Bool = false) -> Bool {
guard app.sleepID == 0, app.reconfigureID == 0 else {
self.savePref(self.smoothBrightnessTransient, for: .brightness)
self.smoothBrightnessRunning = false
os_log("Pushing brightness stopped for Display %{public}@ because of sleep or reconfiguration", type: .info, String(self.identifier))
return false
}
if slow {
self.smoothBrightnessSlow = true
}
var stepDivider: Float = 6
if self.smoothBrightnessSlow {
stepDivider = 16
}
var dontPushAgain = false
if to != -1 {
os_log("Pushing brightness towards goal of %{public}@ for Display %{public}@", type: .info, String(to), String(self.identifier))
let value = max(min(to, 1), 0)
self.savePref(value, for: .brightness)
self.brightnessSyncSourceValue = value
self.smoothBrightnessSlow = slow
if self.smoothBrightnessRunning {
return true
}
}
let brightness = self.readPrefAsFloat(for: .brightness)
if brightness != self.smoothBrightnessTransient {
if abs(brightness - self.smoothBrightnessTransient) < 0.01 {
self.smoothBrightnessTransient = brightness
os_log("Pushing brightness finished for Display %{public}@", type: .info, String(self.identifier))
dontPushAgain = true
self.smoothBrightnessRunning = false
} else if brightness > self.smoothBrightnessTransient {
self.smoothBrightnessTransient += max((brightness - self.smoothBrightnessTransient) / stepDivider, 1 / 100)
} else {
self.smoothBrightnessTransient += min((brightness - self.smoothBrightnessTransient) / stepDivider, 1 / 100)
}
_ = self.setDirectBrightness(self.smoothBrightnessTransient, transient: true)
if !dontPushAgain {
self.smoothBrightnessRunning = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.02) {
_ = self.setSmoothBrightness()
}
}
} else {
os_log("No more need to push brightness for Display %{public}@ (setting one final time)", type: .info, String(self.identifier))
_ = self.setDirectBrightness(self.smoothBrightnessTransient, transient: true)
self.smoothBrightnessRunning = false
}
self.swBrightnessSemaphore.signal()
return true
}
对于声音的控制。
func stepVolume(isUp: Bool, isSmallIncrement: Bool) {
guard !self.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume) else {
OSDUtils.showOsdVolumeDisabled(displayID: self.identifier)
return
}
let currentValue = self.readPrefAsFloat(for: .audioSpeakerVolume)
var muteValue: Int?
let volumeOSDValue = self.calcNewValue(currentValue: currentValue, isUp: isUp, isSmallIncrement: isSmallIncrement)
if self.readPrefAsInt(for: .audioMuteScreenBlank) == 1, volumeOSDValue > 0 {
muteValue = 2
} else if self.readPrefAsInt(for: .audioMuteScreenBlank) != 1, volumeOSDValue == 0 {
muteValue = 1
}
let isAlreadySet = volumeOSDValue == self.readPrefAsFloat(for: .audioSpeakerVolume)
if !isAlreadySet {
if let muteValue = muteValue, self.readPrefAsBool(key: .enableMuteUnmute) {
self.writeDDCValues(command: .audioMuteScreenBlank, value: UInt16(muteValue))
self.savePref(muteValue, for: .audioMuteScreenBlank)
}
if !self.readPrefAsBool(key: .enableMuteUnmute) || volumeOSDValue != 0 {
self.writeDDCValues(command: .audioSpeakerVolume, value: self.convValueToDDC(for: .audioSpeakerVolume, from: volumeOSDValue))
}
}
if !self.readPrefAsBool(key: .hideOsd) {
OSDUtils.showOsd(displayID: self.identifier, command: .audioSpeakerVolume, value: volumeOSDValue, roundChiclet: !isSmallIncrement)
}
if !isAlreadySet {
self.savePref(volumeOSDValue, for: .audioSpeakerVolume)
if let slider = self.sliderHandler[.audioSpeakerVolume] {
slider.setValue(volumeOSDValue, displayID: self.identifier)
}
}
}