mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +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
|
||||
}
|
||||
|
||||
enum MediaKeyStep {
|
||||
case standard
|
||||
case fine
|
||||
}
|
||||
|
||||
struct MediaKeyConfiguration {
|
||||
var interceptVolume: Bool
|
||||
var interceptBrightness: Bool
|
||||
|
|
@ -25,8 +30,8 @@ struct MediaKeyConfiguration {
|
|||
}
|
||||
|
||||
protocol MediaKeyInterceptorDelegate: AnyObject {
|
||||
func mediaKeyInterceptor(_ interceptor: MediaKeyInterceptor, didReceiveVolumeCommand direction: MediaKeyDirection, isRepeat: Bool)
|
||||
func mediaKeyInterceptor(_ interceptor: MediaKeyInterceptor, didReceiveBrightnessCommand direction: MediaKeyDirection, isRepeat: Bool)
|
||||
func mediaKeyInterceptor(_ interceptor: MediaKeyInterceptor, didReceiveVolumeCommand direction: MediaKeyDirection, step: MediaKeyStep, isRepeat: Bool)
|
||||
func mediaKeyInterceptor(_ interceptor: MediaKeyInterceptor, didReceiveBrightnessCommand direction: MediaKeyDirection, step: MediaKeyStep, isRepeat: Bool)
|
||||
func mediaKeyInterceptorDidToggleMute(_ interceptor: MediaKeyInterceptor)
|
||||
}
|
||||
|
||||
|
|
@ -132,8 +137,8 @@ final class MediaKeyInterceptor {
|
|||
}
|
||||
|
||||
private func handleEvent(cgEvent: CGEvent, type: CGEventType) -> Unmanaged<CGEvent>? {
|
||||
guard let systemDefinedType = systemDefinedEventType,
|
||||
type == systemDefinedType,
|
||||
guard let systemDefinedType = systemDefinedEventType,
|
||||
type == systemDefinedType,
|
||||
let nsEvent = NSEvent(cgEvent: cgEvent),
|
||||
nsEvent.subtype.rawValue == 8 else {
|
||||
return Unmanaged.passUnretained(cgEvent)
|
||||
|
|
@ -144,6 +149,7 @@ final class MediaKeyInterceptor {
|
|||
let keyFlags = data1 & 0x0000FFFF
|
||||
let keyState = ((keyFlags & 0xFF00) >> 8) == 0xA // 0xA = keyDown, 0xB = keyUp
|
||||
let isRepeat = (keyFlags & 0x0001) == 1
|
||||
let step = step(for: nsEvent)
|
||||
|
||||
guard keyState else {
|
||||
// Swallow key-up events only when intercepting, otherwise let them pass through
|
||||
|
|
@ -156,11 +162,11 @@ final class MediaKeyInterceptor {
|
|||
switch Int32(keyCode) {
|
||||
case NX_KEYTYPE_SOUND_UP:
|
||||
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
|
||||
case NX_KEYTYPE_SOUND_DOWN:
|
||||
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
|
||||
case NX_KEYTYPE_MUTE:
|
||||
guard configuration.interceptVolume else { return Unmanaged.passUnretained(cgEvent) }
|
||||
|
|
@ -168,11 +174,11 @@ final class MediaKeyInterceptor {
|
|||
return nil
|
||||
case NX_KEYTYPE_BRIGHTNESS_UP:
|
||||
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
|
||||
case NX_KEYTYPE_BRIGHTNESS_DOWN:
|
||||
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
|
||||
default:
|
||||
return Unmanaged.passUnretained(cgEvent)
|
||||
|
|
@ -189,6 +195,14 @@ final class MediaKeyInterceptor {
|
|||
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)
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ final class SystemChangesObserver: MediaKeyInterceptorDelegate {
|
|||
private let brightnessController = SystemBrightnessController.shared
|
||||
private let mediaKeyInterceptor = MediaKeyInterceptor.shared
|
||||
|
||||
private let volumeStep: Float = 1.0 / 16.0
|
||||
private let brightnessStep: Float = 1.0 / 16.0
|
||||
private let standardVolumeStep: 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 brightnessEnabled = false
|
||||
|
|
@ -73,9 +74,15 @@ final class SystemChangesObserver: 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 }
|
||||
let delta = (direction == .up ? volumeStep : -volumeStep)
|
||||
let baseStep = stepSize(for: step, base: standardVolumeStep)
|
||||
let delta = direction == .up ? baseStep : -baseStep
|
||||
volumeController.adjust(by: delta)
|
||||
}
|
||||
|
||||
|
|
@ -84,9 +91,15 @@ final class SystemChangesObserver: MediaKeyInterceptorDelegate {
|
|||
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 }
|
||||
let delta = (direction == .up ? brightnessStep : -brightnessStep)
|
||||
let baseStep = stepSize(for: step, base: standardBrightnessStep)
|
||||
let delta = direction == .up ? baseStep : -baseStep
|
||||
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 brightnessAnimationTarget: Float = 0
|
||||
private var brightnessAnimationStartDate: Date?
|
||||
private let brightnessAnimationDuration: TimeInterval = 0.18
|
||||
private var currentBrightnessAnimationDuration: TimeInterval = 0.18
|
||||
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 init() {
|
||||
|
|
@ -507,8 +510,9 @@ final class SystemBrightnessController {
|
|||
brightnessAnimationStart = start
|
||||
brightnessAnimationTarget = target
|
||||
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
|
||||
guard let self else { return }
|
||||
guard let startDate = self.brightnessAnimationStartDate else {
|
||||
|
|
@ -517,7 +521,7 @@ final class SystemBrightnessController {
|
|||
return
|
||||
}
|
||||
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 value = self.brightnessAnimationStart + (self.brightnessAnimationTarget - self.brightnessAnimationStart) * Float(eased)
|
||||
self.applyBrightness(value)
|
||||
|
|
@ -532,6 +536,11 @@ final class SystemBrightnessController {
|
|||
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) {
|
||||
let clamped = max(0, min(1, value))
|
||||
if setBrightnessViaDisplayServices(clamped) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue