mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-28 23:54:32 +00:00
Add Granular Control for Volume/Brightness
This commit is contained in:
parent
35fb3b285f
commit
1bdad16eff
3 changed files with 64 additions and 17 deletions
|
|
@ -17,6 +17,11 @@ enum MediaKeyDirection {
|
||||||
case down
|
case down
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum MediaKeyStep {
|
||||||
|
case standard
|
||||||
|
case fine
|
||||||
|
}
|
||||||
|
|
||||||
struct MediaKeyConfiguration {
|
struct MediaKeyConfiguration {
|
||||||
var interceptVolume: Bool
|
var interceptVolume: Bool
|
||||||
var interceptBrightness: Bool
|
var interceptBrightness: Bool
|
||||||
|
|
@ -25,8 +30,8 @@ struct MediaKeyConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol MediaKeyInterceptorDelegate: AnyObject {
|
protocol MediaKeyInterceptorDelegate: AnyObject {
|
||||||
func mediaKeyInterceptor(_ interceptor: MediaKeyInterceptor, didReceiveVolumeCommand direction: MediaKeyDirection, isRepeat: Bool)
|
func mediaKeyInterceptor(_ interceptor: MediaKeyInterceptor, didReceiveVolumeCommand direction: MediaKeyDirection, step: MediaKeyStep, isRepeat: Bool)
|
||||||
func mediaKeyInterceptor(_ interceptor: MediaKeyInterceptor, didReceiveBrightnessCommand direction: MediaKeyDirection, isRepeat: Bool)
|
func mediaKeyInterceptor(_ interceptor: MediaKeyInterceptor, didReceiveBrightnessCommand direction: MediaKeyDirection, step: MediaKeyStep, isRepeat: Bool)
|
||||||
func mediaKeyInterceptorDidToggleMute(_ interceptor: MediaKeyInterceptor)
|
func mediaKeyInterceptorDidToggleMute(_ interceptor: MediaKeyInterceptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,6 +149,7 @@ final class MediaKeyInterceptor {
|
||||||
let keyFlags = data1 & 0x0000FFFF
|
let keyFlags = data1 & 0x0000FFFF
|
||||||
let keyState = ((keyFlags & 0xFF00) >> 8) == 0xA // 0xA = keyDown, 0xB = keyUp
|
let keyState = ((keyFlags & 0xFF00) >> 8) == 0xA // 0xA = keyDown, 0xB = keyUp
|
||||||
let isRepeat = (keyFlags & 0x0001) == 1
|
let isRepeat = (keyFlags & 0x0001) == 1
|
||||||
|
let step = step(for: nsEvent)
|
||||||
|
|
||||||
guard keyState else {
|
guard keyState else {
|
||||||
// Swallow key-up events only when intercepting, otherwise let them pass through
|
// Swallow key-up events only when intercepting, otherwise let them pass through
|
||||||
|
|
@ -156,11 +162,11 @@ final class MediaKeyInterceptor {
|
||||||
switch Int32(keyCode) {
|
switch Int32(keyCode) {
|
||||||
case NX_KEYTYPE_SOUND_UP:
|
case NX_KEYTYPE_SOUND_UP:
|
||||||
guard configuration.interceptVolume else { return Unmanaged.passUnretained(cgEvent) }
|
guard configuration.interceptVolume else { return Unmanaged.passUnretained(cgEvent) }
|
||||||
delegate?.mediaKeyInterceptor(self, didReceiveVolumeCommand: .up, isRepeat: isRepeat)
|
delegate?.mediaKeyInterceptor(self, didReceiveVolumeCommand: .up, step: step, isRepeat: isRepeat)
|
||||||
return nil
|
return nil
|
||||||
case NX_KEYTYPE_SOUND_DOWN:
|
case NX_KEYTYPE_SOUND_DOWN:
|
||||||
guard configuration.interceptVolume else { return Unmanaged.passUnretained(cgEvent) }
|
guard configuration.interceptVolume else { return Unmanaged.passUnretained(cgEvent) }
|
||||||
delegate?.mediaKeyInterceptor(self, didReceiveVolumeCommand: .down, isRepeat: isRepeat)
|
delegate?.mediaKeyInterceptor(self, didReceiveVolumeCommand: .down, step: step, isRepeat: isRepeat)
|
||||||
return nil
|
return nil
|
||||||
case NX_KEYTYPE_MUTE:
|
case NX_KEYTYPE_MUTE:
|
||||||
guard configuration.interceptVolume else { return Unmanaged.passUnretained(cgEvent) }
|
guard configuration.interceptVolume else { return Unmanaged.passUnretained(cgEvent) }
|
||||||
|
|
@ -168,11 +174,11 @@ final class MediaKeyInterceptor {
|
||||||
return nil
|
return nil
|
||||||
case NX_KEYTYPE_BRIGHTNESS_UP:
|
case NX_KEYTYPE_BRIGHTNESS_UP:
|
||||||
guard configuration.interceptBrightness else { return Unmanaged.passUnretained(cgEvent) }
|
guard configuration.interceptBrightness else { return Unmanaged.passUnretained(cgEvent) }
|
||||||
delegate?.mediaKeyInterceptor(self, didReceiveBrightnessCommand: .up, isRepeat: isRepeat)
|
delegate?.mediaKeyInterceptor(self, didReceiveBrightnessCommand: .up, step: step, isRepeat: isRepeat)
|
||||||
return nil
|
return nil
|
||||||
case NX_KEYTYPE_BRIGHTNESS_DOWN:
|
case NX_KEYTYPE_BRIGHTNESS_DOWN:
|
||||||
guard configuration.interceptBrightness else { return Unmanaged.passUnretained(cgEvent) }
|
guard configuration.interceptBrightness else { return Unmanaged.passUnretained(cgEvent) }
|
||||||
delegate?.mediaKeyInterceptor(self, didReceiveBrightnessCommand: .down, isRepeat: isRepeat)
|
delegate?.mediaKeyInterceptor(self, didReceiveBrightnessCommand: .down, step: step, isRepeat: isRepeat)
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
return Unmanaged.passUnretained(cgEvent)
|
return Unmanaged.passUnretained(cgEvent)
|
||||||
|
|
@ -189,6 +195,14 @@ final class MediaKeyInterceptor {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func step(for event: NSEvent) -> MediaKeyStep {
|
||||||
|
let modifiers = event.modifierFlags
|
||||||
|
if modifiers.contains(.option) && modifiers.contains(.shift) {
|
||||||
|
return .fine
|
||||||
|
}
|
||||||
|
return .standard
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if canImport(ApplicationServices)
|
#if canImport(ApplicationServices)
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,9 @@ final class SystemChangesObserver: MediaKeyInterceptorDelegate {
|
||||||
private let brightnessController = SystemBrightnessController.shared
|
private let brightnessController = SystemBrightnessController.shared
|
||||||
private let mediaKeyInterceptor = MediaKeyInterceptor.shared
|
private let mediaKeyInterceptor = MediaKeyInterceptor.shared
|
||||||
|
|
||||||
private let volumeStep: Float = 1.0 / 16.0
|
private let standardVolumeStep: Float = 1.0 / 16.0
|
||||||
private let brightnessStep: Float = 1.0 / 16.0
|
private let standardBrightnessStep: Float = 1.0 / 16.0
|
||||||
|
private let fineStepDivisor: Float = 4.0
|
||||||
|
|
||||||
private var volumeEnabled = false
|
private var volumeEnabled = false
|
||||||
private var brightnessEnabled = false
|
private var brightnessEnabled = false
|
||||||
|
|
@ -73,9 +74,15 @@ final class SystemChangesObserver: MediaKeyInterceptorDelegate {
|
||||||
|
|
||||||
// MARK: - MediaKeyInterceptorDelegate
|
// MARK: - MediaKeyInterceptorDelegate
|
||||||
|
|
||||||
func mediaKeyInterceptor(_ interceptor: MediaKeyInterceptor, didReceiveVolumeCommand direction: MediaKeyDirection, isRepeat: Bool) {
|
func mediaKeyInterceptor(
|
||||||
|
_ interceptor: MediaKeyInterceptor,
|
||||||
|
didReceiveVolumeCommand direction: MediaKeyDirection,
|
||||||
|
step: MediaKeyStep,
|
||||||
|
isRepeat: Bool
|
||||||
|
) {
|
||||||
guard volumeEnabled else { return }
|
guard volumeEnabled else { return }
|
||||||
let delta = (direction == .up ? volumeStep : -volumeStep)
|
let baseStep = stepSize(for: step, base: standardVolumeStep)
|
||||||
|
let delta = direction == .up ? baseStep : -baseStep
|
||||||
volumeController.adjust(by: delta)
|
volumeController.adjust(by: delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,9 +91,15 @@ final class SystemChangesObserver: MediaKeyInterceptorDelegate {
|
||||||
volumeController.toggleMute()
|
volumeController.toggleMute()
|
||||||
}
|
}
|
||||||
|
|
||||||
func mediaKeyInterceptor(_ interceptor: MediaKeyInterceptor, didReceiveBrightnessCommand direction: MediaKeyDirection, isRepeat: Bool) {
|
func mediaKeyInterceptor(
|
||||||
|
_ interceptor: MediaKeyInterceptor,
|
||||||
|
didReceiveBrightnessCommand direction: MediaKeyDirection,
|
||||||
|
step: MediaKeyStep,
|
||||||
|
isRepeat: Bool
|
||||||
|
) {
|
||||||
guard brightnessEnabled else { return }
|
guard brightnessEnabled else { return }
|
||||||
let delta = (direction == .up ? brightnessStep : -brightnessStep)
|
let baseStep = stepSize(for: step, base: standardBrightnessStep)
|
||||||
|
let delta = direction == .up ? baseStep : -baseStep
|
||||||
brightnessController.adjust(by: delta)
|
brightnessController.adjust(by: delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,4 +127,15 @@ final class SystemChangesObserver: MediaKeyInterceptorDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extension SystemChangesObserver {
|
||||||
|
func stepSize(for step: MediaKeyStep, base: Float) -> Float {
|
||||||
|
switch step {
|
||||||
|
case .standard:
|
||||||
|
return base
|
||||||
|
case .fine:
|
||||||
|
return base / fineStepDivisor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -445,8 +445,11 @@ final class SystemBrightnessController {
|
||||||
private var brightnessAnimationStart: Float = 0
|
private var brightnessAnimationStart: Float = 0
|
||||||
private var brightnessAnimationTarget: Float = 0
|
private var brightnessAnimationTarget: Float = 0
|
||||||
private var brightnessAnimationStartDate: Date?
|
private var brightnessAnimationStartDate: Date?
|
||||||
private let brightnessAnimationDuration: TimeInterval = 0.18
|
private var currentBrightnessAnimationDuration: TimeInterval = 0.18
|
||||||
private let brightnessAnimationSteps = 10
|
private let brightnessAnimationSteps = 10
|
||||||
|
private let minimumBrightnessAnimationDuration: TimeInterval = 0.08
|
||||||
|
private let maximumBrightnessAnimationDuration: TimeInterval = 0.3
|
||||||
|
private let brightnessAnimationDurationScale: TimeInterval = 1.6
|
||||||
private var lastEmittedBrightness: Float = 0.5
|
private var lastEmittedBrightness: Float = 0.5
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
|
|
@ -507,8 +510,9 @@ final class SystemBrightnessController {
|
||||||
brightnessAnimationStart = start
|
brightnessAnimationStart = start
|
||||||
brightnessAnimationTarget = target
|
brightnessAnimationTarget = target
|
||||||
brightnessAnimationStartDate = Date()
|
brightnessAnimationStartDate = Date()
|
||||||
|
currentBrightnessAnimationDuration = animationDuration(forDelta: abs(target - start))
|
||||||
|
|
||||||
let interval = brightnessAnimationDuration / Double(brightnessAnimationSteps)
|
let interval = currentBrightnessAnimationDuration / Double(brightnessAnimationSteps)
|
||||||
let timer = Timer(timeInterval: interval, repeats: true) { [weak self] timer in
|
let timer = Timer(timeInterval: interval, repeats: true) { [weak self] timer in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
guard let startDate = self.brightnessAnimationStartDate else {
|
guard let startDate = self.brightnessAnimationStartDate else {
|
||||||
|
|
@ -517,7 +521,7 @@ final class SystemBrightnessController {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let elapsed = Date().timeIntervalSince(startDate)
|
let elapsed = Date().timeIntervalSince(startDate)
|
||||||
let progress = min(elapsed / self.brightnessAnimationDuration, 1)
|
let progress = min(elapsed / self.currentBrightnessAnimationDuration, 1)
|
||||||
let eased = self.ease(progress)
|
let eased = self.ease(progress)
|
||||||
let value = self.brightnessAnimationStart + (self.brightnessAnimationTarget - self.brightnessAnimationStart) * Float(eased)
|
let value = self.brightnessAnimationStart + (self.brightnessAnimationTarget - self.brightnessAnimationStart) * Float(eased)
|
||||||
self.applyBrightness(value)
|
self.applyBrightness(value)
|
||||||
|
|
@ -532,6 +536,11 @@ final class SystemBrightnessController {
|
||||||
timer.fire()
|
timer.fire()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func animationDuration(forDelta delta: Float) -> TimeInterval {
|
||||||
|
let scaled = minimumBrightnessAnimationDuration + TimeInterval(delta) * brightnessAnimationDurationScale
|
||||||
|
return min(maximumBrightnessAnimationDuration, max(minimumBrightnessAnimationDuration, scaled))
|
||||||
|
}
|
||||||
|
|
||||||
private func applyBrightness(_ value: Float) {
|
private func applyBrightness(_ value: Float) {
|
||||||
let clamped = max(0, min(1, value))
|
let clamped = max(0, min(1, value))
|
||||||
if setBrightnessViaDisplayServices(clamped) {
|
if setBrightnessViaDisplayServices(clamped) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue