Update HUDs to show up betterdisplay

This commit is contained in:
Hariharan Mudaliar 2026-03-03 00:19:51 +05:30
commit cead5f9ce8
No known key found for this signature in database
GPG key ID: 025583EB0BE5640E
7 changed files with 66 additions and 28 deletions

View file

@ -249,8 +249,9 @@ struct ContentView: View {
} }
/// Whether the notch/island should hide off-screen when closed on a non-notch display. /// Whether the notch/island should hide off-screen when closed on a non-notch display.
/// Temporarily reveals the notch when a sneakPeek HUD (volume, brightness, music, etc.) is active.
private var shouldHideUntilHover: Bool { private var shouldHideUntilHover: Bool {
hideNonNotchUntilHover && isNonNotchScreen && vm.notchState == .closed hideNonNotchUntilHover && isNonNotchScreen && vm.notchState == .closed && !coordinator.sneakPeek.show
} }
/// Pill shape for Dynamic Island mode with animated corner radius transitions. /// Pill shape for Dynamic Island mode with animated corner radius transitions.

View file

@ -212,7 +212,7 @@ struct InlineHUD: View {
.frame(width: 20, height: 15, alignment: .leading) .frame(width: 20, height: 15, alignment: .leading)
} }
case .brightness: case .brightness:
Image(systemName: BrightnessSymbol(value)) Image(systemName: !icon.isEmpty ? icon : BrightnessSymbol(value))
.contentTransition(.interpolate) .contentTransition(.interpolate)
.frame(width: 20, height: 15, alignment: .center) .frame(width: 20, height: 15, alignment: .center)
case .backlight: case .backlight:

View file

@ -205,40 +205,44 @@ final class BetterDisplayManager: ObservableObject {
private func routeOSDToHUD(_ osd: BetterDisplayOSDNotification) { private func routeOSDToHUD(_ osd: BetterDisplayOSDNotification) {
let category = classifyControlTarget(osd.controlTarget, systemIconID: osd.systemIconID) let category = classifyControlTarget(osd.controlTarget, systemIconID: osd.systemIconID)
let normalizedValue = normalizeValue(osd.value, maxValue: osd.maxValue) let normalizedValue = normalizeValue(osd.value, maxValue: osd.maxValue)
let targetScreen = resolveScreen(for: osd.displayID)
let isExternalDisplay = isExternal(displayID: osd.displayID)
switch category { switch category {
case .brightness: case .brightness:
guard Defaults[.enableBrightnessHUD] else { return } guard Defaults[.enableBrightnessHUD] else { return }
dispatchBrightnessHUD(value: normalizedValue) let icon = isExternalDisplay ? "display" : nil
dispatchBrightnessHUD(value: normalizedValue, customSymbol: icon, onScreen: targetScreen)
case .volume: case .volume:
guard Defaults[.enableVolumeHUD] else { return } guard Defaults[.enableVolumeHUD] else { return }
let isMuted = osd.controlTarget == "mute" || osd.systemIconID == 4 let isMuted = osd.controlTarget == "mute" || osd.systemIconID == 4
dispatchVolumeHUD(value: normalizedValue, isMuted: isMuted) dispatchVolumeHUD(value: normalizedValue, isMuted: isMuted, onScreen: targetScreen)
case .other: case .other:
// For unsupported control targets (contrast, gamma, temperature, etc.), // For unsupported control targets (contrast, gamma, temperature, etc.),
// show a generic brightness-style HUD if the user has brightness HUD enabled // show a generic brightness-style HUD if the user has brightness HUD enabled
guard Defaults[.enableBrightnessHUD] else { return } guard Defaults[.enableBrightnessHUD] else { return }
dispatchBrightnessHUD(value: normalizedValue, customSymbol: osd.customSymbol) let icon = isExternalDisplay ? "display" : osd.customSymbol
dispatchBrightnessHUD(value: normalizedValue, customSymbol: icon, onScreen: targetScreen)
} }
} }
// MARK: - HUD Dispatch (mirrors SystemChangesObserver logic) // MARK: - HUD Dispatch (mirrors SystemChangesObserver logic)
private func dispatchVolumeHUD(value: CGFloat, isMuted: Bool) { private func dispatchVolumeHUD(value: CGFloat, isMuted: Bool, onScreen targetScreen: NSScreen? = nil) {
if HUDSuppressionCoordinator.shared.shouldSuppressVolumeHUD { return } if HUDSuppressionCoordinator.shared.shouldSuppressVolumeHUD { return }
if Defaults[.enableCircularHUD] { if Defaults[.enableCircularHUD] {
CircularHUDWindowManager.shared.show(type: .volume, value: value) CircularHUDWindowManager.shared.show(type: .volume, value: value, onScreen: targetScreen)
return return
} }
if Defaults[.enableVerticalHUD] { if Defaults[.enableVerticalHUD] {
VerticalHUDWindowManager.shared.show(type: .volume, value: value, icon: "") VerticalHUDWindowManager.shared.show(type: .volume, value: value, icon: "", onScreen: targetScreen)
return return
} }
if Defaults[.enableCustomOSD] && Defaults[.enableOSDVolume] { if Defaults[.enableCustomOSD] && Defaults[.enableOSDVolume] {
CustomOSDWindowManager.shared.showVolume(value: value) CustomOSDWindowManager.shared.showVolume(value: value, onScreen: targetScreen)
} }
if Defaults[.enableSystemHUD] && !Defaults[.enableCustomOSD] && !Defaults[.enableVerticalHUD] && !Defaults[.enableCircularHUD] { if Defaults[.enableSystemHUD] && !Defaults[.enableCustomOSD] && !Defaults[.enableVerticalHUD] && !Defaults[.enableCircularHUD] {
coordinator?.toggleSneakPeek( coordinator?.toggleSneakPeek(
@ -250,30 +254,49 @@ final class BetterDisplayManager: ObservableObject {
} }
} }
private func dispatchBrightnessHUD(value: CGFloat, customSymbol: String? = nil) { private func dispatchBrightnessHUD(value: CGFloat, customSymbol: String? = nil, onScreen targetScreen: NSScreen? = nil) {
let icon = customSymbol ?? ""
if Defaults[.enableCircularHUD] { if Defaults[.enableCircularHUD] {
CircularHUDWindowManager.shared.show(type: .brightness, value: value) CircularHUDWindowManager.shared.show(type: .brightness, value: value, icon: icon, onScreen: targetScreen)
return return
} }
if Defaults[.enableVerticalHUD] { if Defaults[.enableVerticalHUD] {
VerticalHUDWindowManager.shared.show(type: .brightness, value: value) VerticalHUDWindowManager.shared.show(type: .brightness, value: value, icon: icon, onScreen: targetScreen)
return return
} }
if Defaults[.enableCustomOSD] && Defaults[.enableOSDBrightness] { if Defaults[.enableCustomOSD] && Defaults[.enableOSDBrightness] {
CustomOSDWindowManager.shared.showBrightness(value: value) CustomOSDWindowManager.shared.showBrightness(value: value, icon: icon, onScreen: targetScreen)
} }
if Defaults[.enableSystemHUD] && !Defaults[.enableCustomOSD] && !Defaults[.enableVerticalHUD] && !Defaults[.enableCircularHUD] { if Defaults[.enableSystemHUD] && !Defaults[.enableCustomOSD] && !Defaults[.enableVerticalHUD] && !Defaults[.enableCircularHUD] {
coordinator?.toggleSneakPeek( coordinator?.toggleSneakPeek(
status: true, status: true,
type: .brightness, type: .brightness,
value: value, value: value,
icon: "" icon: icon
) )
} }
} }
// MARK: - Helpers // MARK: - Helpers
/// Resolve a BetterDisplay `displayID` (CGDirectDisplayID) to the matching NSScreen, if any.
private func resolveScreen(for displayID: Int?) -> NSScreen? {
guard let displayID else { return nil }
let target = UInt32(displayID)
return NSScreen.screens.first { screen in
guard let screenNumber = screen.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? NSNumber else {
return false
}
return screenNumber.uint32Value == target
}
}
/// Whether the given displayID refers to an external (non-built-in) display.
private func isExternal(displayID: Int?) -> Bool {
guard let displayID else { return false }
return CGDisplayIsBuiltin(UInt32(displayID)) == 0
}
private func classifyControlTarget(_ target: String?, systemIconID: Int?) -> BetterDisplayControlCategory { private func classifyControlTarget(_ target: String?, systemIconID: Int?) -> BetterDisplayControlCategory {
if let target { if let target {
if brightnessControlTargets.contains(target) { return .brightness } if brightnessControlTargets.contains(target) { return .brightness }

View file

@ -66,13 +66,13 @@ final class CircularHUDWindowManager {
} }
} }
func show(type: SneakContentType, value: CGFloat, icon: String = "") { func show(type: SneakContentType, value: CGFloat, icon: String = "", onScreen targetScreen: NSScreen? = nil) {
guard Defaults[.enableCircularHUD] else { return } guard Defaults[.enableCircularHUD] else { return }
let screens = NSScreen.screens let screens = targetScreen.map { [$0] } ?? NSScreen.screens
guard !screens.isEmpty else { return } guard !screens.isEmpty else { return }
// Show on all screens // Show on target screen(s)
for screen in screens { for screen in screens {
let windowContext = ensureWindow(for: type, screen: screen) let windowContext = ensureWindow(for: type, screen: screen)
updateContent(window: windowContext, type: type, value: value, icon: icon) updateContent(window: windowContext, type: type, value: value, icon: icon)

View file

@ -46,14 +46,14 @@ final class CustomOSDWindowManager {
// MARK: - Public API // MARK: - Public API
func showVolume(value: CGFloat, isMuted: Bool = false, icon: String = "") { func showVolume(value: CGFloat, isMuted: Bool = false, icon: String = "", onScreen targetScreen: NSScreen? = nil) {
guard Defaults[.enableCustomOSD], isInitialized else { return } guard Defaults[.enableCustomOSD], isInitialized else { return }
show(type: .volume, value: value, icon: icon) show(type: .volume, value: value, icon: icon, onScreen: targetScreen)
} }
func showBrightness(value: CGFloat) { func showBrightness(value: CGFloat, icon: String = "", onScreen targetScreen: NSScreen? = nil) {
guard Defaults[.enableCustomOSD], isInitialized else { return } guard Defaults[.enableCustomOSD], isInitialized else { return }
show(type: .brightness, value: value, icon: "") show(type: .brightness, value: value, icon: icon, onScreen: targetScreen)
} }
func showBacklight(value: CGFloat) { func showBacklight(value: CGFloat) {
@ -67,14 +67,14 @@ final class CustomOSDWindowManager {
// MARK: - Private Implementation // MARK: - Private Implementation
private func show(type: SneakContentType, value: CGFloat, icon: String) { private func show(type: SneakContentType, value: CGFloat, icon: String, onScreen targetScreen: NSScreen? = nil) {
let screens = NSScreen.screens let screens = targetScreen.map { [$0] } ?? NSScreen.screens
guard !screens.isEmpty else { return } guard !screens.isEmpty else { return }
// Close other windows first // Close other windows first
hideAllWindowsExcept(type: type) hideAllWindowsExcept(type: type)
// Show on all screens // Show on target screen(s)
for screen in screens { for screen in screens {
let window = ensureWindow(for: type, screen: screen) let window = ensureWindow(for: type, screen: screen)
updateContent(window: window, type: type, value: value, icon: icon) updateContent(window: window, type: type, value: value, icon: icon)

View file

@ -174,6 +174,14 @@ class SystemHUDManager {
keyboardBacklightEnabled: change.newValue keyboardBacklightEnabled: change.newValue
) )
}.store(in: &cancellables) }.store(in: &cancellables)
// When BetterDisplay integration toggled, restart observer to update Cmd+Brightness key interception
Defaults.publisher(.enableBetterDisplayIntegration, options: []).sink { [weak self] _ in
guard let self = self, self.isSetupComplete else { return }
Task { @MainActor in
await self.startSystemObserver()
}
}.store(in: &cancellables)
} }
private var cancellables = Set<AnyCancellable>() private var cancellables = Set<AnyCancellable>()
@ -218,7 +226,7 @@ class SystemHUDManager {
// Determine which controls to enable based on HUD or OSD mode // Determine which controls to enable based on HUD or OSD mode
let volumeEnabled: Bool let volumeEnabled: Bool
let brightnessEnabled: Bool let brightnessEnabled: Bool
let keyboardBacklightEnabled: Bool var keyboardBacklightEnabled: Bool
if Defaults[.enableCircularHUD] || Defaults[.enableVerticalHUD] { if Defaults[.enableCircularHUD] || Defaults[.enableVerticalHUD] {
// Respect per-control toggles so the system HUD can handle disabled events // Respect per-control toggles so the system HUD can handle disabled events
@ -235,6 +243,12 @@ class SystemHUDManager {
keyboardBacklightEnabled = Defaults[.enableKeyboardBacklightHUD] keyboardBacklightEnabled = Defaults[.enableKeyboardBacklightHUD]
} }
// When BetterDisplay integration is on, disable Cmd+Brightness key interception
// because BetterDisplay uses Cmd+F1/F2 for its own display controls.
if Defaults[.enableBetterDisplayIntegration] {
keyboardBacklightEnabled = false
}
changesObserver?.startObserving( changesObserver?.startObserving(
volumeEnabled: volumeEnabled, volumeEnabled: volumeEnabled,
brightnessEnabled: brightnessEnabled, brightnessEnabled: brightnessEnabled,

View file

@ -136,13 +136,13 @@ final class VerticalHUDWindowManager {
windows.removeAll() windows.removeAll()
} }
func show(type: SneakContentType, value: CGFloat, icon: String = "") { func show(type: SneakContentType, value: CGFloat, icon: String = "", onScreen targetScreen: NSScreen? = nil) {
guard Defaults[.enableVerticalHUD] else { return } guard Defaults[.enableVerticalHUD] else { return }
let screens = NSScreen.screens let screens = targetScreen.map { [$0] } ?? NSScreen.screens
guard !screens.isEmpty else { return } guard !screens.isEmpty else { return }
// Show on all screens // Show on target screen(s)
for screen in screens { for screen in screens {
let windowContext = ensureWindow(for: type, screen: screen) let windowContext = ensureWindow(for: type, screen: screen)
updateContent(window: windowContext, type: type, value: value, icon: icon) updateContent(window: windowContext, type: type, value: value, icon: icon)