diff --git a/MonitorControl.xcodeproj/project.pbxproj b/MonitorControl.xcodeproj/project.pbxproj index 0583586..47e741f 100644 --- a/MonitorControl.xcodeproj/project.pbxproj +++ b/MonitorControl.xcodeproj/project.pbxproj @@ -58,6 +58,7 @@ F06792F6200A745F0066C438 /* MonitorControlHelper.app in [Login] Copy Helper to start at Login */ = {isa = PBXBuildFile; fileRef = F06792E7200A73460066C438 /* MonitorControlHelper.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; F0A489C4279C71B200BEDFD6 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A489C3279C71B200BEDFD6 /* OnboardingViewController.swift */; }; FE4E0896249D584C003A50BB /* OSDUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE4E0895249D584C003A50BB /* OSDUtils.swift */; }; + CE12345678901234003A50BB /* CustomHUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE12345678901234003A50BC /* CustomHUD.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -181,6 +182,7 @@ FB5DB28F2AD54C4600306223 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/InternetAccessPolicy.strings"; sourceTree = ""; }; FB5DB2902AD54C4600306223 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = ""; }; FE4E0895249D584C003A50BB /* OSDUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSDUtils.swift; sourceTree = ""; }; + CE12345678901234003A50BC /* CustomHUD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomHUD.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -317,6 +319,7 @@ AA16139A26BE772E00DCF027 /* Arm64DDC.swift */, AA4398A826DD55DA00943F16 /* IntelDDC.swift */, FE4E0895249D584C003A50BB /* OSDUtils.swift */, + CE12345678901234003A50BC /* CustomHUD.swift */, ); path = Support; sourceTree = ""; @@ -629,6 +632,7 @@ AA062E8E26CA7BE5007E628C /* DisplaysPrefsCellView.swift in Sources */, AA25F6D726E68C160087F3A2 /* MediaKeyTapManager.swift in Sources */, FE4E0896249D584C003A50BB /* OSDUtils.swift in Sources */, + CE12345678901234003A50BB /* CustomHUD.swift in Sources */, 6CBFE27A23DB266000D1BC41 /* Display.swift in Sources */, AA44E70727038F7F00E06865 /* KeyboardShortcutsManager.swift in Sources */, F03A8DF21FFBAA6F0034DC27 /* OtherDisplay.swift in Sources */, diff --git a/MonitorControl/Info.plist b/MonitorControl/Info.plist index 69810af..a992cf1 100644 --- a/MonitorControl/Info.plist +++ b/MonitorControl/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 7141 + 7168 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/MonitorControl/Support/CustomHUD.swift b/MonitorControl/Support/CustomHUD.swift new file mode 100644 index 0000000..8136c33 --- /dev/null +++ b/MonitorControl/Support/CustomHUD.swift @@ -0,0 +1,225 @@ +// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others +// CustomHUD.swift - Custom OSD overlay for macOS Tahoe 26+ compatibility + +import Cocoa + +#if swift(>=5.3) +import SwiftUI +#endif + +// MARK: - Custom HUD Manager + +/// Manages custom HUD windows for brightness/volume display on macOS 26+ +/// where the native OSD API no longer works correctly +class CustomHUDManager { + static let shared = CustomHUDManager() + + private var hudWindows: [CGDirectDisplayID: NSWindow] = [:] + private let hudLock = NSLock() + + private init() {} + + /// Shows a custom HUD on the specified display + func showHUD(displayID: CGDirectDisplayID, type: HUDType, value: Float, maxValue: Float = 1.0) { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + + self.hudLock.lock() + defer { self.hudLock.unlock() } + + // Get or create HUD window for this display + let window: NSWindow + if let existingWindow = self.hudWindows[displayID] { + window = existingWindow + } else { + window = self.createHUDWindow(displayID: displayID) + self.hudWindows[displayID] = window + } + + // Update and show the HUD + self.updateWindowContent(window: window, displayID: displayID, type: type, value: value, maxValue: maxValue) + self.showWindowWithFade(window: window) + } + } + + private func createHUDWindow(displayID: CGDirectDisplayID) -> NSWindow { + let window = NSPanel( + contentRect: NSRect(x: 0, y: 0, width: 220, height: 60), + styleMask: [.nonactivatingPanel, .hudWindow], + backing: .buffered, + defer: false + ) + + window.level = .floating + window.isFloatingPanel = true + window.hidesOnDeactivate = false + window.collectionBehavior = [.canJoinAllSpaces, .stationary, .ignoresCycle] + window.isOpaque = false + window.backgroundColor = .clear + window.hasShadow = true + window.isMovable = false + window.isMovableByWindowBackground = false + window.ignoresMouseEvents = true + + positionWindow(window, onDisplay: displayID) + + return window + } + + private func positionWindow(_ window: NSWindow, onDisplay displayID: CGDirectDisplayID) { + guard let screen = getScreen(for: displayID) else { return } + + let screenFrame = screen.visibleFrame + let windowSize = window.frame.size + + // Position at center-bottom of screen, above the dock + let x = screenFrame.origin.x + (screenFrame.width - windowSize.width) / 2 + let y = screenFrame.origin.y + 100 // 100 points from bottom + + window.setFrameOrigin(NSPoint(x: x, y: y)) + } + + private func getScreen(for displayID: CGDirectDisplayID) -> NSScreen? { + return NSScreen.screens.first { screen in + guard let screenNumber = screen.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? NSNumber else { + return false + } + return CGDirectDisplayID(screenNumber.uint32Value) == displayID + } + } + + private func updateWindowContent(window: NSWindow, displayID: CGDirectDisplayID, type: HUDType, value: Float, maxValue: Float) { + // Create content view using AppKit for maximum compatibility + let contentView = createHUDContentView(type: type, value: value, maxValue: maxValue) + window.contentView = contentView + + // Re-position in case screen changed + positionWindow(window, onDisplay: displayID) + } + + private func createHUDContentView(type: HUDType, value: Float, maxValue: Float) -> NSView { + let containerView = NSVisualEffectView(frame: NSRect(x: 0, y: 0, width: 220, height: 60)) + containerView.material = .hudWindow + containerView.blendingMode = .behindWindow + containerView.state = .active + containerView.wantsLayer = true + containerView.layer?.cornerRadius = 12 + containerView.layer?.masksToBounds = true + + // Icon + let iconView = NSImageView(frame: NSRect(x: 16, y: 18, width: 24, height: 24)) + if #available(macOS 11.0, *) { + if let icon = NSImage(systemSymbolName: type.iconSystemName, accessibilityDescription: nil) { + iconView.image = icon + iconView.contentTintColor = type.iconNSColor + } + } else { + // Fallback for older macOS: use bundled or system icons + let fallbackIcon: String + switch type { + case .brightness: fallbackIcon = NSImage.touchBarComposeTemplateName + case .volume: fallbackIcon = NSImage.touchBarAudioOutputVolumeHighTemplateName + case .volumeMuted: fallbackIcon = NSImage.touchBarAudioOutputMuteTemplateName + case .contrast: fallbackIcon = NSImage.touchBarColorPickerFillName + } + iconView.image = NSImage(named: fallbackIcon) + iconView.contentTintColor = type.iconNSColor + } + containerView.addSubview(iconView) + + // Progress bar background + let progressBg = NSView(frame: NSRect(x: 52, y: 26, width: 108, height: 8)) + progressBg.wantsLayer = true + progressBg.layer?.backgroundColor = NSColor.white.withAlphaComponent(0.2).cgColor + progressBg.layer?.cornerRadius = 4 + containerView.addSubview(progressBg) + + // Progress bar fill + let normalizedValue = CGFloat(min(max(value / maxValue, 0), 1)) + let progressFill = NSView(frame: NSRect(x: 52, y: 26, width: 108 * normalizedValue, height: 8)) + progressFill.wantsLayer = true + progressFill.layer?.backgroundColor = NSColor.white.cgColor + progressFill.layer?.cornerRadius = 4 + containerView.addSubview(progressFill) + + // Percentage label + let percentage = Int(normalizedValue * 100) + let label = NSTextField(labelWithString: "\(percentage)%") + label.frame = NSRect(x: 168, y: 20, width: 42, height: 20) + label.font = NSFont.monospacedDigitSystemFont(ofSize: 14, weight: .medium) + label.textColor = .white + label.alignment = .right + containerView.addSubview(label) + + return containerView + } + + private var fadeTimers: [CGDirectDisplayID: Timer] = [:] + + private func showWindowWithFade(window: NSWindow) { + // Find the displayID for this window + guard let displayID = hudWindows.first(where: { $0.value === window })?.key else { return } + + // Cancel any existing fade timer + fadeTimers[displayID]?.invalidate() + + // Make window fully visible + window.alphaValue = 1.0 + window.orderFrontRegardless() + + // Schedule fade out after 1.5 seconds + fadeTimers[displayID] = Timer.scheduledTimer(withTimeInterval: 1.5, repeats: false) { [weak self, weak window] _ in + guard let window = window else { return } + self?.fadeOut(window: window) + } + } + + private func fadeOut(window: NSWindow) { + NSAnimationContext.runAnimationGroup { context in + context.duration = 0.3 + window.animator().alphaValue = 0 + } completionHandler: { + window.orderOut(nil) + } + } + + /// Cleans up HUD windows for removed displays + func cleanupDisplay(_ displayID: CGDirectDisplayID) { + hudLock.lock() + defer { hudLock.unlock() } + + fadeTimers[displayID]?.invalidate() + fadeTimers.removeValue(forKey: displayID) + + if let window = hudWindows[displayID] { + window.close() + hudWindows.removeValue(forKey: displayID) + } + } +} + +// MARK: - HUD Type + +enum HUDType { + case brightness + case volume + case volumeMuted + case contrast + + var iconSystemName: String { + switch self { + case .brightness: return "sun.max.fill" + case .volume: return "speaker.wave.2.fill" + case .volumeMuted: return "speaker.slash.fill" + case .contrast: return "circle.lefthalf.filled" + } + } + + var iconNSColor: NSColor { + switch self { + case .brightness: return .systemYellow + case .volume, .volumeMuted: return .systemBlue + case .contrast: return .systemGray + } + } +} diff --git a/MonitorControl/Support/OSDUtils.swift b/MonitorControl/Support/OSDUtils.swift index a250273..60623d6 100644 --- a/MonitorControl/Support/OSDUtils.swift +++ b/MonitorControl/Support/OSDUtils.swift @@ -21,7 +21,37 @@ class OSDUtils: NSObject { return osdImage } + /// Check if we're running on macOS 26 (Tahoe) or later where native OSD is broken + private static var shouldUseCustomHUD: Bool { + if #available(macOS 26, *) { + return true + } + return false + } + + /// Convert Command to HUDType for custom HUD + private static func getHUDType(command: Command, value: Float) -> HUDType { + switch command { + case .audioSpeakerVolume: + return value > 0 ? .volume : .volumeMuted + case .audioMuteScreenBlank: + return .volumeMuted + case .contrast: + return .contrast + default: + return .brightness + } + } + static func showOsd(displayID: CGDirectDisplayID, command: Command, value: Float, maxValue: Float = 1, roundChiclet: Bool = false, lock: Bool = false) { + // Use custom HUD on macOS 26+ where native OSD is broken + if shouldUseCustomHUD { + let hudType = getHUDType(command: command, value: value) + CustomHUDManager.shared.showHUD(displayID: displayID, type: hudType, value: value, maxValue: maxValue) + return + } + + // Fallback to native OSD for older macOS versions guard let manager = OSDManager.sharedManager() as? OSDManager else { return } @@ -40,6 +70,11 @@ class OSDUtils: NSObject { } static func showOsdVolumeDisabled(displayID: CGDirectDisplayID) { + if shouldUseCustomHUD { + CustomHUDManager.shared.showHUD(displayID: displayID, type: .volumeMuted, value: 0, maxValue: 1) + return + } + guard let manager = OSDManager.sharedManager() as? OSDManager else { return } @@ -47,6 +82,11 @@ class OSDUtils: NSObject { } static func showOsdMuteDisabled(displayID: CGDirectDisplayID) { + if shouldUseCustomHUD { + CustomHUDManager.shared.showHUD(displayID: displayID, type: .volumeMuted, value: 0, maxValue: 1) + return + } + guard let manager = OSDManager.sharedManager() as? OSDManager else { return } @@ -54,6 +94,12 @@ class OSDUtils: NSObject { } static func popEmptyOsd(displayID: CGDirectDisplayID, command: Command) { + if shouldUseCustomHUD { + let hudType = getHUDType(command: command, value: 0) + CustomHUDManager.shared.showHUD(displayID: displayID, type: hudType, value: 0, maxValue: 1) + return + } + guard let manager = OSDManager.sharedManager() as? OSDManager else { return } @@ -75,3 +121,4 @@ class OSDUtils: NSObject { abs(chiclet.rounded(.towardZero) - chiclet) } } + diff --git a/MonitorControl/Support/SliderHandler.swift b/MonitorControl/Support/SliderHandler.swift index 1d7d20e..ff98e78 100644 --- a/MonitorControl/Support/SliderHandler.swift +++ b/MonitorControl/Support/SliderHandler.swift @@ -219,8 +219,8 @@ class SliderHandler { self.slider = slider if !DEBUG_MACOS10, #available(macOS 11.0, *) { slider.frame.size.width = 180 - slider.frame.origin = NSPoint(x: 15, y: 5) - let view = NSView(frame: NSRect(x: 0, y: 0, width: slider.frame.width + 30 + (showPercent ? 38 : 0), height: slider.frame.height + 14)) + slider.frame.origin = NSPoint(x: 15, y: 8) + let view = NSView(frame: NSRect(x: 0, y: 0, width: slider.frame.width + 30 + (showPercent ? 38 : 0), height: slider.frame.height + 42)) view.frame.origin = NSPoint(x: 12, y: 0) var iconName = "circle.dashed" switch command { @@ -232,13 +232,15 @@ class SliderHandler { let icon = SliderHandler.ClickThroughImageView() icon.image = NSImage(systemSymbolName: iconName, accessibilityDescription: title) icon.contentTintColor = NSColor.black.withAlphaComponent(0.6) - icon.frame = NSRect(x: view.frame.origin.x + 6.5, y: view.frame.origin.y + 13, width: 15, height: 15) + // Position icon at horizontal left (start), 8px above slider + let iconSize: CGFloat = 18 + icon.frame = NSRect(x: slider.frame.origin.x, y: slider.frame.origin.y + slider.frame.height + 8, width: iconSize, height: iconSize) icon.imageAlignment = .alignCenter view.addSubview(slider) view.addSubview(icon) self.icon = icon if showPercent { - let percentageBox = NSTextField(frame: NSRect(x: 15 + slider.frame.size.width - 2, y: 17, width: 40, height: 12)) + let percentageBox = NSTextField(frame: NSRect(x: 15 + slider.frame.size.width - 2, y: slider.frame.origin.y + (slider.frame.height - 12) / 2, width: 40, height: 12)) self.setupPercentageBox(percentageBox) self.percentageBox = percentageBox view.addSubview(percentageBox) diff --git a/MonitorControl/UI/Base.lproj/Main.storyboard b/MonitorControl/UI/Base.lproj/Main.storyboard index f5a9bb4..c269c53 100644 --- a/MonitorControl/UI/Base.lproj/Main.storyboard +++ b/MonitorControl/UI/Base.lproj/Main.storyboard @@ -709,7 +709,7 @@ - + @@ -1164,7 +1164,7 @@ - + @@ -1280,7 +1280,7 @@ - + @@ -1340,7 +1340,7 @@ - + @@ -1987,7 +1987,7 @@ - + diff --git a/MonitorControl/UI/cs.lproj/Localizable.strings b/MonitorControl/UI/cs.lproj/Localizable.strings index 1256215..6ef2563 100644 --- a/MonitorControl/UI/cs.lproj/Localizable.strings +++ b/MonitorControl/UI/cs.lproj/Localizable.strings @@ -8,7 +8,7 @@ "App menu" = "Nabídka"; /* Shown in the alert dialog */ -"Are you sure you want to enable a longer delay? Doing so may freeze your system and require a restart. Start at login will be disabled as a safety measure." = "Opravdu chcete zapnout delší prodlevu? Může dojít k zamrznutí systému, což by vyžadovalo restart. Volba "Spustit po přihlášení" se z bezpečnostních důvodů vypne."; +"Are you sure you want to enable a longer delay? Doing so may freeze your system and require a restart. Start at login will be disabled as a safety measure." = "Opravdu chcete zapnout delší prodlevu? Může dojít k zamrznutí systému, což by vyžadovalo restart. Volba \\\"Spustit po přihlášení\\\" se z bezpečnostních důvodů vypne."; /* Shown in the alert dialog */ "Are you sure you want to reset all settings?" = "Opravdu chcete obnovit všechna nastavení?"; diff --git a/MonitorControl/View Controllers/Preferences/KeyboardPrefsViewController.swift b/MonitorControl/View Controllers/Preferences/KeyboardPrefsViewController.swift index 8bccf3a..74eddfc 100644 --- a/MonitorControl/View Controllers/Preferences/KeyboardPrefsViewController.swift +++ b/MonitorControl/View Controllers/Preferences/KeyboardPrefsViewController.swift @@ -4,6 +4,7 @@ import Cocoa import KeyboardShortcuts import ServiceManagement import Settings +import os.log class KeyboardPrefsViewController: NSViewController, SettingsPane { let paneIdentifier = Settings.PaneIdentifier.keyboard @@ -46,6 +47,10 @@ class KeyboardPrefsViewController: NSViewController, SettingsPane { @IBOutlet var rowUseAudioMouseText: NSGridRow! @IBOutlet var rowUseAudioNameText: NSGridRow! + // Accessibility troubleshooting UI + var accessibilityHelpButton: NSButton? + var resetAccessibilityButton: NSButton? + func updateGridLayout() { if self.keyboardBrightness.selectedTag() == KeyboardBrightness.media.rawValue { self.rowKeyboardBrightnessPopUp.bottomPadding = -13 @@ -141,9 +146,142 @@ class KeyboardPrefsViewController: NSViewController, SettingsPane { self.customVolumeDown.addSubview(customVolumeDownRecorder) self.customMute.addSubview(customMuteRecorder) + self.setupAccessibilityTroubleshootingUI() self.populateSettings() } + // MARK: - Accessibility Troubleshooting UI + + private func setupAccessibilityTroubleshootingUI() { + // Create container view - height matches other grid rows + let containerHeight: CGFloat = 25 + let containerView = NSView() + containerView.translatesAutoresizingMaskIntoConstraints = false + + // Create "Troubleshooting:" label to match the existing UI style + let label = NSTextField(labelWithString: NSLocalizedString("Troubleshooting:", comment: "Label for troubleshooting section")) + label.font = NSFont.systemFont(ofSize: 13) + label.textColor = NSColor.labelColor + label.alignment = .right + label.translatesAutoresizingMaskIntoConstraints = false + + // Create help button with system help style + let helpButton = NSButton() + helpButton.bezelStyle = .helpButton + helpButton.title = "" + helpButton.toolTip = NSLocalizedString("Keyboard shortcuts troubleshooting", comment: "Tooltip for help button") + helpButton.target = self + helpButton.action = #selector(showAccessibilityHelp(_:)) + helpButton.translatesAutoresizingMaskIntoConstraints = false + self.accessibilityHelpButton = helpButton + + // Create Reset Accessibility button + let resetButton = NSButton() + resetButton.title = NSLocalizedString("Reset Accessibility Permission", comment: "Button to reset accessibility") + resetButton.bezelStyle = .rounded + resetButton.toolTip = NSLocalizedString("Reset and re-request accessibility permission (fixes keyboard shortcuts on macOS Tahoe)", comment: "Tooltip for reset button") + resetButton.target = self + resetButton.action = #selector(resetAccessibilityPermission(_:)) + resetButton.translatesAutoresizingMaskIntoConstraints = false + self.resetAccessibilityButton = resetButton + + // Add all elements to container + containerView.addSubview(label) + containerView.addSubview(helpButton) + containerView.addSubview(resetButton) + + // Add container to the main view + self.view.addSubview(containerView) + + // Layout constraints - positioning to match grid spacing (10px from grid bottom) + NSLayoutConstraint.activate([ + // Container positioning - bottom of view with grid-like padding + containerView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 20), + containerView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -20), + containerView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -18), + containerView.heightAnchor.constraint(equalToConstant: containerHeight), + + // Label - right aligned at 212 points width to match storyboard column + label.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), + label.widthAnchor.constraint(equalToConstant: 210), + label.centerYAnchor.constraint(equalTo: containerView.centerYAnchor), + + // Help button - after label + helpButton.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 8), + helpButton.centerYAnchor.constraint(equalTo: containerView.centerYAnchor), + + // Reset button - after help button + resetButton.leadingAnchor.constraint(equalTo: helpButton.trailingAnchor, constant: 8), + resetButton.centerYAnchor.constraint(equalTo: containerView.centerYAnchor), + ]) + } + + @objc func showAccessibilityHelp(_ sender: NSButton) { + let alert = NSAlert() + alert.messageText = NSLocalizedString("Keyboard Shortcuts Not Working?", comment: "Alert title") + alert.informativeText = NSLocalizedString(""" +On macOS Tahoe (26+), you may need to reset accessibility permissions for keyboard shortcuts to work. + +To fix this: +1. Click "Reset Accessibility Permission" below +2. When prompted, click "Open System Settings" +3. Enable MonitorControl in the Accessibility list +4. Restart MonitorControl if needed + +Alternatively, go to: +System Settings → Privacy & Security → Accessibility +Remove MonitorControl, then add it back. +""", comment: "Accessibility troubleshooting guide") + alert.alertStyle = .informational + if #available(macOS 11.0, *) { + alert.icon = NSImage(systemSymbolName: "keyboard", accessibilityDescription: nil) + } + alert.addButton(withTitle: NSLocalizedString("OK", comment: "OK button")) + alert.addButton(withTitle: NSLocalizedString("Open Accessibility Settings", comment: "Button to open settings")) + + let response = alert.runModal() + if response == .alertSecondButtonReturn { + self.openAccessibilitySettings() + } + } + + @objc func resetAccessibilityPermission(_ sender: NSButton) { + let alert = NSAlert() + alert.messageText = NSLocalizedString("Reset Accessibility Permission?", comment: "Confirmation alert title") + alert.informativeText = NSLocalizedString("This will reset MonitorControl's accessibility permission. You will need to grant permission again for keyboard shortcuts to work.", comment: "Confirmation message") + alert.alertStyle = .warning + alert.addButton(withTitle: NSLocalizedString("Reset & Re-request", comment: "Reset button")) + alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel button")) + + let response = alert.runModal() + guard response == .alertFirstButtonReturn else { return } + + // Run tccutil to reset accessibility for this app + let bundleId = Bundle.main.bundleIdentifier ?? "me.guillaumeb.MonitorControl" + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/tccutil") + process.arguments = ["reset", "Accessibility", bundleId] + + do { + try process.run() + process.waitUntilExit() + os_log("Reset accessibility permission for %{public}@, exit code: %{public}d", type: .info, bundleId, process.terminationStatus) + } catch { + os_log("Failed to reset accessibility: %{public}@", type: .error, error.localizedDescription) + } + + // Re-prompt for accessibility permission + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + MediaKeyTapManager.acquirePrivileges() + } + } + + private func openAccessibilitySettings() { + if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility") { + NSWorkspace.shared.open(url) + } + } + func populateSettings() { self.keyboardBrightness.selectItem(withTag: prefs.integer(forKey: PrefKey.keyboardBrightness.rawValue)) self.keyboardVolume.selectItem(withTag: prefs.integer(forKey: PrefKey.keyboardVolume.rawValue)) @@ -224,3 +362,4 @@ class KeyboardPrefsViewController: NSViewController, SettingsPane { self.updateGridLayout() } } + diff --git a/MonitorControlHelper/Info.plist b/MonitorControlHelper/Info.plist index e6cc0a2..de3f068 100644 --- a/MonitorControlHelper/Info.plist +++ b/MonitorControlHelper/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 7141 + 7168 LSApplicationCategoryType public.app-category.utilities LSBackgroundOnly