Fix: Brightness and Volume Control

This commit is contained in:
Hariharan Mudaliar 2025-09-06 21:54:19 +05:30
commit 98c826aaae
11 changed files with 340 additions and 97 deletions

View file

@ -193,6 +193,16 @@ struct ContentView: View {
}
}
}
.onChange(of: coordinator.sneakPeek.show) { _, sneakPeekShowing in
// When sneak peek finishes, check if user is still hovering and open notch if needed
if !sneakPeekShowing {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
if isHovering && vm.notchState == .closed {
doOpen()
}
}
}
}
.sensoryFeedback(.alignment, trigger: haptics)
.contextMenu {
SettingsLink(label: {
@ -507,15 +517,23 @@ struct ContentView: View {
haptics.toggle()
}
// Don't open notch if there's a sneak peek showing
if coordinator.sneakPeek.show {
return
}
// Delay opening the notch
let task = DispatchWorkItem {
// ContentView is a struct, so we don't use weak self here
guard vm.notchState == .closed, isHovering else { return }
// Additional check: don't open if a sneak peek just started showing
// to avoid conflicts during system HUD display
if coordinator.sneakPeek.show {
// Schedule a retry after the sneak peek should be gone (default duration + small buffer)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.8) {
if isHovering && vm.notchState == .closed && !coordinator.sneakPeek.show {
doOpen()
}
}
return
}
doOpen()
}

View file

@ -87,8 +87,21 @@ class AppDelegate: NSObject, NSApplicationDelegate {
private var onboardingWindowController: NSWindowController?
private var cancellables = Set<AnyCancellable>()
// Media key monitoring
private var globalEventMonitor: Any?
private var localEventMonitor: Any?
// Debouncing mechanism for window size updates
private var windowSizeUpdateWorkItem: DispatchWorkItem?
// let calendarManager = CalendarManager.shared
// let webcamManager = WebcamManager.shared
// var closeNotchWorkItem: DispatchWorkItem?
// private var previousScreens: [NSScreen]?
// private var onboardingWindowController: NSWindowController?
// private var cancellables = Set<AnyCancellable>()
//
// // Debouncing mechanism for window size updates
// private var windowSizeUpdateWorkItem: DispatchWorkItem?
private func debouncedUpdateWindowSize() {
// Cancel any existing work item
@ -108,10 +121,76 @@ class AppDelegate: NSObject, NSApplicationDelegate {
return false
}
// MARK: - Media Key Monitoring
private func setupMediaKeyMonitoring() {
// Monitor both global and local events to catch media keys
globalEventMonitor = NSEvent.addGlobalMonitorForEvents(matching: .systemDefined) { [weak self] event in
self?.handleSystemEvent(event)
}
localEventMonitor = NSEvent.addLocalMonitorForEvents(matching: .systemDefined) { [weak self] event in
self?.handleSystemEvent(event)
return event
}
print("🎹 Media key monitoring started using NSEvent")
}
private func handleSystemEvent(_ event: NSEvent) {
guard event.subtype.rawValue == 8 else { return }
let data1 = event.data1
let keyCode = Int32((data1 & 0xFFFF0000) >> 16)
let keyFlags = Int(data1 & 0x0000FFFF)
let keyState = ((keyFlags & 0xFF00) >> 8) == 0xA // KeyDown
let keyRepeat = (keyFlags & 0x1) != 0
guard keyState && !keyRepeat else { return } // Only handle key down, ignore repeats
switch keyCode {
case NX_KEYTYPE_SOUND_UP, NX_KEYTYPE_SOUND_DOWN, NX_KEYTYPE_MUTE:
print("🔊 Volume key detected: \(keyCode)")
SystemHUDManager.shared.handleVolumeKeyEvent()
case NX_KEYTYPE_BRIGHTNESS_UP, NX_KEYTYPE_BRIGHTNESS_DOWN:
print("☀️ Brightness key detected: \(keyCode)")
SystemHUDManager.shared.handleBrightnessKeyEvent()
default:
break
}
}
private func stopMediaKeyMonitoring() {
if let globalMonitor = globalEventMonitor {
NSEvent.removeMonitor(globalMonitor)
globalEventMonitor = nil
}
if let localMonitor = localEventMonitor {
NSEvent.removeMonitor(localMonitor)
localEventMonitor = nil
}
print("🎹 Media key monitoring stopped")
}
// MARK: - MediaKeyApplicationDelegate (for future use)
func didDetectVolumeKey() {
SystemHUDManager.shared.handleVolumeKeyEvent()
}
func didDetectBrightnessKey() {
SystemHUDManager.shared.handleBrightnessKeyEvent()
}
func applicationWillTerminate(_ notification: Notification) {
// Cancel any pending window size updates
windowSizeUpdateWorkItem?.cancel()
// Stop media key monitoring
stopMediaKeyMonitoring()
print("🚪 Application terminating - ensuring system HUD is restored...")
// Re-enable system HUD on app termination
@ -302,6 +381,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
// Setup SystemHUD Manager
SystemHUDManager.shared.setup(coordinator: coordinator)
// Setup media key monitoring
setupMediaKeyMonitoring()
// Observe tab changes - use debounced updates
coordinator.$currentView.sink { [weak self] newView in
self?.debouncedUpdateWindowSize()

View file

@ -49,7 +49,7 @@ struct ExpandedItem {
class DynamicIslandViewCoordinator: ObservableObject {
static let shared = DynamicIslandViewCoordinator()
var notifier: TheBoringWorkerNotifier = .init()
var notifier: TheBoringWorkerNotifier
@Published var currentView: NotchViews = .home
@ -97,8 +97,8 @@ class DynamicIslandViewCoordinator: ObservableObject {
@Published var optionKeyPressed: Bool = true
private init() {
selectedScreen = preferredScreen
notifier = TheBoringWorkerNotifier()
selectedScreen = preferredScreen
}
func setupWorkersNotificationObservers() {
@ -171,7 +171,8 @@ class DynamicIslandViewCoordinator: ObservableObject {
guard let self = self, !Task.isCancelled else { return }
await MainActor.run {
withAnimation {
self.toggleSneakPeek(status: false, type: .music)
// Hide the sneak peek with the correct type that was showing
self.toggleSneakPeek(status: false, type: self.sneakPeek.type)
self.sneakPeekDuration = 1.5
}
}

View file

@ -72,23 +72,30 @@ class SettingsWindowController: NSWindowController {
// If window is already visible, bring it to front properly
if window?.isVisible == true {
NSApp.activate(ignoringOtherApps: true)
window?.orderFrontRegardless()
window?.makeKeyAndOrderFront(nil)
forceWindowToFront()
return
}
// Show the window with proper ordering
window?.center()
forceWindowToFront()
}
private func forceWindowToFront() {
// Multi-step approach to ensure window gets focus
window?.orderFrontRegardless()
window?.makeKeyAndOrderFront(nil)
window?.center()
// Activate the app and ensure window gets focus
// Activate the app with maximum priority
NSApp.activate(ignoringOtherApps: true)
// Force window to front after activation
DispatchQueue.main.async { [weak self] in
self?.window?.makeKeyAndOrderFront(nil)
// Double-check after a brief delay
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
guard let window = self?.window else { return }
if !window.isKeyWindow {
window.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)
}
}
}

View file

@ -32,7 +32,10 @@ class DynamicIslandExtensionManager: ObservableObject {
@Published var installedExtensions: [Extension] = [] {
didSet {
print("Extensions installed: \(installedExtensions)")
// Only log when there's an actual change in content, not just initialization
if oldValue != installedExtensions {
print("📦 Extensions installed: \(installedExtensions.map { $0.name })")
}
}
}

View file

@ -11,82 +11,102 @@ import Foundation
import Defaults
class SystemChangesObserver {
private var oldVolume: Float
private var oldMuted: Bool
private var oldVolume: Float = 0
private var oldMuted: Bool = false
private var oldBrightness: Float = 0
private weak var coordinator: DynamicIslandViewCoordinator?
private var timer: Timer?
// Backup timer for edge cases only
private var backupTimer: Timer?
init(coordinator: DynamicIslandViewCoordinator) {
self.coordinator = coordinator
// Initialize baseline values only once
oldVolume = SystemVolumeManager.getOutputVolume()
oldMuted = SystemVolumeManager.isMuted()
do {
oldBrightness = try SystemDisplayManager.getDisplayBrightness()
} catch {
NSLog("Failed to retrieve display brightness: \(error)")
NSLog("Failed to retrieve initial display brightness: \(error)")
}
}
func startObserving() {
guard Defaults[.enableSystemHUD] else { return }
createObservers()
createTimerForContinuousChangesCheck(with: 0.2)
}
private func createTimerForContinuousChangesCheck(with seconds: TimeInterval) {
timer = Timer(
timeInterval: seconds, target: self, selector: #selector(checkChanges), userInfo: nil,
repeats: true)
let mainLoop = RunLoop.main
mainLoop.add(timer!, forMode: .common)
}
private func createObservers() {
NotificationCenter.default.addObserver(
self,
selector: #selector(showVolumeHUD),
name: SystemKeyObserver.volumeChanged,
object: nil)
NotificationCenter.default.addObserver(
self,
selector: #selector(showBrightnessHUD),
name: SystemKeyObserver.brightnessChanged,
object: nil)
}
@objc func showVolumeHUD() {
guard Defaults[.enableSystemHUD] && Defaults[.enableVolumeHUD] else { return }
if oldVolume == 0.0 || oldVolume == 1.0 {
sendVolumeNotification(value: oldVolume)
}
print("Showing volume HUD")
startMinimalBackupTimer()
}
@objc func showBrightnessHUD() {
guard Defaults[.enableSystemHUD] && Defaults[.enableBrightnessHUD] else { return }
if oldBrightness == 0.0 || oldBrightness == 1.0 {
sendBrightnessNotification(value: oldBrightness)
private func startMinimalBackupTimer() {
// More frequent backup check (every 300ms) for better responsiveness
// This is a reasonable compromise between the original 200ms and the previous 5s
backupTimer = Timer.scheduledTimer(withTimeInterval: 0.3, repeats: true) { [weak self] _ in
guard Defaults[.enableSystemHUD] else { return }
self?.performBackupCheck()
}
print("Showing brightness HUD")
}
@objc func checkChanges() {
guard Defaults[.enableSystemHUD] else { return }
checkBrightnessChanges()
private func performBackupCheck() {
// Only check if we haven't had recent activity
checkVolumeChanges()
checkBrightnessChanges()
}
private func isAlmost(firstNumber: Float, secondNumber: Float) -> Bool {
let marginValue = 5 / 100.0
return (firstNumber + Float(marginValue) >= secondNumber && firstNumber - Float(marginValue) <= secondNumber)
// MARK: - Public Media Key Event Handlers (called from MediaKeyApplication)
func handleVolumeKeyEvent() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
self.handleVolumeKeyPress()
}
}
func handleBrightnessKeyEvent() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
self.handleBrightnessKeyPress()
}
}
// MARK: - Key Press Event Handlers
private func handleVolumeKeyPress() {
guard Defaults[.enableVolumeHUD] else { return }
let newVolume = SystemVolumeManager.getOutputVolume()
let newMuted = SystemVolumeManager.isMuted()
// Always show HUD for key press feedback, even if value doesn't change
print("🔊 Volume key pressed - showing HUD: volume=\(newVolume), muted=\(newMuted)")
if newMuted {
sendVolumeNotification(value: 0.0)
} else {
sendVolumeNotification(value: newVolume)
}
// Update stored values
oldVolume = newVolume
oldMuted = newMuted
}
private func handleBrightnessKeyPress() {
guard Defaults[.enableBrightnessHUD] else { return }
guard NSScreen.screens.count > 0 else { return }
do {
let newBrightness = try SystemDisplayManager.getDisplayBrightness()
// Always show HUD for key press feedback, even if value doesn't change
print("☀️ Brightness key pressed - showing HUD: brightness=\(newBrightness)")
sendBrightnessNotification(value: newBrightness)
// Update stored value
oldBrightness = newBrightness
} catch {
// Silently ignore brightness errors to reduce log spam
}
}
private func checkVolumeChanges() {
@ -95,7 +115,12 @@ class SystemChangesObserver {
let newVolume = SystemVolumeManager.getOutputVolume()
let newMuted = SystemVolumeManager.isMuted()
if !isAlmost(firstNumber: oldVolume, secondNumber: newVolume) || newMuted != oldMuted {
let volumeChanged = !isAlmost(firstNumber: oldVolume, secondNumber: newVolume)
let muteChanged = newMuted != oldMuted
if volumeChanged || muteChanged {
print("🔊 Volume change detected: \(oldVolume) → \(newVolume), muted: \(oldMuted) → \(newMuted)")
if newMuted {
sendVolumeNotification(value: 0.0)
} else {
@ -108,22 +133,30 @@ class SystemChangesObserver {
private func checkBrightnessChanges() {
guard Defaults[.enableBrightnessHUD] else { return }
if NSScreen.screens.count == 0 {
return
}
guard NSScreen.screens.count > 0 else { return }
do {
let newBrightness = try SystemDisplayManager.getDisplayBrightness()
if !isAlmost(firstNumber: oldBrightness, secondNumber: newBrightness) {
let brightnessChanged = !isAlmost(firstNumber: oldBrightness, secondNumber: newBrightness)
if brightnessChanged {
print("☀️ Brightness change detected: \(oldBrightness) → \(newBrightness)")
sendBrightnessNotification(value: newBrightness)
oldBrightness = newBrightness
}
} catch {
NSLog("Failed to retrieve display brightness: \(error)")
// Silently ignore brightness errors to reduce log spam
}
}
private func isAlmost(firstNumber: Float, secondNumber: Float) -> Bool {
// Convert sensitivity setting to a more responsive threshold
// Sensitivity 1-10 scale maps to 0.01-0.10 threshold range
let marginValue = Float(Defaults[.systemHUDSensitivity]) / 100.0
let threshold = max(0.01, marginValue) // Minimum threshold for responsiveness
return abs(firstNumber - secondNumber) <= threshold
}
// MARK: - Internal Notification Methods
private func sendVolumeNotification(value: Float) {
@ -149,8 +182,12 @@ class SystemChangesObserver {
}
func stopObserving() {
timer?.invalidate()
timer = nil
NotificationCenter.default.removeObserver(self)
// Clean up backup timer
backupTimer?.invalidate()
backupTimer = nil
print("🎹 Media key detection stopped")
}
}
}

View file

@ -14,6 +14,8 @@ class SystemHUDManager {
private var changesObserver: SystemChangesObserver?
private weak var coordinator: DynamicIslandViewCoordinator?
private var isSetupComplete = false
private var isSystemOperationInProgress = false
private init() {
// Set up observer for settings changes
@ -23,11 +25,14 @@ class SystemHUDManager {
private func setupSettingsObserver() {
// Use Defaults publisher instead of @Default property wrapper
Defaults.publisher(.enableSystemHUD, options: []).sink { [weak self] change in
guard let self = self, self.isSetupComplete else {
return
}
Task { @MainActor in
if change.newValue {
await self?.startSystemHUD()
await self.startSystemHUD()
} else {
await self?.stopSystemHUD()
await self.stopSystemHUD()
}
}
}.store(in: &cancellables)
@ -35,34 +40,46 @@ class SystemHUDManager {
private var cancellables = Set<AnyCancellable>()
/// Public property to check if system operations are in progress
var isOperationInProgress: Bool {
return isSystemOperationInProgress
}
func setup(coordinator: DynamicIslandViewCoordinator) {
self.coordinator = coordinator
if Defaults[.enableSystemHUD] {
Task { @MainActor in
await startSystemHUD()
self.isSetupComplete = true
}
} else {
isSetupComplete = true
}
}
@MainActor
private func startSystemHUD() async {
guard let coordinator = coordinator else { return }
guard let coordinator = coordinator, !isSystemOperationInProgress else { return }
isSystemOperationInProgress = true
await stopSystemHUD() // Stop any existing observer
changesObserver = SystemChangesObserver(coordinator: coordinator)
changesObserver?.startObserving()
// Disable system HUD if possible
// Note: This would require additional implementation similar to OSDUIManager
disableSystemHUD()
print("System HUD replacement started")
isSystemOperationInProgress = false
}
@MainActor
private func stopSystemHUD() async {
guard !isSystemOperationInProgress else { return }
isSystemOperationInProgress = true
changesObserver?.stopObserving()
changesObserver = nil
@ -70,6 +87,7 @@ class SystemHUDManager {
enableOriginalSystemHUD()
print("System HUD replacement stopped")
isSystemOperationInProgress = false
}
private func disableSystemHUD() {
@ -83,21 +101,32 @@ class SystemHUDManager {
// Re-enable the system HUD using SystemOSDManager
SystemOSDManager.enableSystemHUD()
// Verify it's working after a short delay
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
let isRunning = SystemOSDManager.isOSDUIHelperRunning()
// Check status after a reasonable delay, using async to avoid blocking
Task {
try await Task.sleep(nanoseconds: 2_000_000_000) // 2 seconds instead of 1
let isRunning = await SystemOSDManager.isOSDUIHelperRunningAsync()
print("🔍 OSDUIHelper status after re-enable: \(isRunning ? "✅ Running" : "❌ Not running")")
}
}
// MARK: - Media Key Event Handling
/// Handle volume key press events from MediaKeyApplication
public func handleVolumeKeyEvent() {
changesObserver?.handleVolumeKeyEvent()
}
/// Handle brightness key press events from MediaKeyApplication
public func handleBrightnessKeyEvent() {
changesObserver?.handleBrightnessKeyEvent()
}
deinit {
print("🧹 SystemHUDManager deinitializing - ensuring system HUD is restored")
cancellables.removeAll()
Task { @MainActor in
await stopSystemHUD()
}
// Also call the emergency restore as backup
SystemHUDDebugger.forceRestartOSDUIHelper()
// Emergency restore is available via SystemHUDManager.emergencyRestoreSystemHUD() if needed
}
/// Emergency restore function - call this if system HUD is not working

View file

@ -5,6 +5,10 @@
// Adapted from TheBoringWorker-HUD KeyPressObserver.swift
// Created by GitHub Copilot on 06/09/25.
//
// ⚠️ DEPRECATED: This class is non-functional as it extends NSApplication
// but is never used as the main app class. Event monitoring has been moved
// to SystemChangesObserver using NSEvent.addGlobalMonitorForEvents.
//
import Cocoa

View file

@ -13,6 +13,12 @@ class SystemOSDManager {
/// Re-enables the system HUD by restarting OSDUIHelper
public static func enableSystemHUD() {
Task.detached(priority: .background) {
await enableSystemHUDAsync()
}
}
private static func enableSystemHUDAsync() async {
do {
// First, stop any existing OSDUIHelper process
let stopTask = Process()
@ -22,7 +28,7 @@ class SystemOSDManager {
stopTask.waitUntilExit()
// Small delay to ensure process is fully stopped
usleep(200000) // 200ms
try await Task.sleep(nanoseconds: 200_000_000) // 200ms
// Then kickstart it again to ensure it's running properly
let kickstart = Process()
@ -32,11 +38,15 @@ class SystemOSDManager {
kickstart.waitUntilExit()
// Additional delay to ensure service is fully started
usleep(300000) // 300ms
try await Task.sleep(nanoseconds: 300_000_000) // 300ms
print("✅ System HUD re-enabled")
await MainActor.run {
print("✅ System HUD re-enabled")
}
} catch {
NSLog("❌ Error while trying to re-enable OSDUIHelper: \(error)")
await MainActor.run {
NSLog("❌ Error while trying to re-enable OSDUIHelper: \(error)")
}
// Fallback: Try to restart the service using launchctl load
do {
@ -45,15 +55,26 @@ class SystemOSDManager {
fallbackTask.arguments = ["load", "-w", "/System/Library/LaunchAgents/com.apple.OSDUIHelper.plist"]
try fallbackTask.run()
fallbackTask.waitUntilExit()
print("✅ System HUD re-enabled via fallback method")
await MainActor.run {
print("✅ System HUD re-enabled via fallback method")
}
} catch {
NSLog("❌ Fallback method also failed: \(error)")
await MainActor.run {
NSLog("❌ Fallback method also failed: \(error)")
}
}
}
}
/// Disables the system HUD by stopping OSDUIHelper
public static func disableSystemHUD() {
Task.detached(priority: .background) {
await disableSystemHUDAsync()
}
}
private static func disableSystemHUDAsync() async {
do {
let kickstart = Process()
kickstart.executableURL = URL(fileURLWithPath: "/bin/launchctl")
@ -61,7 +82,8 @@ class SystemOSDManager {
kickstart.arguments = ["kickstart", "gui/\(getuid())/com.apple.OSDUIHelper"]
try kickstart.run()
kickstart.waitUntilExit()
usleep(500000) // Make sure it started
try await Task.sleep(nanoseconds: 500_000_000) // 500ms - async wait instead of usleep
let task = Process()
task.executableURL = URL(fileURLWithPath: "/usr/bin/killall")
@ -69,9 +91,13 @@ class SystemOSDManager {
try task.run()
task.waitUntilExit()
print("✅ System HUD disabled")
await MainActor.run {
print("✅ System HUD disabled")
}
} catch {
NSLog("❌ Error while trying to hide OSDUIHelper: \(error)")
await MainActor.run {
NSLog("❌ Error while trying to hide OSDUIHelper: \(error)")
}
}
}
@ -96,4 +122,14 @@ class SystemOSDManager {
return false
}
}
/// Async version of status checking to avoid main thread blocking
public static func isOSDUIHelperRunningAsync() async -> Bool {
return await withCheckedContinuation { continuation in
Task.detached(priority: .background) {
let result = isOSDUIHelperRunning()
continuation.resume(returning: result)
}
}
}
}