Merge pull request #442 from lugoues/feature/configurable-media-key-steps

Add configurable step sizes for volume and brightness keys
This commit is contained in:
Hariharan Mudaliar 2026-05-18 21:45:48 +05:30 • committed by GitHub
commit 756b6b8a34
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 192 additions and 57 deletions

View file

@ -9051,6 +9051,9 @@
} }
} }
} }
},
"Brightness fine step" : {
}, },
"Brightness HUD" : { "Brightness HUD" : {
"localizations" : { "localizations" : {
@ -9127,6 +9130,9 @@
} }
} }
} }
},
"Brightness step" : {
}, },
"Built-in" : { "Built-in" : {
"comment" : "A label indicating that an animation is a built-in one.", "comment" : "A label indicating that an animation is a built-in one.",
@ -16870,6 +16876,9 @@
} }
} }
} }
},
"Disabled while external display brightness integration is active." : {
}, },
"Disabled while external display integration is active — brightness keys are handled by the external app." : { "Disabled while external display integration is active — brightness keys are handled by the external app." : {
"localizations" : { "localizations" : {
@ -16898,6 +16907,9 @@
} }
} }
} }
},
"Disabled while external display volume integration is active." : {
}, },
"Discrete transitions snap between Green (0–60%), Yellow (60–85%), and Red (85–100%)." : { "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.", "comment" : "A description of how the color-coded progress bars work when not using smooth transitions.",
@ -17175,6 +17187,9 @@
} }
} }
} }
},
"Display Overlay" : {
}, },
"Display Style" : { "Display Style" : {
"localizations" : { "localizations" : {
@ -35276,6 +35291,9 @@
} }
} }
} }
},
"Percent change per key press. Fine step applies when holding Shift+Option." : {
}, },
"Percentage" : { "Percentage" : {
"localizations" : { "localizations" : {
@ -46124,6 +46142,9 @@
} }
} }
} }
},
"Step size" : {
}, },
"Stop" : { "Stop" : {
"localizations" : { "localizations" : {
@ -51018,6 +51039,9 @@
} }
} }
} }
},
"Volume fine step" : {
}, },
"Volume HUD" : { "Volume HUD" : {
"localizations" : { "localizations" : {
@ -51094,6 +51118,9 @@
} }
} }
} }
},
"Volume step" : {
}, },
"W " : { "W " : {
"comment" : "Label for the write data point in the disk usage graph.", "comment" : "Label for the write data point in the disk usage graph.",

View file

@ -56,6 +56,7 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
case timer case timer
case calendar case calendar
case hudAndOSD case hudAndOSD
case systemControls
case battery case battery
case stats case stats
case clipboard case clipboard
@ -75,7 +76,7 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
switch self { switch self {
case .general, .appearance: return .core case .general, .appearance: return .core
case .media, .liveActivities, .lockScreen, .devices: return .mediaAndDisplay case .media, .liveActivities, .lockScreen, .devices: return .mediaAndDisplay
case .hudAndOSD, .battery: return .system case .systemControls, .hudAndOSD, .battery: return .system
case .timer, .calendar, .notes: return .productivity case .timer, .calendar, .notes: return .productivity
case .clipboard, .screenAssistant, .colorPicker, .shelf, case .clipboard, .screenAssistant, .colorPicker, .shelf,
.downloads, .shortcuts: return .utilities .downloads, .shortcuts: return .utilities
@ -96,7 +97,8 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
case .extensions: return String(localized: "Extensions") case .extensions: return String(localized: "Extensions")
case .timer: return String(localized: "Timer") case .timer: return String(localized: "Timer")
case .calendar: return String(localized: "Calendar") case .calendar: return String(localized: "Calendar")
case .hudAndOSD: return String(localized: "Controls") case .hudAndOSD: return String(localized: "Display Overlay")
case .systemControls: return String(localized: "Controls")
case .battery: return String(localized: "Battery") case .battery: return String(localized: "Battery")
case .stats: return String(localized: "Stats") case .stats: return String(localized: "Stats")
case .clipboard: return String(localized: "Clipboard") case .clipboard: return String(localized: "Clipboard")
@ -113,27 +115,28 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
var systemImage: String { var systemImage: String {
switch self { switch self {
case .general: return String(localized: "gear") case .general: return "gear"
case .liveActivities: return String(localized: "waveform.path.ecg") case .liveActivities: return "waveform.path.ecg"
case .appearance: return String(localized: "paintpalette") case .appearance: return "paintpalette"
case .lockScreen: return String(localized: "lock.laptopcomputer") case .lockScreen: return "lock.laptopcomputer"
case .media: return String(localized: "play.laptopcomputer") case .media: return "play.laptopcomputer"
case .devices: return String(localized: "headphones") case .devices: return "headphones"
case .extensions: return String(localized: "puzzlepiece.extension") case .extensions: return "puzzlepiece.extension"
case .timer: return String(localized: "timer") case .timer: return "timer"
case .calendar: return String(localized: "calendar") case .calendar: return "calendar"
case .hudAndOSD: return String(localized: "dial.medium.fill") case .hudAndOSD: return "rectangle.inset.filled.on.rectangle"
case .battery: return String(localized: "battery.100.bolt") case .systemControls: return "dial.medium.fill"
case .stats: return String(localized: "chart.xyaxis.line") case .battery: return "battery.100.bolt"
case .clipboard: return String(localized: "clipboard") case .stats: return "chart.xyaxis.line"
case .screenAssistant: return String(localized: "brain.head.profile") case .clipboard: return "clipboard"
case .colorPicker: return String(localized: "eyedropper") case .screenAssistant: return "brain.head.profile"
case .downloads: return String(localized: "square.and.arrow.down") case .colorPicker: return "eyedropper"
case .shelf: return String(localized: "books.vertical") case .downloads: return "square.and.arrow.down"
case .shortcuts: return String(localized: "keyboard") case .shelf: return "books.vertical"
case .notes: return String(localized: "note.text") case .shortcuts: return "keyboard"
case .terminal: return String(localized: "apple.terminal") case .notes: return "note.text"
case .about: return String(localized: "info.circle") case .terminal: return "apple.terminal"
case .about: return "info.circle"
} }
} }
@ -148,7 +151,8 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
case .extensions: return Color(red: 0.557, green: 0.353, blue: 0.957) case .extensions: return Color(red: 0.557, green: 0.353, blue: 0.957)
case .timer: return .red case .timer: return .red
case .calendar: return .cyan case .calendar: return .cyan
case .hudAndOSD: return .indigo case .hudAndOSD: return .orange
case .systemControls: return .indigo
case .battery: return Color(red: 0.202, green: 0.783, blue: 0.348, opacity: 1.000) case .battery: return Color(red: 0.202, green: 0.783, blue: 0.348, opacity: 1.000)
case .stats: return .teal case .stats: return .teal
case .clipboard: return .mint case .clipboard: return .mint
@ -491,6 +495,7 @@ struct SettingsView: View {
.lockScreen, .lockScreen,
.devices, .devices,
// System // System
.systemControls,
.hudAndOSD, .hudAndOSD,
.battery, .battery,
// Productivity // Productivity
@ -745,6 +750,12 @@ struct SettingsView: View {
SettingsSearchEntry(tab: .battery, title: "Full battery style", keywords: ["battery", "style", "compact", "standard"], highlightID: SettingsTab.battery.highlightID(for: "Full battery style")), SettingsSearchEntry(tab: .battery, title: "Full battery style", keywords: ["battery", "style", "compact", "standard"], highlightID: SettingsTab.battery.highlightID(for: "Full battery style")),
SettingsSearchEntry(tab: .battery, title: "Full charge threshold", keywords: ["battery", "threshold", "full"], highlightID: SettingsTab.battery.highlightID(for: "Full charge threshold")), SettingsSearchEntry(tab: .battery, title: "Full charge threshold", keywords: ["battery", "threshold", "full"], highlightID: SettingsTab.battery.highlightID(for: "Full charge threshold")),
// System Controls
SettingsSearchEntry(tab: .systemControls, title: "Step size", keywords: ["brightness", "volume", "step",], highlightID: SettingsTab.systemControls.highlightID(for: "Step size")),
SettingsSearchEntry(tab: .systemControls, title: "Third-party DDC app integration", keywords: ["ddc", "third party", "external", "display", "betterdisplay", "lunar"], highlightID: SettingsTab.systemControls.highlightID(for: "Third-party DDC app integration")),
SettingsSearchEntry(tab: .systemControls, title: "Third-party DDC provider", keywords: ["provider", "betterdisplay", "lunar", "integration", "refresh detection"], highlightID: SettingsTab.systemControls.highlightID(for: "Third-party DDC provider")),
SettingsSearchEntry(tab: .systemControls, title: "Enable external volume control listener", keywords: ["external volume", "ddc volume", "betterdisplay volume", "lunar volume", "disable native volume"], highlightID: SettingsTab.systemControls.highlightID(for: "Enable external volume control listener")),
// HUDs // HUDs
SettingsSearchEntry(tab: .devices, title: "Show Bluetooth device connections", keywords: ["bluetooth", "hud"], highlightID: SettingsTab.devices.highlightID(for: "Show Bluetooth device connections")), SettingsSearchEntry(tab: .devices, title: "Show Bluetooth device connections", keywords: ["bluetooth", "hud"], highlightID: SettingsTab.devices.highlightID(for: "Show Bluetooth device connections")),
SettingsSearchEntry(tab: .devices, title: "Use circular battery indicator", keywords: ["battery", "circular"], highlightID: SettingsTab.devices.highlightID(for: "Use circular battery indicator")), SettingsSearchEntry(tab: .devices, title: "Use circular battery indicator", keywords: ["battery", "circular"], highlightID: SettingsTab.devices.highlightID(for: "Use circular battery indicator")),
@ -767,9 +778,6 @@ struct SettingsView: View {
SettingsSearchEntry(tab: .hudAndOSD, title: "Keyboard Backlight OSD", keywords: ["keyboard", "backlight", "osd"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Keyboard Backlight OSD")), SettingsSearchEntry(tab: .hudAndOSD, title: "Keyboard Backlight OSD", keywords: ["keyboard", "backlight", "osd"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Keyboard Backlight OSD")),
SettingsSearchEntry(tab: .hudAndOSD, title: "Material", keywords: ["material", "frosted", "liquid", "glass", "solid", "osd"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Material")), SettingsSearchEntry(tab: .hudAndOSD, title: "Material", keywords: ["material", "frosted", "liquid", "glass", "solid", "osd"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Material")),
SettingsSearchEntry(tab: .hudAndOSD, title: "Icon & Progress Color", keywords: ["color", "icon", "white", "black", "gray", "osd"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Icon & Progress Color")), SettingsSearchEntry(tab: .hudAndOSD, title: "Icon & Progress Color", keywords: ["color", "icon", "white", "black", "gray", "osd"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Icon & Progress Color")),
SettingsSearchEntry(tab: .hudAndOSD, title: "Third-party DDC app integration", keywords: ["ddc", "third party", "external", "display", "betterdisplay", "lunar"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Third-party DDC app integration")),
SettingsSearchEntry(tab: .hudAndOSD, title: "Third-party DDC provider", keywords: ["provider", "betterdisplay", "lunar", "integration", "refresh detection"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Third-party DDC provider")),
SettingsSearchEntry(tab: .hudAndOSD, title: "Enable external volume control listener", keywords: ["external volume", "ddc volume", "betterdisplay volume", "lunar volume", "disable native volume"], highlightID: SettingsTab.hudAndOSD.highlightID(for: "Enable external volume control listener")),
// Media // Media
SettingsSearchEntry(tab: .media, title: "Music Source", keywords: ["media source", "controller"], highlightID: SettingsTab.media.highlightID(for: "Music Source")), SettingsSearchEntry(tab: .media, title: "Music Source", keywords: ["media source", "controller"], highlightID: SettingsTab.media.highlightID(for: "Music Source")),
@ -976,6 +984,10 @@ struct SettingsView: View {
SettingsForm(tab: .hudAndOSD) { SettingsForm(tab: .hudAndOSD) {
HUDAndOSDSettingsView() HUDAndOSDSettingsView()
} }
case .systemControls:
SettingsForm(tab: .systemControls) {
SystemControlsView()
}
case .battery: case .battery:
SettingsForm(tab: .battery) { SettingsForm(tab: .battery) {
Charge() Charge()
@ -2192,28 +2204,29 @@ private struct HUDAndOSDSettingsView: View {
} }
} }
} }
// Third-party display integrations (shared across all HUD variants)
ExternalDisplayIntegrationsSection()
} }
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.background(paneBackgroundColor)
.navigationTitle("Controls")
.onAppear { .onAppear {
if #unavailable(macOS 26.0), verticalHUDMaterial == .liquid { if #unavailable(macOS 26.0), verticalHUDMaterial == .liquid {
verticalHUDMaterial = .frosted verticalHUDMaterial = .frosted
verticalHUDLiquidGlassCustomizationMode = .standard verticalHUDLiquidGlassCustomizationMode = .standard
} }
} }
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.navigationTitle("Display Overlay")
} }
} }
// MARK: - External Display Integrations Settings Section // MARK: - External Display Integrations Settings Section
private struct ExternalDisplayIntegrationsSection: View { private struct SystemControlsView: View {
@Default(.enableThirdPartyDDCIntegration) var enableThirdPartyDDCIntegration @Default(.enableThirdPartyDDCIntegration) var enableThirdPartyDDCIntegration
@Default(.thirdPartyDDCProvider) var thirdPartyDDCProvider @Default(.thirdPartyDDCProvider) var thirdPartyDDCProvider
@Default(.enableExternalVolumeControlListener) var enableExternalVolumeControlListener @Default(.enableExternalVolumeControlListener) var enableExternalVolumeControlListener
@Default(.volumeStepPercent) var volumeStepPercent
@Default(.volumeFineStepPercent) var volumeFineStepPercent
@Default(.brightnessStepPercent) var brightnessStepPercent
@Default(.brightnessFineStepPercent) var brightnessFineStepPercent
@ObservedObject private var betterDisplayManager = BetterDisplayManager.shared @ObservedObject private var betterDisplayManager = BetterDisplayManager.shared
@ObservedObject private var lunarManager = LunarManager.shared @ObservedObject private var lunarManager = LunarManager.shared
@ -2284,9 +2297,79 @@ private struct ExternalDisplayIntegrationsSection: View {
var body: some View { var body: some View {
Form { Form {
Section {
Stepper(value: $volumeStepPercent, in: 1...25) {
HStack {
Text("Volume step")
Spacer()
Text("\(volumeStepPercent)%")
.foregroundStyle(.secondary)
.monospacedDigit()
}
}
.settingsHighlight(id: highlightID("Volume step"))
.disabled(enableExternalVolumeControlListener)
Stepper(value: $volumeFineStepPercent, in: 1...25) {
HStack {
Text("Volume fine step")
Spacer()
Text("\(volumeFineStepPercent)%")
.foregroundStyle(.secondary)
.monospacedDigit()
}
}
.settingsHighlight(id: highlightID("Volume fine step"))
.disabled(enableExternalVolumeControlListener)
if enableExternalVolumeControlListener {
Text("Disabled while external display volume integration is active.")
.font(.caption)
.foregroundStyle(.secondary)
}
Stepper(value: $brightnessStepPercent, in: 1...25) {
HStack {
Text("Brightness step")
Spacer()
Text("\(brightnessStepPercent)%")
.foregroundStyle(.secondary)
.monospacedDigit()
}
}
.settingsHighlight(id: highlightID("Brightness step"))
.disabled(enableThirdPartyDDCIntegration)
Stepper(value: $brightnessFineStepPercent, in: 1...25) {
HStack {
Text("Brightness fine step")
Spacer()
Text("\(brightnessFineStepPercent)%")
.foregroundStyle(.secondary)
.monospacedDigit()
}
}
.settingsHighlight(id: highlightID("Brightness fine step"))
.disabled(enableThirdPartyDDCIntegration)
if enableThirdPartyDDCIntegration {
Text("Disabled while external display brightness integration is active.")
.font(.caption)
.foregroundStyle(.secondary)
}
} header: {
Text("Step size")
} footer: {
Text("Percent change per key press. Fine step applies when holding Shift+Option.")
.foregroundStyle(.secondary)
.font(.caption)
}
Section { Section {
Toggle("Enable third-party DDC app integration", isOn: $enableThirdPartyDDCIntegration) Toggle("Enable third-party DDC app integration", isOn: $enableThirdPartyDDCIntegration)
.settingsHighlight(id: highlightID("Third-party DDC app integration")) .settingsHighlight(id: highlightID("Third-party DDC app integration"))
.onChange(of: enableThirdPartyDDCIntegration) { _, _ in
enableExternalVolumeControlListener = false
}
if enableThirdPartyDDCIntegration { if enableThirdPartyDDCIntegration {
Picker("Provider", selection: $thirdPartyDDCProvider) { Picker("Provider", selection: $thirdPartyDDCProvider) {
@ -2347,6 +2430,8 @@ private struct ExternalDisplayIntegrationsSection: View {
} }
} }
} }
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.navigationTitle("Controls")
} }
} }
@ -2502,6 +2587,7 @@ struct HUD: View {
@Default(.enableBrightnessHUD) var enableBrightnessHUD @Default(.enableBrightnessHUD) var enableBrightnessHUD
@Default(.enableKeyboardBacklightHUD) var enableKeyboardBacklightHUD @Default(.enableKeyboardBacklightHUD) var enableKeyboardBacklightHUD
@Default(.enableThirdPartyDDCIntegration) var enableThirdPartyDDCIntegration @Default(.enableThirdPartyDDCIntegration) var enableThirdPartyDDCIntegration
@Default(.enableExternalVolumeControlListener) var enableExternalVolumeControlListener
@Default(.systemHUDSensitivity) var systemHUDSensitivity @Default(.systemHUDSensitivity) var systemHUDSensitivity
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared @ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
@ObservedObject private var accessibilityPermission = AccessibilityPermissionStore.shared @ObservedObject private var accessibilityPermission = AccessibilityPermissionStore.shared
@ -2639,7 +2725,6 @@ struct HUD: View {
} }
} }
} }
.navigationTitle("Controls")
.onAppear { .onAppear {
accessibilityPermission.refreshStatus() accessibilityPermission.refreshStatus()
} }
@ -2672,7 +2757,7 @@ struct Media: View {
@Default(.autoHideInactiveNotchMediaPlayer) private var autoHideInactiveNotchMediaPlayer @Default(.autoHideInactiveNotchMediaPlayer) private var autoHideInactiveNotchMediaPlayer
@Default(.parallaxEffectIntensity) private var parallaxEffectIntensity @Default(.parallaxEffectIntensity) private var parallaxEffectIntensity
@ObservedObject private var musicManager = MusicManager.shared @ObservedObject private var musicManager = MusicManager.shared
private var isAppleMusicActive: Bool { private var isAppleMusicActive: Bool {
@ -2831,7 +2916,7 @@ struct Media: View {
} }
.settingsHighlight(id: highlightID("Show live canvas in Dynamic Island")) .settingsHighlight(id: highlightID("Show live canvas in Dynamic Island"))
.help("Replaces the artwork tile with the live canvas when the current app provides one, and reuses that moving canvas for the surrounding lighting effect.") .help("Replaces the artwork tile with the live canvas when the current app provides one, and reuses that moving canvas for the surrounding lighting effect.")
//Parallax Effect Intensity to control how much parallax is wanted //Parallax Effect Intensity to control how much parallax is wanted
Slider(value: $parallaxEffectIntensity, in: 0...12, step: 1.0) { Slider(value: $parallaxEffectIntensity, in: 0...12, step: 1.0) {
HStack { HStack {
@ -2842,7 +2927,7 @@ struct Media: View {
} }
} }
.settingsHighlight(id: highlightID("Enable album art parallax effect")) .settingsHighlight(id: highlightID("Enable album art parallax effect"))
Picker("Sneak Peek Style", selection: $sneakPeekStyles){ Picker("Sneak Peek Style", selection: $sneakPeekStyles){
ForEach(SneakPeekStyle.allCases) { style in ForEach(SneakPeekStyle.allCases) { style in
Text(style.rawValue).tag(style) Text(style.rawValue).tag(style)
@ -3314,7 +3399,7 @@ struct CalendarSettings: View {
.disabled(!lockScreenShowCalendarEvent) .disabled(!lockScreenShowCalendarEvent)
.settingsHighlight(id: highlightID("Show start time after event begins")) .settingsHighlight(id: highlightID("Show start time after event begins"))
} }
// MARK: - Third-party Calendar Integration // MARK: - Third-party Calendar Integration
Section { Section {
Defaults.Toggle(key: .enableThirdPartyCalendarApp) { Defaults.Toggle(key: .enableThirdPartyCalendarApp) {
@ -3326,7 +3411,7 @@ struct CalendarSettings: View {
} }
} }
.settingsHighlight(id: highlightID("Enable third-party calendar app launch")) .settingsHighlight(id: highlightID("Enable third-party calendar app launch"))
if enableThirdPartyCalendarApp { if enableThirdPartyCalendarApp {
Picker("Calendar App", selection: $selectedCalendarApp) { Picker("Calendar App", selection: $selectedCalendarApp) {
ForEach(ThirdPartyCalendarApp.allCases) { app in ForEach(ThirdPartyCalendarApp.allCases) { app in
@ -3342,7 +3427,7 @@ struct CalendarSettings: View {
} }
} }
.settingsHighlight(id: highlightID("Calendar App")) .settingsHighlight(id: highlightID("Calendar App"))
if selectedCalendarApp == .fantastical { if selectedCalendarApp == .fantastical {
Picker("Default View", selection: $fantasticalDefaultView) { Picker("Default View", selection: $fantasticalDefaultView) {
ForEach(FantasticalViewStyle.allCases, id: \.self) { style in ForEach(FantasticalViewStyle.allCases, id: \.self) { style in
@ -3806,7 +3891,7 @@ struct Shelf: View {
.font(.caption) .font(.caption)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
if quickShareProvider == "LocalSend" { if quickShareProvider == "LocalSend" {
LocalSendSettingsSection(highlightID: highlightID) LocalSendSettingsSection(highlightID: highlightID)
} }
@ -3824,10 +3909,10 @@ struct Shelf: View {
private struct LocalSendSettingsSection: View { private struct LocalSendSettingsSection: View {
let highlightID: (String) -> String let highlightID: (String) -> String
@Default(.localSendDevicePickerGlassMode) private var glassMode @Default(.localSendDevicePickerGlassMode) private var glassMode
@Default(.localSendDevicePickerLiquidGlassVariant) private var liquidGlassVariant @Default(.localSendDevicePickerLiquidGlassVariant) private var liquidGlassVariant
var body: some View { var body: some View {
Section { Section {
Picker("Device Picker Style", selection: $glassMode) { Picker("Device Picker Style", selection: $glassMode) {
@ -3836,7 +3921,7 @@ private struct LocalSendSettingsSection: View {
} }
} }
.pickerStyle(.menu) .pickerStyle(.menu)
if glassMode == .customLiquid { if glassMode == .customLiquid {
Picker("Liquid Glass Variant", selection: $liquidGlassVariant) { Picker("Liquid Glass Variant", selection: $liquidGlassVariant) {
ForEach(LiquidGlassVariant.allCases) { variant in ForEach(LiquidGlassVariant.allCases) { variant in
@ -4497,7 +4582,7 @@ struct Appearance: View {
.tag(MirrorShapeEnum.rectangle) .tag(MirrorShapeEnum.rectangle)
} }
.settingsHighlight(id: highlightID("Mirror shape")) .settingsHighlight(id: highlightID("Mirror shape"))
if webcamManager.cameraAvailable { if webcamManager.cameraAvailable {
Picker("Mirror Camera", selection: $selectedCameraID) { Picker("Mirror Camera", selection: $selectedCameraID) {
ForEach(webcamManager.availableCameras, id: \.uniqueID) { device in ForEach(webcamManager.availableCameras, id: \.uniqueID) { device in
@ -7849,14 +7934,14 @@ struct CustomOSDSettings: View {
previewType = .brightness previewType = .brightness
} }
.buttonStyle(.bordered) .buttonStyle(.bordered)
Button("Backlight") { Button("Backlight") {
previewType = .backlight previewType = .backlight
} }
.buttonStyle(.bordered) .buttonStyle(.bordered)
} }
.controlSize(.small) .controlSize(.small)
Slider(value: $previewValue, in: 0...1) Slider(value: $previewValue, in: 0...1)
.frame(width: 160) .frame(width: 160)
} }
@ -7872,7 +7957,6 @@ struct CustomOSDSettings: View {
} }
} }
} }
.navigationTitle("Custom OSD")
.onAppear { .onAppear {
accessibilityPermission.refreshStatus() accessibilityPermission.refreshStatus()
if #unavailable(macOS 26.0), osdMaterial == .liquid { if #unavailable(macOS 26.0), osdMaterial == .liquid {

View file

@ -38,9 +38,18 @@ final class SystemChangesObserver: MediaKeyInterceptorDelegate {
"headphones" "headphones"
] ]
private let standardVolumeStep: Float = 1.0 / 16.0 private var standardVolumeStep: Float {
private let standardBrightnessStep: Float = 1.0 / 16.0 Float(Defaults[.volumeStepPercent]) / 100.0
private let fineStepDivisor: Float = 4.0 }
private var fineVolumeStep: Float {
Float(Defaults[.volumeFineStepPercent]) / 100.0
}
private var standardBrightnessStep: Float {
Float(Defaults[.brightnessStepPercent]) / 100.0
}
private var fineBrightnessStep: Float {
Float(Defaults[.brightnessFineStepPercent]) / 100.0
}
private var volumeEnabled = false private var volumeEnabled = false
private var brightnessEnabled = false private var brightnessEnabled = false
@ -157,7 +166,7 @@ final class SystemChangesObserver: MediaKeyInterceptorDelegate {
} }
} }
let baseStep = stepSize(for: step, base: standardVolumeStep) let baseStep = volumeStep(for: step)
let delta = direction == .up ? baseStep : -baseStep let delta = direction == .up ? baseStep : -baseStep
volumeController.adjust(by: delta) volumeController.adjust(by: delta)
volumeFeedbackPlayer.playIfNeeded() volumeFeedbackPlayer.playIfNeeded()
@ -185,7 +194,7 @@ final class SystemChangesObserver: MediaKeyInterceptorDelegate {
} }
} }
let baseStep = stepSize(for: step, base: standardBrightnessStep) let baseStep = brightnessStep(for: step)
let delta = direction == .up ? baseStep : -baseStep let delta = direction == .up ? baseStep : -baseStep
if modifiers.contains(.command) && keyboardBacklightEnabled { if modifiers.contains(.command) && keyboardBacklightEnabled {
keyboardBacklightController.adjust(by: delta) keyboardBacklightController.adjust(by: delta)
@ -336,12 +345,21 @@ final class SystemChangesObserver: MediaKeyInterceptorDelegate {
} }
private extension SystemChangesObserver { private extension SystemChangesObserver {
func stepSize(for step: MediaKeyStep, base: Float) -> Float { func volumeStep(for step: MediaKeyStep) -> Float {
switch step { switch step {
case .standard: case .standard:
return base return standardVolumeStep
case .fine: case .fine:
return base / fineStepDivisor return fineVolumeStep
}
}
func brightnessStep(for step: MediaKeyStep) -> Float {
switch step {
case .standard:
return standardBrightnessStep
case .fine:
return fineBrightnessStep
} }
} }
} }

View file

@ -1133,6 +1133,12 @@ extension Defaults.Keys {
static let enableKeyboardBacklightHUD = Key<Bool>("enableKeyboardBacklightHUD", default: true) static let enableKeyboardBacklightHUD = Key<Bool>("enableKeyboardBacklightHUD", default: true)
static let systemHUDSensitivity = Key<Int>("systemHUDSensitivity", default: 5) static let systemHUDSensitivity = Key<Int>("systemHUDSensitivity", default: 5)
static let playVolumeChangeFeedback = Key<Bool>("playVolumeChangeFeedback", default: false) static let playVolumeChangeFeedback = Key<Bool>("playVolumeChangeFeedback", default: false)
// Step sizes for hardware media keys (percent of full range, 1-25)
static let volumeStepPercent = Key<Int>("volumeStepPercent", default: 6)
static let volumeFineStepPercent = Key<Int>("volumeFineStepPercent", default: 2)
static let brightnessStepPercent = Key<Int>("brightnessStepPercent", default: 6)
static let brightnessFineStepPercent = Key<Int>("brightnessFineStepPercent", default: 2)
// MARK: Custom OSD Window Feature // MARK: Custom OSD Window Feature
static let enableCustomOSD = Key<Bool>("enableCustomOSD", default: false) static let enableCustomOSD = Key<Bool>("enableCustomOSD", default: false)