From 1a45d4e7f4eb1dbd8dfb0143c0fd6dee496b078b Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Sat, 6 Sep 2025 19:28:38 +0530 Subject: [PATCH 1/4] Working Indicators --- DynamicIsland.xcodeproj/project.pbxproj | 20 +++ DynamicIsland/DynamicIsland.entitlements | 12 -- DynamicIsland/DynamicIslandApp.swift | 7 + DynamicIsland/Localizable.xcstrings | 27 ++++ .../components/Settings/SettingsView.swift | 59 ++++++- DynamicIsland/helpers/AppleScriptError.swift | 15 ++ DynamicIsland/helpers/AppleScriptRunner.swift | 28 ++++ DynamicIsland/helpers/SensorError.swift | 22 +++ DynamicIsland/helpers/SensorMethod.swift | 15 ++ .../managers/SystemChangesObserver.swift | 153 ++++++++++++++++++ .../managers/SystemDisplayManager.swift | 73 +++++++++ DynamicIsland/managers/SystemHUDManager.swift | 82 ++++++++++ .../managers/SystemKeyObserver.swift | 44 +++++ DynamicIsland/managers/SystemOSDManager.swift | 47 ++++++ .../managers/SystemVolumeManager.swift | 35 ++++ DynamicIsland/models/Constants.swift | 7 + 16 files changed, 629 insertions(+), 17 deletions(-) create mode 100644 DynamicIsland/helpers/AppleScriptError.swift create mode 100644 DynamicIsland/helpers/AppleScriptRunner.swift create mode 100644 DynamicIsland/helpers/SensorError.swift create mode 100644 DynamicIsland/helpers/SensorMethod.swift create mode 100644 DynamicIsland/managers/SystemChangesObserver.swift create mode 100644 DynamicIsland/managers/SystemDisplayManager.swift create mode 100644 DynamicIsland/managers/SystemHUDManager.swift create mode 100644 DynamicIsland/managers/SystemKeyObserver.swift create mode 100644 DynamicIsland/managers/SystemOSDManager.swift create mode 100644 DynamicIsland/managers/SystemVolumeManager.swift diff --git a/DynamicIsland.xcodeproj/project.pbxproj b/DynamicIsland.xcodeproj/project.pbxproj index b03bbc4..c730875 100644 --- a/DynamicIsland.xcodeproj/project.pbxproj +++ b/DynamicIsland.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 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 */; }; 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 */; }; @@ -39,6 +42,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 7E25ECA02E6C6C3800700E72 /* CoreBrightness.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBrightness.framework; path = /System/Library/PrivateFrameworks/CoreBrightness.framework; sourceTree = ""; }; + 7E25ECA22E6C6E2600700E72 /* OSD.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OSD.framework; path = /System/Library/PrivateFrameworks/OSD.framework; sourceTree = ""; }; + 7E25ECA42E6C6E7800700E72 /* DisplayServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DisplayServices.framework; path = /System/Library/PrivateFrameworks/DisplayServices.framework; sourceTree = ""; }; 7E81303D2E3FB40500E08F3F /* mediaremote-adapter.pl */ = {isa = PBXFileReference; lastKnownFileType = text.script.perl; path = "mediaremote-adapter.pl"; sourceTree = ""; }; 7E81303E2E3FB40500E08F3F /* NowPlayingTestClient */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = NowPlayingTestClient; sourceTree = ""; }; 7EE1D73C2E6B1E0B00997219 /* ApplicationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ApplicationServices.framework; path = System/Library/Frameworks/ApplicationServices.framework; sourceTree = SDKROOT; }; @@ -60,12 +66,15 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 7E25ECA12E6C6C3800700E72 /* CoreBrightness.framework in Frameworks */, 7EE1D73F2E6B1E1200997219 /* IOKit.framework in Frameworks */, 7EE1D73D2E6B1E0B00997219 /* ApplicationServices.framework in Frameworks */, BA90505C953246D0AB206451 /* MediaRemoteAdapter.framework in Frameworks */, 7E61181C2E24071E0050006C /* MacroVisionKit in Frameworks */, 7E61181B2E24071E0050006C /* LottieUI in Frameworks */, 7E61181A2E24071E0050006C /* SwiftUIIntrospect in Frameworks */, + 7E25ECA32E6C6E2600700E72 /* OSD.framework in Frameworks */, + 7E25ECA52E6C6E7800700E72 /* DisplayServices.framework in Frameworks */, 7E6118192E24071E0050006C /* Defaults in Frameworks */, 7E6118182E24071E0050006C /* TheBoringWorkerNotifier in Frameworks */, 7E6118172E24071E0050006C /* Collections in Frameworks */, @@ -91,6 +100,9 @@ 7E81304F2E3FB42700E08F3F /* Frameworks */ = { isa = PBXGroup; children = ( + 7E25ECA02E6C6C3800700E72 /* CoreBrightness.framework */, + 7E25ECA42E6C6E7800700E72 /* DisplayServices.framework */, + 7E25ECA22E6C6E2600700E72 /* OSD.framework */, 7EE1D73E2E6B1E1200997219 /* IOKit.framework */, 7EE1D73C2E6B1E0B00997219 /* ApplicationServices.framework */, ); @@ -371,6 +383,10 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; + SYSTEM_FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + ); }; name = Debug; }; @@ -408,6 +424,10 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; + SYSTEM_FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + ); }; name = Release; }; diff --git a/DynamicIsland/DynamicIsland.entitlements b/DynamicIsland/DynamicIsland.entitlements index 2f52f33..2e86356 100644 --- a/DynamicIsland/DynamicIsland.entitlements +++ b/DynamicIsland/DynamicIsland.entitlements @@ -2,20 +2,8 @@ - com.apple.security.app-sandbox - com.apple.security.automation.apple-events - com.apple.security.device.camera - - com.apple.security.files.user-selected.read-write - - com.apple.security.network.client - - com.apple.security.network.server - - com.apple.security.personal-information.calendars - com.apple.security.temporary-exception.apple-events com.spotify.client diff --git a/DynamicIsland/DynamicIslandApp.swift b/DynamicIsland/DynamicIslandApp.swift index 5c14556..19fa7cd 100644 --- a/DynamicIsland/DynamicIslandApp.swift +++ b/DynamicIsland/DynamicIslandApp.swift @@ -111,6 +111,10 @@ class AppDelegate: NSObject, NSApplicationDelegate { func applicationWillTerminate(_ notification: Notification) { // Cancel any pending window size updates windowSizeUpdateWorkItem?.cancel() + + // Re-enable system HUD on app termination + SystemOSDManager.enableSystemHUD() + NotificationCenter.default.removeObserver(self) } @@ -290,6 +294,9 @@ class AppDelegate: NSObject, NSApplicationDelegate { coordinator.setupWorkersNotificationObservers() + // Setup SystemHUD Manager + SystemHUDManager.shared.setup(coordinator: coordinator) + // Observe tab changes - use debounced updates coordinator.$currentView.sink { [weak self] newView in self?.debouncedUpdateWindowSize() diff --git a/DynamicIsland/Localizable.xcstrings b/DynamicIsland/Localizable.xcstrings index b58aaa9..372ee37 100644 --- a/DynamicIsland/Localizable.xcstrings +++ b/DynamicIsland/Localizable.xcstrings @@ -202,6 +202,15 @@ } } } + }, + "Brightness HUD" : { + + }, + "Built-in System Monitoring" : { + + }, + "Built-in system monitoring detects volume, brightness, and keyboard backlight changes directly without requiring external apps." : { + }, "Buy us a coffee" : { @@ -467,6 +476,12 @@ }, "Email address" : { + }, + "Enable Built-in System HUD" : { + + }, + "Enable built-in system monitoring to replace macOS HUD notifications with Dynamic Island displays." : { + }, "Enable colored spectrograms" : { "extractionState" : "stale", @@ -638,6 +653,9 @@ }, "Installed extensions" : { + }, + "Keyboard Backlight HUD" : { + }, "Keyboard shortcuts are disabled" : { @@ -1027,6 +1045,9 @@ }, "Remember last tab" : { + }, + "Replaces system HUD notifications with Dynamic Island displays." : { + }, "Reset to Default" : { @@ -1080,6 +1101,9 @@ }, "Selected animation" : { + }, + "Sensitivity" : { + }, "Set a custom duration for the timer. This will be used when you press the 'Custom' button in the timer interface." : { @@ -1337,6 +1361,9 @@ }, "View on GitHub: th-ch/youtube-music" : { + }, + "Volume HUD" : { + }, "Welcome" : { diff --git a/DynamicIsland/components/Settings/SettingsView.swift b/DynamicIsland/components/Settings/SettingsView.swift index e2dee3e..9f8c4ed 100644 --- a/DynamicIsland/components/Settings/SettingsView.swift +++ b/DynamicIsland/components/Settings/SettingsView.swift @@ -41,11 +41,8 @@ struct SettingsView: View { NavigationLink(value: "Calendar") { Label("Calendar", systemImage: "calendar") } - if extensionManager.installedExtensions - .contains(where: { $0.bundleIdentifier == hudExtension }) { - NavigationLink(value: "HUD") { - Label("HUDs", systemImage: "dial.medium.fill") - } + NavigationLink(value: "HUD") { + Label("HUDs", systemImage: "dial.medium.fill") } NavigationLink(value: "Battery") { Label("Battery", systemImage: "battery.100.bolt") @@ -423,14 +420,66 @@ struct HUD: View { @EnvironmentObject var vm: DynamicIslandViewModel @Default(.inlineHUD) var inlineHUD @Default(.enableGradient) var enableGradient + @Default(.enableSystemHUD) var enableSystemHUD + @Default(.enableVolumeHUD) var enableVolumeHUD + @Default(.enableBrightnessHUD) var enableBrightnessHUD + @Default(.enableKeyboardBacklightHUD) var enableKeyboardBacklightHUD + @Default(.systemHUDSensitivity) var systemHUDSensitivity @ObservedObject var coordinator = DynamicIslandViewCoordinator.shared + var body: some View { Form { Section { Toggle("Enable HUD replacement", isOn: $coordinator.hudReplacement) } header: { Text("General") + } footer: { + Text("Replaces system HUD notifications with Dynamic Island displays.") + .foregroundStyle(.secondary) + .font(.caption) } + + Section { + Toggle("Enable Built-in System HUD", isOn: $enableSystemHUD) + .onChange(of: enableSystemHUD) { _ in + // SystemHUDManager will automatically handle start/stop via its @Default observer + } + + if enableSystemHUD { + Toggle("Volume HUD", isOn: $enableVolumeHUD) + Toggle("Brightness HUD", isOn: $enableBrightnessHUD) + Toggle("Keyboard Backlight HUD", isOn: $enableKeyboardBacklightHUD) + + HStack { + Text("Sensitivity") + Spacer() + Slider(value: Binding( + get: { Double(systemHUDSensitivity) }, + set: { systemHUDSensitivity = Int($0) } + ), in: 1...10, step: 1) { + Text("Sensitivity") + } + .frame(width: 120) + Text("\(systemHUDSensitivity)") + .foregroundStyle(.secondary) + .font(.caption) + .frame(width: 20) + } + } + } header: { + Text("Built-in System Monitoring") + } footer: { + if enableSystemHUD { + Text("Built-in system monitoring detects volume, brightness, and keyboard backlight changes directly without requiring external apps.") + .foregroundStyle(.secondary) + .font(.caption) + } else { + Text("Enable built-in system monitoring to replace macOS HUD notifications with Dynamic Island displays.") + .foregroundStyle(.secondary) + .font(.caption) + } + } + Section { Picker("HUD style", selection: $inlineHUD) { Text("Default") diff --git a/DynamicIsland/helpers/AppleScriptError.swift b/DynamicIsland/helpers/AppleScriptError.swift new file mode 100644 index 0000000..b59cf1f --- /dev/null +++ b/DynamicIsland/helpers/AppleScriptError.swift @@ -0,0 +1,15 @@ +// +// AppleScriptError.swift +// DynamicIsland +// +// Adapted from TheBoringWorker-HUD +// Created by GitHub Copilot on 06/09/25. +// + +import Foundation + +enum AppleScriptError: Error { + case initScriptFailed + case runtimeError + case emptyOutput +} \ No newline at end of file diff --git a/DynamicIsland/helpers/AppleScriptRunner.swift b/DynamicIsland/helpers/AppleScriptRunner.swift new file mode 100644 index 0000000..bf9d0b0 --- /dev/null +++ b/DynamicIsland/helpers/AppleScriptRunner.swift @@ -0,0 +1,28 @@ +// +// AppleScriptRunner.swift +// DynamicIsland +// +// Adapted from TheBoringWorker-HUD +// Created by GitHub Copilot on 06/09/25. +// + +import Foundation + +class AppleScriptRunner { + private init() {} + + static func run(script: String) throws -> String { + var error: NSDictionary? + if let scriptObject = NSAppleScript(source: script) { + let output: NSAppleEventDescriptor = scriptObject.executeAndReturnError(&error) + guard error == nil else { + throw AppleScriptError.runtimeError + } + if let outputString = output.stringValue { + return outputString + } + throw AppleScriptError.emptyOutput + } + throw AppleScriptError.initScriptFailed + } +} \ No newline at end of file diff --git a/DynamicIsland/helpers/SensorError.swift b/DynamicIsland/helpers/SensorError.swift new file mode 100644 index 0000000..19a8972 --- /dev/null +++ b/DynamicIsland/helpers/SensorError.swift @@ -0,0 +1,22 @@ +// +// SensorError.swift +// DynamicIsland +// +// Adapted from TheBoringWorker-HUD +// Created by GitHub Copilot on 06/09/25. +// + +import Foundation + +enum SensorError: Error { + enum Display: Error { + case notFound + case notSilicon + case notStandard + } + enum Keyboard: Error { + case notFound + case notSilicon + case notStandard + } +} \ No newline at end of file diff --git a/DynamicIsland/helpers/SensorMethod.swift b/DynamicIsland/helpers/SensorMethod.swift new file mode 100644 index 0000000..ea149d1 --- /dev/null +++ b/DynamicIsland/helpers/SensorMethod.swift @@ -0,0 +1,15 @@ +// +// SensorMethod.swift +// DynamicIsland +// +// Adapted from TheBoringWorker-HUD +// Created by GitHub Copilot on 06/09/25. +// + +import Foundation + +enum SensorMethod { + case standard + case m1 + case allFailed +} \ No newline at end of file diff --git a/DynamicIsland/managers/SystemChangesObserver.swift b/DynamicIsland/managers/SystemChangesObserver.swift new file mode 100644 index 0000000..1156a0e --- /dev/null +++ b/DynamicIsland/managers/SystemChangesObserver.swift @@ -0,0 +1,153 @@ +// +// SystemChangesObserver.swift +// DynamicIsland +// +// Adapted from TheBoringWorker-HUD ChangesObserver.swift +// Created by GitHub Copilot on 06/09/25. +// + +import Cocoa +import Foundation +import Defaults + +class SystemChangesObserver: ObservableObject { + private var oldVolume: Float + private var oldMuted: Bool + private var oldBrightness: Float = 0 + + private weak var coordinator: DynamicIslandViewCoordinator? + + @Default(.enableSystemHUD) var enableSystemHUD + @Default(.enableVolumeHUD) var enableVolumeHUD + @Default(.enableBrightnessHUD) var enableBrightnessHUD + + init(coordinator: DynamicIslandViewCoordinator) { + self.coordinator = coordinator + oldVolume = SystemVolumeManager.getOutputVolume() + oldMuted = SystemVolumeManager.isMuted() + + do { + oldBrightness = try SystemDisplayManager.getDisplayBrightness() + } catch { + NSLog("Failed to retrieve display brightness: \(error)") + } + } + + func startObserving() { + guard enableSystemHUD else { return } + + createObservers() + createTimerForContinuousChangesCheck(with: 0.2) + } + + private func createTimerForContinuousChangesCheck(with seconds: TimeInterval) { + let 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 enableSystemHUD && enableVolumeHUD else { return } + + if oldVolume == 0.0 || oldVolume == 1.0 { + sendVolumeNotification(value: oldVolume) + } + print("Showing volume HUD") + } + + @objc func showBrightnessHUD() { + guard enableSystemHUD && enableBrightnessHUD else { return } + + if oldBrightness == 0.0 || oldBrightness == 1.0 { + sendBrightnessNotification(value: oldBrightness) + } + print("Showing brightness HUD") + } + + @objc func checkChanges() { + guard enableSystemHUD else { return } + + checkBrightnessChanges() + checkVolumeChanges() + } + + private func isAlmost(firstNumber: Float, secondNumber: Float) -> Bool { + let marginValue = 5 / 100.0 + return (firstNumber + Float(marginValue) >= secondNumber && firstNumber - Float(marginValue) <= secondNumber) + } + + private func checkVolumeChanges() { + guard enableVolumeHUD else { return } + + let newVolume = SystemVolumeManager.getOutputVolume() + let newMuted = SystemVolumeManager.isMuted() + + if !isAlmost(firstNumber: oldVolume, secondNumber: newVolume) || newMuted != oldMuted { + if newMuted { + sendVolumeNotification(value: 0.0) + } else { + sendVolumeNotification(value: newVolume) + } + oldVolume = newVolume + oldMuted = newMuted + } + } + + private func checkBrightnessChanges() { + guard enableBrightnessHUD else { return } + + if NSScreen.screens.count == 0 { + return + } + + do { + let newBrightness = try SystemDisplayManager.getDisplayBrightness() + if !isAlmost(firstNumber: oldBrightness, secondNumber: newBrightness) { + sendBrightnessNotification(value: newBrightness) + oldBrightness = newBrightness + } + } catch { + NSLog("Failed to retrieve display brightness: \(error)") + } + } + + // MARK: - Internal Notification Methods + + private func sendVolumeNotification(value: Float) { + coordinator?.toggleSneakPeek( + status: true, + type: .volume, + value: CGFloat(value), + icon: "" + ) + } + + private func sendBrightnessNotification(value: Float) { + coordinator?.toggleSneakPeek( + status: true, + type: .brightness, + value: CGFloat(value), + icon: "" + ) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } +} \ No newline at end of file diff --git a/DynamicIsland/managers/SystemDisplayManager.swift b/DynamicIsland/managers/SystemDisplayManager.swift new file mode 100644 index 0000000..ca327f6 --- /dev/null +++ b/DynamicIsland/managers/SystemDisplayManager.swift @@ -0,0 +1,73 @@ +// +// SystemDisplayManager.swift +// DynamicIsland +// +// Adapted from TheBoringWorker-HUD DisplayManager.swift +// Created by GitHub Copilot on 06/09/25. +// + +import Foundation +import Cocoa + +class SystemDisplayManager { + private init() {} + + private static var method = SensorMethod.standard + + static func getDisplayBrightness() throws -> Float { + switch SystemDisplayManager.method { + case .standard: + do { + return try getStandardDisplayBrightness() + } catch { + method = .m1 + } + case .m1: + do { + return try getM1DisplayBrightness() + } catch { + method = .allFailed + } + case .allFailed: + throw SensorError.Display.notFound + } + return try getDisplayBrightness() + } + + private static func getStandardDisplayBrightness() throws -> Float { + var brightness: float_t = 1 + let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IODisplayConnect")) + defer { + IOObjectRelease(service) + } + + let result = IODisplayGetFloatParameter(service, 0, kIODisplayBrightnessKey as CFString, &brightness) + if result != kIOReturnSuccess { + throw SensorError.Display.notStandard + } + return brightness + } + + private static func getM1DisplayBrightness() throws -> Float { + let task = Process() + task.launchPath = "/usr/libexec/corebrightnessdiag" + task.arguments = ["status-info"] + let pipe = Pipe() + task.standardOutput = pipe + try task.run() + let data = pipe.fileHandleForReading.readDataToEndOfFile() + task.waitUntilExit() + + if let plist = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? NSDictionary, + let displays = plist["CBDisplays"] as? [String: [String: Any]] { + for display in displays.values { + if let displayInfo = display["Display"] as? [String: Any], + displayInfo["DisplayServicesIsBuiltInDisplay"] as? Bool == true, + let brightness = displayInfo["DisplayServicesBrightness"] as? Float { + return brightness + } + } + } + throw SensorError.Display.notSilicon + } +} \ No newline at end of file diff --git a/DynamicIsland/managers/SystemHUDManager.swift b/DynamicIsland/managers/SystemHUDManager.swift new file mode 100644 index 0000000..220665a --- /dev/null +++ b/DynamicIsland/managers/SystemHUDManager.swift @@ -0,0 +1,82 @@ +// +// SystemHUDManager.swift +// DynamicIsland +// +// Created by GitHub Copilot on 06/09/25. +// + +import Foundation +import Defaults + +class SystemHUDManager: ObservableObject { + static let shared = SystemHUDManager() + + private var changesObserver: SystemChangesObserver? + private weak var coordinator: DynamicIslandViewCoordinator? + + @Default(.enableSystemHUD) var enableSystemHUD { + didSet { + Task { @MainActor in + if enableSystemHUD { + await startSystemHUD() + } else { + await stopSystemHUD() + } + } + } + } + + private init() {} + + func setup(coordinator: DynamicIslandViewCoordinator) { + self.coordinator = coordinator + + if enableSystemHUD { + Task { @MainActor in + await startSystemHUD() + } + } + } + + @MainActor + private func startSystemHUD() async { + guard let coordinator = coordinator else { return } + + 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") + } + + @MainActor + private func stopSystemHUD() async { + changesObserver = nil + + // Re-enable system HUD + enableOriginalSystemHUD() + + print("System HUD replacement stopped") + } + + private func disableSystemHUD() { + // Disable the system HUD using SystemOSDManager + SystemOSDManager.disableSystemHUD() + } + + private func enableOriginalSystemHUD() { + // Re-enable the system HUD using SystemOSDManager + SystemOSDManager.enableSystemHUD() + } + + deinit { + Task { @MainActor in + await stopSystemHUD() + } + } +} \ No newline at end of file diff --git a/DynamicIsland/managers/SystemKeyObserver.swift b/DynamicIsland/managers/SystemKeyObserver.swift new file mode 100644 index 0000000..e0b72b8 --- /dev/null +++ b/DynamicIsland/managers/SystemKeyObserver.swift @@ -0,0 +1,44 @@ +// +// SystemKeyObserver.swift +// DynamicIsland +// +// Adapted from TheBoringWorker-HUD KeyPressObserver.swift +// Created by GitHub Copilot on 06/09/25. +// + +import Cocoa + +class SystemKeyObserver: NSApplication { + static let volumeChanged = Notification.Name("DynamicIsland.volumeChanged") + static let brightnessChanged = Notification.Name("DynamicIsland.brightnessChanged") + static let keyboardIlluminationChanged = Notification.Name("DynamicIsland.keyboardIlluminationChanged") + + override func sendEvent(_ event: NSEvent) { + if event.type == .systemDefined && event.subtype.rawValue == 8 { + let keyCode = ((event.data1 & 0xFFFF0000) >> 16) + let keyFlags = (event.data1 & 0x0000FFFF) + // Get the key state. 0xA is KeyDown, OxB is KeyUp + let keyState = ((keyFlags & 0xFF00) >> 8) == 0xA + let keyRepeat = keyFlags & 0x1 + mediaKeyEvent(key: Int32(keyCode), state: keyState, keyRepeat: Bool(truncating: keyRepeat as NSNumber)) + } + + super.sendEvent(event) + } + + func mediaKeyEvent(key: Int32, state: Bool, keyRepeat: Bool) { + // Only send events on KeyDown. Without this check, these events will happen twice + if state { + switch key { + case NX_KEYTYPE_SOUND_DOWN, NX_KEYTYPE_SOUND_UP, NX_KEYTYPE_MUTE: + NotificationCenter.default.post(name: SystemKeyObserver.volumeChanged, object: self) + case NX_KEYTYPE_BRIGHTNESS_UP, NX_KEYTYPE_BRIGHTNESS_DOWN: + NotificationCenter.default.post(name: SystemKeyObserver.brightnessChanged, object: self) + case NX_KEYTYPE_ILLUMINATION_DOWN, NX_KEYTYPE_ILLUMINATION_UP: + NotificationCenter.default.post(name: SystemKeyObserver.keyboardIlluminationChanged, object: self) + default: + break + } + } + } +} \ No newline at end of file diff --git a/DynamicIsland/managers/SystemOSDManager.swift b/DynamicIsland/managers/SystemOSDManager.swift new file mode 100644 index 0000000..319b75e --- /dev/null +++ b/DynamicIsland/managers/SystemOSDManager.swift @@ -0,0 +1,47 @@ +// +// SystemOSDManager.swift +// DynamicIsland +// +// Adapted from TheBoringWorker-HUD OSDUIManager.swift +// Created by GitHub Copilot on 06/09/25. +// + +import Foundation + +class SystemOSDManager { + private init() {} + + /// Re-enables the system HUD by restarting OSDUIHelper + public static func enableSystemHUD() { + do { + let task = Process() + task.executableURL = URL(fileURLWithPath: "/usr/bin/killall") + task.arguments = ["-9", "OSDUIHelper"] + try task.run() + print("System HUD re-enabled") + } catch { + NSLog("Error while trying to re-enable OSDUIHelper. \(error)") + } + } + + /// Disables the system HUD by stopping OSDUIHelper + public static func disableSystemHUD() { + do { + let kickstart = Process() + kickstart.executableURL = URL(fileURLWithPath: "/bin/launchctl") + // When macOS boots, OSDUIHelper does not start until a volume button is pressed. We can workaround this by kickstarting it. + kickstart.arguments = ["kickstart", "gui/\(getuid())/com.apple.OSDUIHelper"] + try kickstart.run() + kickstart.waitUntilExit() + usleep(500000) // Make sure it started + + let task = Process() + task.executableURL = URL(fileURLWithPath: "/usr/bin/killall") + task.arguments = ["-STOP", "OSDUIHelper"] + try task.run() + print("System HUD disabled") + } catch { + NSLog("Error while trying to hide OSDUIHelper. Please create an issue on GitHub. Error: \(error)") + } + } +} \ No newline at end of file diff --git a/DynamicIsland/managers/SystemVolumeManager.swift b/DynamicIsland/managers/SystemVolumeManager.swift new file mode 100644 index 0000000..ee2a2bb --- /dev/null +++ b/DynamicIsland/managers/SystemVolumeManager.swift @@ -0,0 +1,35 @@ +// +// SystemVolumeManager.swift +// DynamicIsland +// +// Adapted from TheBoringWorker-HUD VolumeManager.swift +// Created by GitHub Copilot on 06/09/25. +// + +import Foundation + +class SystemVolumeManager { + private init() {} + + static func isMuted() -> Bool { + do { + return try AppleScriptRunner.run(script: "return output muted of (get volume settings)") == "true" + } catch { + NSLog("Error while trying to retrieve muted properties of device: \(error). Returning default value false.") + return false + } + } + + static func getOutputVolume() -> Float { + do { + if let volumeStr = Float(try AppleScriptRunner.run(script: "return output volume of (get volume settings)")) { + return volumeStr / 100 + } else { + NSLog("Error while trying to parse volume string value. Returning default volume value 1.") + } + } catch { + NSLog("Error while trying to retrieve volume properties of device: \(error). Returning default volume value 1.") + } + return 0.01 + } +} \ No newline at end of file diff --git a/DynamicIsland/models/Constants.swift b/DynamicIsland/models/Constants.swift index e9b2cf6..18fd8f3 100644 --- a/DynamicIsland/models/Constants.swift +++ b/DynamicIsland/models/Constants.swift @@ -221,6 +221,13 @@ extension Defaults.Keys { // MARK: Keyboard Shortcuts static let enableShortcuts = Key("enableShortcuts", default: true) + // MARK: System HUD Feature + static let enableSystemHUD = Key("enableSystemHUD", default: false) + static let enableVolumeHUD = Key("enableVolumeHUD", default: true) + static let enableBrightnessHUD = Key("enableBrightnessHUD", default: true) + static let enableKeyboardBacklightHUD = Key("enableKeyboardBacklightHUD", default: true) + static let systemHUDSensitivity = Key("systemHUDSensitivity", default: 5) + // Helper to determine the default media controller based on macOS version static var defaultMediaController: MediaControllerType { if #available(macOS 15.4, *) { From f4bcb96906f0afd2c0d59e55edb1827e5d91dd23 Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Sat, 6 Sep 2025 19:43:40 +0530 Subject: [PATCH 2/4] BugFix: Multiple object creation --- DynamicIsland/DynamicIslandApp.swift | 5 ++ .../Notch/DynamicIslandHeader.swift | 2 +- .../components/Notch/NotchShelfView.swift | 2 +- .../components/Shelf/DropItemView.swift | 2 +- DynamicIsland/helpers/SystemHUDDebugger.swift | 83 +++++++++++++++++++ .../managers/SystemChangesObserver.swift | 29 ++++--- DynamicIsland/managers/SystemHUDManager.swift | 52 +++++++++--- DynamicIsland/managers/SystemOSDManager.swift | 68 +++++++++++++-- 8 files changed, 206 insertions(+), 37 deletions(-) create mode 100644 DynamicIsland/helpers/SystemHUDDebugger.swift diff --git a/DynamicIsland/DynamicIslandApp.swift b/DynamicIsland/DynamicIslandApp.swift index 19fa7cd..e016eef 100644 --- a/DynamicIsland/DynamicIslandApp.swift +++ b/DynamicIsland/DynamicIslandApp.swift @@ -112,9 +112,14 @@ class AppDelegate: NSObject, NSApplicationDelegate { // Cancel any pending window size updates windowSizeUpdateWorkItem?.cancel() + print("๐Ÿšช Application terminating - ensuring system HUD is restored...") + // Re-enable system HUD on app termination SystemOSDManager.enableSystemHUD() + // Give it time to complete + usleep(500000) // 500ms + NotificationCenter.default.removeObserver(self) } diff --git a/DynamicIsland/components/Notch/DynamicIslandHeader.swift b/DynamicIsland/components/Notch/DynamicIslandHeader.swift index 449d8b4..2e6fea2 100644 --- a/DynamicIsland/components/Notch/DynamicIslandHeader.swift +++ b/DynamicIsland/components/Notch/DynamicIslandHeader.swift @@ -14,7 +14,7 @@ struct DynamicIslandHeader: View { @ObservedObject var batteryModel = BatteryStatusViewModel.shared @ObservedObject var coordinator = DynamicIslandViewCoordinator.shared @ObservedObject var clipboardManager = ClipboardManager.shared - @StateObject var tvm = TrayDrop.shared + @ObservedObject var tvm = TrayDrop.shared @State private var showClipboardPopover = false @State private var showColorPickerPopover = false diff --git a/DynamicIsland/components/Notch/NotchShelfView.swift b/DynamicIsland/components/Notch/NotchShelfView.swift index 5ec7528..aa925b9 100644 --- a/DynamicIsland/components/Notch/NotchShelfView.swift +++ b/DynamicIsland/components/Notch/NotchShelfView.swift @@ -3,7 +3,7 @@ import SwiftUI struct NotchShelfView: View { @EnvironmentObject var vm: DynamicIslandViewModel - @StateObject var tvm = TrayDrop.shared + @ObservedObject var tvm = TrayDrop.shared var body: some View { HStack { diff --git a/DynamicIsland/components/Shelf/DropItemView.swift b/DynamicIsland/components/Shelf/DropItemView.swift index 131d70c..3c8eb50 100644 --- a/DynamicIsland/components/Shelf/DropItemView.swift +++ b/DynamicIsland/components/Shelf/DropItemView.swift @@ -13,7 +13,7 @@ struct DropItemView: View { let item: TrayDrop.DropItem @EnvironmentObject var vm: DynamicIslandViewModel @ObservedObject var coordinator = DynamicIslandViewCoordinator.shared - @StateObject var tvm = TrayDrop.shared + @ObservedObject var tvm = TrayDrop.shared @State var hover = false diff --git a/DynamicIsland/helpers/SystemHUDDebugger.swift b/DynamicIsland/helpers/SystemHUDDebugger.swift new file mode 100644 index 0000000..bf93cd5 --- /dev/null +++ b/DynamicIsland/helpers/SystemHUDDebugger.swift @@ -0,0 +1,83 @@ +// +// SystemHUDDebugger.swift +// DynamicIsland +// +// Debug utility for System HUD functionality +// Created by GitHub Copilot on 06/09/25. +// + +import Foundation + +class SystemHUDDebugger { + + /// Test system HUD functionality and print status + public static func testSystemHUD() { + print("\n๐Ÿ” === System HUD Debug Report ===") + + // Check current OSDUIHelper status + let isRunning = SystemOSDManager.isOSDUIHelperRunning() + print("๐Ÿ“Š OSDUIHelper Status: \(isRunning ? "โœ… Running" : "โŒ Not running")") + + // Test disable + print("๐Ÿ”‡ Testing disable...") + SystemOSDManager.disableSystemHUD() + + // Wait and check + usleep(500000) + let isRunningAfterDisable = SystemOSDManager.isOSDUIHelperRunning() + print("๐Ÿ“Š After disable: \(isRunningAfterDisable ? "โœ… Running (stopped)" : "โŒ Not running")") + + // Test enable + print("๐Ÿ”Š Testing re-enable...") + SystemOSDManager.enableSystemHUD() + + // Wait and check + usleep(1000000) + let isRunningAfterEnable = SystemOSDManager.isOSDUIHelperRunning() + print("๐Ÿ“Š After re-enable: \(isRunningAfterEnable ? "โœ… Running" : "โŒ Not running")") + + print("๐Ÿ” === End Debug Report ===\n") + + if !isRunningAfterEnable { + print("โš ๏ธ WARNING: System HUD may not be working properly!") + print("๐Ÿ’ก Try pressing volume keys to test system HUD functionality") + } + } + + /// Force restart OSDUIHelper using multiple methods + public static func forceRestartOSDUIHelper() { + print("๐Ÿ”„ Force restarting OSDUIHelper...") + + // Method 1: Kill and kickstart + SystemOSDManager.enableSystemHUD() + + // Method 2: Try launchctl bootstrap (more aggressive) + do { + let bootstrap = Process() + bootstrap.executableURL = URL(fileURLWithPath: "/bin/launchctl") + bootstrap.arguments = ["bootstrap", "gui/\(getuid())", "/System/Library/LaunchAgents/com.apple.OSDUIHelper.plist"] + try bootstrap.run() + bootstrap.waitUntilExit() + print("โœ… Bootstrap method completed") + } catch { + print("โŒ Bootstrap method failed: \(error)") + } + + // Method 3: Try direct service restart + do { + let restart = Process() + restart.executableURL = URL(fileURLWithPath: "/bin/launchctl") + restart.arguments = ["restart", "gui/\(getuid())/com.apple.OSDUIHelper"] + try restart.run() + restart.waitUntilExit() + print("โœ… Restart method completed") + } catch { + print("โŒ Restart method failed: \(error)") + } + + // Check final status + usleep(1000000) + let finalStatus = SystemOSDManager.isOSDUIHelperRunning() + print("๐Ÿ“Š Final OSDUIHelper status: \(finalStatus ? "โœ… Running" : "โŒ Not running")") + } +} \ No newline at end of file diff --git a/DynamicIsland/managers/SystemChangesObserver.swift b/DynamicIsland/managers/SystemChangesObserver.swift index 1156a0e..1da8eed 100644 --- a/DynamicIsland/managers/SystemChangesObserver.swift +++ b/DynamicIsland/managers/SystemChangesObserver.swift @@ -10,16 +10,13 @@ import Cocoa import Foundation import Defaults -class SystemChangesObserver: ObservableObject { +class SystemChangesObserver { private var oldVolume: Float private var oldMuted: Bool private var oldBrightness: Float = 0 private weak var coordinator: DynamicIslandViewCoordinator? - - @Default(.enableSystemHUD) var enableSystemHUD - @Default(.enableVolumeHUD) var enableVolumeHUD - @Default(.enableBrightnessHUD) var enableBrightnessHUD + private var timer: Timer? init(coordinator: DynamicIslandViewCoordinator) { self.coordinator = coordinator @@ -34,18 +31,18 @@ class SystemChangesObserver: ObservableObject { } func startObserving() { - guard enableSystemHUD else { return } + guard Defaults[.enableSystemHUD] else { return } createObservers() createTimerForContinuousChangesCheck(with: 0.2) } private func createTimerForContinuousChangesCheck(with seconds: TimeInterval) { - let timer = Timer( + timer = Timer( timeInterval: seconds, target: self, selector: #selector(checkChanges), userInfo: nil, repeats: true) let mainLoop = RunLoop.main - mainLoop.add(timer, forMode: .common) + mainLoop.add(timer!, forMode: .common) } private func createObservers() { @@ -63,7 +60,7 @@ class SystemChangesObserver: ObservableObject { } @objc func showVolumeHUD() { - guard enableSystemHUD && enableVolumeHUD else { return } + guard Defaults[.enableSystemHUD] && Defaults[.enableVolumeHUD] else { return } if oldVolume == 0.0 || oldVolume == 1.0 { sendVolumeNotification(value: oldVolume) @@ -72,7 +69,7 @@ class SystemChangesObserver: ObservableObject { } @objc func showBrightnessHUD() { - guard enableSystemHUD && enableBrightnessHUD else { return } + guard Defaults[.enableSystemHUD] && Defaults[.enableBrightnessHUD] else { return } if oldBrightness == 0.0 || oldBrightness == 1.0 { sendBrightnessNotification(value: oldBrightness) @@ -81,7 +78,7 @@ class SystemChangesObserver: ObservableObject { } @objc func checkChanges() { - guard enableSystemHUD else { return } + guard Defaults[.enableSystemHUD] else { return } checkBrightnessChanges() checkVolumeChanges() @@ -93,7 +90,7 @@ class SystemChangesObserver: ObservableObject { } private func checkVolumeChanges() { - guard enableVolumeHUD else { return } + guard Defaults[.enableVolumeHUD] else { return } let newVolume = SystemVolumeManager.getOutputVolume() let newMuted = SystemVolumeManager.isMuted() @@ -110,7 +107,7 @@ class SystemChangesObserver: ObservableObject { } private func checkBrightnessChanges() { - guard enableBrightnessHUD else { return } + guard Defaults[.enableBrightnessHUD] else { return } if NSScreen.screens.count == 0 { return @@ -148,6 +145,12 @@ class SystemChangesObserver: ObservableObject { } deinit { + stopObserving() + } + + func stopObserving() { + timer?.invalidate() + timer = nil NotificationCenter.default.removeObserver(self) } } \ No newline at end of file diff --git a/DynamicIsland/managers/SystemHUDManager.swift b/DynamicIsland/managers/SystemHUDManager.swift index 220665a..13965a1 100644 --- a/DynamicIsland/managers/SystemHUDManager.swift +++ b/DynamicIsland/managers/SystemHUDManager.swift @@ -7,31 +7,38 @@ import Foundation import Defaults +import Combine -class SystemHUDManager: ObservableObject { +class SystemHUDManager { static let shared = SystemHUDManager() private var changesObserver: SystemChangesObserver? private weak var coordinator: DynamicIslandViewCoordinator? - @Default(.enableSystemHUD) var enableSystemHUD { - didSet { - Task { @MainActor in - if enableSystemHUD { - await startSystemHUD() - } else { - await stopSystemHUD() - } - } - } + private init() { + // Set up observer for settings changes + setupSettingsObserver() } - private init() {} + private func setupSettingsObserver() { + // Use Defaults publisher instead of @Default property wrapper + Defaults.publisher(.enableSystemHUD, options: []).sink { [weak self] change in + Task { @MainActor in + if change.newValue { + await self?.startSystemHUD() + } else { + await self?.stopSystemHUD() + } + } + }.store(in: &cancellables) + } + + private var cancellables = Set() func setup(coordinator: DynamicIslandViewCoordinator) { self.coordinator = coordinator - if enableSystemHUD { + if Defaults[.enableSystemHUD] { Task { @MainActor in await startSystemHUD() } @@ -56,6 +63,7 @@ class SystemHUDManager: ObservableObject { @MainActor private func stopSystemHUD() async { + changesObserver?.stopObserving() changesObserver = nil // Re-enable system HUD @@ -65,18 +73,36 @@ class SystemHUDManager: ObservableObject { } private func disableSystemHUD() { + print("๐Ÿ”‡ Disabling system HUD (OSDUIHelper running: \(SystemOSDManager.isOSDUIHelperRunning()))") // Disable the system HUD using SystemOSDManager SystemOSDManager.disableSystemHUD() } private func enableOriginalSystemHUD() { + print("๐Ÿ”Š Re-enabling system HUD (OSDUIHelper running: \(SystemOSDManager.isOSDUIHelperRunning()))") // 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() + print("๐Ÿ” OSDUIHelper status after re-enable: \(isRunning ? "โœ… Running" : "โŒ Not running")") + } } 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 function - call this if system HUD is not working + public static func emergencyRestoreSystemHUD() { + print("๐Ÿšจ Emergency system HUD restore initiated") + SystemHUDDebugger.forceRestartOSDUIHelper() } } \ No newline at end of file diff --git a/DynamicIsland/managers/SystemOSDManager.swift b/DynamicIsland/managers/SystemOSDManager.swift index 319b75e..fb2e369 100644 --- a/DynamicIsland/managers/SystemOSDManager.swift +++ b/DynamicIsland/managers/SystemOSDManager.swift @@ -14,13 +14,41 @@ class SystemOSDManager { /// Re-enables the system HUD by restarting OSDUIHelper public static func enableSystemHUD() { do { - let task = Process() - task.executableURL = URL(fileURLWithPath: "/usr/bin/killall") - task.arguments = ["-9", "OSDUIHelper"] - try task.run() - print("System HUD re-enabled") + // First, stop any existing OSDUIHelper process + let stopTask = Process() + stopTask.executableURL = URL(fileURLWithPath: "/usr/bin/killall") + stopTask.arguments = ["-9", "OSDUIHelper"] + try stopTask.run() + stopTask.waitUntilExit() + + // Small delay to ensure process is fully stopped + usleep(200000) // 200ms + + // Then kickstart it again to ensure it's running properly + let kickstart = Process() + kickstart.executableURL = URL(fileURLWithPath: "/bin/launchctl") + kickstart.arguments = ["kickstart", "gui/\(getuid())/com.apple.OSDUIHelper"] + try kickstart.run() + kickstart.waitUntilExit() + + // Additional delay to ensure service is fully started + usleep(300000) // 300ms + + print("โœ… System HUD re-enabled") } catch { - NSLog("Error while trying to re-enable OSDUIHelper. \(error)") + NSLog("โŒ Error while trying to re-enable OSDUIHelper: \(error)") + + // Fallback: Try to restart the service using launchctl load + do { + let fallbackTask = Process() + fallbackTask.executableURL = URL(fileURLWithPath: "/bin/launchctl") + fallbackTask.arguments = ["load", "-w", "/System/Library/LaunchAgents/com.apple.OSDUIHelper.plist"] + try fallbackTask.run() + fallbackTask.waitUntilExit() + print("โœ… System HUD re-enabled via fallback method") + } catch { + NSLog("โŒ Fallback method also failed: \(error)") + } } } @@ -39,9 +67,33 @@ class SystemOSDManager { task.executableURL = URL(fileURLWithPath: "/usr/bin/killall") task.arguments = ["-STOP", "OSDUIHelper"] try task.run() - print("System HUD disabled") + task.waitUntilExit() + + print("โœ… System HUD disabled") } catch { - NSLog("Error while trying to hide OSDUIHelper. Please create an issue on GitHub. Error: \(error)") + NSLog("โŒ Error while trying to hide OSDUIHelper: \(error)") + } + } + + /// Check if OSDUIHelper is currently running + public static func isOSDUIHelperRunning() -> Bool { + let task = Process() + task.executableURL = URL(fileURLWithPath: "/usr/bin/pgrep") + task.arguments = ["OSDUIHelper"] + + let pipe = Pipe() + task.standardOutput = pipe + + do { + try task.run() + task.waitUntilExit() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + let output = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) + + return task.terminationStatus == 0 && !output!.isEmpty + } catch { + return false } } } \ No newline at end of file From 98c826aaaebd134bc68c732ef24eb2a2399fffc2 Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Sat, 6 Sep 2025 21:54:19 +0530 Subject: [PATCH 3/4] 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 From 278b1aeaad8307d15e0bd1c1d7921e1a121e4f76 Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Sat, 6 Sep 2025 22:13:57 +0530 Subject: [PATCH 4/4] Fix:Frequent Calls --- DynamicIsland/managers/SystemChangesObserver.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DynamicIsland/managers/SystemChangesObserver.swift b/DynamicIsland/managers/SystemChangesObserver.swift index d656e3e..6db5e45 100644 --- a/DynamicIsland/managers/SystemChangesObserver.swift +++ b/DynamicIsland/managers/SystemChangesObserver.swift @@ -43,7 +43,7 @@ class SystemChangesObserver { 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 + backupTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in guard Defaults[.enableSystemHUD] else { return } self?.performBackupCheck() }