From 311e2551c5083cc182d8a35247625d703d9ef2df Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Mon, 16 Feb 2026 14:21:58 +0530 Subject: [PATCH 1/3] Partial Fix: Apple Music, untested --- .../AppleMusicController.swift | 100 ++++++++++++------ 1 file changed, 70 insertions(+), 30 deletions(-) diff --git a/DynamicIsland/MediaControllers/AppleMusicController.swift b/DynamicIsland/MediaControllers/AppleMusicController.swift index d97139f..f832199 100644 --- a/DynamicIsland/MediaControllers/AppleMusicController.swift +++ b/DynamicIsland/MediaControllers/AppleMusicController.swift @@ -145,38 +145,78 @@ class AppleMusicController: MediaControllerProtocol { } private func fetchPlaybackInfoAsync() async throws -> NSAppleEventDescriptor? { - let script = """ - tell application "Music" - set isRunning to true - try - set playerState to player state is playing - set currentTrackName to name of current track - set currentTrackArtist to artist of current track - set currentTrackAlbum to album of current track - set trackPosition to player position - set trackDuration to duration of current track - set shuffleState to shuffle enabled - set repeatState to song repeat - if repeatState is off then - set repeatValue to 1 - else if repeatState is one then - set repeatValue to 2 - else if repeatState is all then - set repeatValue to 3 - end if - + if #available(macOS 26.0, *) { + let script = """ + tell application "Music" try - set artData to data of artwork 1 of current track - on error + set playerState to player state is playing + set currentTrackName to name of current track + set currentTrackArtist to artist of current track + set currentTrackAlbum to album of current track + set trackPosition to player position + set trackDuration to duration of current track + set shuffleState to shuffle enabled + set repeatState to song repeat + if repeatState is off then + set repeatValue to 1 + else if repeatState is one then + set repeatValue to 2 + else if repeatState is all then + set repeatValue to 3 + end if + set artData to "" + try + set libraryTrack to first track of playlist "Library" whose name is currentTrackName and artist is currentTrackArtist + set artData to data of artwork 1 of libraryTrack + on error + try + set artData to data of artwork 1 of current track + on error + set artData to "" + end try + end try + + return {playerState, currentTrackName, currentTrackArtist, currentTrackAlbum, trackPosition, trackDuration, shuffleState, repeatValue, artData} + on error + return {false, "Not Playing", "Unknown", "Unknown", 0, 0, false, 0, ""} end try - return {playerState, currentTrackName, currentTrackArtist, currentTrackAlbum, trackPosition, trackDuration, shuffleState, repeatValue, artData} - on error - return {false, "Not Playing", "Unknown", "Unknown", 0, 0, false, 0, ""} - end try - end tell - """ - - return try await AppleScriptHelper.execute(script) + end tell + """ + return try await AppleScriptHelper.execute(script) + } else { + let script = """ + tell application "Music" + set isRunning to true + try + set playerState to player state is playing + set currentTrackName to name of current track + set currentTrackArtist to artist of current track + set currentTrackAlbum to album of current track + set trackPosition to player position + set trackDuration to duration of current track + set shuffleState to shuffle enabled + set repeatState to song repeat + if repeatState is off then + set repeatValue to 1 + else if repeatState is one then + set repeatValue to 2 + else if repeatState is all then + set repeatValue to 3 + end if + + try + set artData to data of artwork 1 of current track + on error + set artData to "" + end try + return {playerState, currentTrackName, currentTrackArtist, currentTrackAlbum, trackPosition, trackDuration, shuffleState, repeatValue, artData} + on error + return {false, "Not Playing", "Unknown", "Unknown", 0, 0, false, 0, ""} + end try + end tell + """ + return try await AppleScriptHelper.execute(script) + } } } From 72d101b1be2a5a804ed8e614e91a90416dad7054 Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Mon, 16 Feb 2026 15:00:03 +0530 Subject: [PATCH 2/3] Add Click Monitor for ensuring consistent open on click --- DynamicIsland/ContentView.swift | 87 +++++++++++++++++-- DynamicIsland/DynamicIslandApp.swift | 8 +- .../Notch/DynamicIslandWindow.swift | 1 + 3 files changed, 90 insertions(+), 6 deletions(-) diff --git a/DynamicIsland/ContentView.swift b/DynamicIsland/ContentView.swift index 033e69a..0a26477 100644 --- a/DynamicIsland/ContentView.swift +++ b/DynamicIsland/ContentView.swift @@ -126,6 +126,8 @@ struct ContentView: View { @State private var hoverTask: Task? @State private var isHovering: Bool = false @State private var lastHapticTime: Date = Date() + @State private var clickMonitor: Any? + @State private var hostingWindow: NSWindow? @State private var gestureProgress: CGFloat = .zero @State private var skipGestureActiveDirection: MusicManager.SkipDirection? @@ -271,6 +273,7 @@ struct ContentView: View { } .conditionalModifier(interactionsEnabled) { view in view + .contentShape(Rectangle()) .onHover { hovering in handleHover(hovering) } @@ -389,12 +392,37 @@ struct ContentView: View { // #endif // .keyboardShortcut("E", modifiers: .command) } + + if interactionsEnabled && vm.notchState == .closed { + Color.clear + .frame(height: 8) + .frame(maxWidth: .infinity, alignment: .top) + .contentShape(Rectangle()) + .onHover { hovering in + handleHover(hovering) + } + .onTapGesture { + if Defaults[.enableHaptics] { + triggerHapticIfAllowed() + } + openNotch() + } + } } - .frame( - maxWidth: dynamicNotchSize.width, - maxHeight: dynamicNotchSize.height + currentShadowPadding, - alignment: .top - ) + .background(WindowAccessor { window in + hostingWindow = window + }) + .onAppear { + startGlobalClickMonitor() + } + .onDisappear { + stopGlobalClickMonitor() + } + .frame( + maxWidth: dynamicNotchSize.width, + maxHeight: dynamicNotchSize.height + currentShadowPadding, + alignment: .top + ) .environmentObject(privacyManager) .onChange(of: dynamicNotchSize) { oldSize, newSize in guard oldSize != newSize else { return } @@ -1430,6 +1458,37 @@ struct ContentView: View { } } + private func startGlobalClickMonitor() { + guard clickMonitor == nil else { return } + clickMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown]) { _ in + handleGlobalClick() + } + } + + private func stopGlobalClickMonitor() { + if let clickMonitor { + NSEvent.removeMonitor(clickMonitor) + self.clickMonitor = nil + } + } + + private func handleGlobalClick() { + guard !lockScreenManager.isLocked else { return } + guard vm.notchState == .closed else { return } + guard let hostingWindow else { return } + + let cursorLocation = NSEvent.mouseLocation + let hitFrame = hostingWindow.frame.insetBy(dx: -2, dy: -2) + guard hitFrame.contains(cursorLocation) else { return } + + DispatchQueue.main.async { + if Defaults[.enableHaptics] { + triggerHapticIfAllowed() + } + openNotch() + } + } + // MARK: - Hover Management /// Handle hover state changes with debouncing @@ -1992,6 +2051,24 @@ struct ContentView: View { } } +private struct WindowAccessor: NSViewRepresentable { + let callback: (NSWindow?) -> Void + + func makeNSView(context: Context) -> NSView { + let view = NSView(frame: .zero) + DispatchQueue.main.async { [weak view] in + callback(view?.window) + } + return view + } + + func updateNSView(_ nsView: NSView, context: Context) { + DispatchQueue.main.async { [weak nsView] in + callback(nsView?.window) + } + } +} + private enum MusicSecondaryLiveActivity: Equatable { case timer case reminder(ReminderLiveActivityManager.ReminderEntry) diff --git a/DynamicIsland/DynamicIslandApp.swift b/DynamicIsland/DynamicIslandApp.swift index 9422ba7..9a98af1 100644 --- a/DynamicIsland/DynamicIslandApp.swift +++ b/DynamicIsland/DynamicIslandApp.swift @@ -79,6 +79,12 @@ struct DynamicNotchApp: App { } } +final class FirstMouseHostingView: NSHostingView { + override func acceptsFirstMouse(for event: NSEvent?) -> Bool { + true + } +} + extension AppDelegate { static var shared: AppDelegate? { NSApplication.shared.delegate as? AppDelegate @@ -223,7 +229,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { window.animationBehavior = .none - window.contentView = NSHostingView( + window.contentView = FirstMouseHostingView( rootView: ContentView() .environmentObject(viewModel) .environmentObject(webcamManager) diff --git a/DynamicIsland/components/Notch/DynamicIslandWindow.swift b/DynamicIsland/components/Notch/DynamicIslandWindow.swift index 8c53e83..ffcb9fb 100644 --- a/DynamicIsland/components/Notch/DynamicIslandWindow.swift +++ b/DynamicIsland/components/Notch/DynamicIslandWindow.swift @@ -64,4 +64,5 @@ class DynamicIslandWindow: NSPanel { override var canBecomeMain: Bool { true } + } From 539d9185a619629d1d527e89330b73b970628bb6 Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Mon, 16 Feb 2026 15:31:23 +0530 Subject: [PATCH 3/3] Remove Click Monitor, partially tested working --- DynamicIsland/ContentView.swift | 100 +++++++++----------------------- 1 file changed, 28 insertions(+), 72 deletions(-) diff --git a/DynamicIsland/ContentView.swift b/DynamicIsland/ContentView.swift index 0a26477..674e0bd 100644 --- a/DynamicIsland/ContentView.swift +++ b/DynamicIsland/ContentView.swift @@ -126,8 +126,7 @@ struct ContentView: View { @State private var hoverTask: Task? @State private var isHovering: Bool = false @State private var lastHapticTime: Date = Date() - @State private var clickMonitor: Any? - @State private var hostingWindow: NSWindow? + @State private var hoverClickMonitor: Any? @State private var gestureProgress: CGFloat = .zero @State private var skipGestureActiveDirection: MusicManager.SkipDirection? @@ -273,7 +272,7 @@ struct ContentView: View { } .conditionalModifier(interactionsEnabled) { view in view - .contentShape(Rectangle()) + .contentShape(currentNotchShape) .onHover { hovering in handleHover(hovering) } @@ -393,30 +392,6 @@ struct ContentView: View { // .keyboardShortcut("E", modifiers: .command) } - if interactionsEnabled && vm.notchState == .closed { - Color.clear - .frame(height: 8) - .frame(maxWidth: .infinity, alignment: .top) - .contentShape(Rectangle()) - .onHover { hovering in - handleHover(hovering) - } - .onTapGesture { - if Defaults[.enableHaptics] { - triggerHapticIfAllowed() - } - openNotch() - } - } - } - .background(WindowAccessor { window in - hostingWindow = window - }) - .onAppear { - startGlobalClickMonitor() - } - .onDisappear { - stopGlobalClickMonitor() } .frame( maxWidth: dynamicNotchSize.width, @@ -452,6 +427,7 @@ struct ContentView: View { releaseMusicControlWindowUpdates(after: musicControlResumeDelay) enqueueMusicControlWindowSync(forceRefresh: true, delay: 0.05) } + } .onChange(of: musicControlWindowEnabled) { _, enabled in if enabled { @@ -530,6 +506,7 @@ struct ContentView: View { } .onDisappear { hoverTask?.cancel() + stopHoverClickMonitor() cancelMusicControlWindowSync() hideMusicControlWindow() cancelMusicControlVisibilityTimer() @@ -1458,34 +1435,25 @@ struct ContentView: View { } } - private func startGlobalClickMonitor() { - guard clickMonitor == nil else { return } - clickMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown]) { _ in - handleGlobalClick() - } - } - - private func stopGlobalClickMonitor() { - if let clickMonitor { - NSEvent.removeMonitor(clickMonitor) - self.clickMonitor = nil - } - } - - private func handleGlobalClick() { - guard !lockScreenManager.isLocked else { return } - guard vm.notchState == .closed else { return } - guard let hostingWindow else { return } - - let cursorLocation = NSEvent.mouseLocation - let hitFrame = hostingWindow.frame.insetBy(dx: -2, dy: -2) - guard hitFrame.contains(cursorLocation) else { return } - - DispatchQueue.main.async { - if Defaults[.enableHaptics] { - triggerHapticIfAllowed() + private func startHoverClickMonitor() { + guard hoverClickMonitor == nil else { return } + hoverClickMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown]) { _ in + Task { @MainActor in + guard !lockScreenManager.isLocked else { return } + guard vm.notchState == .closed else { return } + guard isHovering else { return } + if Defaults[.enableHaptics] { + triggerHapticIfAllowed() + } + openNotch() } - openNotch() + } + } + + private func stopHoverClickMonitor() { + if let hoverClickMonitor { + NSEvent.removeMonitor(hoverClickMonitor) + self.hoverClickMonitor = nil } } @@ -1495,6 +1463,12 @@ struct ContentView: View { private func handleHover(_ hovering: Bool) { hoverTask?.cancel() + if hovering { + startHoverClickMonitor() + } else { + stopHoverClickMonitor() + } + if hovering { withAnimation(.bouncy.speed(1.2)) { isHovering = true @@ -2051,24 +2025,6 @@ struct ContentView: View { } } -private struct WindowAccessor: NSViewRepresentable { - let callback: (NSWindow?) -> Void - - func makeNSView(context: Context) -> NSView { - let view = NSView(frame: .zero) - DispatchQueue.main.async { [weak view] in - callback(view?.window) - } - return view - } - - func updateNSView(_ nsView: NSView, context: Context) { - DispatchQueue.main.async { [weak nsView] in - callback(nsView?.window) - } - } -} - private enum MusicSecondaryLiveActivity: Equatable { case timer case reminder(ReminderLiveActivityManager.ReminderEntry)