mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Add accessibility request button
This commit is contained in:
parent
f782c9d135
commit
05ce48968d
3 changed files with 194 additions and 4 deletions
|
|
@ -1803,6 +1803,13 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"Accessibility" : {
|
||||
|
||||
},
|
||||
"Accessibility permission required" : {
|
||||
"comment" : "A label explaining that accessibility permissions are required.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Actions" : {
|
||||
"localizations" : {
|
||||
"fr" : {
|
||||
|
|
@ -9526,6 +9533,12 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Grant Accessibility permission in System Settings to control macOS HUD replacements from here." : {
|
||||
|
||||
},
|
||||
"Grant Accessibility permission in System Settings to customize the on-screen display replacements here." : {
|
||||
|
||||
},
|
||||
"Graph Visibility" : {
|
||||
"localizations" : {
|
||||
|
|
@ -14756,6 +14769,10 @@
|
|||
},
|
||||
"Open Model Settings" : {
|
||||
|
||||
},
|
||||
"Open Settings" : {
|
||||
"comment" : "A button that opens system settings when pressed.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Open System Settings" : {
|
||||
|
||||
|
|
|
|||
|
|
@ -1282,16 +1282,33 @@ struct HUD: View {
|
|||
@Default(.enableKeyboardBacklightHUD) var enableKeyboardBacklightHUD
|
||||
@Default(.systemHUDSensitivity) var systemHUDSensitivity
|
||||
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
|
||||
@ObservedObject private var accessibilityPermission = AccessibilityPermissionStore.shared
|
||||
|
||||
private func highlightID(_ title: String) -> String {
|
||||
SettingsTab.hud.highlightID(for: title)
|
||||
}
|
||||
|
||||
private var hasAccessibilityPermission: Bool {
|
||||
accessibilityPermission.isAuthorized
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
if !hasAccessibilityPermission {
|
||||
Section {
|
||||
SettingsPermissionCallout(
|
||||
message: "Accessibility permission lets Dynamic Island replace the native volume, brightness, and keyboard HUDs.",
|
||||
requestAction: { accessibilityPermission.requestAuthorizationPrompt() },
|
||||
openSettingsAction: { accessibilityPermission.openSystemSettings() }
|
||||
)
|
||||
} header: {
|
||||
Text("Accessibility")
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
Toggle("Enable HUDs", isOn: $enableSystemHUD)
|
||||
.disabled(Defaults[.enableCustomOSD])
|
||||
.disabled(Defaults[.enableCustomOSD] || !hasAccessibilityPermission)
|
||||
} header: {
|
||||
Text("General")
|
||||
} footer: {
|
||||
|
|
@ -1299,6 +1316,10 @@ struct HUD: View {
|
|||
Text("HUDs are disabled because Custom OSD is enabled. Disable Custom OSD in the Custom OSD tab to use HUDs.")
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.caption)
|
||||
} else if !hasAccessibilityPermission {
|
||||
Text("Grant Accessibility permission in System Settings to control macOS HUD replacements from here.")
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.caption)
|
||||
} else {
|
||||
Text("Replaces macOS system HUD with Dynamic Island displays for volume, brightness, and keyboard backlight.")
|
||||
.foregroundStyle(.secondary)
|
||||
|
|
@ -1306,7 +1327,7 @@ struct HUD: View {
|
|||
}
|
||||
}
|
||||
|
||||
if enableSystemHUD && !Defaults[.enableCustomOSD] {
|
||||
if enableSystemHUD && !Defaults[.enableCustomOSD] && hasAccessibilityPermission {
|
||||
Section {
|
||||
Toggle("Volume HUD", isOn: $enableVolumeHUD)
|
||||
Toggle("Brightness HUD", isOn: $enableBrightnessHUD)
|
||||
|
|
@ -1407,6 +1428,14 @@ struct HUD: View {
|
|||
}
|
||||
}
|
||||
.navigationTitle("HUDs")
|
||||
.onAppear {
|
||||
accessibilityPermission.refreshStatus()
|
||||
}
|
||||
.onChange(of: accessibilityPermission.isAuthorized) { _, granted in
|
||||
if !granted {
|
||||
enableSystemHUD = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4418,6 +4447,7 @@ struct CustomOSDSettings: View {
|
|||
@Default(.osdMaterial) var osdMaterial
|
||||
@Default(.osdIconColorStyle) var osdIconColorStyle
|
||||
@Default(.enableSystemHUD) var enableSystemHUD
|
||||
@ObservedObject private var accessibilityPermission = AccessibilityPermissionStore.shared
|
||||
|
||||
@State private var showAlphaWarning = false
|
||||
@State private var previewValue: CGFloat = 0.65
|
||||
|
|
@ -4427,12 +4457,32 @@ struct CustomOSDSettings: View {
|
|||
SettingsTab.osd.highlightID(for: title)
|
||||
}
|
||||
|
||||
private var hasAccessibilityPermission: Bool {
|
||||
accessibilityPermission.isAuthorized
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
if !hasAccessibilityPermission {
|
||||
Section {
|
||||
SettingsPermissionCallout(
|
||||
message: "Accessibility permission is needed to intercept system controls for the Custom OSD.",
|
||||
requestAction: { accessibilityPermission.requestAuthorizationPrompt() },
|
||||
openSettingsAction: { accessibilityPermission.openSystemSettings() }
|
||||
)
|
||||
} header: {
|
||||
Text("Accessibility")
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
Toggle("Enable Custom OSD", isOn: Binding(
|
||||
get: { enableCustomOSD },
|
||||
set: { newValue in
|
||||
guard hasAccessibilityPermission else {
|
||||
enableCustomOSD = false
|
||||
return
|
||||
}
|
||||
if newValue && !hasSeenOSDAlphaWarning {
|
||||
showAlphaWarning = true
|
||||
} else {
|
||||
|
|
@ -4445,7 +4495,7 @@ struct CustomOSDSettings: View {
|
|||
}
|
||||
))
|
||||
.settingsHighlight(id: highlightID("Enable Custom OSD"))
|
||||
.disabled(enableSystemHUD)
|
||||
.disabled(enableSystemHUD || !hasAccessibilityPermission)
|
||||
} header: {
|
||||
Text("General")
|
||||
} footer: {
|
||||
|
|
@ -4453,6 +4503,10 @@ struct CustomOSDSettings: View {
|
|||
Text("Custom OSD is disabled because HUDs are enabled. Disable HUDs in the HUDs tab to use Custom OSD.")
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.caption)
|
||||
} else if !hasAccessibilityPermission {
|
||||
Text("Grant Accessibility permission in System Settings to customize the on-screen display replacements here.")
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.caption)
|
||||
} else {
|
||||
Text("Display custom macOS-style OSD windows at the bottom center of the screen when adjusting volume, brightness, or keyboard backlight.")
|
||||
.foregroundStyle(.secondary)
|
||||
|
|
@ -4460,7 +4514,7 @@ struct CustomOSDSettings: View {
|
|||
}
|
||||
}
|
||||
|
||||
if enableCustomOSD {
|
||||
if enableCustomOSD && hasAccessibilityPermission {
|
||||
Section {
|
||||
Toggle("Volume OSD", isOn: $enableOSDVolume)
|
||||
.settingsHighlight(id: highlightID("Volume OSD"))
|
||||
|
|
@ -4572,6 +4626,49 @@ struct CustomOSDSettings: View {
|
|||
Text("Custom OSD is an experimental alpha feature and may contain bugs or unexpected behavior.\n\nThis feature requires macOS 15 or later and is still under active development. It's recommended to keep it disabled unless you want to help test it.")
|
||||
}
|
||||
.navigationTitle("Custom OSD")
|
||||
.onAppear {
|
||||
accessibilityPermission.refreshStatus()
|
||||
}
|
||||
.onChange(of: accessibilityPermission.isAuthorized) { _, granted in
|
||||
if !granted {
|
||||
enableCustomOSD = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsPermissionCallout: View {
|
||||
let message: String
|
||||
let requestAction: () -> Void
|
||||
let openSettingsAction: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Label("Accessibility permission required", systemImage: "exclamationmark.triangle.fill")
|
||||
.font(.subheadline.weight(.semibold))
|
||||
.foregroundStyle(.orange)
|
||||
|
||||
Text(message)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
HStack(spacing: 8) {
|
||||
Button("Request Access") {
|
||||
requestAction()
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
|
||||
Button("Open Settings") {
|
||||
openSettingsAction()
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
}
|
||||
.controlSize(.small)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(12)
|
||||
.background(Color.secondary.opacity(0.08))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
76
DynamicIsland/helpers/AccessibilityPermissionStore.swift
Normal file
76
DynamicIsland/helpers/AccessibilityPermissionStore.swift
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import Foundation
|
||||
import Combine
|
||||
import AppKit
|
||||
#if canImport(ApplicationServices)
|
||||
import ApplicationServices
|
||||
#endif
|
||||
|
||||
/// Tracks accessibility permission status and exposes helpers to request access.
|
||||
@MainActor
|
||||
final class AccessibilityPermissionStore: ObservableObject {
|
||||
static let shared = AccessibilityPermissionStore()
|
||||
|
||||
@Published private(set) var isAuthorized: Bool = AccessibilityPermissionStore.isAccessibilityAuthorized()
|
||||
|
||||
private var pollingTask: Task<Void, Never>?
|
||||
|
||||
private init() {}
|
||||
|
||||
deinit {
|
||||
pollingTask?.cancel()
|
||||
}
|
||||
|
||||
func refreshStatus() {
|
||||
updateAuthorizationStatus(to: Self.isAccessibilityAuthorized())
|
||||
}
|
||||
|
||||
func requestAuthorizationPrompt() {
|
||||
#if canImport(ApplicationServices)
|
||||
let promptKey = kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String
|
||||
let options: CFDictionary = [promptKey: true] as CFDictionary
|
||||
_ = AXIsProcessTrustedWithOptions(options)
|
||||
#endif
|
||||
beginPollingForStatusChanges()
|
||||
}
|
||||
|
||||
func openSystemSettings() {
|
||||
#if os(macOS)
|
||||
if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility") {
|
||||
NSWorkspace.shared.open(url)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private func beginPollingForStatusChanges() {
|
||||
pollingTask?.cancel()
|
||||
pollingTask = Task { [weak self] in
|
||||
for _ in 0..<30 {
|
||||
try? await Task.sleep(nanoseconds: 500_000_000)
|
||||
let status = Self.isAccessibilityAuthorized()
|
||||
await MainActor.run {
|
||||
guard let self else { return }
|
||||
self.updateAuthorizationStatus(to: status)
|
||||
}
|
||||
if status {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateAuthorizationStatus(to newValue: Bool) {
|
||||
guard newValue != isAuthorized else { return }
|
||||
isAuthorized = newValue
|
||||
if !newValue {
|
||||
beginPollingForStatusChanges()
|
||||
}
|
||||
}
|
||||
|
||||
private static func isAccessibilityAuthorized() -> Bool {
|
||||
#if canImport(ApplicationServices)
|
||||
return AXIsProcessTrusted()
|
||||
#else
|
||||
return true
|
||||
#endif
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue