Update per display windows for betterdisplay

This commit is contained in:
Hariharan Mudaliar 2026-03-03 00:41:49 +05:30
commit 12f8bc28ba
No known key found for this signature in database
GPG key ID: 025583EB0BE5640E
4 changed files with 99 additions and 59 deletions

View file

@ -8747,6 +8747,9 @@
}
}
}
},
"Disabled while BetterDisplay integration is active — BetterDisplay uses Cmd+Brightness keys for its own controls." : {
},
"Discrete transitions snap between Green (0–60%), Yellow (60–85%), and Red (85–100%)." : {
"comment" : "A description of how the color-coded progress bars work when not using smooth transitions.",

View file

@ -1384,8 +1384,7 @@ private struct HUDAndOSDSettingsView: View {
@Default(.enableVolumeHUD) var enableVolumeHUD
@Default(.enableBrightnessHUD) var enableBrightnessHUD
@Default(.enableKeyboardBacklightHUD) var enableKeyboardBacklightHUD
// Vertical HUD Props
@Default(.enableBetterDisplayIntegration) var enableBetterDisplayIntegration
@Default(.verticalHUDShowValue) var verticalHUDShowValue
@Default(.verticalHUDInteractive) var verticalHUDInteractive
@Default(.verticalHUDHeight) var verticalHUDHeight
@ -1621,7 +1620,7 @@ private struct HUDAndOSDSettingsView: View {
}
case .vertical:
Form {
if !accessibilityPermission.isAuthorized {
if !accessibilityPermission.isAuthorized && !enableBetterDisplayIntegration {
Section {
SettingsPermissionCallout(
message: "Accessibility permission is needed to intercept system controls for the Vertical HUD.",
@ -1637,11 +1636,13 @@ private struct HUDAndOSDSettingsView: View {
}
}
if accessibilityPermission.isAuthorized {
if accessibilityPermission.isAuthorized || enableBetterDisplayIntegration {
Section {
Toggle("Volume HUD", isOn: $enableVolumeHUD)
Toggle("Brightness HUD", isOn: $enableBrightnessHUD)
Toggle("Keyboard Backlight HUD", isOn: $enableKeyboardBacklightHUD)
.disabled(enableBetterDisplayIntegration)
.help(enableBetterDisplayIntegration ? "Disabled while BetterDisplay integration is active — BetterDisplay uses Cmd+Brightness keys for its own controls." : "")
} header: {
Text("Controls")
} footer: {
@ -1736,7 +1737,7 @@ private struct HUDAndOSDSettingsView: View {
case .circular:
Form {
if !accessibilityPermission.isAuthorized {
if !accessibilityPermission.isAuthorized && !enableBetterDisplayIntegration {
Section {
SettingsPermissionCallout(
message: "Accessibility permission is needed to intercept system controls for the Circular HUD.",
@ -1752,11 +1753,13 @@ private struct HUDAndOSDSettingsView: View {
}
}
if accessibilityPermission.isAuthorized {
if accessibilityPermission.isAuthorized || enableBetterDisplayIntegration {
Section {
Toggle("Volume HUD", isOn: $enableVolumeHUD)
Toggle("Brightness HUD", isOn: $enableBrightnessHUD)
Toggle("Keyboard Backlight HUD", isOn: $enableKeyboardBacklightHUD)
.disabled(enableBetterDisplayIntegration)
.help(enableBetterDisplayIntegration ? "Disabled while BetterDisplay integration is active — BetterDisplay uses Cmd+Brightness keys for its own controls." : "")
} header: {
Text("Controls")
} footer: {
@ -2020,6 +2023,7 @@ struct HUD: View {
@Default(.enableVolumeHUD) var enableVolumeHUD
@Default(.enableBrightnessHUD) var enableBrightnessHUD
@Default(.enableKeyboardBacklightHUD) var enableKeyboardBacklightHUD
@Default(.enableBetterDisplayIntegration) var enableBetterDisplayIntegration
@Default(.systemHUDSensitivity) var systemHUDSensitivity
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
@ObservedObject private var accessibilityPermission = AccessibilityPermissionStore.shared
@ -2038,7 +2042,7 @@ struct HUD: View {
var body: some View {
Form {
if !hasAccessibilityPermission {
if !hasAccessibilityPermission && !enableBetterDisplayIntegration {
Section {
SettingsPermissionCallout(
message: "Accessibility permission lets Dynamic Island replace the native volume, brightness, and keyboard HUDs.",
@ -2052,11 +2056,13 @@ struct HUD: View {
if enableSystemHUD && !Defaults[.enableCustomOSD] && hasAccessibilityPermission {
if enableSystemHUD && !Defaults[.enableCustomOSD] && (hasAccessibilityPermission || enableBetterDisplayIntegration) {
Section {
Toggle("Volume HUD", isOn: $enableVolumeHUD)
Toggle("Brightness HUD", isOn: $enableBrightnessHUD)
Toggle("Keyboard Backlight HUD", isOn: $enableKeyboardBacklightHUD)
.disabled(enableBetterDisplayIntegration)
.help(enableBetterDisplayIntegration ? "Disabled while BetterDisplay integration is active \u{2014} BetterDisplay uses Cmd+Brightness keys for its own controls." : "")
} header: {
Text("Controls")
} footer: {
@ -6702,6 +6708,7 @@ struct CustomOSDSettings: View {
@Default(.enableOSDVolume) var enableOSDVolume
@Default(.enableOSDBrightness) var enableOSDBrightness
@Default(.enableOSDKeyboardBacklight) var enableOSDKeyboardBacklight
@Default(.enableBetterDisplayIntegration) var enableBetterDisplayIntegration
@Default(.osdMaterial) var osdMaterial
@Default(.osdLiquidGlassCustomizationMode) var osdLiquidGlassCustomizationMode
@Default(.osdLiquidGlassVariant) var osdLiquidGlassVariant
@ -6744,7 +6751,7 @@ struct CustomOSDSettings: View {
var body: some View {
Form {
if !hasAccessibilityPermission {
if !hasAccessibilityPermission && !enableBetterDisplayIntegration {
Section {
SettingsPermissionCallout(
message: "Accessibility permission is needed to intercept system controls for the Custom OSD.",
@ -6756,7 +6763,7 @@ struct CustomOSDSettings: View {
}
}
if hasAccessibilityPermission {
if hasAccessibilityPermission || enableBetterDisplayIntegration {
Section {
Toggle("Volume OSD", isOn: $enableOSDVolume)
.settingsHighlight(id: highlightID("Volume OSD"))
@ -6764,6 +6771,8 @@ struct CustomOSDSettings: View {
.settingsHighlight(id: highlightID("Brightness OSD"))
Toggle("Keyboard Backlight OSD", isOn: $enableOSDKeyboardBacklight)
.settingsHighlight(id: highlightID("Keyboard Backlight OSD"))
.disabled(enableBetterDisplayIntegration)
.help(enableBetterDisplayIntegration ? "Disabled while BetterDisplay integration is active \u{2014} BetterDisplay uses Cmd+Brightness keys for its own controls." : "")
} header: {
Text("Controls")
} footer: {

View file

@ -190,6 +190,8 @@ final class BetterDisplayManager: ObservableObject {
return
}
NSLog("📺 BetterDisplay OSD raw payload: \(notificationString)")
do {
let osd = try JSONDecoder().decode(
BetterDisplayOSDNotification.self,
@ -208,21 +210,20 @@ final class BetterDisplayManager: ObservableObject {
let targetScreen = resolveScreen(for: osd.displayID)
let isExternalDisplay = isExternal(displayID: osd.displayID)
NSLog("📺 BetterDisplay OSD: target=\(osd.controlTarget ?? "nil") displayID=\(osd.displayID.map(String.init) ?? "nil") resolvedScreen=\(targetScreen?.localizedName ?? "nil") isExternal=\(isExternalDisplay) value=\(osd.value ?? -1)")
switch category {
case .brightness:
guard Defaults[.enableBrightnessHUD] else { return }
let icon = isExternalDisplay ? "display" : nil
dispatchBrightnessHUD(value: normalizedValue, customSymbol: icon, onScreen: targetScreen)
case .volume:
guard Defaults[.enableVolumeHUD] else { return }
let isMuted = osd.controlTarget == "mute" || osd.systemIconID == 4
dispatchVolumeHUD(value: normalizedValue, isMuted: isMuted, onScreen: targetScreen)
case .other:
// For unsupported control targets (contrast, gamma, temperature, etc.),
// show a generic brightness-style HUD if the user has brightness HUD enabled
guard Defaults[.enableBrightnessHUD] else { return }
let icon = isExternalDisplay ? "display" : osd.customSymbol
dispatchBrightnessHUD(value: normalizedValue, customSymbol: icon, onScreen: targetScreen)
}
@ -281,14 +282,26 @@ final class BetterDisplayManager: ObservableObject {
/// Resolve a BetterDisplay `displayID` (CGDirectDisplayID) to the matching NSScreen, if any.
private func resolveScreen(for displayID: Int?) -> NSScreen? {
guard let displayID else { return nil }
guard let displayID else {
NSLog("📺 BetterDisplay resolveScreen: displayID is nil, falling back to all screens")
return nil
}
let target = UInt32(displayID)
return NSScreen.screens.first { screen in
let availableScreens = NSScreen.screens.map { screen -> (String, UInt32) in
let num = (screen.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? NSNumber)?.uint32Value ?? 0
return (screen.localizedName, num)
}
NSLog("📺 BetterDisplay resolveScreen: looking for displayID=\(displayID) (UInt32=\(target)) among screens: \(availableScreens)")
let matched = NSScreen.screens.first { screen in
guard let screenNumber = screen.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? NSNumber else {
return false
}
return screenNumber.uint32Value == target
}
if matched == nil {
NSLog("📺 BetterDisplay resolveScreen: no match found for displayID=\(displayID), HUD will show on all screens")
}
return matched
}
/// Whether the given displayID refers to an external (non-built-in) display.

View file

@ -108,70 +108,76 @@ class SystemHUDManager {
}.store(in: &cancellables)
// Observe individual HUD toggles
Defaults.publisher(.enableVolumeHUD, options: []).sink { [weak self] change in
Defaults.publisher(.enableVolumeHUD, options: []).sink { [weak self] _ in
guard let self = self, self.isSetupComplete, self.requiresSystemToggleHandling else {
return
}
let flags = self.resolvedControlFlags()
self.changesObserver?.update(
volumeEnabled: change.newValue,
brightnessEnabled: Defaults[.enableBrightnessHUD],
keyboardBacklightEnabled: Defaults[.enableKeyboardBacklightHUD]
volumeEnabled: flags.volume,
brightnessEnabled: flags.brightness,
keyboardBacklightEnabled: flags.backlight
)
}.store(in: &cancellables)
Defaults.publisher(.enableBrightnessHUD, options: []).sink { [weak self] change in
Defaults.publisher(.enableBrightnessHUD, options: []).sink { [weak self] _ in
guard let self = self, self.isSetupComplete, self.requiresSystemToggleHandling else {
return
}
let flags = self.resolvedControlFlags()
self.changesObserver?.update(
volumeEnabled: Defaults[.enableVolumeHUD],
brightnessEnabled: change.newValue,
keyboardBacklightEnabled: Defaults[.enableKeyboardBacklightHUD]
volumeEnabled: flags.volume,
brightnessEnabled: flags.brightness,
keyboardBacklightEnabled: flags.backlight
)
}.store(in: &cancellables)
Defaults.publisher(.enableKeyboardBacklightHUD, options: []).sink { [weak self] change in
Defaults.publisher(.enableKeyboardBacklightHUD, options: []).sink { [weak self] _ in
guard let self = self, self.isSetupComplete, self.requiresSystemToggleHandling else {
return
}
let flags = self.resolvedControlFlags()
self.changesObserver?.update(
volumeEnabled: Defaults[.enableVolumeHUD],
brightnessEnabled: Defaults[.enableBrightnessHUD],
keyboardBacklightEnabled: change.newValue
volumeEnabled: flags.volume,
brightnessEnabled: flags.brightness,
keyboardBacklightEnabled: flags.backlight
)
}.store(in: &cancellables)
// Observe individual OSD toggles
Defaults.publisher(.enableOSDVolume, options: []).sink { [weak self] change in
Defaults.publisher(.enableOSDVolume, options: []).sink { [weak self] _ in
guard let self = self, self.isSetupComplete, Defaults[.enableCustomOSD] else {
return
}
let flags = self.resolvedControlFlags()
self.changesObserver?.update(
volumeEnabled: change.newValue,
brightnessEnabled: Defaults[.enableOSDBrightness],
keyboardBacklightEnabled: Defaults[.enableOSDKeyboardBacklight]
volumeEnabled: flags.volume,
brightnessEnabled: flags.brightness,
keyboardBacklightEnabled: flags.backlight
)
}.store(in: &cancellables)
Defaults.publisher(.enableOSDBrightness, options: []).sink { [weak self] change in
Defaults.publisher(.enableOSDBrightness, options: []).sink { [weak self] _ in
guard let self = self, self.isSetupComplete, Defaults[.enableCustomOSD] else {
return
}
let flags = self.resolvedControlFlags()
self.changesObserver?.update(
volumeEnabled: Defaults[.enableOSDVolume],
brightnessEnabled: change.newValue,
keyboardBacklightEnabled: Defaults[.enableOSDKeyboardBacklight]
volumeEnabled: flags.volume,
brightnessEnabled: flags.brightness,
keyboardBacklightEnabled: flags.backlight
)
}.store(in: &cancellables)
Defaults.publisher(.enableOSDKeyboardBacklight, options: []).sink { [weak self] change in
Defaults.publisher(.enableOSDKeyboardBacklight, options: []).sink { [weak self] _ in
guard let self = self, self.isSetupComplete, Defaults[.enableCustomOSD] else {
return
}
let flags = self.resolvedControlFlags()
self.changesObserver?.update(
volumeEnabled: Defaults[.enableOSDVolume],
brightnessEnabled: Defaults[.enableOSDBrightness],
keyboardBacklightEnabled: change.newValue
volumeEnabled: flags.volume,
brightnessEnabled: flags.brightness,
keyboardBacklightEnabled: flags.backlight
)
}.store(in: &cancellables)
@ -214,22 +220,13 @@ class SystemHUDManager {
}
}
@MainActor
private func startSystemObserver() async {
guard let coordinator = coordinator, !isSystemOperationInProgress else { return }
isSystemOperationInProgress = true
await stopSystemObserver() // Stop any existing observer
changesObserver = SystemChangesObserver(coordinator: coordinator)
// Determine which controls to enable based on HUD or OSD mode
let volumeEnabled: Bool
let brightnessEnabled: Bool
/// Resolves the effective control flags, applying BetterDisplay overrides.
private func resolvedControlFlags() -> (volume: Bool, brightness: Bool, backlight: Bool) {
var volumeEnabled: Bool
var brightnessEnabled: Bool
var keyboardBacklightEnabled: Bool
if Defaults[.enableCircularHUD] || Defaults[.enableVerticalHUD] {
// Respect per-control toggles so the system HUD can handle disabled events
volumeEnabled = Defaults[.enableVolumeHUD]
brightnessEnabled = Defaults[.enableBrightnessHUD]
keyboardBacklightEnabled = Defaults[.enableKeyboardBacklightHUD]
@ -243,22 +240,40 @@ class SystemHUDManager {
keyboardBacklightEnabled = Defaults[.enableKeyboardBacklightHUD]
}
// When BetterDisplay integration is on, disable Cmd+Brightness key interception
// because BetterDisplay uses Cmd+F1/F2 for its own display controls.
// When BetterDisplay integration is on, stop intercepting brightness and
// Cmd+Brightness keys so BetterDisplay receives them and sends OSD notifications.
if Defaults[.enableBetterDisplayIntegration] {
brightnessEnabled = false
keyboardBacklightEnabled = false
}
return (volumeEnabled, brightnessEnabled, keyboardBacklightEnabled)
}
@MainActor
private func startSystemObserver() async {
guard let coordinator = coordinator, !isSystemOperationInProgress else { return }
isSystemOperationInProgress = true
// Stop any existing observer directly (cannot call stopSystemObserver()
// because isSystemOperationInProgress is already true).
changesObserver?.stopObserving()
changesObserver = nil
changesObserver = SystemChangesObserver(coordinator: coordinator)
let flags = resolvedControlFlags()
changesObserver?.startObserving(
volumeEnabled: volumeEnabled,
brightnessEnabled: brightnessEnabled,
keyboardBacklightEnabled: keyboardBacklightEnabled
volumeEnabled: flags.volume,
brightnessEnabled: flags.brightness,
keyboardBacklightEnabled: flags.backlight
)
// Force disable system HUD to ensure no duplicates
SystemOSDManager.disableSystemHUD()
print("System observer started (HUD: \(Defaults[.enableSystemHUD]), OSD: \(Defaults[.enableCustomOSD]), Vertical: \(Defaults[.enableVerticalHUD]))")
print("System observer started (HUD: \(Defaults[.enableSystemHUD]), OSD: \(Defaults[.enableCustomOSD]), Vertical: \(Defaults[.enableVerticalHUD]), BD: \(Defaults[.enableBetterDisplayIntegration]))")
isSystemOperationInProgress = false
}