mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Update HUDs to show up betterdisplay
This commit is contained in:
parent
dd726d6401
commit
cead5f9ce8
7 changed files with 66 additions and 28 deletions
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -234,6 +242,12 @@ class SystemHUDManager {
|
||||||
brightnessEnabled = Defaults[.enableBrightnessHUD]
|
brightnessEnabled = Defaults[.enableBrightnessHUD]
|
||||||
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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue