diff --git a/DynamicIsland/managers/MediaKeyInterceptor.swift b/DynamicIsland/managers/MediaKeyInterceptor.swift index 2821769..2c118f0 100644 --- a/DynamicIsland/managers/MediaKeyInterceptor.swift +++ b/DynamicIsland/managers/MediaKeyInterceptor.swift @@ -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? { - 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) diff --git a/DynamicIsland/managers/SystemChangesObserver.swift b/DynamicIsland/managers/SystemChangesObserver.swift index 44441ec..644ce54 100644 --- a/DynamicIsland/managers/SystemChangesObserver.swift +++ b/DynamicIsland/managers/SystemChangesObserver.swift @@ -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 + } + } +} + diff --git a/DynamicIsland/managers/SystemMediaControllers.swift b/DynamicIsland/managers/SystemMediaControllers.swift index 9e41d55..fe43156 100644 --- a/DynamicIsland/managers/SystemMediaControllers.swift +++ b/DynamicIsland/managers/SystemMediaControllers.swift @@ -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) {