diff --git a/DynamicIsland/ContentView.swift b/DynamicIsland/ContentView.swift index 033e69a..674e0bd 100644 --- a/DynamicIsland/ContentView.swift +++ b/DynamicIsland/ContentView.swift @@ -126,6 +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 hoverClickMonitor: Any? @State private var gestureProgress: CGFloat = .zero @State private var skipGestureActiveDirection: MusicManager.SkipDirection? @@ -271,6 +272,7 @@ struct ContentView: View { } .conditionalModifier(interactionsEnabled) { view in view + .contentShape(currentNotchShape) .onHover { hovering in handleHover(hovering) } @@ -389,12 +391,13 @@ struct ContentView: View { // #endif // .keyboardShortcut("E", modifiers: .command) } + } - .frame( - maxWidth: dynamicNotchSize.width, - maxHeight: dynamicNotchSize.height + currentShadowPadding, - alignment: .top - ) + .frame( + maxWidth: dynamicNotchSize.width, + maxHeight: dynamicNotchSize.height + currentShadowPadding, + alignment: .top + ) .environmentObject(privacyManager) .onChange(of: dynamicNotchSize) { oldSize, newSize in guard oldSize != newSize else { return } @@ -424,6 +427,7 @@ struct ContentView: View { releaseMusicControlWindowUpdates(after: musicControlResumeDelay) enqueueMusicControlWindowSync(forceRefresh: true, delay: 0.05) } + } .onChange(of: musicControlWindowEnabled) { _, enabled in if enabled { @@ -502,6 +506,7 @@ struct ContentView: View { } .onDisappear { hoverTask?.cancel() + stopHoverClickMonitor() cancelMusicControlWindowSync() hideMusicControlWindow() cancelMusicControlVisibilityTimer() @@ -1430,12 +1435,40 @@ struct ContentView: View { } } + 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() + } + } + } + + private func stopHoverClickMonitor() { + if let hoverClickMonitor { + NSEvent.removeMonitor(hoverClickMonitor) + self.hoverClickMonitor = nil + } + } + // MARK: - Hover Management /// Handle hover state changes with debouncing private func handleHover(_ hovering: Bool) { hoverTask?.cancel() + if hovering { + startHoverClickMonitor() + } else { + stopHoverClickMonitor() + } + if hovering { withAnimation(.bouncy.speed(1.2)) { isHovering = true 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/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) + } } } 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 } + }