From 98c826aaaebd134bc68c732ef24eb2a2399fffc2 Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Sat, 6 Sep 2025 21:54:19 +0530 Subject: [PATCH] Fix: Brightness and Volume Control --- DynamicIsland.xcodeproj/project.pbxproj | 17 ++ .../xcshareddata/swiftpm/Package.resolved | 11 +- DynamicIsland/ContentView.swift | 28 ++- DynamicIsland/DynamicIslandApp.swift | 82 +++++++++ .../DynamicIslandViewCoordinator.swift | 7 +- .../Settings/SettingsWindowController.swift | 23 ++- .../DynamicIslandExtensionManager.swift | 5 +- .../managers/SystemChangesObserver.swift | 165 +++++++++++------- DynamicIsland/managers/SystemHUDManager.swift | 49 ++++-- .../managers/SystemKeyObserver.swift | 4 + DynamicIsland/managers/SystemOSDManager.swift | 54 +++++- 11 files changed, 344 insertions(+), 101 deletions(-) diff --git a/DynamicIsland.xcodeproj/project.pbxproj b/DynamicIsland.xcodeproj/project.pbxproj index c730875..e11cfe4 100644 --- a/DynamicIsland.xcodeproj/project.pbxproj +++ b/DynamicIsland.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 7E25ECA12E6C6C3800700E72 /* CoreBrightness.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E25ECA02E6C6C3800700E72 /* CoreBrightness.framework */; }; 7E25ECA32E6C6E2600700E72 /* OSD.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E25ECA22E6C6E2600700E72 /* OSD.framework */; }; 7E25ECA52E6C6E7800700E72 /* DisplayServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E25ECA42E6C6E7800700E72 /* DisplayServices.framework */; }; + 7E25ECB52E6C8BFB00700E72 /* MediaKeyTap in Frameworks */ = {isa = PBXBuildFile; productRef = 7E25ECB42E6C8BFB00700E72 /* MediaKeyTap */; }; 7E6118142E24071E0050006C /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = 14D032192C68F32E0096E6A1 /* LaunchAtLogin */; }; 7E6118152E24071E0050006C /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 14D0321C2C68F3350096E6A1 /* Sparkle */; }; 7E6118162E24071E0050006C /* KeyboardShortcuts in Frameworks */ = {isa = PBXBuildFile; productRef = B18654382C6F4990000B926A /* KeyboardShortcuts */; }; @@ -75,6 +76,7 @@ 7E61181A2E24071E0050006C /* SwiftUIIntrospect in Frameworks */, 7E25ECA32E6C6E2600700E72 /* OSD.framework in Frameworks */, 7E25ECA52E6C6E7800700E72 /* DisplayServices.framework in Frameworks */, + 7E25ECB52E6C8BFB00700E72 /* MediaKeyTap in Frameworks */, 7E6118192E24071E0050006C /* Defaults in Frameworks */, 7E6118182E24071E0050006C /* TheBoringWorkerNotifier in Frameworks */, 7E6118172E24071E0050006C /* Collections in Frameworks */, @@ -157,6 +159,7 @@ B1628B912CC260C0003D8DF3 /* SwiftUIIntrospect */, B194240A2CD10017003E5DC2 /* LottieUI */, 146BC4362D366E9B0047C66A /* MacroVisionKit */, + 7E25ECB42E6C8BFB00700E72 /* MediaKeyTap */, ); productName = DynamicIsland; productReference = F54406E72CD4987A00C7AFD9 /* DynamicIsland.app */; @@ -197,6 +200,7 @@ B1628B902CC260C0003D8DF3 /* XCRemoteSwiftPackageReference "swiftui-introspect" */, B19424072CD0DB52003E5DC2 /* XCRemoteSwiftPackageReference "LottieUI" */, 146BC4352D366E9B0047C66A /* XCRemoteSwiftPackageReference "MacroVisionKit" */, + 7E25ECB32E6C8BFB00700E72 /* XCRemoteSwiftPackageReference "MediaKeyTap" */, ); preferredProjectObjectVersion = 77; productRefGroup = F54406E82CD4987A00C7AFD9 /* Products */; @@ -487,6 +491,14 @@ minimumVersion = 2.6.4; }; }; + 7E25ECB32E6C8BFB00700E72 /* XCRemoteSwiftPackageReference "MediaKeyTap" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/nhurden/MediaKeyTap"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.3.0; + }; + }; 9A987A112C73CAA1005CA465 /* XCRemoteSwiftPackageReference "Pow" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/EmergeTools/Pow"; @@ -558,6 +570,11 @@ package = 14D0321B2C68F3350096E6A1 /* XCRemoteSwiftPackageReference "Sparkle" */; productName = Sparkle; }; + 7E25ECB42E6C8BFB00700E72 /* MediaKeyTap */ = { + isa = XCSwiftPackageProductDependency; + package = 7E25ECB32E6C8BFB00700E72 /* XCRemoteSwiftPackageReference "MediaKeyTap" */; + productName = MediaKeyTap; + }; 9A987A0F2C73CA8D005CA465 /* Collections */ = { isa = XCSwiftPackageProductDependency; package = F54407082CD4A23F00C7AFD9 /* XCRemoteSwiftPackageReference "swift-collections" */; diff --git a/DynamicIsland.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DynamicIsland.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d8400f2..e6d1490 100644 --- a/DynamicIsland.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DynamicIsland.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "2741287b65cf0758abd89baadf314f5f8cbfcb46cb8d236a83f98731e5501ee3", + "originHash" : "424f1d62134dd1f9036d13269149d3ce62d9433217dddecb8fb77a10d8189a85", "pins" : [ { "identity" : "defaults", @@ -55,6 +55,15 @@ "version" : "0.1.0" } }, + { + "identity" : "mediakeytap", + "kind" : "remoteSourceControl", + "location" : "https://github.com/nhurden/MediaKeyTap", + "state" : { + "revision" : "7d1b8169d228ef6e5ec7db676a348f2f3e50c734", + "version" : "2.3.0" + } + }, { "identity" : "pow", "kind" : "remoteSourceControl", diff --git a/DynamicIsland/ContentView.swift b/DynamicIsland/ContentView.swift index d0639dd..a3fce63 100644 --- a/DynamicIsland/ContentView.swift +++ b/DynamicIsland/ContentView.swift @@ -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() } diff --git a/DynamicIsland/DynamicIslandApp.swift b/DynamicIsland/DynamicIslandApp.swift index e016eef..fc9bf04 100644 --- a/DynamicIsland/DynamicIslandApp.swift +++ b/DynamicIsland/DynamicIslandApp.swift @@ -87,8 +87,21 @@ class AppDelegate: NSObject, NSApplicationDelegate { private var onboardingWindowController: NSWindowController? private var cancellables = Set() + // 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() +// +// // 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() diff --git a/DynamicIsland/DynamicIslandViewCoordinator.swift b/DynamicIsland/DynamicIslandViewCoordinator.swift index 23c4553..6147995 100644 --- a/DynamicIsland/DynamicIslandViewCoordinator.swift +++ b/DynamicIsland/DynamicIslandViewCoordinator.swift @@ -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 } } diff --git a/DynamicIsland/components/Settings/SettingsWindowController.swift b/DynamicIsland/components/Settings/SettingsWindowController.swift index c7e99da..1dbf35b 100644 --- a/DynamicIsland/components/Settings/SettingsWindowController.swift +++ b/DynamicIsland/components/Settings/SettingsWindowController.swift @@ -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) + } } } diff --git a/DynamicIsland/managers/DynamicIslandExtensionManager.swift b/DynamicIsland/managers/DynamicIslandExtensionManager.swift index 0f70175..4c6e417 100644 --- a/DynamicIsland/managers/DynamicIslandExtensionManager.swift +++ b/DynamicIsland/managers/DynamicIslandExtensionManager.swift @@ -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 })") + } } } diff --git a/DynamicIsland/managers/SystemChangesObserver.swift b/DynamicIsland/managers/SystemChangesObserver.swift index 1da8eed..d656e3e 100644 --- a/DynamicIsland/managers/SystemChangesObserver.swift +++ b/DynamicIsland/managers/SystemChangesObserver.swift @@ -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") } -} \ No newline at end of file +} + + diff --git a/DynamicIsland/managers/SystemHUDManager.swift b/DynamicIsland/managers/SystemHUDManager.swift index 13965a1..34153e6 100644 --- a/DynamicIsland/managers/SystemHUDManager.swift +++ b/DynamicIsland/managers/SystemHUDManager.swift @@ -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() + /// 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 diff --git a/DynamicIsland/managers/SystemKeyObserver.swift b/DynamicIsland/managers/SystemKeyObserver.swift index e0b72b8..c6e5a65 100644 --- a/DynamicIsland/managers/SystemKeyObserver.swift +++ b/DynamicIsland/managers/SystemKeyObserver.swift @@ -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 diff --git a/DynamicIsland/managers/SystemOSDManager.swift b/DynamicIsland/managers/SystemOSDManager.swift index fb2e369..9a9cfe2 100644 --- a/DynamicIsland/managers/SystemOSDManager.swift +++ b/DynamicIsland/managers/SystemOSDManager.swift @@ -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) + } + } + } } \ No newline at end of file