From 8a2e7f1e62953dd9dfd0e5660398afacd11b9512 Mon Sep 17 00:00:00 2001 From: Abdul Rehman Date: Mon, 6 Apr 2026 13:59:26 +0500 Subject: [PATCH] Tahoe PR: HUD cleanup, force volume keys, slider/CustomHUD fixes - Add KeyboardVolume.mediaForce and UI for always capturing volume/mute keys - Clean up CustomHUD windows when displays are cleared - Fix SliderHandler percentage label update (remove tautology check) - CustomHUD: RunLoop.common timer, drop unused SwiftUI import - Widen volume control popup for new menu item; en Main.strings Made-with: Cursor --- MonitorControl/Enums/PrefKey.swift | 2 ++ MonitorControl/Info.plist | 2 +- MonitorControl/Support/AppDelegate.swift | 2 +- MonitorControl/Support/CustomHUD.swift | 10 ++++------ MonitorControl/Support/DisplayManager.swift | 3 +++ MonitorControl/Support/MediaKeyTapManager.swift | 6 +++--- MonitorControl/Support/SliderHandler.swift | 8 ++------ MonitorControl/UI/Base.lproj/Main.storyboard | 3 ++- MonitorControl/UI/en.lproj/Main.strings | 3 +++ .../Onboarding/OnboardingViewController.swift | 2 +- MonitorControlHelper/Info.plist | 2 +- 11 files changed, 23 insertions(+), 20 deletions(-) diff --git a/MonitorControl/Enums/PrefKey.swift b/MonitorControl/Enums/PrefKey.swift index 76b5f29..3f9a18e 100644 --- a/MonitorControl/Enums/PrefKey.swift +++ b/MonitorControl/Enums/PrefKey.swift @@ -212,4 +212,6 @@ enum KeyboardVolume: Int { case custom = 1 case both = 2 case disabled = 3 + /// Like `media`, but always captures volume/mute keys even when macOS can control the default output device. + case mediaForce = 4 } diff --git a/MonitorControl/Info.plist b/MonitorControl/Info.plist index 39d5cf8..e06e3af 100644 --- a/MonitorControl/Info.plist +++ b/MonitorControl/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 7175 + 7176 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/MonitorControl/Support/AppDelegate.swift b/MonitorControl/Support/AppDelegate.swift index 6d1013b..7d7525b 100644 --- a/MonitorControl/Support/AppDelegate.swift +++ b/MonitorControl/Support/AppDelegate.swift @@ -161,7 +161,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { } func checkPermissions(firstAsk: Bool = false) { - let permissionsRequired: Bool = [KeyboardVolume.media.rawValue, KeyboardVolume.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardVolume.rawValue)) || [KeyboardBrightness.media.rawValue, KeyboardBrightness.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardBrightness.rawValue)) + let permissionsRequired: Bool = [KeyboardVolume.media.rawValue, KeyboardVolume.both.rawValue, KeyboardVolume.mediaForce.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardVolume.rawValue)) || [KeyboardBrightness.media.rawValue, KeyboardBrightness.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardBrightness.rawValue)) if !MediaKeyTapManager.readPrivileges(prompt: false), permissionsRequired { MediaKeyTapManager.acquirePrivileges(firstAsk: firstAsk) } diff --git a/MonitorControl/Support/CustomHUD.swift b/MonitorControl/Support/CustomHUD.swift index 9b69c7f..7a18777 100644 --- a/MonitorControl/Support/CustomHUD.swift +++ b/MonitorControl/Support/CustomHUD.swift @@ -3,10 +3,6 @@ import Cocoa -#if swift(>=5.3) -import SwiftUI -#endif - // MARK: - Custom HUD Manager /// Manages custom HUD windows for brightness/volume display on macOS 26+ @@ -181,11 +177,13 @@ class CustomHUDManager { window.alphaValue = 1.0 window.orderFrontRegardless() - // Schedule fade out after 1.5 seconds - fadeTimers[displayID] = Timer.scheduledTimer(withTimeInterval: 1.5, repeats: false) { [weak self, weak window] _ in + // Schedule fade out after 1.5 seconds (common mode so it still fires during nested run-loop work) + let timer = Timer(timeInterval: 1.5, repeats: false) { [weak self, weak window] _ in guard let window = window else { return } self?.fadeOut(window: window) } + fadeTimers[displayID] = timer + RunLoop.main.add(timer, forMode: .common) } private func fadeOut(window: NSWindow) { diff --git a/MonitorControl/Support/DisplayManager.swift b/MonitorControl/Support/DisplayManager.swift index f53d5bb..babf0c3 100644 --- a/MonitorControl/Support/DisplayManager.swift +++ b/MonitorControl/Support/DisplayManager.swift @@ -311,6 +311,9 @@ class DisplayManager { } func clearDisplays() { + for display in self.displays { + CustomHUDManager.shared.cleanupDisplay(display.identifier) + } self.displays = [] } diff --git a/MonitorControl/Support/MediaKeyTapManager.swift b/MonitorControl/Support/MediaKeyTapManager.swift index f484a6e..5ebb97e 100644 --- a/MonitorControl/Support/MediaKeyTapManager.swift +++ b/MonitorControl/Support/MediaKeyTapManager.swift @@ -150,7 +150,7 @@ class MediaKeyTapManager: MediaKeyTapDelegate { if [KeyboardBrightness.media.rawValue, KeyboardBrightness.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardBrightness.rawValue)) { keys.append(contentsOf: [.brightnessUp, .brightnessDown]) } - if [KeyboardVolume.media.rawValue, KeyboardVolume.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardVolume.rawValue)) { + if [KeyboardVolume.media.rawValue, KeyboardVolume.both.rawValue, KeyboardVolume.mediaForce.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardVolume.rawValue)) { keys.append(contentsOf: [.mute, .volumeUp, .volumeDown]) } // Remove brightness keys if no external displays are connected, but only if brightness fine control is not active @@ -166,8 +166,8 @@ class MediaKeyTapManager: MediaKeyTapDelegate { let keysToDelete: [MediaKey] = [.brightnessUp, .brightnessDown] keys.removeAll { keysToDelete.contains($0) } } - // Remove volume related keys if audio device is controllable - if let defaultAudioDevice = app.coreAudio.defaultOutputDevice { + // Remove volume related keys if audio device is controllable (skip when user chose force-capture mode) + if prefs.integer(forKey: PrefKey.keyboardVolume.rawValue) != KeyboardVolume.mediaForce.rawValue, let defaultAudioDevice = app.coreAudio.defaultOutputDevice { let keysToDelete: [MediaKey] = [.volumeUp, .volumeDown, .mute] if prefs.integer(forKey: PrefKey.multiKeyboardVolume.rawValue) == MultiKeyboardVolume.audioDeviceNameMatching.rawValue { if DisplayManager.shared.updateAudioControlTargetDisplays(deviceName: defaultAudioDevice.name) == 0 { diff --git a/MonitorControl/Support/SliderHandler.swift b/MonitorControl/Support/SliderHandler.swift index 320623f..7d2a3a0 100644 --- a/MonitorControl/Support/SliderHandler.swift +++ b/MonitorControl/Support/SliderHandler.swift @@ -360,9 +360,7 @@ class SliderHandler { slider.floatValue = value } } - if self.percentageBox == self.percentageBox { - self.percentageBox?.stringValue = "" + String(Int(value * 100)) + "%" - } + self.percentageBox?.stringValue = "" + String(Int(value * 100)) + "%" for display in self.displays { slider.setHighlightItem(display.identifier, value: value) if self.command == .brightness, let appleDisplay = display as? AppleDisplay { @@ -418,9 +416,7 @@ class SliderHandler { } else { slider.setDisplayHighlightItems(false) } - if self.percentageBox == self.percentageBox { - self.percentageBox?.stringValue = "" + String(Int(value * 100)) + "%" - } + self.percentageBox?.stringValue = "" + String(Int(value * 100)) + "%" } } } diff --git a/MonitorControl/UI/Base.lproj/Main.storyboard b/MonitorControl/UI/Base.lproj/Main.storyboard index c269c53..a3b1e56 100644 --- a/MonitorControl/UI/Base.lproj/Main.storyboard +++ b/MonitorControl/UI/Base.lproj/Main.storyboard @@ -997,13 +997,14 @@ - + + diff --git a/MonitorControl/UI/en.lproj/Main.strings b/MonitorControl/UI/en.lproj/Main.strings index 7c40246..41aa602 100644 --- a/MonitorControl/UI/en.lproj/Main.strings +++ b/MonitorControl/UI/en.lproj/Main.strings @@ -415,5 +415,8 @@ /* Class = "NSButtonCell"; title = "Show percentages"; ObjectID = "ZUu-MR-XwA"; */ "ZUu-MR-XwA.title" = "Show percentages"; +/* Class = "NSMenuItem"; title = "Standard keys (force capture)"; ObjectID = "fVk-mR-9Xp"; */ +"fVk-mR-9Xp.title" = "Standard keys (force capture)"; + /* Class = "NSTextFieldCell"; title = "Combined dimming switchover point:"; ObjectID = "zv8-pZ-OPy"; */ "zv8-pZ-OPy.title" = "Combined dimming switchover point:"; diff --git a/MonitorControl/View Controllers/Onboarding/OnboardingViewController.swift b/MonitorControl/View Controllers/Onboarding/OnboardingViewController.swift index 4d07325..130dcbb 100644 --- a/MonitorControl/View Controllers/Onboarding/OnboardingViewController.swift +++ b/MonitorControl/View Controllers/Onboarding/OnboardingViewController.swift @@ -27,7 +27,7 @@ class OnboardingViewController: NSViewController { // MARK: - Style private func setPermissionsButtonState() { - let volumePermissions: Bool = [KeyboardVolume.media.rawValue, KeyboardVolume.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardVolume.rawValue)) + let volumePermissions: Bool = [KeyboardVolume.media.rawValue, KeyboardVolume.both.rawValue, KeyboardVolume.mediaForce.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardVolume.rawValue)) let brigthnessPermissions: Bool = [KeyboardBrightness.media.rawValue, KeyboardBrightness.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardBrightness.rawValue)) let permissionsRequired: Bool = volumePermissions || brigthnessPermissions let enabled: Bool = !MediaKeyTapManager.readPrivileges(prompt: false) && permissionsRequired diff --git a/MonitorControlHelper/Info.plist b/MonitorControlHelper/Info.plist index 00249ad..1c04b68 100644 --- a/MonitorControlHelper/Info.plist +++ b/MonitorControlHelper/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 7175 + 7176 LSApplicationCategoryType public.app-category.utilities LSBackgroundOnly