├── .gitignore
├── DynamicIsland.xcodeproj
├── project.pbxproj
├── project.pbxproj.backup
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
└── xcuserdata
│ └── admin63.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
├── DynamicIsland
├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── notch-stage-icon2 10.png
│ │ ├── notch-stage-icon2 9.png
│ │ ├── upscaled-2.png
│ │ ├── upscaled-3 1.png
│ │ ├── upscaled-3.png
│ │ ├── upscaled-4.png
│ │ ├── upscaled-5.png
│ │ ├── upscaled-6 1.png
│ │ ├── upscaled-6.png
│ │ └── upscaled-7.png
│ ├── Contents.json
│ ├── Github.imageset
│ │ ├── Contents.json
│ │ ├── GitHub Mark White 1.svg
│ │ ├── GitHub Mark White 2.svg
│ │ └── GitHub Mark White.svg
│ ├── LinkedIn.imageset
│ │ ├── Contents.json
│ │ └── LinkedIn_logo_initials.png
│ ├── bolt.imageset
│ │ ├── Contents.json
│ │ └── bolt.png
│ ├── chrome.imageset
│ │ ├── Contents.json
│ │ ├── Google Chrome macOS BigSur Icon 1.png
│ │ ├── Google Chrome macOS BigSur Icon 2.png
│ │ └── Google Chrome macOS BigSur Icon.png
│ ├── defaultmusic.imageset
│ │ └── Contents.json
│ ├── ebullioscopic.imageset
│ │ ├── 819b59197be07c800478c728143326f1.png
│ │ └── Contents.json
│ ├── logo.imageset
│ │ ├── ChatGPT Image Aug 13, 2025, 11_50_32 PM.png
│ │ └── Contents.json
│ ├── logo2.imageset
│ │ ├── ChatGPT_Image_Aug_13__2025__11_50_32_PM-removebg-preview.png
│ │ └── Contents.json
│ ├── plug.imageset
│ │ ├── Contents.json
│ │ └── plug.png
│ ├── sparkle.imageset
│ │ ├── Contents.json
│ │ └── sparkle.svg
│ └── spotlight.imageset
│ │ ├── Contents.json
│ │ └── spotlight.svg
├── ContentView.swift
├── DynamicIsland.entitlements
├── DynamicIslandApp.swift
├── DynamicIslandViewCoordinator.swift
├── Info.plist
├── Localizable.xcstrings
├── Localizable.xcstrings.backup
├── MediaControllers
│ ├── AppleMusicController.swift
│ ├── MediaControllerProtocol.swift
│ ├── NowPlayingController.swift
│ ├── SpotifyController.swift
│ └── YouTube Music Controller
│ │ ├── YouTubeMusicAuthentication.swift
│ │ ├── YouTubeMusicController.swift
│ │ ├── YouTubeMusicModels.swift
│ │ └── YouTubeMusicNetworking.swift
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Providers
│ └── CalendarServiceProviding.swift
├── Shortcuts
│ └── ShortcutConstants.swift
├── animations
│ ├── HelloAnimation.swift
│ └── drop.swift
├── components
│ ├── AnimatedFace.swift
│ ├── BottomRoundedRectangle.swift
│ ├── Calendar
│ │ └── DynamicIslandCalendar.swift
│ ├── Clipboard
│ │ ├── ClipboardPanel.swift
│ │ ├── ClipboardPopover.swift
│ │ ├── ClipboardShared.swift
│ │ └── ClipboardWindow.swift
│ ├── ColorCodedProgressBar.swift
│ ├── ColorPicker
│ │ ├── ColorPickedFeedbackView.swift
│ │ ├── ColorPickerPanel.swift
│ │ └── ColorPickerPopover.swift
│ ├── DynamicIslandSystemTiles.swift
│ ├── EmptyState.swift
│ ├── HoverButton.swift
│ ├── Live activities
│ │ ├── DownloadView.swift
│ │ ├── DynamicIslandBattery.swift
│ │ ├── InlineHUD.swift
│ │ ├── LiveActivityModifier.swift
│ │ ├── MarqueeTextView.swift
│ │ └── SystemEventIndicatorModifier.swift
│ ├── MinimalisticMusicView.swift
│ ├── Music
│ │ ├── LottieAnimationView.swift
│ │ └── MusicVisualizer.swift
│ ├── Notch
│ │ ├── ClipboardHistoryPopover.swift
│ │ ├── DynamicIslandExtrasMenu.swift
│ │ ├── DynamicIslandHeader.swift
│ │ ├── DynamicIslandWindow.swift
│ │ ├── MinimalisticMusicPlayerView.swift
│ │ ├── NotchColorPickerView.swift
│ │ ├── NotchHomeView.swift
│ │ ├── NotchShape.swift
│ │ ├── NotchShelfView.swift
│ │ ├── NotchStatsView.swift
│ │ └── NotchTimerView.swift
│ ├── Onboarding
│ │ ├── ActivationView.swift
│ │ ├── MusicControllerSelectionView.swift
│ │ ├── OnboardingFinishView.swift
│ │ ├── OnboardingView.swift
│ │ ├── PermissionsRequestView.swift
│ │ ├── ProOnboarding.swift
│ │ ├── ProfileSelectionView.swift
│ │ ├── SparkleView.swift
│ │ └── WelcomeView.swift
│ ├── ProgressIndicator.swift
│ ├── Recording
│ │ └── RecordingLiveActivity.swift
│ ├── ScreenAssistant
│ │ ├── ChatPanels.swift
│ │ ├── ModelSelectionPanel.swift
│ │ ├── ScreenAssistantPanel.swift
│ │ └── ScreenshotSnippingTool.swift
│ ├── Settings
│ │ ├── EditPanelView.swift
│ │ ├── ListItemPopover.swift
│ │ ├── SettingsView.swift
│ │ ├── SettingsWindowController.swift
│ │ └── SoftwareUpdater.swift
│ ├── Shelf
│ │ ├── AirDrop.swift
│ │ ├── AirDropView.swift
│ │ ├── DragDropView.swift
│ │ ├── DropItem.swift
│ │ ├── DropItemView.swift
│ │ ├── Ext+FileProvider.swift
│ │ ├── Ext+NSAlert.swift
│ │ ├── Ext+NSImage.swift
│ │ ├── Ext+URL.swift
│ │ └── TrayDrop.swift
│ ├── Stats
│ │ ├── DetailedTimelineGraph.swift
│ │ ├── DualTimelineGraph.swift
│ │ ├── StatsPanel.swift
│ │ └── StatsPanelView.swift
│ ├── Tabs
│ │ ├── TabButton.swift
│ │ └── TabSelectionView.swift
│ ├── TestView.swift
│ ├── Timer
│ │ ├── TimerIconAnimation.swift
│ │ └── TimerLiveActivity.swift
│ ├── Tips
│ │ └── TipStore.swift
│ ├── UI
│ │ └── RecordingIndicator.swift
│ ├── Webcam
│ │ └── WebcamView.swift
│ └── WhatsNewView.swift
├── dynamic.m4a
├── enums
│ └── generic.swift
├── extensions
│ ├── ActionBar.swift
│ ├── BundleInfos.swift
│ ├── Button+Bouncing.swift
│ ├── ConditionalModifier.swift
│ ├── DataTypes+Extensions.swift
│ ├── KeyboardShortcutsHelper.swift
│ ├── MouseTracker.swift
│ ├── NSImage+Extensions.swift
│ └── PanGesture.swift
├── helpers
│ ├── AppIcons.swift
│ ├── AppleScriptError.swift
│ ├── AppleScriptHelper.swift
│ ├── AppleScriptRunner.swift
│ ├── AudioPlayer.swift
│ ├── Clipboard+Content.swift
│ ├── MediaChecker.swift
│ ├── SensorError.swift
│ ├── SensorMethod.swift
│ └── SystemHUDDebugger.swift
├── managers
│ ├── BatteryActivityManager.swift
│ ├── BluetoothAudioManager.swift
│ ├── CalendarManager.swift
│ ├── ClipboardManager.swift
│ ├── ClipboardPanelManager.swift
│ ├── ClipboardWindowManager.swift
│ ├── ColorPickerManager.swift
│ ├── ColorPickerManager_Fixed.swift
│ ├── DoNotDisturbManager.swift
│ ├── DynamicIslandExtensionManager.swift
│ ├── ImageService.swift
│ ├── MusicManager.swift
│ ├── NotchSpaceManager.swift
│ ├── ScreenAssistantManager.swift
│ ├── ScreenAssistantPanelManager.swift
│ ├── ScreenRecordingManager.swift
│ ├── StatsManager.swift
│ ├── StatsPanelManager.swift
│ ├── SystemChangesObserver.swift
│ ├── SystemDisplayManager.swift
│ ├── SystemHUDManager.swift
│ ├── SystemKeyObserver.swift
│ ├── SystemOSDManager.swift
│ ├── SystemVolumeManager.swift
│ ├── TimerManager.swift
│ └── WebcamManager.swift
├── menu
│ └── StatusBarMenu.swift
├── metal
│ └── visualizer.metal
├── models
│ ├── BatteryStatusViewModel.swift
│ ├── CalendarModel.swift
│ ├── Constants.swift
│ ├── DynamicIslandViewModel.swift
│ ├── EventModel.swift
│ ├── PickedColor.swift
│ └── PlaybackState.swift
├── observers
│ └── FullscreenMediaDetection.swift
├── private
│ └── CGSSpace.swift
├── sizing
│ └── matters.swift
├── strings
│ └── constants.swift
└── utils
│ └── Logger.swift
├── DynamicIslandSamples
├── clipboardpanel.png
├── clipboardpopover.png
├── colorpickerpanel.png
├── colorpickerpopover.png
├── dynamicisland-minimalistic.png
├── dynamicislandscreenrecord.gif
├── logo.png
├── media.png
└── statsmonitor.png
├── Frameworks
└── MediaRemoteAdapter.framework
│ ├── MediaRemoteAdapter
│ ├── Resources
│ └── Versions
│ ├── A
│ ├── MediaRemoteAdapter
│ ├── Resources
│ │ └── Info.plist
│ └── _CodeSignature
│ │ └── CodeResources
│ └── Current
├── LICENSE
├── MIGRATION_SUMMARY.md
├── ONBOARDING_ENHANCEMENT.md
├── ReadMe.md
├── Updates
└── appcast.xml
└── mediaremote-adapter
├── MediaRemoteAdapter.framework
├── MediaRemoteAdapter
├── Resources
└── Versions
│ ├── A
│ ├── MediaRemoteAdapter
│ ├── Resources
│ │ └── Info.plist
│ └── _CodeSignature
│ │ └── CodeResources
│ └── Current
├── NowPlayingTestClient
└── mediaremote-adapter.pl
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | # Xcode
3 | #
4 | build/
5 | DerivedData/
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata/
15 | *.xcscmblueprint
16 |
17 | # SwiftPM
18 | #
19 | .build/
20 | Package.resolved
21 |
22 | # CocoaPods
23 | #
24 | Pods/
25 | Podfile.lock
26 |
27 | # Carthage
28 | #
29 | Carthage/Build/
30 |
31 | # Fastlane
32 | #
33 | fastlane/report.xml
34 | fastlane/Preview.html
35 | fastlane/screenshots
36 | fastlane/test_output
37 |
38 | # Archives
39 | #
40 | *.xcarchive
41 |
42 | # App data
43 | #
44 | *.ipa
45 | *.dSYM.zip
46 | *.dSYM
47 |
48 | # Playgrounds
49 | #
50 | timeline.xctimeline
51 | playground.xcworkspace
52 |
53 | # Xcode Server
54 | #
55 | integration.json
56 |
57 | # Other
58 | #
59 | *.moved-aside
60 | *.xcuserstate
61 |
62 | # GitHub instructions and agent files
63 | #
64 | .github/
65 | rough_note.md
66 | Solid
67 | SettingsView.swift.md
68 | logic.md
69 | TheBoringWorker-HUD
70 | boringnotch + meteo + airpods + traduzione modificabile
71 | segmentedhud.swift.ref
72 | gemini-apireference.md
73 |
74 | ScreenshotApp-main
75 | stats
76 | boring.notch
77 | referenceimages
78 |
79 | PrivateWorks
80 |
81 | *.py
82 | *.sh
83 | comparison_report.json
84 | *.txt
--------------------------------------------------------------------------------
/DynamicIsland.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/DynamicIsland.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/DynamicIsland.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "originHash" : "2741287b65cf0758abd89baadf314f5f8cbfcb46cb8d236a83f98731e5501ee3",
3 | "pins" : [
4 | {
5 | "identity" : "defaults",
6 | "kind" : "remoteSourceControl",
7 | "location" : "https://github.com/sindresorhus/Defaults",
8 | "state" : {
9 | "revision" : "00c82eff4550c87cf9c547d7e5493a6a97837061",
10 | "version" : "9.0.3"
11 | }
12 | },
13 | {
14 | "identity" : "keyboardshortcuts",
15 | "kind" : "remoteSourceControl",
16 | "location" : "https://github.com/sindresorhus/KeyboardShortcuts",
17 | "state" : {
18 | "revision" : "045cf174010beb335fa1d2567d18c057b8787165",
19 | "version" : "2.3.0"
20 | }
21 | },
22 | {
23 | "identity" : "launchatlogin-modern",
24 | "kind" : "remoteSourceControl",
25 | "location" : "https://github.com/sindresorhus/LaunchAtLogin-Modern",
26 | "state" : {
27 | "branch" : "main",
28 | "revision" : "a04ec1c363be3627734f6dad757d82f5d4fa8fcc"
29 | }
30 | },
31 | {
32 | "identity" : "lottie-spm",
33 | "kind" : "remoteSourceControl",
34 | "location" : "https://github.com/airbnb/lottie-spm.git",
35 | "state" : {
36 | "branch" : "main",
37 | "revision" : "90fa25ba0feb39c22915d41b55226cc95955dfcc"
38 | }
39 | },
40 | {
41 | "identity" : "lottieui",
42 | "kind" : "remoteSourceControl",
43 | "location" : "https://github.com/jasudev/LottieUI.git",
44 | "state" : {
45 | "branch" : "main",
46 | "revision" : "0cd5b54a1c8467b19c01f56395aec179087b62e2"
47 | }
48 | },
49 | {
50 | "identity" : "macrovisionkit",
51 | "kind" : "remoteSourceControl",
52 | "location" : "https://github.com/TheBoredTeam/MacroVisionKit",
53 | "state" : {
54 | "revision" : "5e8b06c448298de182638bb28ca064eaf6b1fe99",
55 | "version" : "0.1.0"
56 | }
57 | },
58 | {
59 | "identity" : "pow",
60 | "kind" : "remoteSourceControl",
61 | "location" : "https://github.com/EmergeTools/Pow",
62 | "state" : {
63 | "revision" : "a504eb6d144bcf49f4f33029a2795345cb39e6b4",
64 | "version" : "1.0.5"
65 | }
66 | },
67 | {
68 | "identity" : "sparkle",
69 | "kind" : "remoteSourceControl",
70 | "location" : "https://github.com/sparkle-project/Sparkle",
71 | "state" : {
72 | "revision" : "0ca3004e98712ea2b39dd881d28448630cce1c99",
73 | "version" : "2.7.0"
74 | }
75 | },
76 | {
77 | "identity" : "swift-collections",
78 | "kind" : "remoteSourceControl",
79 | "location" : "https://github.com/apple/swift-collections.git",
80 | "state" : {
81 | "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7",
82 | "version" : "1.1.4"
83 | }
84 | },
85 | {
86 | "identity" : "swift-syntax",
87 | "kind" : "remoteSourceControl",
88 | "location" : "https://github.com/swiftlang/swift-syntax",
89 | "state" : {
90 | "revision" : "f99ae8aa18f0cf0d53481901f88a0991dc3bd4a2",
91 | "version" : "601.0.1"
92 | }
93 | },
94 | {
95 | "identity" : "swiftui-introspect",
96 | "kind" : "remoteSourceControl",
97 | "location" : "https://github.com/siteline/swiftui-introspect",
98 | "state" : {
99 | "revision" : "807f73ce09a9b9723f12385e592b4e0aaebd3336",
100 | "version" : "1.3.0"
101 | }
102 | },
103 | {
104 | "identity" : "theboringworkernotifier",
105 | "kind" : "remoteSourceControl",
106 | "location" : "https://github.com/TheBoredTeam/TheBoringWorkerNotifier.git",
107 | "state" : {
108 | "branch" : "main",
109 | "revision" : "bc2b292c88035dcfc41fc7e3d8021689e624d137"
110 | }
111 | }
112 | ],
113 | "version" : 3
114 | }
115 |
--------------------------------------------------------------------------------
/DynamicIsland.xcodeproj/xcuserdata/admin63.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | DynamicIsland.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "upscaled-7.png",
5 | "idiom" : "mac",
6 | "scale" : "1x",
7 | "size" : "16x16"
8 | },
9 | {
10 | "filename" : "upscaled-6 1.png",
11 | "idiom" : "mac",
12 | "scale" : "2x",
13 | "size" : "16x16"
14 | },
15 | {
16 | "filename" : "upscaled-6.png",
17 | "idiom" : "mac",
18 | "scale" : "1x",
19 | "size" : "32x32"
20 | },
21 | {
22 | "filename" : "upscaled-5.png",
23 | "idiom" : "mac",
24 | "scale" : "2x",
25 | "size" : "32x32"
26 | },
27 | {
28 | "filename" : "upscaled-4.png",
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "filename" : "upscaled-3 1.png",
35 | "idiom" : "mac",
36 | "scale" : "2x",
37 | "size" : "128x128"
38 | },
39 | {
40 | "filename" : "upscaled-3.png",
41 | "idiom" : "mac",
42 | "scale" : "1x",
43 | "size" : "256x256"
44 | },
45 | {
46 | "filename" : "upscaled-2.png",
47 | "idiom" : "mac",
48 | "scale" : "2x",
49 | "size" : "256x256"
50 | },
51 | {
52 | "filename" : "notch-stage-icon2 9.png",
53 | "idiom" : "mac",
54 | "scale" : "1x",
55 | "size" : "512x512"
56 | },
57 | {
58 | "filename" : "notch-stage-icon2 10.png",
59 | "idiom" : "mac",
60 | "scale" : "2x",
61 | "size" : "512x512"
62 | }
63 | ],
64 | "info" : {
65 | "author" : "xcode",
66 | "version" : 1
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/AppIcon.appiconset/notch-stage-icon2 10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/Assets.xcassets/AppIcon.appiconset/notch-stage-icon2 10.png
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/AppIcon.appiconset/notch-stage-icon2 9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/Assets.xcassets/AppIcon.appiconset/notch-stage-icon2 9.png
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/AppIcon.appiconset/upscaled-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/Assets.xcassets/AppIcon.appiconset/upscaled-2.png
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/AppIcon.appiconset/upscaled-3 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/Assets.xcassets/AppIcon.appiconset/upscaled-3 1.png
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/AppIcon.appiconset/upscaled-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/Assets.xcassets/AppIcon.appiconset/upscaled-3.png
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/AppIcon.appiconset/upscaled-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/Assets.xcassets/AppIcon.appiconset/upscaled-4.png
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/AppIcon.appiconset/upscaled-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/Assets.xcassets/AppIcon.appiconset/upscaled-5.png
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/AppIcon.appiconset/upscaled-6 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/Assets.xcassets/AppIcon.appiconset/upscaled-6 1.png
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/AppIcon.appiconset/upscaled-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/Assets.xcassets/AppIcon.appiconset/upscaled-6.png
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/AppIcon.appiconset/upscaled-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/Assets.xcassets/AppIcon.appiconset/upscaled-7.png
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/Github.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "GitHub Mark White.svg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "GitHub Mark White 1.svg",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "GitHub Mark White 2.svg",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/Github.imageset/GitHub Mark White 1.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/Github.imageset/GitHub Mark White 2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/Github.imageset/GitHub Mark White.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/LinkedIn.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "LinkedIn_logo_initials.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/LinkedIn.imageset/LinkedIn_logo_initials.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/Assets.xcassets/LinkedIn.imageset/LinkedIn_logo_initials.png
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/bolt.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "bolt.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/bolt.imageset/bolt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/Assets.xcassets/bolt.imageset/bolt.png
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/chrome.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Google Chrome macOS BigSur Icon.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "Google Chrome macOS BigSur Icon 1.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "Google Chrome macOS BigSur Icon 2.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/chrome.imageset/Google Chrome macOS BigSur Icon 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/Assets.xcassets/chrome.imageset/Google Chrome macOS BigSur Icon 1.png
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/chrome.imageset/Google Chrome macOS BigSur Icon 2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/Assets.xcassets/chrome.imageset/Google Chrome macOS BigSur Icon 2.png
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/chrome.imageset/Google Chrome macOS BigSur Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/Assets.xcassets/chrome.imageset/Google Chrome macOS BigSur Icon.png
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/defaultmusic.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "scale" : "3x"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/ebullioscopic.imageset/819b59197be07c800478c728143326f1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/Assets.xcassets/ebullioscopic.imageset/819b59197be07c800478c728143326f1.png
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/ebullioscopic.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "819b59197be07c800478c728143326f1.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/logo.imageset/ChatGPT Image Aug 13, 2025, 11_50_32 PM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/Assets.xcassets/logo.imageset/ChatGPT Image Aug 13, 2025, 11_50_32 PM.png
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/logo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "ChatGPT Image Aug 13, 2025, 11_50_32 PM.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/logo2.imageset/ChatGPT_Image_Aug_13__2025__11_50_32_PM-removebg-preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/Assets.xcassets/logo2.imageset/ChatGPT_Image_Aug_13__2025__11_50_32_PM-removebg-preview.png
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/logo2.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "ChatGPT_Image_Aug_13__2025__11_50_32_PM-removebg-preview.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/plug.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "plug.png",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/plug.imageset/plug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/Assets.xcassets/plug.imageset/plug.png
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/sparkle.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "sparkle.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/sparkle.imageset/sparkle.svg:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/spotlight.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "spotlight.svg",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/DynamicIsland/Assets.xcassets/spotlight.imageset/spotlight.svg:
--------------------------------------------------------------------------------
1 |
36 |
--------------------------------------------------------------------------------
/DynamicIsland/DynamicIsland.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.automation.apple-events
6 |
7 | com.apple.security.temporary-exception.apple-events
8 |
9 | com.spotify.client
10 | com.apple.Music
11 |
12 | com.apple.security.temporary-exception.mach-lookup.global-name
13 |
14 | $(PRODUCT_BUNDLE_IDENTIFIER)-spks
15 | $(PRODUCT_BUNDLE_IDENTIFIER)-spki
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/DynamicIsland/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSAppTransportSecurity
6 |
7 | NSAllowsArbitraryLoads
8 |
9 |
10 | NSBluetoothAlwaysUsageDescription
11 | Dynamic Island needs Bluetooth access to detect when audio devices connect and display their battery status in the HUD.
12 | NSScreenCaptureUsageDescription
13 | Dynamic Island needs screen recording permission to capture screenshots for the AI assistant feature.
14 | SUEnableDownloaderService
15 |
16 | SUEnableInstallerLauncherService
17 |
18 | SUFeedURL
19 | https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/main/Updates/appcast.xml
20 | SUPublicEDKey
21 | q2YQaJ1umGkaIJWMGN9Isj5fx/YlUtxnzHEBqFtfZcg=
22 | UTImportedTypeDeclarations
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/DynamicIsland/MediaControllers/MediaControllerProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MediaControllerProtocol.swift
3 | // DynamicIsland
4 | //
5 | // Created by Alexander Greco on 2025-03-29.
6 | //
7 |
8 | import Foundation
9 | import AppKit
10 | import Combine
11 |
12 | protocol MediaControllerProtocol: ObservableObject {
13 | var playbackStatePublisher: AnyPublisher { get }
14 | var isWorking: Bool { get }
15 | func play() async
16 | func pause() async
17 | func seek(to time: Double) async
18 | func nextTrack() async
19 | func previousTrack() async
20 | func togglePlay() async
21 | func toggleShuffle() async
22 | func toggleRepeat() async
23 | func isActive() -> Bool
24 | func updatePlaybackInfo() async
25 | }
26 |
--------------------------------------------------------------------------------
/DynamicIsland/MediaControllers/YouTube Music Controller/YouTubeMusicAuthentication.swift:
--------------------------------------------------------------------------------
1 | //
2 | // YouTubeMusicAuthentication.swift
3 | // DynamicIsland
4 | //
5 | // Created by Alexander on 2025-09-14.
6 | //
7 |
8 | import Foundation
9 |
10 | // MARK: - Authentication Manager
11 | actor YouTubeMusicAuthManager {
12 | private var accessToken: String?
13 | private var authenticationTask: Task?
14 | private let httpClient: YouTubeMusicHTTPClient
15 |
16 | init(httpClient: YouTubeMusicHTTPClient) {
17 | self.httpClient = httpClient
18 | }
19 |
20 | var currentToken: String? {
21 | accessToken
22 | }
23 |
24 | func authenticate() async throws -> String {
25 | // Return existing token if valid
26 | if let token = accessToken {
27 | return token
28 | }
29 |
30 | // Wait for ongoing authentication if in progress
31 | if let task = authenticationTask {
32 | return try await task.value
33 | }
34 |
35 | // Start new authentication
36 | let task = Task {
37 | do {
38 | let token = try await httpClient.authenticate()
39 | await setToken(token)
40 | return token
41 | } catch {
42 | await clearAuthenticationTask()
43 | throw error
44 | }
45 | }
46 |
47 | authenticationTask = task
48 | return try await task.value
49 | }
50 |
51 | func invalidateToken() async {
52 | accessToken = nil
53 | authenticationTask?.cancel()
54 | authenticationTask = nil
55 | }
56 |
57 | private func setToken(_ token: String) async {
58 | accessToken = token
59 | authenticationTask = nil
60 | }
61 |
62 | private func clearAuthenticationTask() async {
63 | authenticationTask = nil
64 | }
65 | }
66 |
67 | // MARK: - Authentication State
68 | enum AuthenticationState: Sendable {
69 | case unauthenticated
70 | case authenticating
71 | case authenticated(String)
72 | case failed(Error)
73 |
74 | var isAuthenticated: Bool {
75 | if case .authenticated = self {
76 | return true
77 | }
78 | return false
79 | }
80 |
81 | var token: String? {
82 | if case .authenticated(let token) = self {
83 | return token
84 | }
85 | return nil
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/DynamicIsland/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/DynamicIsland/Shortcuts/ShortcutConstants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Constants.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 16/08/2024.
6 | //
7 |
8 | import KeyboardShortcuts
9 | import SwiftUI
10 |
11 | extension KeyboardShortcuts.Name {
12 | static let clipboardHistoryPanel = Self("clipboardHistoryPanel", default: .init(.c, modifiers: [.shift, .command]))
13 | static let colorPickerPanel = Self("colorPickerPanel", default: .init(.p, modifiers: [.shift, .command]))
14 | static let screenAssistantPanel = Self("screenAssistantPanel", default: .init(.a, modifiers: [.shift, .command]))
15 | static let statsPanel = Self("statsPanel", default: .init(.s, modifiers: [.shift, .command]))
16 | static let toggleMicrophone = Self("toggleMicrophone", default: .init(.f5, modifiers: [.function]))
17 | static let decreaseBacklight = Self("decreaseBacklight", default: .init(.f1, modifiers: [.command]))
18 | static let increaseBacklight = Self("increaseBacklight", default: .init(.f2, modifiers: [.command]))
19 | static let toggleSneakPeek = Self("toggleSneakPeek", default: .init(.h, modifiers: [.command, .shift]))
20 | static let toggleNotchOpen = Self("toggleNotchOpen", default: .init(.i, modifiers: [.command, .shift]))
21 | static let startDemoTimer = Self("startDemoTimer", default: .init(.t, modifiers: [.command, .shift]))
22 | }
23 |
--------------------------------------------------------------------------------
/DynamicIsland/animations/drop.swift:
--------------------------------------------------------------------------------
1 | //
2 | // drop.swift
3 | // DynamicIsland
4 | //
5 | // Created by Harsh Vardhan Goswami on 04/08/24.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 |
12 | public class DynamicIslandAnimations {
13 | @Published var notchStyle: Style = .notch
14 |
15 | init() {
16 | self.notchStyle = .notch
17 | }
18 |
19 | var animation: Animation {
20 | if #available(macOS 14.0, *), notchStyle == .notch {
21 | Animation.spring(.bouncy(duration: 0.4))
22 | } else {
23 | Animation.timingCurve(0.16, 1, 0.3, 1, duration: 0.7)
24 | }
25 | }
26 |
27 | // TODO: Move all animations to this file
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/DynamicIsland/components/AnimatedFace.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnimatedFace.swift
3 | //
4 | // Created by Harsh Vardhan Goswami on 04/08/24.
5 | //
6 |
7 | import SwiftUI
8 |
9 | struct MinimalFaceFeatures: View {
10 | @State private var isBlinking = false
11 | @State var height:CGFloat = 20;
12 | @State var width:CGFloat = 30;
13 |
14 | var body: some View {
15 | VStack(spacing: 4) { // Adjusted spacing to fit within 30x30
16 | // Eyes
17 | HStack(spacing: 4) { // Adjusted spacing to fit within 30x30
18 | Eye(isBlinking: $isBlinking)
19 | Eye(isBlinking: $isBlinking)
20 | }
21 |
22 | // Nose and mouth combined
23 | VStack(spacing: 2) { // Adjusted spacing to fit within 30x30
24 | // Nose
25 | RoundedRectangle(cornerRadius: 2)
26 | .fill(Color.white)
27 | .frame(width: 3, height: 4)
28 |
29 | // Mouth (happy)
30 | GeometryReader { geometry in
31 | Path { path in
32 | let width = geometry.size.width
33 | let height = geometry.size.height
34 | path.move(to: CGPoint(x: 0, y: height / 2))
35 | path.addQuadCurve(to: CGPoint(x: width, y: height / 2), control: CGPoint(x: width / 2, y: height))
36 | }
37 | .stroke(Color.white, lineWidth: 2)
38 | }
39 | .frame(width: 14, height: 10)
40 | }
41 | }
42 | .frame(width: self.width, height: self.height) // Maximum size of face
43 | .onAppear {
44 | startBlinking()
45 | }
46 | }
47 |
48 | func startBlinking() {
49 | Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { _ in
50 | withAnimation(.spring(duration: 0.2)) {
51 | isBlinking = true
52 | }
53 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
54 | withAnimation(.spring(duration: 0.2)) {
55 | isBlinking = false
56 | }
57 | }
58 | }
59 | }
60 | }
61 |
62 | struct Eye: View {
63 | @Binding var isBlinking: Bool
64 |
65 | var body: some View {
66 | RoundedRectangle(cornerRadius: 10)
67 | .fill(Color.white)
68 | .frame(width: 4, height: isBlinking ? 1 : 4)
69 | .frame(maxWidth: 15, maxHeight: 15) // Adjusted max size
70 | .animation(.easeInOut(duration: 0.1), value: isBlinking)
71 | }
72 | }
73 |
74 | struct MinimalFaceFeatures_Previews: PreviewProvider {
75 | static var previews: some View {
76 | ZStack {
77 | Color.black
78 | MinimalFaceFeatures()
79 | }
80 | .previewLayout(.fixed(width: 60, height: 60)) // Adjusted preview size for better visibility
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/DynamicIsland/components/BottomRoundedRectangle.swift:
--------------------------------------------------------------------------------
1 |
2 |
3 | import SwiftUI
4 |
5 |
6 | struct BottomRoundedRectangle: Shape {
7 | var radius: CGFloat
8 |
9 | func path(in rect: CGRect) -> Path {
10 | var path = Path()
11 |
12 | // Top left corner
13 | path.move(to: CGPoint(x: rect.minX, y: rect.minY))
14 |
15 | // Top right corner
16 | path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
17 |
18 | // Bottom right corner (rounded)
19 | path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - radius))
20 | path.addArc(center: CGPoint(x: rect.maxX - radius, y: rect.maxY - radius),
21 | radius: radius,
22 | startAngle: Angle(degrees: 0),
23 | endAngle: Angle(degrees: 90),
24 | clockwise: false)
25 |
26 | // Bottom left corner (rounded)
27 | path.addLine(to: CGPoint(x: rect.minX + radius, y: rect.maxY))
28 | path.addArc(center: CGPoint(x: rect.minX + radius, y: rect.maxY - radius),
29 | radius: radius,
30 | startAngle: Angle(degrees: 90),
31 | endAngle: Angle(degrees: 180),
32 | clockwise: false)
33 |
34 | // Back to top left to close the path
35 | path.closeSubpath()
36 |
37 | return path
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Clipboard/ClipboardShared.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClipboardShared.swift
3 | // DynamicIsland
4 | //
5 | // Created by Ebullioscopic on 12/08/25.
6 | //
7 |
8 | import SwiftUI
9 |
10 | enum ClipboardTab: String, CaseIterable {
11 | case history = "History"
12 | case favorites = "Favorites"
13 |
14 | var icon: String {
15 | switch self {
16 | case .history: return "clock"
17 | case .favorites: return "heart.fill"
18 | }
19 | }
20 | }
21 |
22 | struct ClipboardTabButton: View {
23 | let tab: ClipboardTab
24 | let isSelected: Bool
25 | let action: () -> Void
26 | @ObservedObject var clipboardManager = ClipboardManager.shared
27 |
28 | var itemCount: Int {
29 | switch tab {
30 | case .history:
31 | return clipboardManager.regularHistory.count
32 | case .favorites:
33 | return clipboardManager.pinnedItems.count
34 | }
35 | }
36 |
37 | var body: some View {
38 | Button(action: action) {
39 | HStack(spacing: 6) {
40 | Image(systemName: tab.icon)
41 | .font(.system(size: 11))
42 |
43 | Text(tab.rawValue)
44 | .font(.system(size: 11, weight: .medium))
45 |
46 | if itemCount > 0 {
47 | Text("\(itemCount)")
48 | .font(.system(size: 9, weight: .bold))
49 | .foregroundColor(isSelected ? .white : .secondary)
50 | .padding(.horizontal, 4)
51 | .padding(.vertical, 1)
52 | .background(
53 | Capsule()
54 | .fill(isSelected ? Color.white.opacity(0.3) : Color.gray.opacity(0.2))
55 | )
56 | }
57 | }
58 | .foregroundColor(isSelected ? .white : .secondary)
59 | .padding(.horizontal, 12)
60 | .padding(.vertical, 6)
61 | .background(
62 | RoundedRectangle(cornerRadius: 6)
63 | .fill(isSelected ? Color.blue : Color.clear)
64 | )
65 | }
66 | .buttonStyle(PlainButtonStyle())
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/DynamicIsland/components/ColorPicker/ColorPickedFeedbackView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ColorPickedFeedbackView.swift
3 | // DynamicIsland
4 | //
5 | // Created by Ebullioscopic on 14/08/25.
6 | //
7 |
8 | import SwiftUI
9 | import Defaults
10 |
11 | struct ColorPickedFeedbackView: View {
12 | let color: PickedColor
13 | @Binding var isShowing: Bool
14 |
15 | var body: some View {
16 | VStack(spacing: 12) {
17 | // Color preview
18 | RoundedRectangle(cornerRadius: 12)
19 | .fill(color.color)
20 | .frame(width: 60, height: 60)
21 | .overlay(
22 | RoundedRectangle(cornerRadius: 12)
23 | .stroke(Color.white.opacity(0.8), lineWidth: 2)
24 | )
25 | .shadow(color: .black.opacity(0.3), radius: 8)
26 |
27 | // Color info
28 | VStack(spacing: 4) {
29 | Text("Color Picked!")
30 | .font(.system(size: 14, weight: .semibold))
31 | .foregroundColor(.primary)
32 |
33 | Text(color.hexString)
34 | .font(.system(size: 12, weight: .medium, design: .monospaced))
35 | .foregroundColor(.secondary)
36 | .padding(.horizontal, 8)
37 | .padding(.vertical, 4)
38 | .background(Color.gray.opacity(0.2))
39 | .cornerRadius(6)
40 | }
41 | }
42 | .padding(16)
43 | .background(VisualEffectView(material: .hudWindow, blendingMode: .behindWindow))
44 | .cornerRadius(16)
45 | .shadow(color: .black.opacity(0.3), radius: 12)
46 | .scaleEffect(isShowing ? 1.0 : 0.8)
47 | .opacity(isShowing ? 1.0 : 0.0)
48 | .animation(.spring(response: 0.4, dampingFraction: 0.7), value: isShowing)
49 | .onTapGesture {
50 | // Copy to clipboard on tap
51 | ColorPickerManager.shared.copyToClipboard(color.hexString)
52 |
53 | if Defaults[.enableHaptics] {
54 | NSHapticFeedbackManager.defaultPerformer.perform(.generic, performanceTime: .default)
55 | }
56 | }
57 | }
58 | }
59 |
60 | #Preview {
61 | ColorPickedFeedbackView(
62 | color: PickedColor(nsColor: NSColor.blue, point: CGPoint(x: 100, y: 100)),
63 | isShowing: .constant(true)
64 | )
65 | .frame(width: 300, height: 200)
66 | }
67 |
--------------------------------------------------------------------------------
/DynamicIsland/components/DynamicIslandSystemTiles.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicIslandSystemTiles.swift
3 | // DynamicIsland
4 | //
5 | // Created by Harsh Vardhan Goswami on 04/08/24.
6 | // DynamicIslandSystemTiles.swift
7 | // DynamicIsland
8 | //
9 | // Created by Harsh Vardhan Goswami on 16/08/24.
10 | //
11 |
12 | import Foundation
13 | import SwiftUI
14 | import Defaults
15 |
16 | struct SystemItemButton: View {
17 | @EnvironmentObject var vm: DynamicIslandViewModel
18 | @State var icon: String = "gear"
19 | var onTap: () -> Void
20 | @State var label: String?
21 | @State var showEmojis: Bool = true
22 | @State var emoji: String = "🔧"
23 |
24 | var body: some View {
25 | Button(action: onTap) {
26 | if Defaults[.tileShowLabels] {
27 | HStack {
28 | if !showEmojis {
29 | Image(systemName: icon)
30 | .resizable()
31 | .aspectRatio(contentMode: .fit)
32 | .frame(width: 10)
33 | .foregroundStyle(.gray)
34 | }
35 |
36 | Text((showEmojis ? "\(emoji) " : "") + label!)
37 | .font(.caption2)
38 | .fontWeight(.regular)
39 | .foregroundStyle(.gray)
40 | .frame(maxWidth: .infinity, alignment: .leading)
41 | .allowsTightening(true)
42 | .minimumScaleFactor(0.7)
43 | .lineLimit(1)
44 | }
45 | } else {
46 | Color.clear
47 | .overlay {
48 | Image(systemName: icon)
49 | .foregroundStyle(.gray)
50 | }
51 | .aspectRatio(1, contentMode: .fit)
52 | }
53 | }
54 | .buttonStyle(BouncingButtonStyle(vm: vm))
55 | }
56 | }
57 |
58 | func logout() {
59 | DispatchQueue.global(qos: .background).async {
60 | let appleScript = """
61 | tell application "System Events" to log out
62 | """
63 |
64 | var error: NSDictionary?
65 | if let scriptObject = NSAppleScript(source: appleScript) {
66 | scriptObject.executeAndReturnError(&error)
67 | if let error = error {
68 | print("Error: \(error)")
69 | }
70 | }
71 | }
72 | }
73 |
74 | struct DynamicIslandSystemTiles: View {
75 | @EnvironmentObject var vm: DynamicIslandViewModel
76 | @ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
77 |
78 | struct ItemButton {
79 | var icon: String
80 | var onTap: () -> Void
81 | }
82 |
83 | var body: some View {
84 | Grid {
85 | GridRow {
86 | // SystemItemButton(icon: "clipboard", onTap: {
87 | // vm.openClipboard()
88 | // }, label: "Clipboard History", showEmojis: Defaults[.showEmojis], emoji: "✨")
89 | // SystemItemButton(icon: "keyboard", onTap: {
90 | // vm?.close()
91 | // vm?.togglesneakPeek(status: true, type: .backlight, value: 1)
92 | // }, label: "💡 Keyboard Backlight")
93 | }
94 | GridRow {
95 | SystemItemButton(icon: coordinator.currentMicStatus ? "mic" : "mic.slash", onTap: {
96 | coordinator.toggleMic()
97 | vm.close()
98 | }, label: "Toggle Microphone", showEmojis: Defaults[.showEmojis], emoji: coordinator.currentMicStatus ? "😀" : "🤫")
99 | // SystemItemButton(icon: "lock", onTap: {
100 | // logout()
101 | // }, label: "🔒 Lock My Device")
102 | }
103 | }
104 | }
105 | }
106 |
107 | #Preview {
108 | DynamicIslandSystemTiles().padding()
109 | }
110 |
--------------------------------------------------------------------------------
/DynamicIsland/components/EmptyState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EmptyState.swift
3 | //
4 | // Created by Harsh Vardhan Goswami on 04/08/24.
5 | //
6 |
7 | import SwiftUI
8 |
9 | struct EmptyStateView: View {
10 | var message: String
11 | @State private var isVisible = true
12 |
13 | var body: some View {
14 | HStack {
15 | MinimalFaceFeatures(
16 | height: 70, width: 80)
17 | Text(message)
18 | .font(.system(size:14))
19 | .foregroundColor(.gray)
20 | }.transition(.blurReplace.animation(.spring(.bouncy(duration: 0.3)))) // Smooth animation
21 | }
22 | }
23 |
24 | #Preview {
25 | EmptyStateView(message: "Play some music babies")
26 | }
27 |
--------------------------------------------------------------------------------
/DynamicIsland/components/HoverButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HoverButton.swift
3 | // DynamicIsland
4 | //
5 | // Created by Kraigo on 04.09.2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct HoverButton: View {
11 | var icon: String
12 | var iconColor: Color = .white;
13 | var scale: Image.Scale = .medium
14 | var action: () -> Void
15 | var contentTransition: ContentTransition = .symbolEffect;
16 |
17 | @State private var isHovering = false
18 |
19 | var body: some View {
20 | let size = CGFloat(scale == .large ? 40 : 30)
21 |
22 | Button(action: action) {
23 | Rectangle()
24 | .fill(.clear)
25 | .contentShape(Rectangle())
26 | .frame(width: size, height: size)
27 | .overlay {
28 | Capsule()
29 | .fill(isHovering ? Color.gray.opacity(0.2) : .clear)
30 | .frame(width: size, height: size)
31 | .overlay {
32 | Image(systemName: icon)
33 | .foregroundColor(iconColor)
34 | .contentTransition(contentTransition)
35 | .font(scale == .large ? .largeTitle : .body)
36 | }
37 | }
38 | }
39 | .buttonStyle(PlainButtonStyle())
40 | .onHover { hovering in
41 | withAnimation(.smooth(duration: 0.3)) {
42 | isHovering = hovering
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Live activities/DownloadView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DownloadView.swift
3 | // DynamicIsland
4 | //
5 | // Created by Harsh Vardhan Goswami on 17/08/24.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | enum Browser {
12 | case safari
13 | case chrome
14 | }
15 |
16 | struct DownloadFile {
17 | var name: String
18 | var size: Int
19 | var formattedSize: String
20 | var browser: Browser
21 | }
22 |
23 | class DownloadWatcher: ObservableObject {
24 | @Published var downloadFiles: [DownloadFile] = []
25 | }
26 |
27 | struct DownloadArea: View {
28 | @EnvironmentObject var watcher: DownloadWatcher
29 |
30 | var body: some View {
31 | HStack(alignment: .center) {
32 | HStack {
33 | if watcher.downloadFiles.first!.browser == .safari {
34 | AppIcon(for: "com.apple.safari")
35 | } else {
36 | Image(.chrome).resizable().scaledToFit().frame(width: 30, height: 30)
37 | }
38 | VStack(alignment: .leading) {
39 | Text("Download")
40 | Text("In progress").font(.system(.footnote)).foregroundStyle(.gray)
41 | }
42 | }
43 | Spacer()
44 | HStack(spacing: 12) {
45 | VStack(alignment: .trailing) {
46 | Text(watcher.downloadFiles.first!.formattedSize)
47 | Text(watcher.downloadFiles.first!.name).font(.caption2).foregroundStyle(.gray)
48 | }
49 | }
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Live activities/LiveActivityModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LiveActivityModifier.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 12/08/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | enum ActivityType {
11 | case mediaPlayback
12 | case charging
13 | case download
14 | }
15 |
16 | struct LiveActivityModifier: ViewModifier {
17 | let `for`: ActivityType
18 | let leftContent: () -> Left
19 | let rightContent: () -> Right
20 |
21 | func body(content: Content) -> some View {
22 | content
23 | .overlay(
24 | HStack {
25 | leftContent()
26 | Spacer()
27 | //.frame(minWidth: vm.closedNotchSize.width)
28 | rightContent()
29 | }
30 | .padding()
31 | )
32 | }
33 | }
34 |
35 | extension View {
36 | func liveActivity(
37 | for activityId: ActivityType,
38 | @ViewBuilder left: @escaping () -> Left,
39 | @ViewBuilder right: @escaping () -> Right
40 | ) -> some View {
41 | self.modifier(LiveActivityModifier(for: activityId, leftContent: left, rightContent: right))
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Live activities/MarqueeTextView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MarqueeTextView.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 08/08/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SizePreferenceKey: PreferenceKey {
11 | static var defaultValue: CGSize = .zero
12 | static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
13 | value = nextValue()
14 | }
15 | }
16 |
17 | struct MeasureSizeModifier: ViewModifier {
18 | func body(content: Content) -> some View {
19 | content.background(GeometryReader { geometry in
20 | Color.clear.preference(key: SizePreferenceKey.self, value: geometry.size)
21 | })
22 | }
23 | }
24 |
25 | struct MarqueeText: View {
26 | @Binding var text: String
27 | let font: Font
28 | let nsFont: NSFont.TextStyle
29 | let textColor: Color
30 | let backgroundColor: Color
31 | let minDuration: Double
32 | let frameWidth: CGFloat
33 |
34 | @State private var animate = false
35 | @State private var textSize: CGSize = .zero
36 | @State private var offset: CGFloat = 0
37 |
38 | init(_ text: Binding, font: Font = .body, nsFont: NSFont.TextStyle = .body, textColor: Color = .primary, backgroundColor: Color = .clear, minDuration: Double = 3.0, frameWidth: CGFloat = 200) {
39 | _text = text
40 | self.font = font
41 | self.nsFont = nsFont
42 | self.textColor = textColor
43 | self.backgroundColor = backgroundColor
44 | self.minDuration = minDuration
45 | self.frameWidth = frameWidth
46 | }
47 |
48 | private var needsScrolling: Bool {
49 | textSize.width > frameWidth
50 | }
51 |
52 | var body: some View {
53 | GeometryReader { geometry in
54 | ZStack(alignment: .leading) {
55 | HStack(spacing: 20) {
56 | Text(text)
57 | Text(text)
58 | .opacity(needsScrolling ? 1 : 0)
59 | }
60 | .id(text)
61 | .font(font)
62 | .foregroundColor(textColor)
63 | .fixedSize(horizontal: true, vertical: false)
64 | .offset(x: self.animate ? offset : 0)
65 | .animation(
66 | self.animate ?
67 | .linear(duration: Double(textSize.width / 30))
68 | .delay(minDuration)
69 | .repeatForever(autoreverses: false) : .none,
70 | value: self.animate
71 | )
72 | .background(backgroundColor)
73 | .modifier(MeasureSizeModifier())
74 | .onPreferenceChange(SizePreferenceKey.self) { size in
75 | self.textSize = CGSize(width: size.width / 2, height: NSFont.preferredFont(forTextStyle: nsFont).pointSize)
76 | self.animate = false
77 | self.offset = 0
78 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.02){
79 | if needsScrolling {
80 | self.animate = true
81 | self.offset = -(textSize.width + 20)
82 | }
83 | }
84 | }
85 | .onChange(of: text) { _, _ in
86 | // Reset animation when text changes
87 | self.animate = false
88 | self.offset = 0
89 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.02){
90 | if needsScrolling {
91 | self.animate = true
92 | self.offset = -(textSize.width + 20)
93 | }
94 | }
95 | }
96 | }
97 | .frame(width: frameWidth, alignment: .leading)
98 | .clipped()
99 | }
100 | .frame(height: textSize.height * 1.3)
101 |
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/DynamicIsland/components/MinimalisticMusicView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MinimalisticMusicView.swift
3 | // DynamicIsland
4 | //
5 | // Created for minimalistic UI mode
6 | // A clean, focused music player for closed notch state
7 | //
8 |
9 | import SwiftUI
10 | import Defaults
11 |
12 | struct MinimalisticMusicView: View {
13 | @EnvironmentObject var vm: DynamicIslandViewModel
14 | @ObservedObject var musicManager = MusicManager.shared
15 | @State private var isHovering: Bool = false
16 |
17 | var body: some View {
18 | HStack(spacing: 0) {
19 | // Left: Album Art
20 | albumArtView
21 |
22 | // Middle: Song Title (scrolling if needed)
23 | Rectangle()
24 | .fill(.black)
25 | .overlay(
26 | HStack(alignment: .center) {
27 | if !musicManager.songTitle.isEmpty {
28 | MarqueeText(
29 | .constant(musicManager.songTitle),
30 | textColor: Defaults[.coloredSpectrogram] ? Color(nsColor: musicManager.avgColor) : Color.gray,
31 | minDuration: 0.4,
32 | frameWidth: 100
33 | )
34 | }
35 | }
36 | )
37 | .frame(width: vm.closedNotchSize.width)
38 |
39 | // Right: Music Visualizer
40 | visualizerView
41 | }
42 | .frame(height: vm.effectiveClosedNotchHeight + (isHovering ? 8 : 0), alignment: .center)
43 | .onHover { hovering in
44 | isHovering = hovering
45 | }
46 | }
47 |
48 | // MARK: - Album Art
49 |
50 | private var albumArtView: some View {
51 | HStack {
52 | Color.clear
53 | .aspectRatio(1, contentMode: .fit)
54 | .background(
55 | Image(nsImage: musicManager.albumArt)
56 | .resizable()
57 | .aspectRatio(contentMode: .fill)
58 | )
59 | .clipped()
60 | .clipShape(RoundedRectangle(cornerRadius: 18)) // Dramatically increased corner radius for minimalistic mode
61 | .frame(width: max(0, vm.effectiveClosedNotchHeight - 12), height: max(0, vm.effectiveClosedNotchHeight - 12))
62 | }
63 | .frame(width: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12)), height: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12)))
64 | }
65 |
66 | // MARK: - Visualizer
67 |
68 | private var visualizerView: some View {
69 | HStack {
70 | Rectangle()
71 | .fill(Defaults[.coloredSpectrogram] ? Color(nsColor: musicManager.avgColor).gradient : Color.gray.gradient)
72 | .frame(width: 50, alignment: .center)
73 | .mask {
74 | AudioSpectrumView(isPlaying: $musicManager.isPlaying)
75 | .frame(width: 16, height: 12)
76 | }
77 | .frame(width: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12)),
78 | height: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12)), alignment: .center)
79 | }
80 | .frame(width: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12)),
81 | height: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12)), alignment: .center)
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Music/LottieAnimationView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LottieAnimationView.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 2024. 10. 29..
6 | //
7 |
8 | import SwiftUI
9 | import Lottie
10 | import LottieUI
11 | import Defaults
12 |
13 | struct LottieAnimationView: View {
14 | let state1 = LUStateData(type: .loadedFrom(URL(string: "https://assets9.lottiefiles.com/packages/lf20_mniampqn.json")!), speed: 1.0, loopMode: .loop)
15 | @Default(.selectedVisualizer) var selectedVisualizer
16 | var body: some View {
17 | if selectedVisualizer == nil {
18 | LottieView(state: state1)
19 | } else {
20 | LottieView(
21 | state: LUStateData(
22 | type: .loadedFrom(selectedVisualizer!.url),
23 | speed: selectedVisualizer!.speed,
24 | loopMode: .loop
25 | )
26 | )
27 | }
28 | }
29 | }
30 |
31 | #Preview {
32 | LottieAnimationView()
33 | }
34 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Music/MusicVisualizer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MusicVisualizer.swift
3 | // DynamicIsland
4 | //
5 | // Created by Harsh Vardhan Goswami on 02/08/24.
6 | //
7 | import AppKit
8 | import Cocoa
9 | import SwiftUI
10 |
11 | class AudioSpectrum: NSView {
12 | private var barLayers: [CAShapeLayer] = []
13 | private var isPlaying: Bool = true
14 | private var animationTimer: Timer?
15 |
16 | override init(frame frameRect: NSRect) {
17 | super.init(frame: frameRect)
18 | wantsLayer = true
19 | setupBars()
20 | }
21 |
22 | required init?(coder: NSCoder) {
23 | super.init(coder: coder)
24 | wantsLayer = true
25 | setupBars()
26 | }
27 |
28 | private func setupBars() {
29 | let barWidth: CGFloat = 2
30 | let barCount = 4
31 | let spacing: CGFloat = barWidth
32 | let totalWidth = CGFloat(barCount) * (barWidth + spacing)
33 | let totalHeight: CGFloat = 14
34 | frame.size = CGSize(width: totalWidth, height: totalHeight)
35 |
36 | for i in 0 ..< barCount {
37 | let xPosition = CGFloat(i) * (barWidth + spacing)
38 | let barLayer = CAShapeLayer()
39 | barLayer.frame = CGRect(x: xPosition, y: 0, width: barWidth, height: totalHeight)
40 | barLayer.position = CGPoint(x: xPosition + barWidth / 2, y: totalHeight / 2)
41 | barLayer.fillColor = NSColor.white.cgColor
42 |
43 | let path = NSBezierPath(roundedRect: CGRect(x: 0, y: 0, width: barWidth, height: totalHeight),
44 | xRadius: barWidth / 2,
45 | yRadius: barWidth / 2)
46 | barLayer.path = path.cgPath
47 |
48 | barLayers.append(barLayer)
49 | layer?.addSublayer(barLayer)
50 | }
51 | }
52 |
53 | private func startAnimating() {
54 | guard animationTimer == nil else { return }
55 | animationTimer = Timer.scheduledTimer(withTimeInterval: 0.3, repeats: true) { [weak self] _ in
56 | self?.updateBars()
57 | }
58 | }
59 |
60 | private func stopAnimating() {
61 | animationTimer?.invalidate()
62 | animationTimer = nil
63 | resetBars()
64 | }
65 |
66 | private func updateBars() {
67 | for barLayer in barLayers {
68 | let animation = CABasicAnimation(keyPath: "transform.scale.y")
69 | animation.fromValue = barLayer.presentation()?.value(forKeyPath: "transform.scale.y") ?? 0.35
70 | animation.toValue = CGFloat.random(in: 0.35 ... 1.0)
71 | animation.duration = 0.3
72 | animation.autoreverses = true
73 | animation.fillMode = .forwards
74 | animation.isRemovedOnCompletion = false
75 | if #available(macOS 13.0, *) {
76 | animation.preferredFrameRateRange = CAFrameRateRange(minimum: 24, maximum: 24, preferred: 24)
77 | }
78 | barLayer.add(animation, forKey: "scaleY")
79 | }
80 | }
81 |
82 | private func resetBars() {
83 | for barLayer in barLayers {
84 | barLayer.removeAllAnimations()
85 | barLayer.transform = CATransform3DMakeScale(1, 0.35, 1)
86 | }
87 | }
88 |
89 | func setPlaying(_ playing: Bool) {
90 | isPlaying = playing
91 | if isPlaying {
92 | startAnimating()
93 | } else {
94 | stopAnimating()
95 | }
96 | }
97 | }
98 |
99 | struct AudioSpectrumView: NSViewRepresentable {
100 | @Binding var isPlaying: Bool
101 |
102 | func makeNSView(context: Context) -> AudioSpectrum {
103 | let spectrum = AudioSpectrum()
104 | spectrum.setPlaying(isPlaying)
105 | return spectrum
106 | }
107 |
108 | func updateNSView(_ nsView: AudioSpectrum, context: Context) {
109 | nsView.setPlaying(isPlaying)
110 | }
111 | }
112 |
113 | #Preview {
114 | AudioSpectrumView(isPlaying: .constant(true))
115 | .frame(width: 16, height: 20)
116 | .padding()
117 | }
118 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Notch/DynamicIslandExtrasMenu.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicIslandExtrasMenu.swift
3 | // DynamicIsland
4 | //
5 | // Created by Harsh Vardhan Goswami on 04/08/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct DynamicIslandLargeButtons: View {
11 | var action: () -> Void
12 | var icon: Image
13 | var title: String
14 | var body: some View {
15 | Button (
16 | action:action,
17 | label: {
18 | ZStack {
19 | RoundedRectangle(cornerRadius: 12.0).fill(.black).frame(width: 70, height: 70)
20 | VStack(spacing: 8) {
21 | icon.resizable()
22 | .aspectRatio(contentMode: .fit).frame(width:20)
23 | Text(title).font(.body)
24 | }
25 | }
26 | }).buttonStyle(PlainButtonStyle()).shadow(color: .black.opacity(0.5), radius: 10)
27 | }
28 | }
29 |
30 | struct DynamicIslandExtrasMenu : View {
31 | @ObservedObject var vm: DynamicIslandViewModel
32 |
33 | var body: some View {
34 | VStack{
35 | HStack(spacing: 20) {
36 | hide
37 | settings
38 | close
39 | }
40 | }
41 | }
42 |
43 | var github: some View {
44 | DynamicIslandLargeButtons(
45 | action: {
46 | NSWorkspace.shared.open(productPage)
47 | },
48 | icon: Image(.github),
49 | title: "Checkout"
50 | )
51 | }
52 |
53 | var donate: some View {
54 | DynamicIslandLargeButtons(
55 | action: {
56 | NSWorkspace.shared.open(sponsorPage)
57 | },
58 | icon: Image(systemName: "heart.fill"),
59 | title: "Love Us"
60 | )
61 | }
62 |
63 | var settings: some View {
64 | Button(action: {
65 | SettingsWindowController.shared.showWindow()
66 | }) {
67 | ZStack {
68 | RoundedRectangle(cornerRadius: 12.0).fill(.black).frame(width: 70, height: 70)
69 | VStack(spacing: 8) {
70 | Image(systemName: "gear").resizable()
71 | .aspectRatio(contentMode: .fit).frame(width:20)
72 | Text("Settings").font(.body)
73 | }
74 | }
75 | }
76 | .buttonStyle(PlainButtonStyle()).shadow(color: .black.opacity(0.5), radius: 10)
77 | }
78 |
79 | var hide: some View {
80 | DynamicIslandLargeButtons(
81 | action: {
82 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
83 | //vm.openMusic()
84 | }
85 | },
86 | icon: Image(systemName: "arrow.down.forward.and.arrow.up.backward"),
87 | title: "Hide"
88 | )
89 | }
90 |
91 | var close: some View {
92 | DynamicIslandLargeButtons(
93 | action: {
94 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
95 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
96 | NSApp.terminate(nil)
97 | }
98 | }
99 | },
100 | icon: Image(systemName: "xmark"),
101 | title: "Exit"
102 | )
103 | }
104 | }
105 |
106 |
107 | #Preview {
108 | DynamicIslandExtrasMenu(vm: DynamicIslandViewModel())
109 | }
110 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Notch/DynamicIslandWindow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicIslandWindow.swift
3 | // DynamicIsland
4 | //
5 | // Created by Harsh Vardhan Goswami on 06/08/24.
6 | //
7 |
8 | import Cocoa
9 |
10 | class DynamicIslandWindow: NSPanel {
11 | override init(
12 | contentRect: NSRect,
13 | styleMask: NSWindow.StyleMask,
14 | backing: NSWindow.BackingStoreType,
15 | defer flag: Bool
16 | ) {
17 | super.init(
18 | contentRect: contentRect,
19 | styleMask: styleMask,
20 | backing: backing,
21 | defer: flag
22 | )
23 |
24 | isFloatingPanel = true
25 | isOpaque = false
26 | titleVisibility = .hidden
27 | titlebarAppearsTransparent = true
28 | backgroundColor = .clear
29 | isMovable = false
30 |
31 | collectionBehavior = [
32 | .fullScreenAuxiliary,
33 | .stationary,
34 | .canJoinAllSpaces,
35 | .ignoresCycle,
36 | ]
37 |
38 | isReleasedWhenClosed = false
39 | level = .mainMenu + 3
40 | hasShadow = false
41 | }
42 |
43 | override var canBecomeKey: Bool {
44 | true
45 | }
46 |
47 | override var canBecomeMain: Bool {
48 | true
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Notch/NotchShape.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotchShape.swift
3 | // DynamicIsland
4 | //
5 | // Created by Kai Azim on 2023-08-24.
6 | // Original source: https://github.com/MrKai77/DynamicNotchKit
7 | // Modified by Alexander on 2025-05-18.
8 |
9 | import SwiftUI
10 |
11 | struct NotchShape: Shape {
12 | private var topCornerRadius: CGFloat
13 | private var bottomCornerRadius: CGFloat
14 |
15 | init(
16 | topCornerRadius: CGFloat? = nil,
17 | bottomCornerRadius: CGFloat? = nil
18 | ) {
19 | self.topCornerRadius = topCornerRadius ?? 6
20 | self.bottomCornerRadius = bottomCornerRadius ?? 14
21 | }
22 |
23 | var animatableData: AnimatablePair {
24 | get {
25 | .init(
26 | topCornerRadius,
27 | bottomCornerRadius
28 | )
29 | }
30 | set {
31 | topCornerRadius = newValue.first
32 | bottomCornerRadius = newValue.second
33 | }
34 | }
35 |
36 | func path(in rect: CGRect) -> Path {
37 | var path = Path()
38 |
39 | path.move(
40 | to: CGPoint(
41 | x: rect.minX,
42 | y: rect.minY
43 | )
44 | )
45 |
46 | path.addQuadCurve(
47 | to: CGPoint(
48 | x: rect.minX + topCornerRadius,
49 | y: rect.minY + topCornerRadius
50 | ),
51 | control: CGPoint(
52 | x: rect.minX + topCornerRadius,
53 | y: rect.minY
54 | )
55 | )
56 |
57 | path.addLine(
58 | to: CGPoint(
59 | x: rect.minX + topCornerRadius,
60 | y: rect.maxY - bottomCornerRadius
61 | )
62 | )
63 |
64 | path.addQuadCurve(
65 | to: CGPoint(
66 | x: rect.minX + topCornerRadius + bottomCornerRadius,
67 | y: rect.maxY
68 | ),
69 | control: CGPoint(
70 | x: rect.minX + topCornerRadius,
71 | y: rect.maxY
72 | )
73 | )
74 |
75 | path.addLine(
76 | to: CGPoint(
77 | x: rect.maxX - topCornerRadius - bottomCornerRadius,
78 | y: rect.maxY
79 | )
80 | )
81 |
82 | path.addQuadCurve(
83 | to: CGPoint(
84 | x: rect.maxX - topCornerRadius,
85 | y: rect.maxY - bottomCornerRadius
86 | ),
87 | control: CGPoint(
88 | x: rect.maxX - topCornerRadius,
89 | y: rect.maxY
90 | )
91 | )
92 |
93 | path.addLine(
94 | to: CGPoint(
95 | x: rect.maxX - topCornerRadius,
96 | y: rect.minY + topCornerRadius
97 | )
98 | )
99 |
100 | path.addQuadCurve(
101 | to: CGPoint(
102 | x: rect.maxX,
103 | y: rect.minY
104 | ),
105 | control: CGPoint(
106 | x: rect.maxX - topCornerRadius,
107 | y: rect.minY
108 | )
109 | )
110 |
111 | path.addLine(
112 | to: CGPoint(
113 | x: rect.minX,
114 | y: rect.minY
115 | )
116 | )
117 |
118 | return path
119 | }
120 | }
121 |
122 | #Preview {
123 | NotchShape(topCornerRadius: 6, bottomCornerRadius: 14)
124 | .frame(width: 200, height: 32)
125 | .padding(10)
126 | }
127 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Notch/NotchShelfView.swift:
--------------------------------------------------------------------------------
1 |
2 | import SwiftUI
3 |
4 | struct NotchShelfView: View {
5 | @EnvironmentObject var vm: DynamicIslandViewModel
6 | @ObservedObject var tvm = TrayDrop.shared
7 |
8 | var body: some View {
9 | HStack {
10 | AirDropView()
11 | panel
12 | .onDrop(of: [.data], isTargeted: $vm.dropZoneTargeting) { providers in
13 | vm.dropEvent = true
14 | DispatchQueue.global().async {
15 | tvm.load(providers)
16 | }
17 | return true
18 | }
19 | }
20 | }
21 |
22 | var panel: some View {
23 | RoundedRectangle(cornerRadius: 16)
24 | .strokeBorder(style: StrokeStyle(lineWidth: 4, dash: [10]))
25 | .foregroundStyle(.white.opacity(0.1))
26 | .overlay {
27 | content
28 | .padding()
29 | }
30 | .animation(vm.animation, value: tvm.items)
31 | .animation(vm.animation, value: tvm.isLoading)
32 | }
33 |
34 | var content: some View {
35 | Group {
36 | if tvm.isEmpty {
37 | VStack(spacing: 10) {
38 | Image(systemName: "tray.and.arrow.down")
39 | .symbolVariant(.fill)
40 | .symbolRenderingMode(.hierarchical)
41 | .foregroundStyle(.white, .gray)
42 | .imageScale(.large)
43 |
44 | Text("Drop files here")
45 | .foregroundStyle(.gray)
46 | .font(.system(.title3, design: .rounded))
47 | .fontWeight(.medium)
48 | }
49 | } else {
50 | ScrollView(.horizontal) {
51 | HStack(spacing: spacing) {
52 | ForEach(tvm.items) { item in
53 | DropItemView(item: item)
54 | }
55 | }
56 | .padding(spacing)
57 | }
58 | .padding(-spacing)
59 | .scrollIndicators(.never)
60 | }
61 | }
62 | }
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Onboarding/ActivationView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OnboardingSettings.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 2024. 09. 26..
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ActivationWindow: View {
11 | @State private var email: String = ""
12 | @State private var key: String = ""
13 | var body: some View {
14 | VStack {
15 | Image("logo")
16 | .resizable()
17 | .aspectRatio(contentMode: .fit)
18 | .frame(height: 80)
19 | .padding(.top, 30)
20 | .padding(.bottom, 10)
21 | Text("Activate your license")
22 | .font(.largeTitle.bold())
23 | .fontDesign(.rounded)
24 | Text("Transform your notch truly yours")
25 | .foregroundStyle(.secondary)
26 | .font(.title2)
27 | .padding(.bottom, 20)
28 | Group {
29 | TextField("Email address", text: $email)
30 | .padding(.horizontal, 8)
31 | .padding(.vertical, 4)
32 | .background(.white.opacity(0.1), in: RoundedRectangle(cornerRadius: 8))
33 | TextField("License key", text: $key)
34 | .padding(.horizontal, 8)
35 | .padding(.vertical, 4)
36 | .background(.white.opacity(0.1), in: RoundedRectangle(cornerRadius: 8))
37 | }
38 | .textFieldStyle(PlainTextFieldStyle())
39 | .scrollContentBackground(.hidden)
40 | .toggleStyle(.switch)
41 | Spacer()
42 | VStack(alignment: .center, spacing: 14) {
43 | HStack(alignment: .center) {
44 | HStack {
45 | Button {} label: {
46 | Text("Cancel")
47 | .padding(.horizontal, 18)
48 | }
49 | .buttonStyle(AccessoryBarButtonStyle())
50 | }
51 | .frame(maxWidth: .infinity, alignment: .leading)
52 | Image("dynamicisland")
53 | .resizable()
54 | .aspectRatio(contentMode: .fit)
55 | .frame(height: 18)
56 | .offset(y: 4)
57 | .blendMode(.overlay)
58 | HStack {
59 | Button {} label: {
60 | Text("Activate")
61 | .padding(.horizontal, 18)
62 | }
63 | .buttonStyle(BorderedProminentButtonStyle())
64 | }
65 | .frame(maxWidth: .infinity, alignment: .trailing)
66 |
67 | }
68 | .controlSize(.extraLarge)
69 | }
70 | }
71 | .padding()
72 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
73 | .ignoresSafeArea()
74 | .background {
75 | VisualEffectView(material: .hudWindow, blendingMode: .behindWindow)
76 | .ignoresSafeArea()
77 | }
78 | .frame(width: 350, height: 350)
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Onboarding/OnboardingFinishView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OnboardingFinishView.swift
3 | // DynamicIsland
4 | //
5 | // Created by Alexander on 2025-06-23.
6 | //
7 |
8 |
9 | import SwiftUI
10 |
11 | struct OnboardingFinishView: View {
12 | let onFinish: () -> Void
13 | let onOpenSettings: () -> Void
14 |
15 | var body: some View {
16 | VStack(spacing: 20) {
17 | Spacer()
18 |
19 | Image(systemName: "sparkles")
20 | .font(.system(size: 60))
21 | .foregroundColor(.accentColor)
22 | .padding()
23 |
24 | Text("You're All Set!")
25 | .font(.largeTitle)
26 | .fontWeight(.bold)
27 |
28 | Text("You can now enjoy the app. If you want to tweak things further, you can always visit the settings.")
29 | .font(.body)
30 | .foregroundColor(.secondary)
31 | .multilineTextAlignment(.center)
32 | .padding(.horizontal, 40)
33 |
34 | Spacer()
35 | Spacer()
36 |
37 | VStack(spacing: 12) {
38 | Button(action: onOpenSettings) {
39 | Label("Customize in Settings", systemImage: "gear")
40 | .controlSize(.large)
41 | }
42 | .controlSize(.large)
43 |
44 | Button("Finish", action: onFinish)
45 | .buttonStyle(.borderedProminent)
46 | .controlSize(.large)
47 | .keyboardShortcut(.defaultAction)
48 |
49 | // Privacy Policy Link
50 | Button(action: {
51 | if let url = URL(string: "https://ebullioscopic.github.io/DynamicIsland/privacy-policy") {
52 | NSWorkspace.shared.open(url)
53 | }
54 | }) {
55 | Text("Privacy Policy")
56 | .font(.caption)
57 | .foregroundColor(.secondary)
58 | }
59 | .buttonStyle(.plain)
60 | .padding(.top, 4)
61 | }
62 | .padding(24)
63 | }
64 | .frame(maxWidth: .infinity, maxHeight: .infinity)
65 | .background(
66 | VisualEffectView(material: .underWindowBackground, blendingMode: .behindWindow)
67 | .ignoresSafeArea()
68 | )
69 | }
70 | }
71 |
72 | #Preview {
73 | OnboardingFinishView(onFinish: { }, onOpenSettings: { })
74 | }
75 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Onboarding/PermissionsRequestView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PermissionsRequestView.swift
3 | // DynamicIsland
4 | //
5 | // Created by Alexander on 2025-06-23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct PermissionRequestView: View {
11 | let icon: Image
12 | let title: String
13 | let description: String
14 | let privacyNote: String?
15 | let onAllow: () -> Void
16 | let onSkip: () -> Void
17 |
18 | var body: some View {
19 | VStack(spacing: 28) {
20 | icon
21 | .resizable()
22 | .scaledToFit()
23 | .frame(width: 70, height: 56)
24 | .foregroundColor(.accentColor)
25 | .padding(.top, 32)
26 |
27 | Text(title)
28 | .font(.title)
29 | .fontWeight(.semibold)
30 |
31 | Text(description)
32 | .multilineTextAlignment(.center)
33 | .padding(.horizontal)
34 |
35 | if let privacyNote = privacyNote {
36 | HStack(spacing: 8) {
37 | Image(systemName: "lock.shield")
38 | .foregroundColor(.secondary)
39 | Text(privacyNote)
40 | .font(.subheadline)
41 | .foregroundColor(.secondary)
42 | .multilineTextAlignment(.leading)
43 | }
44 | .padding(.bottom, 8)
45 | .padding(.horizontal)
46 | }
47 |
48 | HStack {
49 | Button("Not Now") { onSkip() }
50 | .buttonStyle(.bordered)
51 | Button("Allow Access") { onAllow() }
52 | .buttonStyle(.borderedProminent)
53 | }
54 | .padding(.top, 10)
55 | }
56 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
57 | .background(
58 | VisualEffectView(material: .underWindowBackground, blendingMode: .behindWindow)
59 | .ignoresSafeArea()
60 | )
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Onboarding/SparkleView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SparkleView.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 2024. 09. 26..
6 | //
7 |
8 | import SwiftUI
9 | import AppKit
10 |
11 | class SparkleNSView: NSView {
12 | private var emitterLayer: CAEmitterLayer?
13 |
14 | override init(frame frameRect: NSRect) {
15 | super.init(frame: frameRect)
16 | self.wantsLayer = true
17 | setupEmitterLayer()
18 | }
19 |
20 | required init?(coder: NSCoder) {
21 | fatalError("init(coder:) has not been implemented")
22 | }
23 |
24 | private func setupEmitterLayer() {
25 | let emitterLayer = CAEmitterLayer()
26 | emitterLayer.emitterShape = .rectangle
27 | emitterLayer.emitterMode = .surface
28 | emitterLayer.renderMode = .oldestFirst
29 |
30 | let cell = CAEmitterCell()
31 | cell.contents = NSImage(named: "sparkle")?.cgImage(forProposedRect: nil, context: nil, hints: nil)
32 | cell.birthRate = 50
33 | cell.lifetime = 5
34 | cell.velocity = 10
35 | cell.velocityRange = 5
36 | cell.emissionRange = .pi * 2
37 | cell.scale = 0.2
38 | cell.scaleRange = 0.1
39 | cell.alphaSpeed = -0.5
40 | cell.yAcceleration = 10 // Add a slight downward motion
41 |
42 | emitterLayer.emitterCells = [cell]
43 |
44 | self.layer?.addSublayer(emitterLayer)
45 | self.emitterLayer = emitterLayer
46 |
47 | updateEmitterForCurrentBounds()
48 | }
49 |
50 | private func updateEmitterForCurrentBounds() {
51 | guard let emitterLayer = self.emitterLayer else { return }
52 |
53 | emitterLayer.frame = self.bounds
54 | emitterLayer.emitterSize = self.bounds.size
55 | emitterLayer.emitterPosition = CGPoint(x: bounds.width / 2, y: bounds.height / 2)
56 |
57 | // Adjust birth rate based on view size
58 | let area = bounds.width * bounds.height
59 | let baseBirthRate: Float = 50
60 | let adjustedBirthRate = 20 // Assuming 200x200 as base size
61 | emitterLayer.emitterCells?.first?.birthRate = Float(adjustedBirthRate)
62 | }
63 |
64 | override func setFrameSize(_ newSize: NSSize) {
65 | super.setFrameSize(newSize)
66 | updateEmitterForCurrentBounds()
67 | }
68 | }
69 |
70 | struct SparkleView: NSViewRepresentable {
71 | func makeNSView(context: Context) -> SparkleNSView {
72 | return SparkleNSView()
73 | }
74 |
75 | func updateNSView(_ nsView: SparkleNSView, context: Context) {}
76 | }
77 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Onboarding/WelcomeView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WelcomeView.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 2024. 09. 26..
6 | //
7 |
8 | import SwiftUI
9 | import SwiftUIIntrospect
10 |
11 | struct WelcomeView: View {
12 | var onGetStarted: (() -> Void)? = nil
13 | var body: some View {
14 | ZStack(alignment: .top) {
15 | ZStack {
16 | Image("spotlight")
17 | .resizable()
18 | .aspectRatio(contentMode: .fit)
19 | .padding(.bottom)
20 | .blur(radius: 3)
21 | .offset(y: -5)
22 | .background(SparkleView().opacity(0.6))
23 | VStack(spacing: 8) {
24 | Image("logo2")
25 | .resizable()
26 | .aspectRatio(contentMode: .fit)
27 | .frame(width: 100, height: 100)
28 | .padding(.bottom, 8)
29 | Text("Dynamic Island")
30 | .font(.system(.largeTitle, design: .default))
31 | .fontWeight(.semibold)
32 | Text("Welcome")
33 | .font(.title)
34 | .foregroundStyle(.secondary)
35 | .padding(.bottom, 30)
36 | if false {
37 | Text("PRO")
38 | .font(.system(size: 18, design: .rounded))
39 | .fontWeight(.bold)
40 | .foregroundStyle(.white)
41 | .padding(.horizontal, 12)
42 | .padding(.vertical, 3)
43 | .background(
44 | Capsule()
45 | .fill(LinearGradient(colors: [.white.opacity(0.7), .white.opacity(0.3)], startPoint: .topLeading, endPoint: .bottomTrailing))
46 | .strokeBorder(LinearGradient(stops: [.init(color: .white.opacity(0.7), location: 0.3), .init(color: .clear, location: 0.6)], startPoint: .topLeading, endPoint: .bottomTrailing))
47 | .blendMode(.overlay)
48 | )
49 | .padding(.bottom, 30)
50 | }
51 |
52 |
53 | Button {
54 | onGetStarted?()
55 | } label: {
56 | Text("Get started")
57 | .padding(.horizontal, 20)
58 | .padding(.vertical, 6)
59 | }
60 | .buttonStyle(BorderedProminentButtonStyle())
61 |
62 | // Privacy Policy Link
63 | Button(action: {
64 | if let url = URL(string: "https://ebullioscopic.github.io/DynamicIsland/privacy-policy") {
65 | NSWorkspace.shared.open(url)
66 | }
67 | }) {
68 | Text("Privacy Policy")
69 | .font(.caption)
70 | .foregroundColor(.secondary)
71 | }
72 | .buttonStyle(.plain)
73 | .padding(.top, 8)
74 | }
75 | .padding(.top)
76 | }
77 |
78 | Image("ebullioscopic")
79 | .resizable()
80 | .aspectRatio(contentMode: .fit)
81 | .frame(height: 22)
82 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
83 | .padding()
84 | .padding(.bottom, 36)
85 | .blendMode(.overlay)
86 | }
87 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
88 | .ignoresSafeArea()
89 | .background {
90 | VisualEffectView(material: .hudWindow, blendingMode: .behindWindow)
91 | .ignoresSafeArea()
92 | }
93 | }
94 | }
95 |
96 | #Preview {
97 | WelcomeView()
98 | }
99 |
--------------------------------------------------------------------------------
/DynamicIsland/components/ProgressIndicator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProgressIndicator.swift
3 | // DynamicIsland
4 | //
5 | // Created by Harsh Vardhan Goswami on 11/08/24.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct CircularProgressView: View {
12 | let progress: Double
13 | let color: Color
14 |
15 | var body: some View {
16 | ZStack {
17 | Circle()
18 | .stroke(
19 | Color.white.opacity(0.2),
20 | lineWidth: 6
21 | )
22 | Circle()
23 | .trim(from: 0, to: progress)
24 | .stroke(
25 | color,
26 | // 1
27 | style: StrokeStyle(
28 | lineWidth: 6,
29 | lineCap: .round
30 | )
31 | )
32 | .rotationEffect(.degrees(-90))
33 | }
34 | }
35 | }
36 |
37 | enum ProgressIndicatorType {
38 | case circle
39 | case text
40 | }
41 |
42 |
43 | // based on type .circle or .text
44 | struct ProgressIndicator: View {
45 | var type: ProgressIndicatorType
46 | var progress: Double
47 | var color: Color
48 |
49 | var body: some View {
50 | switch type {
51 | case .circle:
52 | CircularProgressView(progress: progress, color: color).frame(
53 | width: 20, height: 20)
54 | case .text:
55 | Text("\(Int(progress * 100))%")
56 | }
57 | }
58 | }
59 |
60 | #Preview {
61 | ProgressIndicator(type: .circle, progress: 0.8, color: Color.blue).padding()
62 | .frame(width: 200, height: 200)
63 | }
64 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Recording/RecordingLiveActivity.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RecordingLiveActivity.swift
3 | // DynamicIsland
4 | //
5 | // Created for screen recording live activity
6 | //
7 |
8 | import SwiftUI
9 | import Defaults
10 |
11 | struct RecordingLiveActivity: View {
12 | @EnvironmentObject var vm: DynamicIslandViewModel
13 | @ObservedObject var recordingManager = ScreenRecordingManager.shared
14 | @State private var isHovering: Bool = false
15 | @State private var gestureProgress: CGFloat = 0
16 | @State private var isExpanded: Bool = false
17 |
18 | var body: some View {
19 | HStack(spacing: 0) {
20 | // Left - Red circle with animation
21 | Color.clear
22 | .background {
23 | if isExpanded {
24 | HStack {
25 | ZStack {
26 | RoundedRectangle(cornerRadius: 6)
27 | .fill(Color.red.opacity(0.15))
28 |
29 | Circle()
30 | .fill(Color.red)
31 | .frame(width: 10, height: 10)
32 | .modifier(PulsingModifier())
33 | }
34 | .frame(width: vm.effectiveClosedNotchHeight - 12, height: vm.effectiveClosedNotchHeight - 12)
35 | }
36 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
37 | }
38 | }
39 | .frame(width: isExpanded ? max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12) + gestureProgress / 2) : 0, height: vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12))
40 |
41 | // Center - Black fill
42 | Rectangle()
43 | .fill(.black)
44 | .frame(width: vm.closedNotchSize.width + (isHovering ? 8 : 0))
45 |
46 | // Right - Empty for symmetry with animation
47 | Color.clear
48 | .frame(width: isExpanded ? max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12) + gestureProgress / 2) : 0, height: vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12))
49 | }
50 | .frame(height: vm.effectiveClosedNotchHeight + (isHovering ? 8 : 0))
51 | .onAppear {
52 | withAnimation(.smooth(duration: 0.4)) {
53 | isExpanded = true
54 | }
55 | }
56 | .onChange(of: recordingManager.isRecording) { _, newValue in
57 | if !newValue {
58 | withAnimation(.smooth(duration: 0.4)) {
59 | isExpanded = false
60 | }
61 | } else {
62 | withAnimation(.smooth(duration: 0.4)) {
63 | isExpanded = true
64 | }
65 | }
66 | }
67 | }
68 | }
69 |
70 | // Pulsing animation modifier for recording indicator
71 | struct PulsingModifier: ViewModifier {
72 | @State private var isPulsing = false
73 |
74 | func body(content: Content) -> some View {
75 | content
76 | .scaleEffect(isPulsing ? 1.2 : 1.0)
77 | .opacity(isPulsing ? 0.7 : 1.0)
78 | .onAppear {
79 | withAnimation(.easeInOut(duration: 0.8).repeatForever(autoreverses: true)) {
80 | isPulsing = true
81 | }
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Settings/EditPanelView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EditPanelView.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 12/08/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct EditPanelView: View {
11 | @State var wallpaperPath: URL?
12 | var body: some View {
13 | VStack {
14 | HStack {
15 | Text("Edit layout")
16 | .font(.system(.largeTitle, design: .rounded))
17 | .foregroundColor(.white.opacity(0.5))
18 | Spacer()
19 | Button {
20 | exit(0)
21 | } label: {
22 | Label("Close", systemImage: "xmark")
23 | }
24 | .controlSize(.extraLarge)
25 | .buttonStyle(AccessoryBarButtonStyle())
26 | }
27 | .padding()
28 | }
29 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
30 | }
31 | }
32 |
33 | #Preview {
34 | EditPanelView()
35 | }
36 |
37 | struct VisualEffectView: NSViewRepresentable {
38 | let material: NSVisualEffectView.Material
39 | let blendingMode: NSVisualEffectView.BlendingMode
40 |
41 | func makeNSView(context _: Context) -> NSVisualEffectView {
42 | let visualEffectView = NSVisualEffectView()
43 | visualEffectView.material = material
44 | visualEffectView.blendingMode = blendingMode
45 | visualEffectView.state = NSVisualEffectView.State.active
46 | visualEffectView.isEmphasized = true
47 | return visualEffectView
48 | }
49 |
50 | func updateNSView(_ visualEffectView: NSVisualEffectView, context _: Context) {
51 | visualEffectView.material = material
52 | visualEffectView.blendingMode = blendingMode
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Settings/ListItemPopover.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListItemPopover.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 15/09/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ListItemPopover: View {
11 | let content: () -> Content
12 |
13 | @State private var isPresented: Bool = false
14 | var body: some View {
15 | Button {
16 | isPresented.toggle()
17 | } label: {
18 | Image(systemName: "info.circle")
19 | .foregroundStyle(.secondary)
20 | }
21 | .controlSize(.regular)
22 | .popover(isPresented: $isPresented, attachmentAnchor: .rect(.bounds), arrowEdge: .trailing, content: {
23 | content()
24 | .padding()
25 | })
26 | }
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Settings/SoftwareUpdater.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SoftwareUpdater.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 09/08/2024.
6 | //
7 |
8 | import SwiftUI
9 | import Sparkle
10 |
11 | final class CheckForUpdatesViewModel: ObservableObject {
12 | @Published var canCheckForUpdates = false
13 |
14 | init(updater: SPUUpdater) {
15 | updater.publisher(for: \.canCheckForUpdates)
16 | .assign(to: &$canCheckForUpdates)
17 | }
18 | }
19 |
20 | struct CheckForUpdatesView: View {
21 | @ObservedObject private var checkForUpdatesViewModel: CheckForUpdatesViewModel
22 | private let updater: SPUUpdater
23 |
24 | init(updater: SPUUpdater) {
25 | self.updater = updater
26 |
27 | // Create our view model for our CheckForUpdatesView
28 | self.checkForUpdatesViewModel = CheckForUpdatesViewModel(updater: updater)
29 | }
30 |
31 | var body: some View {
32 | Button("Check for Updates…", action: updater.checkForUpdates)
33 | .disabled(!checkForUpdatesViewModel.canCheckForUpdates)
34 | }
35 | }
36 |
37 | struct UpdaterSettingsView: View {
38 | private let updater: SPUUpdater
39 |
40 | @State private var automaticallyChecksForUpdates: Bool
41 | @State private var automaticallyDownloadsUpdates: Bool
42 |
43 | init(updater: SPUUpdater) {
44 | self.updater = updater
45 | self.automaticallyChecksForUpdates = updater.automaticallyChecksForUpdates
46 | self.automaticallyDownloadsUpdates = updater.automaticallyDownloadsUpdates
47 | }
48 |
49 | var body: some View {
50 | Section {
51 | Toggle("Automatically check for updates", isOn: $automaticallyChecksForUpdates)
52 | .onChange(of: automaticallyChecksForUpdates) { _, newValue in
53 | updater.automaticallyChecksForUpdates = newValue
54 | }
55 |
56 | Toggle("Automatically download updates", isOn: $automaticallyDownloadsUpdates)
57 | .disabled(!automaticallyChecksForUpdates)
58 | .onChange(of: automaticallyDownloadsUpdates) { _, newValue in
59 | updater.automaticallyDownloadsUpdates = newValue
60 | }
61 | } header: {
62 | HStack {
63 | Text("Software updates")
64 | }
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Shelf/AirDrop.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AirDrop.swift
3 | // NotchDrop
4 | //
5 | // Created by 秋星桥 on 2024/7/7.
6 | //
7 |
8 | import Cocoa
9 |
10 | class AirDrop: NSObject, NSSharingServiceDelegate {
11 | let files: [URL]
12 |
13 | init(files: [URL]) {
14 | self.files = files
15 | super.init()
16 | }
17 |
18 | func begin() {
19 | do {
20 | try sendEx(files)
21 | } catch {
22 | NSAlert.popError(error)
23 | }
24 | }
25 |
26 | private func sendEx(_ files: [URL]) throws {
27 | guard let service = NSSharingService(named: .sendViaAirDrop) else {
28 | throw NSError(domain: "AirDrop", code: 1, userInfo: [
29 | NSLocalizedDescriptionKey: NSLocalizedString("AirDrop service not available", comment: ""),
30 | ])
31 | }
32 | guard service.canPerform(withItems: files) else {
33 | throw NSError(domain: "AirDrop", code: 2, userInfo: [
34 | NSLocalizedDescriptionKey: NSLocalizedString("AirDrop service not available", comment: ""),
35 | ])
36 | }
37 | service.delegate = self
38 | service.perform(withItems: files)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Shelf/AirDropView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AirDrop+View.swift
3 | // NotchDrop
4 | //
5 | // Created by 秋星桥 on 2024/7/8.
6 | //
7 |
8 | import SwiftUI
9 | import UniformTypeIdentifiers
10 |
11 | struct AirDropView: View {
12 | @EnvironmentObject var vm: DynamicIslandViewModel
13 |
14 | @State var trigger: UUID = .init()
15 | @State var targeting = false
16 |
17 | var body: some View {
18 | dropArea
19 | .onDrop(of: [.data], isTargeted: $vm.dropZoneTargeting) { providers in
20 | trigger = .init()
21 | vm.dropEvent = true
22 | DispatchQueue.global().async { beginDrop(providers) }
23 | return true
24 | }
25 | }
26 |
27 | var dropArea: some View {
28 | Rectangle()
29 | .fill(.white.opacity(0.1))
30 | .opacity(0.5)
31 | .clipShape(RoundedRectangle(cornerRadius: 10))
32 | .overlay { dropLabel }
33 | .aspectRatio(1, contentMode: .fit)
34 | .contentShape(Rectangle())
35 | }
36 |
37 | var dropLabel: some View {
38 | VStack(spacing: 8) {
39 | Image(systemName: "airplayaudio")
40 | Text("AirDrop")
41 | }
42 | .foregroundStyle(.gray)
43 | .font(.system(.headline, design: .rounded))
44 | .contentShape(Rectangle())
45 | .onTapGesture {
46 | trigger = .init()
47 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
48 | let picker = NSOpenPanel()
49 | picker.allowsMultipleSelection = true
50 | picker.canChooseDirectories = true
51 | picker.canChooseFiles = true
52 | picker.begin { response in
53 | if response == .OK {
54 | let drop = AirDrop(files: picker.urls)
55 | drop.begin()
56 | }
57 | }
58 | }
59 | }
60 | }
61 |
62 | func beginDrop(_ providers: [NSItemProvider]) {
63 | assert(!Thread.isMainThread)
64 | guard let urls = providers.interfaceConvert() else { return }
65 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
66 | let drop = AirDrop(files: urls)
67 | drop.begin()
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Shelf/DragDropView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DragDropView.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 2024. 10. 19..
6 | //
7 |
8 | import SwiftUI
9 | import AppKit
10 |
11 | class DragDropView: NSView {
12 | var onDragEntered: () -> Void = {}
13 | var onDragExited: () -> Void = {}
14 | var onDrop: () -> Void = {}
15 |
16 | override init(frame frameRect: NSRect) {
17 | super.init(frame: frameRect)
18 | registerForDraggedTypes([.fileURL])
19 | }
20 |
21 | required init?(coder: NSCoder) {
22 | fatalError("init(coder:) has not been implemented")
23 | }
24 |
25 | override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
26 | onDragEntered()
27 | return .copy
28 | }
29 |
30 | override func draggingExited(_ sender: NSDraggingInfo?) {
31 | onDragExited()
32 | }
33 |
34 | override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
35 | onDrop()
36 | return true
37 | }
38 | }
39 |
40 | struct DragDropViewRepresentable: NSViewRepresentable {
41 | @Binding var isTargeted: Bool
42 | var onDrop: () -> Void
43 |
44 | func makeNSView(context: Context) -> DragDropView {
45 | let view = DragDropView()
46 | view.onDragEntered = { isTargeted = true }
47 | view.onDragExited = { isTargeted = false }
48 | view.onDrop = onDrop
49 |
50 | view.autoresizingMask = [.width, .height]
51 |
52 | return view
53 | }
54 |
55 | func updateNSView(_ nsView: DragDropView, context: Context) {}
56 | }
57 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Shelf/DropItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TrayDrop+DropItem.swift
3 | // TrayDrop
4 | //
5 | // Created by 秋星桥 on 2024/7/8.
6 | //
7 |
8 | import Cocoa
9 | import Foundation
10 | import QuickLook
11 |
12 | extension TrayDrop {
13 | struct DropItem: Identifiable, Codable, Equatable, Hashable {
14 | let id: UUID
15 |
16 | let fileName: String
17 | let size: Int
18 |
19 | let copiedDate: Date
20 | let workspacePreviewImageData: Data
21 |
22 | init(url: URL) throws {
23 | assert(!Thread.isMainThread)
24 |
25 | id = UUID()
26 | fileName = url.lastPathComponent
27 |
28 | size = (try? url.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? 0
29 | copiedDate = Date()
30 | workspacePreviewImageData = url.snapshotPreview().pngRepresentation
31 |
32 | try FileManager.default.createDirectory(
33 | at: storageURL.deletingLastPathComponent(),
34 | withIntermediateDirectories: true
35 | )
36 | try FileManager.default.copyItem(at: url, to: storageURL)
37 | }
38 | }
39 | }
40 |
41 | extension TrayDrop.DropItem {
42 | static let mainDir = "CopiedItems"
43 |
44 | var storageURL: URL {
45 | documentsDirectory
46 | .appendingPathComponent(Self.mainDir)
47 | .appendingPathComponent(id.uuidString)
48 | .appendingPathComponent(fileName)
49 | }
50 |
51 | var workspacePreviewImage: NSImage {
52 | .init(data: workspacePreviewImageData) ?? .init()
53 | }
54 |
55 | var shouldClean: Bool { // TODO: In the future clean if old
56 | return false
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Shelf/DropItemView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TrayDrop+DropItemView.swift
3 | // NotchDrop
4 | //
5 | // Created by 秋星桥 on 2024/7/8.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 | import UniformTypeIdentifiers
11 |
12 | struct DropItemView: View {
13 | let item: TrayDrop.DropItem
14 | @EnvironmentObject var vm: DynamicIslandViewModel
15 | @ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
16 | @ObservedObject var tvm = TrayDrop.shared
17 |
18 | @State var hover = false
19 |
20 | var body: some View {
21 | ZStack(alignment: .topTrailing) {
22 | VStack {
23 | RoundedRectangle(cornerRadius: 6)
24 | .fill(.clear)
25 | .background {
26 | Image(nsImage: item.workspacePreviewImage)
27 | .resizable()
28 | .aspectRatio(contentMode: .fit)
29 | .clipShape(RoundedRectangle(cornerRadius: 6))
30 | }
31 |
32 | Text(item.fileName)
33 | .multilineTextAlignment(.center)
34 | .font(.footnote)
35 | .foregroundStyle(hover ? .white : .gray)
36 | .lineLimit(2)
37 | .allowsTightening(true)
38 | }
39 | .contentShape(Rectangle())
40 | .onDrag {
41 | handleOnDrag(for: item)
42 | }
43 | .frame(width: 64, height: 64)
44 |
45 | if hover {
46 | Circle()
47 | .fill(.white)
48 | .overlay(Image(systemName: "xmark").foregroundStyle(.black).font(.system(size: 7)).fontWeight(.semibold))
49 | .frame(width: spacing, height: spacing)
50 | .opacity(hover ? 1 : 0) // TODO: Use option key pressed to show delete
51 | .scaleEffect(coordinator.optionKeyPressed ? 1 : 0.5)
52 | .transition(.blurReplace.combined(with: .scale))
53 | .offset(x: spacing / 2, y: -spacing / 2)
54 | .onTapGesture { tvm.delete(item.id) }
55 | .shadow(color: .black, radius: 3)
56 | }
57 | }
58 | .onHover { hovering in
59 | withAnimation(.smooth) {
60 | if hovering {
61 | hover.toggle()
62 | } else {
63 | hover.toggle()
64 | }
65 | }
66 | }
67 | }
68 |
69 | private func handleOnDrag(for item: TrayDrop.DropItem) -> NSItemProvider {
70 | guard let itemProvider = NSItemProvider(contentsOf: item.storageURL) else {
71 | return NSItemProvider()
72 | }
73 |
74 | let nameWithoutExtension = (item.fileName as NSString).deletingPathExtension
75 | itemProvider.suggestedName = nameWithoutExtension
76 |
77 | return itemProvider
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Shelf/Ext+FileProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Ext+FileProvider.swift
3 | // NotchDrop
4 | //
5 | // Created by 秋星桥 on 2024/7/8.
6 | //
7 |
8 | import Cocoa
9 | import Foundation
10 | import UniformTypeIdentifiers
11 |
12 | extension NSItemProvider {
13 | private func duplicateToOurStorage(_ url: URL?) throws -> URL? {
14 | guard let url else { return nil }
15 | let temp = temporaryDirectory
16 | .appendingPathComponent("TemporaryDrop")
17 | .appendingPathComponent(UUID().uuidString)
18 | .appendingPathComponent(url.lastPathComponent)
19 | try? FileManager.default.createDirectory(
20 | at: temp.deletingLastPathComponent(),
21 | withIntermediateDirectories: true
22 | )
23 | try FileManager.default.copyItem(at: url, to: temp)
24 | return temp
25 | }
26 |
27 | func convertToFilePathThatIsWhatWeThinkItWillWorkWithNotchDrop() -> URL? {
28 | var url: URL?
29 | let sem = DispatchSemaphore(value: 0)
30 | _ = loadObject(ofClass: URL.self) { item, _ in
31 | url = try? self.duplicateToOurStorage(item)
32 | sem.signal()
33 | }
34 | sem.wait()
35 | if url == nil {
36 | loadInPlaceFileRepresentation(
37 | forTypeIdentifier: UTType.data.identifier
38 | ) { input, _, _ in
39 | defer { sem.signal() }
40 | url = try? self.duplicateToOurStorage(input)
41 | }
42 | sem.wait()
43 | }
44 | return url
45 | }
46 | }
47 |
48 | extension [NSItemProvider] {
49 | func interfaceConvert() -> [URL]? {
50 | let urls = compactMap { provider -> URL? in
51 | provider.convertToFilePathThatIsWhatWeThinkItWillWorkWithNotchDrop()
52 | }
53 | guard urls.count == count else {
54 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
55 | NSAlert.popError(NSLocalizedString("One or more files failed to load", comment: ""))
56 | }
57 | return nil
58 | }
59 | return urls
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Shelf/Ext+NSAlert.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Ext+NSAlert.swift
3 | // NotchDrop
4 | //
5 | // Created by 秋星桥 on 2024/7/9.
6 | //
7 |
8 | import Cocoa
9 |
10 | extension NSAlert {
11 | static func popError(_ error: String) {
12 | let alert = NSAlert()
13 | alert.messageText = NSLocalizedString("Error", comment: "")
14 | alert.alertStyle = .critical
15 | alert.informativeText = error
16 | alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
17 | alert.runModal()
18 | }
19 |
20 | static func popRestart(_ error: String, completion: @escaping () -> Void) {
21 | let alert = NSAlert()
22 | alert.messageText = NSLocalizedString("Need Restart", comment: "")
23 | alert.alertStyle = .critical
24 | alert.informativeText = error
25 | alert.addButton(withTitle: NSLocalizedString("Exit", comment: ""))
26 | alert.addButton(withTitle: NSLocalizedString("Later", comment: ""))
27 | let response = alert.runModal()
28 | if response == .alertFirstButtonReturn {
29 | completion()
30 | }
31 | }
32 |
33 | static func popError(_ error: Error) {
34 | popError(error.localizedDescription)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Shelf/Ext+NSImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Ext+NSImage.swift
3 | // NotchDrop
4 | //
5 | // Created by 秋星桥 on 2024/7/8.
6 | //
7 |
8 | import Cocoa
9 |
10 | extension NSImage {
11 | var pngRepresentation: Data {
12 | guard let cgImage = cgImage(forProposedRect: nil, context: nil, hints: nil) else {
13 | return .init()
14 | }
15 | let imageRep = NSBitmapImageRep(cgImage: cgImage)
16 | imageRep.size = size
17 | return imageRep.representation(using: .png, properties: [:]) ?? .init()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Shelf/Ext+URL.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Ext+URL.swift
3 | // NotchDrop
4 | //
5 | // Created by 秋星桥 on 2024/7/8.
6 | //
7 |
8 | import Cocoa
9 | import Foundation
10 | import QuickLook
11 |
12 | extension URL {
13 | func snapshotPreview() -> NSImage {
14 | if let preview = QLThumbnailImageCreate(
15 | kCFAllocatorDefault,
16 | self as CFURL,
17 | CGSize(width: 128, height: 128),
18 | nil
19 | )?.takeRetainedValue() {
20 | return NSImage(cgImage: preview, size: .zero)
21 | }
22 | return NSWorkspace.shared.icon(forFile: path)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Shelf/TrayDrop.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import Combine
3 | import Foundation
4 | import OrderedCollections
5 |
6 | class TrayDrop: ObservableObject {
7 | static let shared = TrayDrop()
8 |
9 | @Published var items: OrderedSet
10 | var isEmpty: Bool { items.isEmpty }
11 | @Published var isLoading: Int = 0
12 |
13 | init(items: OrderedSet = .init(), isLoading: Int = 0) {
14 | self.items = items
15 | self.isLoading = isLoading
16 | }
17 |
18 | func load(_ providers: [NSItemProvider]) {
19 | assert(!Thread.isMainThread)
20 | DispatchQueue.main.asyncAndWait { isLoading += 1 }
21 |
22 | guard let urls = providers.interfaceConvert() else {
23 | DispatchQueue.main.asyncAndWait { isLoading -= 1 }
24 | print("Faield to load items")
25 | return
26 | }
27 | let dropItems = urls.map { url in
28 | try? DropItem(url: url)
29 | }.compactMap { $0 }
30 |
31 | DispatchQueue.main.async {
32 | dropItems.forEach { self.items.updateOrInsert($0, at: 0) }
33 | self.isLoading -= 1
34 | }
35 | print("DONE")
36 | }
37 |
38 | func cleanExpiredFiles() {
39 | var inEdit = items
40 | let shouldCleanItems = items.filter(\.shouldClean)
41 | for item in shouldCleanItems {
42 | inEdit.remove(item)
43 | }
44 | items = inEdit
45 | }
46 |
47 | func delete(_ item: DropItem.ID) {
48 | guard let item = items.first(where: { $0.id == item }) else { return }
49 | delete(item: item)
50 | }
51 |
52 | private func delete(item: DropItem) {
53 | var inEdit = items
54 |
55 | var url = item.storageURL
56 | try? FileManager.default.removeItem(at: url)
57 |
58 | do {
59 | // loops up to the main directory
60 | url = url.deletingLastPathComponent()
61 | while url.lastPathComponent != DropItem.mainDir, url != documentsDirectory {
62 | let contents = try FileManager.default.contentsOfDirectory(atPath: url.path)
63 | guard contents.isEmpty else { break }
64 | try FileManager.default.removeItem(at: url)
65 | url = url.deletingLastPathComponent()
66 | }
67 | } catch {}
68 |
69 | inEdit.remove(item)
70 | items = inEdit
71 | }
72 |
73 | func removeAll() {
74 | items.forEach { delete(item: $0) }
75 | }
76 | }
77 |
78 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Stats/DetailedTimelineGraph.swift:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/components/Stats/DetailedTimelineGraph.swift
--------------------------------------------------------------------------------
/DynamicIsland/components/Stats/DualTimelineGraph.swift:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/components/Stats/DualTimelineGraph.swift
--------------------------------------------------------------------------------
/DynamicIsland/components/Tabs/TabButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabButton.swift
3 | // DynamicIsland
4 | //
5 | // Created by Hugo Persson on 2024-08-24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct TabButton: View {
11 | let label: String
12 | let icon: String
13 | let selected: Bool
14 | let onClick: () -> Void
15 |
16 | var body: some View {
17 | Button(action: onClick) {
18 | Image(systemName: icon)
19 | .padding(.horizontal, 15)
20 | .contentShape(Capsule())
21 | }
22 | .buttonStyle(PlainButtonStyle())
23 | }
24 | }
25 |
26 | #Preview {
27 | TabButton(label: "Home", icon: "tray.fill", selected: true) {
28 | print("Tapped")
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Tabs/TabSelectionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabSelectionView.swift
3 | // DynamicIsland
4 | //
5 | // Created by Hugo Persson on 2024-08-25.
6 | // Modified by Hariharan Mudaliar
7 |
8 | import SwiftUI
9 | import Defaults
10 |
11 | struct TabModel: Identifiable {
12 | let id = UUID()
13 | let label: String
14 | let icon: String
15 | let view: NotchViews
16 | }
17 |
18 | struct TabSelectionView: View {
19 | @ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
20 | @Default(.enableTimerFeature) var enableTimerFeature
21 | @Default(.enableStatsFeature) var enableStatsFeature
22 | @Default(.enableColorPickerFeature) var enableColorPickerFeature
23 | @Namespace var animation
24 |
25 | private var tabs: [TabModel] {
26 | var tabsArray: [TabModel] = []
27 |
28 | tabsArray.append(TabModel(label: "Home", icon: "house.fill", view: .home))
29 |
30 | tabsArray.append(TabModel(label: "Shelf", icon: "tray.fill", view: .shelf))
31 |
32 | // Timer tab only shown when timer feature is enabled
33 | if Defaults[.enableTimerFeature] {
34 | tabsArray.append(TabModel(label: "Timer", icon: "timer", view: .timer))
35 | }
36 |
37 | // Stats tab only shown when stats feature is enabled
38 | if Defaults[.enableStatsFeature] {
39 | tabsArray.append(TabModel(label: "Stats", icon: "chart.xyaxis.line", view: .stats))
40 | }
41 |
42 | return tabsArray
43 | }
44 | var body: some View {
45 | HStack(spacing: 0) {
46 | ForEach(tabs) { tab in
47 | TabButton(label: tab.label, icon: tab.icon, selected: coordinator.currentView == tab.view) {
48 | withAnimation(.smooth) {
49 | coordinator.currentView = tab.view
50 | }
51 | }
52 | .frame(height: 26)
53 | .foregroundStyle(tab.view == coordinator.currentView ? .white : .gray)
54 | .background {
55 | if tab.view == coordinator.currentView {
56 | Capsule()
57 | .fill(coordinator.currentView == tab.view ? Color(nsColor: .secondarySystemFill) : Color.clear)
58 | .matchedGeometryEffect(id: "capsule", in: animation)
59 | } else {
60 | Capsule()
61 | .fill(coordinator.currentView == tab.view ? Color(nsColor: .secondarySystemFill) : Color.clear)
62 | .matchedGeometryEffect(id: "capsule", in: animation)
63 | .hidden()
64 | }
65 | }
66 | }
67 | }
68 | .clipShape(Capsule())
69 | }
70 | }
71 |
72 | #Preview {
73 | DynamicIslandHeader().environmentObject(DynamicIslandViewModel())
74 | }
75 |
--------------------------------------------------------------------------------
/DynamicIsland/components/TestView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestView.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 14/08/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct FluidSlider: View {
11 | private let color: Color = Color.white
12 | @State private var offset: CGFloat = 0
13 | var rectSize = CGSize(width: 300, height: 50)
14 | var rectSize2 = CGSize(width: 200, height: 18)
15 | var circleSize: CGFloat = 35
16 | @GestureState var isDragging: Bool = false
17 | @State var previousOffset: CGFloat = 0
18 | @State private var isBeating: Bool = false
19 |
20 | var body: some View {
21 | HStack {
22 | slider
23 | .frame(width: rectSize2.width, height: circleSize)
24 | }
25 | .padding()
26 | .background(.black)
27 | }
28 |
29 | private var slider: some View {
30 | ZStack {
31 | Canvas { context, size in
32 | context.addFilter(.alphaThreshold(min: 0.5, max: 1, color: color))
33 | context.addFilter(.blur(radius: 10))
34 |
35 | context.drawLayer { ctx in
36 | if let rectangle = ctx.resolveSymbol(id: "Capsule") {
37 | ctx.draw(rectangle, at: CGPoint(x: size.width/2, y: size.height/2))
38 | }
39 | if let circle = ctx.resolveSymbol(id: "Circle") {
40 | ctx.draw(circle, at: CGPoint(x: size.width/2 - rectSize2.width/2 + circleSize/2, y: size.height/2))
41 | }
42 | }
43 | } symbols: {
44 | Capsule()
45 | .frame(width: rectSize2.width, height: rectSize2.height, alignment: .center)
46 | .tag("Capsule")
47 |
48 | Circle()
49 | .frame(width: circleSize, height: circleSize, alignment: .center)
50 | .offset(x: offset)
51 | .animation(.spring(), value: isDragging)
52 | .tag("Circle")
53 | }
54 | .simultaneousGesture(
55 | DragGesture(minimumDistance: 0)
56 | .updating($isDragging, body: { _, state, _ in
57 | state = true
58 | })
59 | .onChanged({ value in
60 | self.offset = min(max(self.previousOffset + value.translation.width, 0), rectSize2.width - circleSize)
61 | })
62 | .onEnded({ value in
63 | self.previousOffset = self.offset
64 | })
65 | )
66 | Circle()
67 | .fill(Color.black)
68 | .frame(width: circleSize * 0.6)
69 | .overlay {
70 | Image(systemName: "speaker.wave.2.fill")
71 | .imageScale(.small)
72 | }
73 | .offset(x: (-rectSize2.width/2) + (circleSize/2))
74 | .offset(x: offset)
75 | .animation(.spring(), value: isDragging)
76 | .allowsHitTesting(false)
77 | }
78 | }
79 |
80 |
81 | private var animation: Animation {
82 | .spring(response: 0.5, dampingFraction: 0.6, blendDuration: 0.5)
83 | }
84 |
85 | private var percentage: Int {
86 | Int((offset) / (rectSize.width - circleSize) * 100)
87 | }
88 | }
89 |
90 | #Preview {
91 | FluidSlider()
92 | }
93 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Timer/TimerLiveActivity.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TimerLiveActivity.swift
3 | // DynamicIsland
4 | //
5 | // Created by Ebullioscopic on 2025-01-13.
6 | //
7 |
8 | import SwiftUI
9 | import Defaults
10 |
11 | struct TimerLiveActivity: View {
12 | @EnvironmentObject var vm: DynamicIslandViewModel
13 | @ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
14 | @ObservedObject var timerManager = TimerManager.shared
15 | @State private var isHovering: Bool = false
16 | @State private var gestureProgress: CGFloat = 0
17 |
18 | var body: some View {
19 | HStack {
20 | // Left side - Timer icon animation
21 | timerIconSection
22 |
23 | // Center - Expandable timer info (similar to music banner)
24 | timerInfoSection
25 |
26 | // Right side - Timer countdown text
27 | timerCountdownSection
28 | }
29 | .frame(height: vm.effectiveClosedNotchHeight + (isHovering ? 8 : 0), alignment: .center)
30 | }
31 |
32 | private var timerIconSection: some View {
33 | HStack {
34 | // Simple timer icon with color, no background circle or animations
35 | Image(systemName: "timer")
36 | .font(.system(size: 16, weight: .medium))
37 | .foregroundStyle(timerManager.timerColor)
38 | .frame(width: 24, height: 24)
39 | .frame(width: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12) + gestureProgress / 2),
40 | height: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12)), alignment: .center)
41 | }
42 | .frame(width: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12) + gestureProgress / 2),
43 | height: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12)))
44 | }
45 |
46 | private var timerInfoSection: some View {
47 | Rectangle()
48 | .fill(.black)
49 | .frame(width: vm.closedNotchSize.width + (isHovering ? 8 : 0))
50 | }
51 |
52 | private var timerCountdownSection: some View {
53 | HStack {
54 | VStack(spacing: 2) {
55 | // Remaining time
56 | Text(timerManager.formattedRemainingTime())
57 | .font(.system(size: 11, weight: .medium, design: .monospaced))
58 | .foregroundColor(timerManager.isOvertime ? .red : .white)
59 |
60 | // Progress indicator
61 | if timerManager.totalDuration > 0 {
62 | ProgressView(value: timerManager.progress)
63 | .progressViewStyle(LinearProgressViewStyle(tint: timerManager.timerColor))
64 | .frame(width: 30, height: 2)
65 | }
66 | }
67 | .frame(maxWidth: .infinity)
68 | }
69 | .frame(width: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12) + gestureProgress / 2),
70 | height: max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12)), alignment: .center)
71 | }
72 | }
73 |
74 | #Preview {
75 | TimerLiveActivity()
76 | .environmentObject(DynamicIslandViewModel())
77 | .frame(width: 300, height: 32)
78 | .background(.black)
79 | .onAppear {
80 | // Start a demo timer for preview
81 | TimerManager.shared.startDemoTimer(duration: 300)
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/DynamicIsland/components/Tips/TipStore.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TipStore.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 15/09/2024.
6 | //
7 |
8 | import SwiftUI
9 | import TipKit
10 |
11 | struct HUDsTip: Tip {
12 | var title: Text {
13 | Text("Enhance your experience with HUDs")
14 | }
15 |
16 |
17 | var message: Text? {
18 | Text("Unlock advanced features and improve your experience. Upgrade now for more customizations!")
19 | }
20 |
21 |
22 | var image: Image? {
23 | AppIcon(for: "dynamicisland.DynamicIsland")
24 | }
25 |
26 | var actions: [Action] {
27 | Action {
28 | Text("More")
29 | }
30 | }
31 | }
32 |
33 | struct CBTip: Tip {
34 | var title: Text {
35 | Text("Boost your productivity with Clipboard Manager")
36 | }
37 |
38 |
39 | var message: Text? {
40 | Text("Easily copy, store, and manage your most-used content. Upgrade now for advanced features like multi-item storage and quick access!")
41 | }
42 |
43 |
44 | var image: Image? {
45 | AppIcon(for: "dynamicisland.DynamicIsland")
46 | }
47 |
48 | var actions: [Action] {
49 | Action {
50 | Text("More")
51 | }
52 | }
53 | }
54 |
55 | struct TipsView: View {
56 | var hudTip = HUDsTip()
57 | var cbTip = CBTip()
58 | var body: some View {
59 | VStack {
60 | TipView(hudTip)
61 | TipView(cbTip)
62 | }
63 | .task {
64 | try? Tips.configure([
65 | .displayFrequency(.immediate),
66 | .datastoreLocation(.applicationDefault)
67 | ])
68 | }
69 | }
70 | }
71 |
72 | #Preview {
73 | TipsView()
74 | }
75 |
--------------------------------------------------------------------------------
/DynamicIsland/components/WhatsNewView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WhatsNewView.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 09/08/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct WhatsNewView: View {
11 | @Binding var isPresented: Bool
12 |
13 | var body: some View {
14 | VStack(spacing: 20) {
15 | Text("What's New")
16 | .font(.largeTitle)
17 |
18 | VStack(alignment: .leading, spacing: 10) {
19 | Text("• New feature 1")
20 | Text("• Improved performance")
21 | Text("• Bug fixes")
22 | }
23 |
24 | Button("Got it!") {
25 | isPresented = false
26 | }
27 | }
28 | .frame(width: 300, height: 200)
29 | .padding()
30 | }
31 | }
32 |
33 | #Preview {
34 | WhatsNewView(isPresented: .constant(true))
35 | }
36 |
--------------------------------------------------------------------------------
/DynamicIsland/dynamic.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/dynamic.m4a
--------------------------------------------------------------------------------
/DynamicIsland/enums/generic.swift:
--------------------------------------------------------------------------------
1 | //
2 | // generic.swift
3 | // DynamicIsland
4 | //
5 | // Created by Harsh Vardhan Goswami on 04/08/24.
6 | // Modified by Hariharan Mudaliar
7 |
8 | import Foundation
9 | import Defaults
10 |
11 | public enum Style {
12 | case notch
13 | case floating
14 | }
15 |
16 | public enum ContentType: Int, Codable, Hashable, Equatable {
17 | case normal
18 | case menu
19 | case settings
20 | }
21 |
22 | public enum NotchState {
23 | case closed
24 | case open
25 | }
26 |
27 | public enum NotchViews {
28 | case home
29 | case shelf
30 | case timer
31 | case stats
32 | case colorPicker
33 | }
34 |
35 | enum SettingsEnum {
36 | case general
37 | case about
38 | case charge
39 | case download
40 | case mediaPlayback
41 | case hud
42 | case shelf
43 | case extensions
44 | }
45 |
46 | enum DownloadIndicatorStyle: String, Defaults.Serializable {
47 | case progress = "Progress"
48 | case percentage = "Percentage"
49 | }
50 |
51 | enum DownloadIconStyle: String, Defaults.Serializable {
52 | case onlyAppIcon = "Only app icon"
53 | case onlyIcon = "Only download icon"
54 | case iconAndAppIcon = "Icon and app icon"
55 | }
56 |
57 | enum MirrorShapeEnum: String, Defaults.Serializable {
58 | case rectangle = "Rectangular"
59 | case circle = "Circular"
60 | }
61 |
62 | enum WindowHeightMode: String, Defaults.Serializable {
63 | case matchMenuBar = "Match menubar height"
64 | case matchRealNotchSize = "Match real notch height"
65 | case custom = "Custom height"
66 | }
67 |
68 | enum SliderColorEnum: String, CaseIterable, Defaults.Serializable {
69 | case white = "White"
70 | case albumArt = "Match album art"
71 | case accent = "Accent color"
72 | }
73 |
--------------------------------------------------------------------------------
/DynamicIsland/extensions/ActionBar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ActionBar.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 15/09/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension View {
11 | func actionBar(padding: CGFloat = 10, @ViewBuilder content: () -> Content) -> some View {
12 | self
13 | .padding(.bottom, 24)
14 | .overlay(alignment: .bottom) {
15 | VStack(spacing: -1) {
16 | Divider()
17 | HStack(spacing: 0) {
18 | content()
19 | .buttonStyle(PlainButtonStyle())
20 | }
21 | .frame(height: 16)
22 | .padding(.vertical, 4)
23 | .padding(.horizontal, padding)
24 | .frame(maxWidth: .infinity, alignment: .leading)
25 | }
26 | .frame(height: 24)
27 | .background(.separator)
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/DynamicIsland/extensions/BundleInfos.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BundleInfos.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 08/08/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension Bundle {
11 | var releaseVersionNumber: String? {
12 | return infoDictionary?["CFBundleShortVersionString"] as? String
13 | }
14 | var buildVersionNumber: String? {
15 | return infoDictionary?["CFBundleVersion"] as? String
16 | }
17 | var releaseVersionNumberPretty: String {
18 | return "v\(releaseVersionNumber ?? "1.0.0")"
19 | }
20 |
21 | var iconFileName: String? {
22 | guard let icons = infoDictionary?["CFBundleIcons"] as? [String: Any],
23 | let primaryIcon = icons["CFBundlePrimaryIcon"] as? [String: Any],
24 | let iconFiles = primaryIcon["CFBundleIconFiles"] as? [String],
25 | let iconFileName = iconFiles.last
26 | else { return nil }
27 | return iconFileName
28 | }
29 | }
30 |
31 | struct BundleAppIcon: View {
32 | var body: some View {
33 | Bundle.main.iconFileName
34 | .flatMap { NSImage(named: $0) }
35 | .map { Image(nsImage: $0) }
36 | }
37 | }
38 |
39 | func isNewVersion() -> Bool {
40 | let defaults = UserDefaults.standard
41 | let currentVersion = Bundle.main.releaseVersionNumber ?? "1.0"
42 | let savedVersion = defaults.string(forKey: "LastVersionRun") ?? ""
43 |
44 | if currentVersion != savedVersion {
45 | defaults.set(currentVersion, forKey: "LastVersionRun")
46 | return true
47 | }
48 | return false
49 | }
50 |
51 | func isExtensionRunning(_ bundleID: String) -> Bool {
52 | if let _ = NSWorkspace.shared.runningApplications.first(where: {$0.bundleIdentifier == bundleID}) {
53 | return true
54 | }
55 |
56 | return false
57 | }
58 |
--------------------------------------------------------------------------------
/DynamicIsland/extensions/Button+Bouncing.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Button+Bouncing.swift
3 | // DynamicIsland
4 | //
5 | // Created by Harsh Vardhan Goswami on 19/08/24.
6 | //
7 | import SwiftUI
8 | import Defaults
9 |
10 | struct BouncingButtonStyle: ButtonStyle {
11 | let vm: DynamicIslandViewModel
12 | @State private var isPressed = false
13 |
14 | func makeBody(configuration: Configuration) -> some View {
15 | configuration.label
16 | .padding(12)
17 | .background(
18 | RoundedRectangle(cornerRadius: Defaults[.cornerRadiusScaling] ? 10 : MusicPlayerImageSizes.cornerRadiusInset.closed)
19 | .fill(Color(red: 20/255, green: 20/255, blue: 20/255))
20 | .strokeBorder(.white.opacity(0.04), lineWidth: 1)
21 | )
22 | .scaleEffect(isPressed ? 0.9 : 1.0)
23 | .onChange(of: configuration.isPressed) { _, _ in
24 | withAnimation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3)) {
25 | isPressed.toggle()
26 | }
27 | }
28 | }
29 | }
30 |
31 | extension Button {
32 | func bouncingStyle(vm: DynamicIslandViewModel) -> some View {
33 | self.buttonStyle(BouncingButtonStyle(vm: vm))
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/DynamicIsland/extensions/ConditionalModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConditionalModifier.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 20/08/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension View {
11 | @ViewBuilder func conditionalModifier(_ condition: Bool, transform: (Self) -> Content) -> some View {
12 | if condition {
13 | transform(self)
14 | } else {
15 | self
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DynamicIsland/extensions/DataTypes+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DataTypes+Extensions.swift
3 | // DynamicIsland
4 | //
5 | // Created by Harsh Vardhan Goswami on 27/08/24.
6 | //
7 |
8 | import Foundation
9 |
10 |
11 |
12 | extension Date {
13 | static var yesterday: Date { return Date().dayBefore }
14 | static var tomorrow: Date { return Date().dayAfter }
15 | var dayBefore: Date {
16 | return Calendar.current.date(byAdding: .day, value: -1, to: noon)!
17 | }
18 | var dayAfter: Date {
19 | return Calendar.current.date(byAdding: .day, value: 1, to: noon)!
20 | }
21 | var noon: Date {
22 | return Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: self)!
23 | }
24 |
25 | var date: String {
26 | let dateFormatter = DateFormatter()
27 | dateFormatter.dateFormat = "dd"
28 | return dateFormatter.string(from: self)
29 | }
30 |
31 | var month: String {
32 | let dateFormatter = DateFormatter()
33 | dateFormatter.dateFormat = "MMMM"
34 | return dateFormatter.string(from: self)
35 | }
36 |
37 | func dayOfTheWeek(dayOfWeek: Int) -> String {
38 | let dateFormatter = DateFormatter()
39 | dateFormatter.dateFormat = "EEE"
40 | let date = Calendar.current.date(bySetting: .weekday, value: dayOfWeek, of: self) ?? self
41 | return dateFormatter.string(from: date)
42 | }
43 | }
44 |
45 | extension NSSize {
46 | var s: String { "\(width.i)×\(height.i)" }
47 |
48 | var aspectRatio: Double {
49 | width / height
50 | }
51 | func scaled(by factor: Double) -> CGSize {
52 | CGSize(width: (width * factor).evenInt, height: (height * factor).evenInt)
53 | }
54 |
55 | }
56 |
57 | extension Int {
58 | var s: String {
59 | String(self)
60 | }
61 | var d: Double {
62 | Double(self)
63 | }
64 | }
65 |
66 | extension Double {
67 | @inline(__always) @inlinable var intround: Int {
68 | rounded().i
69 | }
70 |
71 | @inline(__always) @inlinable var i: Int {
72 | Int(self)
73 | }
74 |
75 | var evenInt: Int {
76 | let x = intround
77 | return x + x % 2
78 | }
79 | }
80 |
81 | extension CGFloat {
82 | @inline(__always) @inlinable var intround: Int {
83 | rounded().i
84 | }
85 |
86 | @inline(__always) @inlinable var i: Int {
87 | Int(self)
88 | }
89 |
90 | var evenInt: Int {
91 | let x = intround
92 | return x + x % 2
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/DynamicIsland/extensions/KeyboardShortcutsHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeyboardShortcutsHelper.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 16/08/2024.
6 | //
7 |
8 | import KeyboardShortcuts
9 | import SwiftUI
10 | import Carbon
11 |
12 |
13 | extension View {
14 |
15 | public func keyboardShortcut(_ shortcut: KeyboardShortcuts.Name) -> some View {
16 | if let shortcut = shortcut.shortcut {
17 | if let keyEquivalent = shortcut.toKeyEquivalent() {
18 | return AnyView(self.keyboardShortcut(keyEquivalent, modifiers: shortcut.toEventModifiers()))
19 | }
20 | }
21 |
22 | return AnyView(self)
23 | }
24 |
25 | }
26 |
27 | extension KeyboardShortcuts.Shortcut {
28 |
29 | func toKeyEquivalent() -> KeyEquivalent? {
30 | let carbonKeyCode = UInt16(self.carbonKeyCode)
31 | let maxNameLength = 4
32 | var nameBuffer = [UniChar](repeating: 0, count : maxNameLength)
33 | var nameLength = 0
34 |
35 | let modifierKeys = UInt32(alphaLock >> 8) & 0xFF // Caps Lock
36 | var deadKeys: UInt32 = 0
37 | let keyboardType = UInt32(LMGetKbdType())
38 |
39 | let source = TISCopyCurrentKeyboardLayoutInputSource().takeRetainedValue()
40 | guard let ptr = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData) else {
41 | NSLog("Could not get keyboard layout data")
42 | return nil
43 | }
44 | let layoutData = Unmanaged.fromOpaque(ptr).takeUnretainedValue() as Data
45 | let osStatus = layoutData.withUnsafeBytes {
46 | UCKeyTranslate($0.bindMemory(to: UCKeyboardLayout.self).baseAddress, carbonKeyCode, UInt16(kUCKeyActionDown),
47 | modifierKeys, keyboardType, UInt32(kUCKeyTranslateNoDeadKeysMask),
48 | &deadKeys, maxNameLength, &nameLength, &nameBuffer)
49 | }
50 | guard osStatus == noErr else {
51 | NSLog("Code: 0x%04X Status: %+i", carbonKeyCode, osStatus);
52 | return nil
53 | }
54 |
55 | return KeyEquivalent(Character(String(utf16CodeUnits: nameBuffer, count: nameLength)))
56 | }
57 |
58 | func toEventModifiers() -> SwiftUI.EventModifiers {
59 | var modifiers: SwiftUI.EventModifiers = []
60 |
61 | if self.modifiers.contains(NSEvent.ModifierFlags.command) {
62 | modifiers.update(with: EventModifiers.command)
63 | }
64 |
65 | if self.modifiers.contains(NSEvent.ModifierFlags.control) {
66 | modifiers.update(with: EventModifiers.control)
67 | }
68 |
69 | if self.modifiers.contains(NSEvent.ModifierFlags.option) {
70 | modifiers.update(with: EventModifiers.option)
71 | }
72 |
73 | if self.modifiers.contains(NSEvent.ModifierFlags.shift) {
74 | modifiers.update(with: EventModifiers.shift)
75 | }
76 |
77 | if self.modifiers.contains(NSEvent.ModifierFlags.capsLock) {
78 | modifiers.update(with: EventModifiers.capsLock)
79 | }
80 |
81 | if self.modifiers.contains(NSEvent.ModifierFlags.numericPad) {
82 | modifiers.update(with: EventModifiers.numericPad)
83 | }
84 |
85 | return modifiers
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/DynamicIsland/extensions/MouseTracker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MouseTracker.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 12/08/2024.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension NSScreen {
11 | static var screenWithMouse: NSScreen? {
12 | let mouseLocation = NSEvent.mouseLocation
13 | let screens = NSScreen.screens
14 | let screenWithMouse = (screens.first { NSMouseInRect(mouseLocation, $0.frame, false) })
15 |
16 | return screenWithMouse
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/DynamicIsland/helpers/AppIcons.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppIcons.swift
3 | // DynamicIsland
4 | //
5 | // Created by Harsh Vardhan Goswami on 16/08/24.
6 | //
7 |
8 | import SwiftUI
9 | import AppKit
10 |
11 | struct AppIcons {
12 |
13 | func getIcon(file path: String) -> NSImage? {
14 | guard FileManager.default.fileExists(atPath: path)
15 | else { return nil }
16 |
17 | return NSWorkspace.shared.icon(forFile: path)
18 | }
19 |
20 | func getIcon(bundleID: String) -> NSImage? {
21 | guard let path = NSWorkspace.shared.urlForApplication(
22 | withBundleIdentifier: bundleID
23 | )?.absoluteString
24 | else { return nil }
25 |
26 | return getIcon(file: path)
27 | }
28 |
29 | /// Easily read Info.plist as a Dictionary from any bundle by accessing .infoDictionary on Bundle
30 | func bundle(forBundleID: String) -> Bundle? {
31 | guard let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: forBundleID)
32 | else { return nil }
33 |
34 | return Bundle(url: url)
35 | }
36 |
37 | }
38 |
39 | func AppIcon(for bundleID: String) -> Image {
40 | let workspace = NSWorkspace.shared
41 |
42 | if let appURL = workspace.urlForApplication(withBundleIdentifier: bundleID) {
43 | let appIcon = workspace.icon(forFile: appURL.path)
44 | return Image(nsImage: appIcon)
45 | }
46 |
47 | return Image(nsImage: workspace.icon(for: .applicationBundle))
48 | }
49 |
50 |
51 | func AppIconAsNSImage(for bundleID: String) -> NSImage? {
52 | let workspace = NSWorkspace.shared
53 |
54 | if let appURL = workspace.urlForApplication(withBundleIdentifier: bundleID) {
55 | let appIcon = workspace.icon(forFile: appURL.path)
56 | return appIcon
57 | }
58 | return nil
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/DynamicIsland/helpers/AppleScriptError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppleScriptError.swift
3 | // DynamicIsland
4 | //
5 | // Adapted from TheBoringWorker-HUD
6 | // Created by GitHub Copilot on 06/09/25.
7 | //
8 |
9 | import Foundation
10 |
11 | enum AppleScriptError: Error {
12 | case initScriptFailed
13 | case runtimeError
14 | case emptyOutput
15 | }
--------------------------------------------------------------------------------
/DynamicIsland/helpers/AppleScriptHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppleScriptHelper.swift
3 | // DynamicIsland
4 | //
5 | // Created by Alexander on 2025-03-29.
6 | //
7 |
8 | import Foundation
9 |
10 | class AppleScriptHelper {
11 | @discardableResult
12 | class func execute(_ scriptText: String) async throws -> NSAppleEventDescriptor? {
13 | try await withCheckedThrowingContinuation { continuation in
14 | Task.detached(priority: .userInitiated) {
15 | let script = NSAppleScript(source: scriptText)
16 | var error: NSDictionary?
17 | if let descriptor = script?.executeAndReturnError(&error) {
18 | continuation.resume(returning: descriptor)
19 | } else if let error = error {
20 | continuation.resume(throwing: NSError(domain: "AppleScriptError", code: 1, userInfo: error as? [String: Any]))
21 | } else {
22 | continuation.resume(throwing: NSError(domain: "AppleScriptError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Unknown error"]))
23 | }
24 | }
25 | }
26 | }
27 |
28 | class func executeVoid(_ scriptText: String) async throws {
29 | _ = try await execute(scriptText)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/DynamicIsland/helpers/AppleScriptRunner.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppleScriptRunner.swift
3 | // DynamicIsland
4 | //
5 | // Adapted from TheBoringWorker-HUD
6 | // Created by GitHub Copilot on 06/09/25.
7 | //
8 |
9 | import Foundation
10 |
11 | class AppleScriptRunner {
12 | private init() {}
13 |
14 | static func run(script: String) throws -> String {
15 | var error: NSDictionary?
16 | if let scriptObject = NSAppleScript(source: script) {
17 | let output: NSAppleEventDescriptor = scriptObject.executeAndReturnError(&error)
18 | guard error == nil else {
19 | throw AppleScriptError.runtimeError
20 | }
21 | if let outputString = output.stringValue {
22 | return outputString
23 | }
24 | throw AppleScriptError.emptyOutput
25 | }
26 | throw AppleScriptError.initScriptFailed
27 | }
28 | }
--------------------------------------------------------------------------------
/DynamicIsland/helpers/AudioPlayer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AudioPlayer.swift
3 | // DynamicIsland
4 | //
5 | // Created by Harsh Vardhan Goswami on 09/08/24.
6 | //
7 |
8 | import Foundation
9 | import AppKit
10 |
11 | class AudioPlayer {
12 | func play(fileName: String, fileExtension: String) {
13 | NSSound(contentsOf:Bundle.main.url(forResource: fileName, withExtension: fileExtension)!, byReference: false)?.play()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/DynamicIsland/helpers/Clipboard+Content.swift:
--------------------------------------------------------------------------------
1 | import AppKit
2 |
3 | func getAttributedString(content: Any, type: NSPasteboard.PasteboardType) -> NSAttributedString? {
4 | if let stringContent = content as? String {
5 | return NSAttributedString(string: stringContent)
6 | }
7 | if type == .rtf, let data = content as? Data {
8 | return NSAttributedString(rtf: data, documentAttributes: nil)
9 | } else if type == .html, let data = content as? Data {
10 | return try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil)
11 | } else if type.rawValue == "public.utf8-plain-text", let data = content as? Data {
12 | return try? NSAttributedString(data: data, documentAttributes: nil)
13 | } else if type == .string {
14 | return NSAttributedString(string: content as? String ?? "")
15 | } else if type == .fileURL {
16 | return NSAttributedString(string: content as? String ?? "")
17 | } else if type == NSPasteboard.PasteboardType("com.apple.webarchive"), let data = content as? Data {
18 | return try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.webArchive], documentAttributes: nil)
19 | }
20 | return nil
21 | }
22 |
23 | func isText(type: NSPasteboard.PasteboardType) -> Bool {
24 | return type == .string || type == .html || type == .rtf || type == .html || type == .string || type.rawValue == "public.utf8-plain-text" || type.rawValue == "public.utf16-external-plain-text" || type.rawValue == "com.apple.webarchive"
25 | }
26 |
--------------------------------------------------------------------------------
/DynamicIsland/helpers/MediaChecker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MediaChecker.swift
3 | // DynamicIsland
4 | //
5 | // Created by Alexander on 2025-07-26.
6 | //
7 |
8 | import Foundation
9 |
10 | @MainActor
11 | final class MediaChecker: Sendable {
12 | enum MediaCheckerError: Error {
13 | case missingResources
14 | case processExecutionFailed
15 | case timeout
16 | }
17 |
18 | func checkDeprecationStatus() async throws -> Bool {
19 | guard let scriptURL = Bundle.main.url(forResource: "mediaremote-adapter", withExtension: "pl"),
20 | let nowPlayingTestClientPath = Bundle.main.url(forResource: "MediaRemoteAdapterTestClient", withExtension: nil)?.path,
21 | let frameworkPath = Bundle.main.privateFrameworksPath?.appending("/MediaRemoteAdapter.framework")
22 | else {
23 | throw MediaCheckerError.missingResources
24 | }
25 |
26 | let process = Process()
27 | process.executableURL = URL(fileURLWithPath: "/usr/bin/perl")
28 | process.arguments = [scriptURL.path, frameworkPath, nowPlayingTestClientPath, "test"]
29 |
30 | do {
31 | try process.run()
32 | } catch {
33 | throw MediaCheckerError.processExecutionFailed
34 | }
35 |
36 | // Timeout after 10 seconds
37 | let didExit: Bool = try await withThrowingTaskGroup(of: Bool.self) { group in
38 | group.addTask {
39 | process.waitUntilExit()
40 | return true
41 | }
42 | group.addTask {
43 | try await Task.sleep(for: .seconds(10))
44 | if process.isRunning {
45 | process.terminate()
46 | }
47 | return false
48 | }
49 | for try await exited in group {
50 | if exited {
51 | group.cancelAll()
52 | return true
53 | }
54 | }
55 | throw MediaCheckerError.timeout
56 | }
57 |
58 | if !didExit {
59 | throw MediaCheckerError.timeout
60 | }
61 |
62 | let isDeprecated = process.terminationStatus == 1
63 | return isDeprecated
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/DynamicIsland/helpers/SensorError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SensorError.swift
3 | // DynamicIsland
4 | //
5 | // Adapted from TheBoringWorker-HUD
6 | // Created by GitHub Copilot on 06/09/25.
7 | //
8 |
9 | import Foundation
10 |
11 | enum SensorError: Error {
12 | enum Display: Error {
13 | case notFound
14 | case notSilicon
15 | case notStandard
16 | }
17 | enum Keyboard: Error {
18 | case notFound
19 | case notSilicon
20 | case notStandard
21 | }
22 | }
--------------------------------------------------------------------------------
/DynamicIsland/helpers/SensorMethod.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SensorMethod.swift
3 | // DynamicIsland
4 | //
5 | // Adapted from TheBoringWorker-HUD
6 | // Created by GitHub Copilot on 06/09/25.
7 | //
8 |
9 | import Foundation
10 |
11 | enum SensorMethod {
12 | case standard
13 | case m1
14 | case allFailed
15 | }
--------------------------------------------------------------------------------
/DynamicIsland/helpers/SystemHUDDebugger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SystemHUDDebugger.swift
3 | // DynamicIsland
4 | //
5 | // Debug utility for System HUD functionality
6 | // Created by GitHub Copilot on 06/09/25.
7 | //
8 |
9 | import Foundation
10 |
11 | class SystemHUDDebugger {
12 |
13 | /// Test system HUD functionality and print status
14 | public static func testSystemHUD() {
15 | print("\n🔍 === System HUD Debug Report ===")
16 |
17 | // Check current OSDUIHelper status
18 | let isRunning = SystemOSDManager.isOSDUIHelperRunning()
19 | print("📊 OSDUIHelper Status: \(isRunning ? "✅ Running" : "❌ Not running")")
20 |
21 | // Test disable
22 | print("🔇 Testing disable...")
23 | SystemOSDManager.disableSystemHUD()
24 |
25 | // Wait and check
26 | usleep(500000)
27 | let isRunningAfterDisable = SystemOSDManager.isOSDUIHelperRunning()
28 | print("📊 After disable: \(isRunningAfterDisable ? "✅ Running (stopped)" : "❌ Not running")")
29 |
30 | // Test enable
31 | print("🔊 Testing re-enable...")
32 | SystemOSDManager.enableSystemHUD()
33 |
34 | // Wait and check
35 | usleep(1000000)
36 | let isRunningAfterEnable = SystemOSDManager.isOSDUIHelperRunning()
37 | print("📊 After re-enable: \(isRunningAfterEnable ? "✅ Running" : "❌ Not running")")
38 |
39 | print("🔍 === End Debug Report ===\n")
40 |
41 | if !isRunningAfterEnable {
42 | print("⚠️ WARNING: System HUD may not be working properly!")
43 | print("💡 Try pressing volume keys to test system HUD functionality")
44 | }
45 | }
46 |
47 | /// Force restart OSDUIHelper using multiple methods
48 | public static func forceRestartOSDUIHelper() {
49 | print("🔄 Force restarting OSDUIHelper...")
50 |
51 | // Method 1: Kill and kickstart
52 | SystemOSDManager.enableSystemHUD()
53 |
54 | // Method 2: Try launchctl bootstrap (more aggressive)
55 | do {
56 | let bootstrap = Process()
57 | bootstrap.executableURL = URL(fileURLWithPath: "/bin/launchctl")
58 | bootstrap.arguments = ["bootstrap", "gui/\(getuid())", "/System/Library/LaunchAgents/com.apple.OSDUIHelper.plist"]
59 | try bootstrap.run()
60 | bootstrap.waitUntilExit()
61 | print("✅ Bootstrap method completed")
62 | } catch {
63 | print("❌ Bootstrap method failed: \(error)")
64 | }
65 |
66 | // Method 3: Try direct service restart
67 | do {
68 | let restart = Process()
69 | restart.executableURL = URL(fileURLWithPath: "/bin/launchctl")
70 | restart.arguments = ["restart", "gui/\(getuid())/com.apple.OSDUIHelper"]
71 | try restart.run()
72 | restart.waitUntilExit()
73 | print("✅ Restart method completed")
74 | } catch {
75 | print("❌ Restart method failed: \(error)")
76 | }
77 |
78 | // Check final status
79 | usleep(1000000)
80 | let finalStatus = SystemOSDManager.isOSDUIHelperRunning()
81 | print("📊 Final OSDUIHelper status: \(finalStatus ? "✅ Running" : "❌ Not running")")
82 | }
83 | }
--------------------------------------------------------------------------------
/DynamicIsland/managers/ClipboardPanelManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClipboardPanelManager.swift
3 | // DynamicIsland
4 | //
5 | // Created by Ebullioscopic on 12/08/25.
6 | //
7 |
8 | import AppKit
9 | import SwiftUI
10 |
11 | class ClipboardPanelManager: ObservableObject {
12 | static let shared = ClipboardPanelManager()
13 |
14 | private var clipboardPanel: ClipboardPanel?
15 |
16 | private init() {}
17 |
18 | func showClipboardPanel() {
19 | hideClipboardPanel() // Close any existing panel
20 |
21 | let panel = ClipboardPanel()
22 | panel.positionNearNotch()
23 |
24 | self.clipboardPanel = panel
25 |
26 | // Make the panel key and order front to ensure it can receive focus
27 | panel.makeKeyAndOrderFront(nil)
28 | panel.orderFrontRegardless()
29 |
30 | // Activate the app to ensure proper focus handling
31 | NSApp.activate(ignoringOtherApps: true)
32 |
33 | // Ensure the panel becomes the key window for text input
34 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
35 | panel.makeKey()
36 | }
37 | }
38 |
39 | func hideClipboardPanel() {
40 | clipboardPanel?.close()
41 | clipboardPanel = nil
42 | }
43 |
44 | func toggleClipboardPanel() {
45 | if let panel = clipboardPanel, panel.isVisible {
46 | hideClipboardPanel()
47 | } else {
48 | showClipboardPanel()
49 | }
50 | }
51 |
52 | var isPanelVisible: Bool {
53 | return clipboardPanel?.isVisible ?? false
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/DynamicIsland/managers/ClipboardWindowManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClipboardWindowManager.swift
3 | // DynamicIsland
4 | //
5 | // Created by Ebullioscopic on 12/08/25.
6 | //
7 |
8 | import SwiftUI
9 | import AppKit
10 |
11 | class ClipboardWindowManager: ObservableObject {
12 | static let shared = ClipboardWindowManager()
13 |
14 | private var clipboardWindow: NSWindow?
15 |
16 | private init() {}
17 |
18 | func showClipboardWindow() {
19 | if let existingWindow = clipboardWindow {
20 | // Ensure window appears above fullscreen apps
21 | existingWindow.level = .screenSaver
22 | existingWindow.makeKeyAndOrderFront(nil)
23 | existingWindow.orderFrontRegardless() // Force window to front even above fullscreen
24 | NSApp.activate(ignoringOtherApps: true)
25 | return
26 | }
27 |
28 | // Create the window
29 | let window = NSWindow(
30 | contentRect: NSRect(x: 0, y: 0, width: 400, height: 300),
31 | styleMask: [.titled, .closable, .resizable],
32 | backing: .buffered,
33 | defer: false
34 | )
35 |
36 | window.title = "Clipboard Manager"
37 | window.isMovableByWindowBackground = true
38 | window.level = .screenSaver // Use screenSaver level to appear above fullscreen apps
39 | window.isReleasedWhenClosed = false
40 | window.hasShadow = true
41 | window.titlebarAppearsTransparent = true
42 | window.styleMask.insert(.fullSizeContentView)
43 | window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary] // Allow on all spaces and above fullscreen
44 |
45 | // Set minimum and maximum sizes
46 | window.minSize = NSSize(width: 350, height: 250)
47 | window.maxSize = NSSize(width: 600, height: 500)
48 |
49 | // Center the window on the current screen (important for fullscreen apps)
50 | let currentScreen = NSScreen.main ?? NSScreen.screens.first!
51 | let screenFrame = currentScreen.frame // Use full frame instead of visibleFrame for fullscreen compatibility
52 | let windowFrame = window.frame
53 | let x = (screenFrame.width - windowFrame.width) / 2 + screenFrame.minX
54 | let y = (screenFrame.height - windowFrame.height) / 2 + screenFrame.minY
55 | window.setFrameOrigin(NSPoint(x: x, y: y))
56 |
57 | // Set the content view
58 | let contentView = ClipboardWindow()
59 | let hostingView = NSHostingView(rootView: contentView)
60 | window.contentView = hostingView
61 |
62 | // Handle window closing
63 | window.delegate = WindowDelegate { [weak self] in
64 | self?.clipboardWindow = nil
65 | }
66 |
67 | self.clipboardWindow = window
68 | window.makeKeyAndOrderFront(nil)
69 | window.orderFrontRegardless() // Force window to front even above fullscreen apps
70 | NSApp.activate(ignoringOtherApps: true)
71 | }
72 |
73 | func hideClipboardWindow() {
74 | clipboardWindow?.close()
75 | }
76 |
77 | func toggleClipboardWindow() {
78 | if let window = clipboardWindow, window.isVisible {
79 | hideClipboardWindow()
80 | } else {
81 | showClipboardWindow()
82 | }
83 | }
84 |
85 | var isWindowVisible: Bool {
86 | return clipboardWindow?.isVisible ?? false
87 | }
88 | }
89 |
90 | private class WindowDelegate: NSObject, NSWindowDelegate {
91 | private let onClose: () -> Void
92 |
93 | init(onClose: @escaping () -> Void) {
94 | self.onClose = onClose
95 | super.init()
96 | }
97 |
98 | func windowWillClose(_ notification: Notification) {
99 | onClose()
100 | }
101 |
102 | func windowShouldClose(_ sender: NSWindow) -> Bool {
103 | // Hide instead of close when user clicks close button
104 | sender.orderOut(nil)
105 | onClose()
106 | return false
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/DynamicIsland/managers/ColorPickerManager_Fixed.swift:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIsland/managers/ColorPickerManager_Fixed.swift
--------------------------------------------------------------------------------
/DynamicIsland/managers/DynamicIslandExtensionManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DynamicIslandExtensionManager.swift
3 | // DynamicIsland
4 | //
5 | // Created by Harsh Vardhan Goswami on 04/08/24.
6 | // DynamicIslandExtensionManager.swift
7 | // DynamicIsland
8 | //
9 | // Created by Harsh Vardhan Goswami on 07/09/24.
10 | //
11 |
12 | import Foundation
13 | import SwiftUI
14 |
15 | var clipboardExtension: String = "dynamicisland.TheDynamicClipboard"
16 | var hudExtension: String = "dynamicisland.TheDynamicHUDs"
17 | var downloadManagerExtension: String = "dynamicisland.TheDynamicDownloadManager"
18 |
19 | struct Extension: Identifiable, Hashable {
20 | var id = UUID()
21 | var name: String
22 | var bundleIdentifier: String
23 | var status: StatusModel = .enabled
24 | }
25 |
26 | enum StatusModel {
27 | case disabled
28 | case enabled
29 | }
30 |
31 | class DynamicIslandExtensionManager: ObservableObject {
32 |
33 | @Published var installedExtensions: [Extension] = [] {
34 | didSet {
35 | // Only log when there's an actual change in content, not just initialization
36 | if oldValue != installedExtensions {
37 | print("📦 Extensions installed: \(installedExtensions.map { $0.name })")
38 | }
39 | }
40 | }
41 |
42 | var extensions = [
43 | clipboardExtension,
44 | hudExtension
45 | ]
46 |
47 | init() {
48 | checkIfExtensionsAreInstalled()
49 |
50 | DistributedNotificationCenter.default().addObserver(self, selector: #selector(checkIfExtensionsAreInstalled), name: NSNotification.Name("NSWorkspaceDidLaunchApplicationNotification"), object: nil)
51 | }
52 |
53 | @objc func checkIfExtensionsAreInstalled() {
54 | installedExtensions = []
55 | for extensionName in extensions {
56 | if NSWorkspace.shared.urlForApplication(withBundleIdentifier: extensionName) != nil {
57 | let ext = Extension(name: extensionName.components(separatedBy: ".").last ?? extensionName, bundleIdentifier: extensionName)
58 | installedExtensions.append(ext)
59 | }
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/DynamicIsland/managers/ImageService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageService.swift
3 | // DynamicIsland
4 | //
5 | // Created by Alexander on 2025-09-13.
6 | //
7 |
8 | import Foundation
9 | import Defaults
10 |
11 | public protocol ImageServiceProtocol {
12 | func fetchImageData(from url: URL) async throws -> Data
13 | }
14 |
15 | public final class ImageService: ImageServiceProtocol {
16 | public static let shared = ImageService()
17 |
18 | private let session: URLSession
19 |
20 | private init() {
21 | let config = URLSessionConfiguration.default
22 | let cache = URLCache(memoryCapacity: 50 * 1024 * 1024, // 50MB
23 | diskCapacity: 100 * 1024 * 1024, // 100MB
24 | diskPath: "artwork_cache")
25 | config.urlCache = cache
26 | config.timeoutIntervalForRequest = 15
27 | config.timeoutIntervalForResource = 30
28 | config.httpShouldSetCookies = false
29 | self.session = URLSession(configuration: config)
30 |
31 | performLegacyCacheCleanupIfNeeded()
32 | }
33 |
34 | private func performLegacyCacheCleanupIfNeeded() {
35 |
36 | if !Defaults[.didClearLegacyURLCacheV1] {
37 | URLCache.shared.removeAllCachedResponses()
38 | Defaults[.didClearLegacyURLCacheV1] = true
39 | }
40 | }
41 |
42 | public func fetchImageData(from url: URL) async throws -> Data {
43 | guard let scheme = url.scheme?.lowercased(), scheme == "http" || scheme == "https" else {
44 | throw URLError(.unsupportedURL)
45 | }
46 | let (data, _) = try await session.data(from: url)
47 | return data
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/DynamicIsland/managers/NotchSpaceManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotchSpaceManager.swift
3 | // DynamicIsland
4 | //
5 | // Created by Alexander on 2024-10-27.
6 | //
7 |
8 | import Foundation
9 |
10 | class NotchSpaceManager {
11 | static let shared = NotchSpaceManager()
12 | let notchSpace: CGSSpace
13 | private var eventTap: CFMachPort?
14 | private var runLoopSource: CFRunLoopSource?
15 |
16 | private init() {
17 | notchSpace = CGSSpace(level: 2147483647) // Max level
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/DynamicIsland/managers/ScreenAssistantPanelManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScreenAssistantPanelManager.swift
3 | // DynamicIsland
4 | //
5 | // Created by Hariharan Mudaliar
6 |
7 | import AppKit
8 | import SwiftUI
9 | import Defaults
10 |
11 | class ScreenAssistantPanelManager: ObservableObject {
12 | static let shared = ScreenAssistantPanelManager()
13 |
14 | // Backward compatibility wrapper - delegates to the new ScreenAssistantManager
15 | private var screenAssistantPanel: ScreenAssistantPanel?
16 |
17 | private init() {}
18 |
19 | func showScreenAssistantPanel() {
20 | hideScreenAssistantPanel() // Close any existing panel
21 |
22 | // Use the new dual-panel system
23 | ScreenAssistantManager.shared.showPanels()
24 |
25 | // Create a dummy panel for backward compatibility
26 | let panel = ScreenAssistantPanel()
27 | self.screenAssistantPanel = panel
28 |
29 | // The actual panels are managed by ScreenAssistantManager
30 | // This is just for compatibility with existing code
31 |
32 | print("ScreenAssistant: New dual-panel system activated")
33 | }
34 |
35 | func hideScreenAssistantPanel() {
36 | // Close the new panel system
37 | ScreenAssistantManager.shared.closePanels()
38 |
39 | // Clean up compatibility panel
40 | screenAssistantPanel?.close()
41 | screenAssistantPanel = nil
42 |
43 | print("ScreenAssistant: Panels hidden")
44 | }
45 |
46 | func toggleScreenAssistantPanel() {
47 | if ScreenAssistantManager.shared.arePanelsVisible() {
48 | hideScreenAssistantPanel()
49 | } else {
50 | showScreenAssistantPanel()
51 | }
52 | }
53 |
54 | var isPanelVisible: Bool {
55 | return ScreenAssistantManager.shared.arePanelsVisible()
56 | }
57 | }
--------------------------------------------------------------------------------
/DynamicIsland/managers/StatsPanelManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StatsPanelManager.swift
3 | // DynamicIsland
4 | //
5 | // Created by DynamicIsland App on 2024.
6 | //
7 |
8 | import AppKit
9 | import SwiftUI
10 |
11 | class StatsPanelManager: ObservableObject {
12 | static let shared = StatsPanelManager()
13 |
14 | private var statsPanel: StatsPanel?
15 |
16 | private init() {}
17 |
18 | func showStatsPanel() {
19 | hideStatsPanel() // Close any existing panel
20 |
21 | let panel = StatsPanel()
22 | panel.positionNearNotch()
23 |
24 | self.statsPanel = panel
25 |
26 | // Make the panel key and order front to ensure it can receive focus
27 | panel.makeKeyAndOrderFront(nil)
28 | panel.orderFrontRegardless()
29 |
30 | // Activate the app to ensure proper focus handling
31 | NSApp.activate(ignoringOtherApps: true)
32 |
33 | // Ensure the panel becomes the key window for input
34 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
35 | panel.makeKey()
36 | }
37 | }
38 |
39 | func hideStatsPanel() {
40 | statsPanel?.close()
41 | statsPanel = nil
42 | }
43 |
44 | func toggleStatsPanel() {
45 | if let panel = statsPanel, panel.isVisible {
46 | hideStatsPanel()
47 | } else {
48 | showStatsPanel()
49 | }
50 | }
51 |
52 | var isPanelVisible: Bool {
53 | return statsPanel?.isVisible ?? false
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/DynamicIsland/managers/SystemDisplayManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SystemDisplayManager.swift
3 | // DynamicIsland
4 | //
5 | // Adapted from TheBoringWorker-HUD DisplayManager.swift
6 | // Created by GitHub Copilot on 06/09/25.
7 | //
8 |
9 | import Foundation
10 | import Cocoa
11 |
12 | class SystemDisplayManager {
13 | private init() {}
14 |
15 | private static var method = SensorMethod.standard
16 |
17 | static func getDisplayBrightness() throws -> Float {
18 | switch SystemDisplayManager.method {
19 | case .standard:
20 | do {
21 | return try getStandardDisplayBrightness()
22 | } catch {
23 | method = .m1
24 | }
25 | case .m1:
26 | do {
27 | return try getM1DisplayBrightness()
28 | } catch {
29 | method = .allFailed
30 | }
31 | case .allFailed:
32 | throw SensorError.Display.notFound
33 | }
34 | return try getDisplayBrightness()
35 | }
36 |
37 | private static func getStandardDisplayBrightness() throws -> Float {
38 | var brightness: float_t = 1
39 | let service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IODisplayConnect"))
40 | defer {
41 | IOObjectRelease(service)
42 | }
43 |
44 | let result = IODisplayGetFloatParameter(service, 0, kIODisplayBrightnessKey as CFString, &brightness)
45 | if result != kIOReturnSuccess {
46 | throw SensorError.Display.notStandard
47 | }
48 | return brightness
49 | }
50 |
51 | private static func getM1DisplayBrightness() throws -> Float {
52 | let task = Process()
53 | task.launchPath = "/usr/libexec/corebrightnessdiag"
54 | task.arguments = ["status-info"]
55 | let pipe = Pipe()
56 | task.standardOutput = pipe
57 | try task.run()
58 | let data = pipe.fileHandleForReading.readDataToEndOfFile()
59 | task.waitUntilExit()
60 |
61 | if let plist = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? NSDictionary,
62 | let displays = plist["CBDisplays"] as? [String: [String: Any]] {
63 | for display in displays.values {
64 | if let displayInfo = display["Display"] as? [String: Any],
65 | displayInfo["DisplayServicesIsBuiltInDisplay"] as? Bool == true,
66 | let brightness = displayInfo["DisplayServicesBrightness"] as? Float {
67 | return brightness
68 | }
69 | }
70 | }
71 | throw SensorError.Display.notSilicon
72 | }
73 | }
--------------------------------------------------------------------------------
/DynamicIsland/managers/SystemKeyObserver.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SystemKeyObserver.swift
3 | // DynamicIsland
4 | //
5 | // Adapted from TheBoringWorker-HUD KeyPressObserver.swift
6 | // Created by GitHub Copilot on 06/09/25.
7 | //
8 | // ⚠️ DEPRECATED: This class is non-functional as it extends NSApplication
9 | // but is never used as the main app class. Event monitoring has been moved
10 | // to SystemChangesObserver using NSEvent.addGlobalMonitorForEvents.
11 | //
12 |
13 | import Cocoa
14 |
15 | class SystemKeyObserver: NSApplication {
16 | static let volumeChanged = Notification.Name("DynamicIsland.volumeChanged")
17 | static let brightnessChanged = Notification.Name("DynamicIsland.brightnessChanged")
18 | static let keyboardIlluminationChanged = Notification.Name("DynamicIsland.keyboardIlluminationChanged")
19 |
20 | override func sendEvent(_ event: NSEvent) {
21 | if event.type == .systemDefined && event.subtype.rawValue == 8 {
22 | let keyCode = ((event.data1 & 0xFFFF0000) >> 16)
23 | let keyFlags = (event.data1 & 0x0000FFFF)
24 | // Get the key state. 0xA is KeyDown, OxB is KeyUp
25 | let keyState = ((keyFlags & 0xFF00) >> 8) == 0xA
26 | let keyRepeat = keyFlags & 0x1
27 | mediaKeyEvent(key: Int32(keyCode), state: keyState, keyRepeat: Bool(truncating: keyRepeat as NSNumber))
28 | }
29 |
30 | super.sendEvent(event)
31 | }
32 |
33 | func mediaKeyEvent(key: Int32, state: Bool, keyRepeat: Bool) {
34 | // Only send events on KeyDown. Without this check, these events will happen twice
35 | if state {
36 | switch key {
37 | case NX_KEYTYPE_SOUND_DOWN, NX_KEYTYPE_SOUND_UP, NX_KEYTYPE_MUTE:
38 | NotificationCenter.default.post(name: SystemKeyObserver.volumeChanged, object: self)
39 | case NX_KEYTYPE_BRIGHTNESS_UP, NX_KEYTYPE_BRIGHTNESS_DOWN:
40 | NotificationCenter.default.post(name: SystemKeyObserver.brightnessChanged, object: self)
41 | case NX_KEYTYPE_ILLUMINATION_DOWN, NX_KEYTYPE_ILLUMINATION_UP:
42 | NotificationCenter.default.post(name: SystemKeyObserver.keyboardIlluminationChanged, object: self)
43 | default:
44 | break
45 | }
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/DynamicIsland/managers/SystemVolumeManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SystemVolumeManager.swift
3 | // DynamicIsland
4 | //
5 | // Adapted from TheBoringWorker-HUD VolumeManager.swift
6 | // Created by GitHub Copilot on 06/09/25.
7 | //
8 |
9 | import Foundation
10 |
11 | class SystemVolumeManager {
12 | private init() {}
13 |
14 | static func isMuted() -> Bool {
15 | do {
16 | return try AppleScriptRunner.run(script: "return output muted of (get volume settings)") == "true"
17 | } catch {
18 | NSLog("Error while trying to retrieve muted properties of device: \(error). Returning default value false.")
19 | return false
20 | }
21 | }
22 |
23 | static func getOutputVolume() -> Float {
24 | do {
25 | if let volumeStr = Float(try AppleScriptRunner.run(script: "return output volume of (get volume settings)")) {
26 | return volumeStr / 100
27 | } else {
28 | NSLog("Error while trying to parse volume string value. Returning default volume value 1.")
29 | }
30 | } catch {
31 | NSLog("Error while trying to retrieve volume properties of device: \(error). Returning default volume value 1.")
32 | }
33 | return 0.01
34 | }
35 | }
--------------------------------------------------------------------------------
/DynamicIsland/menu/StatusBarMenu.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 |
3 | class StatusBarMenu: NSMenu {
4 |
5 | var statusItem: NSStatusItem!
6 |
7 | override init(title: String) {
8 | super.init(title: title)
9 | setupStatusItem()
10 | }
11 |
12 | convenience init() {
13 | self.init(title: "DynamicIsland")
14 | }
15 |
16 | required init(coder: NSCoder) {
17 | super.init(coder: coder)
18 | setupStatusItem()
19 | }
20 |
21 | private func setupStatusItem() {
22 | // Initialize the status item
23 | statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
24 |
25 | // Set the menu bar icon
26 | if let button = statusItem.button {
27 | button.image = NSImage(named: "logo")
28 | }
29 |
30 | // Set up the menu
31 | self.addItem(NSMenuItem(title: "Quit", action: #selector(NSApp.terminate(_:)), keyEquivalent: "q"))
32 | statusItem.menu = self
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/DynamicIsland/metal/visualizer.metal:
--------------------------------------------------------------------------------
1 | //
2 | // visualizer.metal
3 | // DynamicIsland
4 | //
5 | // Created by Harsh Vardhan Goswami on 28/08/24.
6 | //
7 |
8 | #include
9 | using namespace metal;
10 |
11 | vertex float4 vertexShader(uint vertexID [[vertex_id]],
12 | constant float2 *vertices [[buffer(0)]]) {
13 | return float4(vertices[vertexID], 0, 1);
14 | }
15 |
16 | fragment float4 fragmentShader(constant float4 &color [[buffer(0)]]) {
17 | return color;
18 | }
19 |
--------------------------------------------------------------------------------
/DynamicIsland/models/CalendarModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CalendarModel.swift
3 | // Calendr
4 | //
5 | // Created by Paker on 31/12/20.
6 | // Original source: https://github.com/pakerwreah/Calendr
7 | //
8 |
9 | import Cocoa
10 |
11 | struct CalendarModel: Identifiable, Hashable {
12 | let id: String
13 | let title: String
14 | let color: NSColor
15 | let isSubscribed: Bool
16 | let isReminder: Bool // true if this is a reminder calendar
17 | }
18 |
--------------------------------------------------------------------------------
/DynamicIsland/models/EventModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EventModel.swift
3 | // Calendr
4 | //
5 | // Created by Paker on 24/12/20.
6 | // Original source: https://github.com/pakerwreah/Calendr
7 | // Modified by Alexander on 2025-05-18.
8 | //
9 |
10 | import Foundation
11 |
12 | struct EventModel: Equatable, Identifiable {
13 | let id: String
14 | let start: Date
15 | let end: Date
16 | let title: String
17 | let location: String?
18 | let notes: String?
19 | let url: URL?
20 | let isAllDay: Bool
21 | let type: EventType
22 | let calendar: CalendarModel
23 | let participants: [Participant]
24 | let timeZone: TimeZone?
25 | let hasRecurrenceRules: Bool
26 | let priority: Priority?
27 | }
28 |
29 | enum AttendanceStatus: Comparable {
30 | case accepted
31 | case maybe
32 | case pending
33 | case declined
34 | case unknown
35 |
36 | private var comparisonValue: Int {
37 | switch self {
38 | case .accepted: return 1
39 | case .maybe: return 2
40 | case .declined: return 3
41 | case .pending: return 4
42 | case .unknown: return 5
43 | }
44 | }
45 |
46 | static func < (lhs: Self, rhs: Self) -> Bool {
47 | return lhs.comparisonValue < rhs.comparisonValue
48 | }
49 | }
50 |
51 | enum EventType: Equatable {
52 | case event(AttendanceStatus)
53 | case birthday
54 | case reminder(completed: Bool)
55 | }
56 |
57 | enum EventStatus: Equatable {
58 | case upcoming
59 | case inProgress
60 | case ended
61 | }
62 |
63 | extension EventType {
64 | var isEvent: Bool { if case .event = self { return true } else { return false } }
65 | var isBirthday: Bool { self ~= .birthday }
66 | var isReminder: Bool { if case .reminder = self { return true } else { return false } }
67 | }
68 |
69 | extension EventModel {
70 |
71 | var eventStatus: EventStatus {
72 | if start > Date() {
73 | return .upcoming
74 | } else if end > Date() {
75 | return .inProgress
76 | } else {
77 | return .ended
78 | }
79 | }
80 |
81 | var attendance: AttendanceStatus { if case .event(let attendance) = type { return attendance } else { return .unknown } }
82 |
83 | var isMeeting: Bool { !participants.isEmpty }
84 |
85 | func calendarAppURL() -> URL? {
86 |
87 | guard let id = id.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else {
88 | return nil
89 | }
90 |
91 | guard !type.isReminder else {
92 | return URL(string: "x-apple-reminderkit://remcdreminder/\(id)")
93 | }
94 |
95 | let date: String
96 | if hasRecurrenceRules {
97 | let formatter = DateFormatter()
98 | formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
99 | if !isAllDay {
100 | formatter.timeZone = .init(secondsFromGMT: 0)
101 | }
102 | if let formattedDate = formatter.string(for: start) {
103 | date = "/\(formattedDate)"
104 | } else {
105 | return nil
106 | }
107 | } else {
108 | date = ""
109 | }
110 | return URL(string: "ical://ekevent\(date)/\(id)?method=show&options=more")
111 | }
112 | }
113 |
114 | struct Participant: Hashable {
115 | let name: String
116 | let status: AttendanceStatus
117 | let isOrganizer: Bool
118 | let isCurrentUser: Bool
119 | }
120 |
121 | enum Priority {
122 | case high
123 | case medium
124 | case low
125 | }
126 |
--------------------------------------------------------------------------------
/DynamicIsland/models/PlaybackState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PlaybackState.swift
3 | // DynamicIsland
4 | //
5 | // Created by Alexander Greco on 2025-03-29.
6 | //
7 |
8 | import Foundation
9 |
10 | enum RepeatMode: Int, Codable {
11 | case off = 1
12 | case one = 2
13 | case all = 3
14 | }
15 |
16 | struct PlaybackState {
17 | var bundleIdentifier: String
18 | var isPlaying: Bool = false
19 | var title: String = "I'm Handsome"
20 | var artist: String = "Me"
21 | var album: String = "Self Love"
22 | var currentTime: Double = 0
23 | var duration: Double = 0
24 | var playbackRate: Double = 1
25 | var isShuffled: Bool = false
26 | var repeatMode: RepeatMode = .off
27 | var lastUpdated: Date = Date.distantPast
28 | var artwork: Data?
29 | }
30 |
31 | extension PlaybackState: Equatable {
32 | static func == (lhs: PlaybackState, rhs: PlaybackState) -> Bool {
33 | return lhs.bundleIdentifier == rhs.bundleIdentifier
34 | && lhs.isPlaying == rhs.isPlaying
35 | && lhs.title == rhs.title
36 | && lhs.artist == rhs.artist
37 | && lhs.album == rhs.album
38 | && lhs.currentTime == rhs.currentTime
39 | && lhs.duration == rhs.duration
40 | && lhs.isShuffled == rhs.isShuffled
41 | && lhs.repeatMode == rhs.repeatMode
42 | && lhs.artwork == rhs.artwork
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/DynamicIsland/observers/FullscreenMediaDetection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FullscreenMediaDetection.swift
3 | // DynamicIsland
4 | //
5 | // Created by Richard Kunkli on 06/09/2024.
6 | //
7 |
8 | import Defaults
9 | import MacroVisionKit
10 | import SwiftUI
11 |
12 | class FullscreenMediaDetector: ObservableObject {
13 | static let shared = FullscreenMediaDetector()
14 | private let detector: MacroVisionKit
15 | @ObservedObject private var musicManager = MusicManager.shared
16 | @MainActor @Published private(set) var fullscreenStatus: [String: Bool] = [:]
17 | private var notificationTask: Task?
18 |
19 | private init() {
20 | self.detector = MacroVisionKit.shared
21 | detector.configuration.includeSystemApps = true
22 | setupNotificationObservers()
23 | updateFullScreenStatus()
24 | }
25 |
26 | private func setupNotificationObservers() {
27 | notificationTask = Task { @Sendable [weak self] in
28 | await withTaskGroup(of: Void.self) { group in
29 | group.addTask {
30 | let activeSpaceNotifications = NSWorkspace.shared.notificationCenter.notifications(
31 | named: NSWorkspace.activeSpaceDidChangeNotification
32 | )
33 |
34 | for await _ in activeSpaceNotifications {
35 | await self?.handleChange()
36 | }
37 | }
38 |
39 | group.addTask {
40 | let screenParameterNotifications = NSWorkspace.shared.notificationCenter.notifications(
41 | named: NSApplication.didChangeScreenParametersNotification
42 | )
43 |
44 | for await _ in screenParameterNotifications {
45 | await self?.handleChange()
46 | }
47 | }
48 | }
49 | }
50 | }
51 |
52 | private func handleChange() async {
53 | try? await Task.sleep(for: .milliseconds(500))
54 | self.updateFullScreenStatus()
55 | }
56 |
57 | private func updateFullScreenStatus() {
58 | guard Defaults[.enableFullscreenMediaDetection] else {
59 | let reset = Dictionary(uniqueKeysWithValues: NSScreen.screens.map { ($0.localizedName, false) })
60 | if reset != fullscreenStatus {
61 | fullscreenStatus = reset
62 | }
63 | return
64 | }
65 |
66 |
67 | let apps = detector.detectFullscreenApps(debug: false)
68 | let names = NSScreen.screens.map { $0.localizedName }
69 | var newStatus: [String: Bool] = [:]
70 | for name in names {
71 | newStatus[name] = apps.contains { $0.screen.localizedName == name && $0.bundleIdentifier != "com.apple.finder" && ($0.bundleIdentifier == musicManager.bundleIdentifier || Defaults[.hideNotchOption] == .always) }
72 | }
73 |
74 | if newStatus != fullscreenStatus {
75 | fullscreenStatus = newStatus
76 | NSLog("✅ Fullscreen status: \(newStatus)")
77 | }
78 | }
79 |
80 | private func cleanupNotificationObservers() {
81 | NSWorkspace.shared.notificationCenter.removeObserver(self)
82 | }
83 |
84 | deinit {
85 | NSWorkspace.shared.notificationCenter.removeObserver(self)
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/DynamicIsland/private/CGSSpace.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGSSpace.swift
3 | // DynamicIsland
4 | //
5 | // This Source Code Form is subject to the terms of the Mozilla Public
6 | // License, v. 2.0. If a copy of the MPL was not distributed with this
7 | // file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 | //
9 | // Original source: https://github.com/avaidyam/Parrot/
10 | // Modified by Alexander Greco on 2024-10-27
11 |
12 | import AppKit
13 |
14 | /// Small Spaces API wrapper.
15 | public final class CGSSpace {
16 | private let identifier: CGSSpaceID
17 | private let createdByInit: Bool
18 |
19 | public var windows: Set = [] {
20 | didSet {
21 | let remove = oldValue.subtracting(self.windows)
22 | let add = self.windows.subtracting(oldValue)
23 |
24 | CGSRemoveWindowsFromSpaces(_CGSDefaultConnection(),
25 | remove.map { $0.windowNumber } as NSArray,
26 | [self.identifier])
27 | CGSAddWindowsToSpaces(_CGSDefaultConnection(),
28 | add.map { $0.windowNumber } as NSArray,
29 | [self.identifier])
30 | }
31 | }
32 |
33 | /// Initialized `CGSSpace`s *MUST* be de-initialized upon app exit!
34 | public init(level: Int = 0) {
35 | let flag = 0x1 // this value MUST be 1, otherwise, Finder decides to draw desktop icons
36 | self.identifier = CGSSpaceCreate(_CGSDefaultConnection(), flag, nil)
37 | CGSSpaceSetAbsoluteLevel(_CGSDefaultConnection(), self.identifier, level)
38 | CGSShowSpaces(_CGSDefaultConnection(), [self.identifier])
39 | self.createdByInit = true // Mark as created by the first init
40 | }
41 |
42 | public init(id: UInt64) {
43 | let flag = 0x1 // this value MUST be 1, otherwise, Finder decides to draw desktop icons
44 | self.identifier = id
45 | CGSShowSpaces(_CGSDefaultConnection(), [self.identifier])
46 | self.createdByInit = false // Mark as created externally
47 | }
48 |
49 | deinit {
50 | CGSHideSpaces(_CGSDefaultConnection(), [self.identifier])
51 | // Only call CGSSpaceDestroy if the space was created by the first init
52 | if createdByInit {
53 | CGSSpaceDestroy(_CGSDefaultConnection(), self.identifier)
54 | }
55 | }
56 | }
57 |
58 | // CGSSpace stuff:
59 | fileprivate typealias CGSConnectionID = UInt
60 | fileprivate typealias CGSSpaceID = UInt64
61 | @_silgen_name("_CGSDefaultConnection")
62 | fileprivate func _CGSDefaultConnection() -> CGSConnectionID
63 | @_silgen_name("CGSSpaceCreate")
64 | fileprivate func CGSSpaceCreate(_ cid: CGSConnectionID, _ unknown: Int, _ options: NSDictionary?) -> CGSSpaceID
65 | @_silgen_name("CGSSpaceDestroy")
66 | fileprivate func CGSSpaceDestroy(_ cid: CGSConnectionID, _ space: CGSSpaceID)
67 | @_silgen_name("CGSSpaceSetAbsoluteLevel")
68 | fileprivate func CGSSpaceSetAbsoluteLevel(_ cid: CGSConnectionID, _ space: CGSSpaceID, _ level: Int)
69 | @_silgen_name("CGSAddWindowsToSpaces")
70 | fileprivate func CGSAddWindowsToSpaces(_ cid: CGSConnectionID, _ windows: NSArray, _ spaces: NSArray)
71 | @_silgen_name("CGSRemoveWindowsFromSpaces")
72 | fileprivate func CGSRemoveWindowsFromSpaces(_ cid: CGSConnectionID, _ windows: NSArray, _ spaces: NSArray)
73 | @_silgen_name("CGSHideSpaces")
74 | fileprivate func CGSHideSpaces(_ cid: CGSConnectionID, _ spaces: NSArray)
75 | @_silgen_name("CGSShowSpaces")
76 | fileprivate func CGSShowSpaces(_ cid: CGSConnectionID, _ spaces: NSArray)
77 |
--------------------------------------------------------------------------------
/DynamicIsland/sizing/matters.swift:
--------------------------------------------------------------------------------
1 | //
2 | //
3 | // matters.swift
4 | // DynamicIsland
5 | //
6 | //
7 | // Created by Harsh Vardhan Goswami on 05/08/24.
8 | //
9 |
10 | import Defaults
11 | import Foundation
12 | import SwiftUI
13 |
14 | let downloadSneakSize: CGSize = .init(width: 65, height: 1)
15 | let batterySneakSize: CGSize = .init(width: 160, height: 1)
16 |
17 | let openNotchSize: CGSize = .init(width: 640, height: 190)
18 | let minimalisticOpenNotchSize: CGSize = .init(width: 420, height: 180)
19 | let cornerRadiusInsets: (opened: (top: CGFloat, bottom: CGFloat), closed: (top: CGFloat, bottom: CGFloat)) = (opened: (top: 19, bottom: 24), closed: (top: 6, bottom: 14))
20 | let minimalisticCornerRadiusInsets: (opened: (top: CGFloat, bottom: CGFloat), closed: (top: CGFloat, bottom: CGFloat)) = (opened: (top: 35, bottom: 35), closed: cornerRadiusInsets.closed)
21 |
22 | enum MusicPlayerImageSizes {
23 | static let cornerRadiusInset: (opened: CGFloat, closed: CGFloat) = (opened: 13.0, closed: 4.0)
24 | static let size = (opened: CGSize(width: 90, height: 90), closed: CGSize(width: 20, height: 20))
25 | }
26 |
27 | func getScreenFrame(_ screen: String? = nil) -> CGRect? {
28 | var selectedScreen = NSScreen.main
29 |
30 | if let customScreen = screen {
31 | selectedScreen = NSScreen.screens.first(where: { $0.localizedName == customScreen })
32 | }
33 |
34 | if let screen = selectedScreen {
35 | return screen.frame
36 | }
37 |
38 | return nil
39 | }
40 |
41 | func getClosedNotchSize(screen: String? = nil) -> CGSize {
42 | // Default notch size, to avoid using optionals
43 | var notchHeight: CGFloat = Defaults[.nonNotchHeight]
44 | var notchWidth: CGFloat = 185
45 |
46 | var selectedScreen = NSScreen.main
47 |
48 | if let customScreen = screen {
49 | selectedScreen = NSScreen.screens.first(where: { $0.localizedName == customScreen })
50 | }
51 |
52 | // Check if the screen is available
53 | if let screen = selectedScreen {
54 | // Calculate and set the exact width of the notch
55 | if let topLeftNotchpadding: CGFloat = screen.auxiliaryTopLeftArea?.width,
56 | let topRightNotchpadding: CGFloat = screen.auxiliaryTopRightArea?.width
57 | {
58 | notchWidth = screen.frame.width - topLeftNotchpadding - topRightNotchpadding + 4
59 | }
60 |
61 | // Check if the Mac has a notch
62 | if screen.safeAreaInsets.top > 0 {
63 | // This is a display WITH a notch - use notch height settings
64 | notchHeight = Defaults[.notchHeight]
65 | if Defaults[.notchHeightMode] == .matchRealNotchSize {
66 | notchHeight = screen.safeAreaInsets.top
67 | } else if Defaults[.notchHeightMode] == .matchMenuBar {
68 | notchHeight = screen.frame.maxY - screen.visibleFrame.maxY
69 | }
70 | } else {
71 | // This is a display WITHOUT a notch - use non-notch height settings
72 | notchHeight = Defaults[.nonNotchHeight]
73 | if Defaults[.nonNotchHeightMode] == .matchMenuBar {
74 | notchHeight = screen.frame.maxY - screen.visibleFrame.maxY
75 | }
76 | }
77 | }
78 |
79 | return .init(width: notchWidth, height: notchHeight)
80 | }
81 |
--------------------------------------------------------------------------------
/DynamicIsland/strings/constants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // constants.swift
3 | // DynamicIsland
4 | //
5 | // Created by Harsh Vardhan Goswami on 04/08/24.
6 | //
7 |
8 | import Foundation
9 |
10 | let productPage = URL(string: "https://github.com/Ebullioscopic/DynamicIsland")!
11 | let sponsorPage = URL(string: "https://www.linkedin.com/in/hariharan-mudaliar/")!
12 |
13 |
14 |
--------------------------------------------------------------------------------
/DynamicIsland/utils/Logger.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SwiftUI
3 |
4 | enum LogCategory: String {
5 | case lifecycle = "🔄"
6 | case memory = "💾"
7 | case performance = "⚡️"
8 | case ui = "🎨"
9 | case network = "🌐"
10 | case error = "❌"
11 | case warning = "⚠️"
12 | case success = "✅"
13 | case debug = "🔍"
14 | }
15 |
16 | struct Logger {
17 | static func log(
18 | _ message: String,
19 | category: LogCategory,
20 | file: String = #file,
21 | function: String = #function,
22 | line: Int = #line
23 | ) {
24 | let fileName = (file as NSString).lastPathComponent
25 | let timestamp = ISO8601DateFormatter().string(from: Date())
26 | print("\(category.rawValue) [\(timestamp)] [\(fileName):\(line)] \(function) - \(message)")
27 | }
28 |
29 | static func trackMemory(
30 | file: String = #file,
31 | function: String = #function,
32 | line: Int = #line
33 | ) {
34 | var info = mach_task_basic_info()
35 | var count = mach_msg_type_number_t(MemoryLayout.size)/4
36 |
37 | let kerr: kern_return_t = withUnsafeMutablePointer(to: &info) {
38 | $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
39 | task_info(mach_task_self_,
40 | task_flavor_t(MACH_TASK_BASIC_INFO),
41 | $0,
42 | &count)
43 | }
44 | }
45 |
46 | if kerr == KERN_SUCCESS {
47 | let usedMB = Double(info.resident_size) / 1024.0 / 1024.0
48 | log(String(format: "Memory used: %.2f MB", usedMB),
49 | category: .memory,
50 | file: file,
51 | function: function,
52 | line: line)
53 | }
54 | }
55 | }
56 |
57 | extension View {
58 | func trackLifecycle(_ identifier: String) -> some View {
59 | self.modifier(ViewLifecycleTracker(identifier: identifier))
60 | }
61 | }
62 |
63 | struct ViewLifecycleTracker: ViewModifier {
64 | let identifier: String
65 |
66 | func body(content: Content) -> some View {
67 | content
68 | .onAppear {
69 | Logger.log("\(identifier) appeared", category: .lifecycle)
70 | Logger.trackMemory()
71 | }
72 | .onDisappear {
73 | Logger.log("\(identifier) disappeared", category: .lifecycle)
74 | Logger.trackMemory()
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/DynamicIslandSamples/clipboardpanel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIslandSamples/clipboardpanel.png
--------------------------------------------------------------------------------
/DynamicIslandSamples/clipboardpopover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIslandSamples/clipboardpopover.png
--------------------------------------------------------------------------------
/DynamicIslandSamples/colorpickerpanel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIslandSamples/colorpickerpanel.png
--------------------------------------------------------------------------------
/DynamicIslandSamples/colorpickerpopover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIslandSamples/colorpickerpopover.png
--------------------------------------------------------------------------------
/DynamicIslandSamples/dynamicisland-minimalistic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIslandSamples/dynamicisland-minimalistic.png
--------------------------------------------------------------------------------
/DynamicIslandSamples/dynamicislandscreenrecord.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIslandSamples/dynamicislandscreenrecord.gif
--------------------------------------------------------------------------------
/DynamicIslandSamples/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIslandSamples/logo.png
--------------------------------------------------------------------------------
/DynamicIslandSamples/media.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIslandSamples/media.png
--------------------------------------------------------------------------------
/DynamicIslandSamples/statsmonitor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/DynamicIslandSamples/statsmonitor.png
--------------------------------------------------------------------------------
/Frameworks/MediaRemoteAdapter.framework/MediaRemoteAdapter:
--------------------------------------------------------------------------------
1 | Versions/Current/MediaRemoteAdapter
--------------------------------------------------------------------------------
/Frameworks/MediaRemoteAdapter.framework/Resources:
--------------------------------------------------------------------------------
1 | Versions/Current/Resources
--------------------------------------------------------------------------------
/Frameworks/MediaRemoteAdapter.framework/Versions/A/MediaRemoteAdapter:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/Frameworks/MediaRemoteAdapter.framework/Versions/A/MediaRemoteAdapter
--------------------------------------------------------------------------------
/Frameworks/MediaRemoteAdapter.framework/Versions/A/Resources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | English
7 | CFBundleExecutable
8 | MediaRemoteAdapter
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | com.vandenbe.MediaRemoteAdapter
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | MediaRemoteAdapter
17 | CFBundlePackageType
18 | FMWK
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 0.1.0
23 | CFBundleShortVersionString
24 | 0.1
25 | CSResourcesFileMapped
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Frameworks/MediaRemoteAdapter.framework/Versions/A/_CodeSignature/CodeResources:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | files
6 |
7 | Resources/Info.plist
8 |
9 | M6AF1VWVJ1A/DSliCSjg170FqsY=
10 |
11 |
12 | files2
13 |
14 | Resources/Info.plist
15 |
16 | hash2
17 |
18 | z3yWmTAqjdrPJEZUQ+t6AVPhw0e/I8PAiVr0HIU2ivg=
19 |
20 |
21 |
22 | rules
23 |
24 | ^Resources/
25 |
26 | ^Resources/.*\.lproj/
27 |
28 | optional
29 |
30 | weight
31 | 1000
32 |
33 | ^Resources/.*\.lproj/locversion.plist$
34 |
35 | omit
36 |
37 | weight
38 | 1100
39 |
40 | ^Resources/Base\.lproj/
41 |
42 | weight
43 | 1010
44 |
45 | ^version.plist$
46 |
47 |
48 | rules2
49 |
50 | .*\.dSYM($|/)
51 |
52 | weight
53 | 11
54 |
55 | ^(.*/)?\.DS_Store$
56 |
57 | omit
58 |
59 | weight
60 | 2000
61 |
62 | ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/
63 |
64 | nested
65 |
66 | weight
67 | 10
68 |
69 | ^.*
70 |
71 | ^Info\.plist$
72 |
73 | omit
74 |
75 | weight
76 | 20
77 |
78 | ^PkgInfo$
79 |
80 | omit
81 |
82 | weight
83 | 20
84 |
85 | ^Resources/
86 |
87 | weight
88 | 20
89 |
90 | ^Resources/.*\.lproj/
91 |
92 | optional
93 |
94 | weight
95 | 1000
96 |
97 | ^Resources/.*\.lproj/locversion.plist$
98 |
99 | omit
100 |
101 | weight
102 | 1100
103 |
104 | ^Resources/Base\.lproj/
105 |
106 | weight
107 | 1010
108 |
109 | ^[^/]+$
110 |
111 | nested
112 |
113 | weight
114 | 10
115 |
116 | ^embedded\.provisionprofile$
117 |
118 | weight
119 | 20
120 |
121 | ^version\.plist$
122 |
123 | weight
124 | 20
125 |
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/Frameworks/MediaRemoteAdapter.framework/Versions/Current:
--------------------------------------------------------------------------------
1 | A
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Hariharan Mudaliar
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/Updates/appcast.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | DynamicIsland
5 | https://github.com/Ebullioscopic/DynamicIsland/releases.atom
6 | Most recent updates to DynamicIsland
7 | en
8 |
9 | -
10 | Version 1.0.0-beta
11 | https://github.com/Ebullioscopic/DynamicIsland
12 | 1
13 | 1.0.0-beta
14 | Initial Beta Release
16 |
17 | - First beta release of DynamicIsland
18 | - Dynamic Island interface for macOS
19 | - Music playback controls
20 | - Battery status monitoring
21 | - Timer functionality
22 | - Stats monitoring
23 |
24 | ]]>
25 | Sun, 05 Oct 2025 00:49:55 +0530
26 |
34 | 15.0
35 |
36 |
37 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/mediaremote-adapter/MediaRemoteAdapter.framework/MediaRemoteAdapter:
--------------------------------------------------------------------------------
1 | Versions/Current/MediaRemoteAdapter
--------------------------------------------------------------------------------
/mediaremote-adapter/MediaRemoteAdapter.framework/Resources:
--------------------------------------------------------------------------------
1 | Versions/Current/Resources
--------------------------------------------------------------------------------
/mediaremote-adapter/MediaRemoteAdapter.framework/Versions/A/MediaRemoteAdapter:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/mediaremote-adapter/MediaRemoteAdapter.framework/Versions/A/MediaRemoteAdapter
--------------------------------------------------------------------------------
/mediaremote-adapter/MediaRemoteAdapter.framework/Versions/A/Resources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | English
7 | CFBundleExecutable
8 | MediaRemoteAdapter
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | com.vandenbe.MediaRemoteAdapter
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | MediaRemoteAdapter
17 | CFBundlePackageType
18 | FMWK
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 0.1.0
23 | CFBundleShortVersionString
24 | 0.1
25 | CSResourcesFileMapped
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/mediaremote-adapter/MediaRemoteAdapter.framework/Versions/A/_CodeSignature/CodeResources:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | files
6 |
7 | Resources/Info.plist
8 |
9 | M6AF1VWVJ1A/DSliCSjg170FqsY=
10 |
11 |
12 | files2
13 |
14 | Resources/Info.plist
15 |
16 | hash2
17 |
18 | z3yWmTAqjdrPJEZUQ+t6AVPhw0e/I8PAiVr0HIU2ivg=
19 |
20 |
21 |
22 | rules
23 |
24 | ^Resources/
25 |
26 | ^Resources/.*\.lproj/
27 |
28 | optional
29 |
30 | weight
31 | 1000
32 |
33 | ^Resources/.*\.lproj/locversion.plist$
34 |
35 | omit
36 |
37 | weight
38 | 1100
39 |
40 | ^Resources/Base\.lproj/
41 |
42 | weight
43 | 1010
44 |
45 | ^version.plist$
46 |
47 |
48 | rules2
49 |
50 | .*\.dSYM($|/)
51 |
52 | weight
53 | 11
54 |
55 | ^(.*/)?\.DS_Store$
56 |
57 | omit
58 |
59 | weight
60 | 2000
61 |
62 | ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/
63 |
64 | nested
65 |
66 | weight
67 | 10
68 |
69 | ^.*
70 |
71 | ^Info\.plist$
72 |
73 | omit
74 |
75 | weight
76 | 20
77 |
78 | ^PkgInfo$
79 |
80 | omit
81 |
82 | weight
83 | 20
84 |
85 | ^Resources/
86 |
87 | weight
88 | 20
89 |
90 | ^Resources/.*\.lproj/
91 |
92 | optional
93 |
94 | weight
95 | 1000
96 |
97 | ^Resources/.*\.lproj/locversion.plist$
98 |
99 | omit
100 |
101 | weight
102 | 1100
103 |
104 | ^Resources/Base\.lproj/
105 |
106 | weight
107 | 1010
108 |
109 | ^[^/]+$
110 |
111 | nested
112 |
113 | weight
114 | 10
115 |
116 | ^embedded\.provisionprofile$
117 |
118 | weight
119 | 20
120 |
121 | ^version\.plist$
122 |
123 | weight
124 | 20
125 |
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/mediaremote-adapter/MediaRemoteAdapter.framework/Versions/Current:
--------------------------------------------------------------------------------
1 | A
--------------------------------------------------------------------------------
/mediaremote-adapter/NowPlayingTestClient:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ebullioscopic/DynamicIsland/b509be88c13bfa7a4004a916e8172732e5faf1fd/mediaremote-adapter/NowPlayingTestClient
--------------------------------------------------------------------------------