├── LICENSE ├── README.md ├── Sherlock.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── mario.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── Sherlock ├── AppDelegate.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── Main.storyboard ├── Info.plist ├── Sherlock.entitlements └── ViewController.swift ├── SherlockDockTilePlugIn ├── Assets.xcassets │ ├── Contents.json │ ├── DockTile-Dark.imageset │ │ ├── AppDark128.png │ │ ├── AppDark128@2x.png │ │ └── Contents.json │ └── DockTile.imageset │ │ ├── App128.png │ │ ├── App128@2x.png │ │ └── Contents.json ├── SherlockDockTilePlugIn-Bridging-Header.h └── SherlockDockTilePlugIn.swift └── images ├── demo.gif ├── fig1.png ├── fig10.png ├── fig11.png ├── fig12.png ├── fig2.png ├── fig3.png ├── fig4.png ├── fig5.png ├── fig6.png ├── fig7.png ├── fig8.png └── fig9.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Mario Guzman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sherlock - NSDockTilePlugIn 2 | 3 | ## What is NSDockTilePlugIn? 4 | 5 | A set of methods implemented by plug-ins that allow an app’s Dock tile to be customized while the app is not running. 6 | 7 | Customizing an application’s Dock tile when the application itself is not running requires that you write a plug-in. The plug-in’s principal class must implement the NSDockTilePlugIn protocol. 8 | 9 | The name of the plugin is indicated by a NSDockTilePlugIn key in the application's Info.plist file. 10 | 11 | The plugin is loaded in a system process at login time or when the application tile is added to the Dock. When the plugin is loaded, the principal class' implementation of `setDockTile(_:)` is invoked, passing an `NSDockTile` for the plug-in to customize. If the principal class implements `dockMenu()` it is invoked whenever the user causes the application's dock menu to be shown. When the dock tile is no longer valid (for example,. the application has been removed from the dock) `setDockTile(_:)` invoked with `nil`. 12 | 13 | ## Where have I seen this be used before? 14 | 15 | `NSDockTilePlugIn` seems to have been created for Mac OS X 10.5 Leopard for iCal (now Calendar). Since Leopard, the Calendar icon displays the current date when shown in the Dock and in Launchpad. Its purpose is to run code (logic) to update its presentation showing the current date even when Calendar is not running. 16 | 17 | It is rare to see apps use `NSDockTilePlugIn` because apps that contain one are not allowed on the Mac App Store. 18 | 19 | # Adding `NSDockTilePlugIn` to your project 20 | 21 | 1. Add a new Target to your Mac application Xcode project. 22 | 23 | Xcode - Add Target to Project 24 | 25 | 2. In the Templates sheet, click the **macOS** tab and then select **Bundle** in the **Framework & Library** section. Click **Next**. 26 | 27 | Xcode - Selecting Bundle from Xcode Templates 28 | 29 | 3. For convenience, name your new Target as [YourAppName]DockTilePlugIn and set the Bundle Extension to `docktileplugin`. In our example, the final name for our new Target will be “SherlockDockTilePlugIn”. Click **Finish**. 30 | 31 | Xcode - Naming the new Target 32 | 33 | 4. Xcode will not create a folder structure for this new Target. Manually create one to keep your project organized. For convenience, we are keeping the same name we’ve been using: “SherlockDockTilePlugIn”. 34 | 35 | Xcode - Adding a File Structure for Target code 36 | 37 | 5. Select the “SherlockDockTilePlugIn” folder and add a new Swift file. Name the Swift file also “SherlockDockTilePlugIn”. Be sure that the correct Target is selected `SherlockDockTilePlugIn`. 38 | 39 | Xcode - Adding the NSDockTilePlugIn class code file 40 | 41 | 6. Select the Host app from the Targets list and click the **Info** tab. Add a new Key to your Info.plist: “Dock Tile plugin path”. Set its String value to the name we chose earlier of **[YourAppName]DockTilePlugIn** and append **.docktileplugin**. In our example, this would be “SherlockDockTilePlugIn.docktileplugin”. 42 | 43 | Xcode - Updating the App’s Info.plist with the NSDockTilePlugIn's details. 44 | 45 | 7. With the Host app still selected, click on the **Build Phases** tab. Click the **+** and choose “New Copy Files Phase”. This is to copy our NSDockTilePlugIn bundle into the Host app’s bundle. For Destination, select “PlugIns and Foundation Extensions” and add the plug-in to the list using the + symbol below. 46 | 47 | Xcode - Adding the Copy Files build phase. 48 | Xcode - Adding the NSDockTilePlugIn to copy files build phase. 49 | 50 | 8. Select the **DockTilePlugIn** again from the Targets list and click the **Info** tab. You will see “Principal” class key already added for you but with an empty value. Add just the name of your class/file created in step 5 *without* the extension. In our case here, it will just be “SherlockDockTilePlugIn”. 51 | 52 | Xcode - Adding the Principal class key/value to Info.plist. 53 | 54 | 9. Open the Swift file and create your class “SherlockDockTilePlugIn”. This class must inherit from NSObject and must conform to NSDockTilePlugIn. Note: NSDockTilePlugIn requires AppKit, so make sure you add “import AppKit” to your import declarations. 55 | 56 | Xcode - Creating the NSDockTilePlugIn Class and code. 57 | 58 | That’s all! Now we add our custom logic to have the icon do what we want it to do when the application is not running. 59 | 60 | # What is this NSDockTilePlugIn sample going to do? 61 | 62 | For this example, I will illustrate with code how to make it so that the app icon can change between light and dark mode when the system changes appearance and it will change when the Host app is not running. 63 | 64 | ![Demo GIF](images/demo.gif) 65 | 66 | ## Initial Setup 67 | 68 | Before starting, add an `Assets.xcassets` catalog to your `NSDockTilePlugIn` Target. Add both a Light and Dark mode variant of your app icon with 128x128 and 256x256 sizes for each mode. For convenience, name them “DockTile” and “DockTile-Dark” since that is what they’re named in this code (or update the code to match your preferred asset names). 69 | 70 | Additionally, add `import Combine` into your import declarations as this sample uses Combine Publishers. 71 | 72 | Xcode - Asset Catalog with Light and Dark mode icons. 73 | 74 | ## Conforming to NSDockTilePlugIn 75 | 76 | `NSDockTilePlugIn` protocol requires you conform to at least `setDockTile(_:)` which is what changes the appearance of your app’s Dock Tile (icon) when in the Dock. 77 | 78 | As mentioned above in the introduction to this article, when the user drags the Host app into the Dock, this function is called with the `dockTile` parameter having a value. When the user drags the Host app’s icon out of the Dock, this function is called again with `dockTile` being `nil`. 79 | 80 | This means, when we conform to `setDockTile(_:)`, we will have an `if-let, else` which will both update the visual appearance of the Dock icon immediately and set up a publisher to get subsequent appearance changes and will tear these publishers down in the `else` case. 81 | 82 | The `appearancePublisher` will get updates from macOS when the user changes between light and dark mode appearances. We call the same `updateTile(tile: appearance:)` functions. 83 | 84 | Please refer to the Xcode project for the entire code. 85 | 86 | ```swift 87 | func setDockTile(_ dockTile: NSDockTile?) { 88 | if let dockTile = dockTile { 89 | 90 | // A DockTile was provided by the system. Perform setup to listen 91 | // for appearance changes and system launch/termination events. 92 | 93 | // Start with an initial update to match the system immediately. 94 | updateTile(tile: dockTile) 95 | 96 | // Add a publisher for the appearance. Will get called whenever 97 | // the system appearance changes. 98 | appearancePublisher = NSApp.publisher(for: \.effectiveAppearance) 99 | .removeDuplicates() 100 | .sink(receiveValue: { appearance in 101 | self.updateTile(tile: dockTile, appearance: appearance) 102 | }) 103 | } else { 104 | 105 | // Application icon was removed from the Dock. We don't need to do 106 | // unnecessary listening and event handling since the icon is not 107 | // showing in the Dock. 108 | 109 | appearancePublisher?.cancel() 110 | appearancePublisher = nil 111 | } 112 | } 113 | ``` 114 | 115 | Xcode - SherlockDockTilePlugIn code. 116 | 117 | The `updateTile(tile: appearance:)` gets the appearance value passed in by the `appearancePublisher` and decides which image (for the app icon) to use. 118 | 119 | *NOTE: Please note that the way we grab an image is slightly different as we use the `NSDockTilePlugIn` class’ Bundle. See full code for details.* 120 | 121 | We also pass in a parameter for the `NSDockTile`. We create a new instance of `NSImageView` with the image we want to set and set that as the `tile`’s `contentView` property. Finally, call its `display()` function to update the icon in the Dock. 122 | 123 | ```swift 124 | private func updateTile(tile: NSDockTile, appearance: NSAppearance = NSApp.effectiveAppearance) { 125 | let isLightMode = appearance.bestMatch(from: [.aqua, .darkAqua]) == .aqua 126 | let iconName: DockTileImage = isLightMode ? .light : .dark 127 | 128 | guard let image = self.dockTilePlugInBundle.image(forResource: iconName.rawValue) 129 | else { return } 130 | 131 | let imageView = NSImageView(image: image) 132 | tile.contentView = imageView 133 | tile.display() 134 | } 135 | ``` 136 | 137 | # Testing & Debugging 138 | 139 | ## Testing 140 | 141 | Because the code you’re writing is meant to run when your application is **not** running, to test you must Build & Run your app from Xcode and then persist the Host app’s icon in the Dock by Control-Clicking on the icon, selection **Options → Keep in Dock**. Otherwise, when you quit the app, the its Dock icon will also disappear. 142 | 143 | Change your app’s Appearance in Control Center or in System Settings. You will notice that the icon in the Dock will change accordingly. 144 | 145 | ![Demo GIF](images/demo.gif) 146 | 147 | ## Debugging 148 | 149 | I have found all suggested ways to debug this code to not work. The only way I have been successful was by using `NSLog` print statements and using Console.app to see code paths and crash errors. I have prefixed my `NSLog` strings with “~ ~” so that I can quickly filter the very noisy Console.app output. 150 | 151 | # What about when the app *is* running? 152 | 153 | You can also alter the icon and add additional menu items to it when the app is running. However, that is more common and *is* allowed for apps published on Mac App Store. For more information on these implementations, visit: 154 | 155 | - [applicationDockMenu(_:)](https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428564-applicationdockmenu) 156 | - [applicationIconImage](https://developer.apple.com/documentation/appkit/nsapplication/1428744-applicationiconimage) 157 | 158 | # Find Me Online 159 | 160 | - [My Website/Portfolio](https://marioaguzman.github.io/) 161 | - [Mastodon (mastodon.social)](https://mastodon.social/@marioguzman) 162 | - [My Music apps for Apple Music on macOS](https://marioaguzman.github.io/music) *This tutorial brought to you by my Music apps — which is where I learned how to implement `NSDockTilePlugIn`.* 163 | -------------------------------------------------------------------------------- /Sherlock.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 77; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | ACA427062CAF9FDE003309E1 /* SherlockDockTilePlugIn.docktileplugin in CopyFiles */ = {isa = PBXBuildFile; fileRef = ACA426F72CAF9DBA003309E1 /* SherlockDockTilePlugIn.docktileplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXCopyFilesBuildPhase section */ 14 | ACA427052CAF9FCC003309E1 /* CopyFiles */ = { 15 | isa = PBXCopyFilesBuildPhase; 16 | buildActionMask = 2147483647; 17 | dstPath = ""; 18 | dstSubfolderSpec = 13; 19 | files = ( 20 | ACA427062CAF9FDE003309E1 /* SherlockDockTilePlugIn.docktileplugin in CopyFiles */, 21 | ); 22 | runOnlyForDeploymentPostprocessing = 0; 23 | }; 24 | /* End PBXCopyFilesBuildPhase section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | ACA426E12CAF9D53003309E1 /* Sherlock.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sherlock.app; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | ACA426F72CAF9DBA003309E1 /* SherlockDockTilePlugIn.docktileplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SherlockDockTilePlugIn.docktileplugin; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ 32 | ACA427022CAF9F2C003309E1 /* Exceptions for "SherlockDockTilePlugIn" folder in "SherlockDockTilePlugIn" target */ = { 33 | isa = PBXFileSystemSynchronizedBuildFileExceptionSet; 34 | membershipExceptions = ( 35 | Assets.xcassets, 36 | SherlockDockTilePlugIn.swift, 37 | ); 38 | target = ACA426F62CAF9DBA003309E1 /* SherlockDockTilePlugIn */; 39 | }; 40 | ACA427042CAF9F4D003309E1 /* Exceptions for "Sherlock" folder in "Sherlock" target */ = { 41 | isa = PBXFileSystemSynchronizedBuildFileExceptionSet; 42 | membershipExceptions = ( 43 | Info.plist, 44 | ); 45 | target = ACA426E02CAF9D53003309E1 /* Sherlock */; 46 | }; 47 | /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ 48 | 49 | /* Begin PBXFileSystemSynchronizedRootGroup section */ 50 | ACA426E32CAF9D53003309E1 /* Sherlock */ = { 51 | isa = PBXFileSystemSynchronizedRootGroup; 52 | exceptions = ( 53 | ACA427042CAF9F4D003309E1 /* Exceptions for "Sherlock" folder in "Sherlock" target */, 54 | ); 55 | path = Sherlock; 56 | sourceTree = ""; 57 | }; 58 | ACA426FB2CAF9DD1003309E1 /* SherlockDockTilePlugIn */ = { 59 | isa = PBXFileSystemSynchronizedRootGroup; 60 | exceptions = ( 61 | ACA427022CAF9F2C003309E1 /* Exceptions for "SherlockDockTilePlugIn" folder in "SherlockDockTilePlugIn" target */, 62 | ); 63 | path = SherlockDockTilePlugIn; 64 | sourceTree = ""; 65 | }; 66 | /* End PBXFileSystemSynchronizedRootGroup section */ 67 | 68 | /* Begin PBXFrameworksBuildPhase section */ 69 | ACA426DE2CAF9D53003309E1 /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | ); 74 | runOnlyForDeploymentPostprocessing = 0; 75 | }; 76 | ACA426F42CAF9DBA003309E1 /* Frameworks */ = { 77 | isa = PBXFrameworksBuildPhase; 78 | buildActionMask = 2147483647; 79 | files = ( 80 | ); 81 | runOnlyForDeploymentPostprocessing = 0; 82 | }; 83 | /* End PBXFrameworksBuildPhase section */ 84 | 85 | /* Begin PBXGroup section */ 86 | ACA426D82CAF9D53003309E1 = { 87 | isa = PBXGroup; 88 | children = ( 89 | ACA426E32CAF9D53003309E1 /* Sherlock */, 90 | ACA426E22CAF9D53003309E1 /* Products */, 91 | ACA426FB2CAF9DD1003309E1 /* SherlockDockTilePlugIn */, 92 | ); 93 | sourceTree = ""; 94 | }; 95 | ACA426E22CAF9D53003309E1 /* Products */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | ACA426E12CAF9D53003309E1 /* Sherlock.app */, 99 | ACA426F72CAF9DBA003309E1 /* SherlockDockTilePlugIn.docktileplugin */, 100 | ); 101 | name = Products; 102 | sourceTree = ""; 103 | }; 104 | /* End PBXGroup section */ 105 | 106 | /* Begin PBXNativeTarget section */ 107 | ACA426E02CAF9D53003309E1 /* Sherlock */ = { 108 | isa = PBXNativeTarget; 109 | buildConfigurationList = ACA426F02CAF9D54003309E1 /* Build configuration list for PBXNativeTarget "Sherlock" */; 110 | buildPhases = ( 111 | ACA426DD2CAF9D53003309E1 /* Sources */, 112 | ACA426DE2CAF9D53003309E1 /* Frameworks */, 113 | ACA426DF2CAF9D53003309E1 /* Resources */, 114 | ACA427052CAF9FCC003309E1 /* CopyFiles */, 115 | ); 116 | buildRules = ( 117 | ); 118 | dependencies = ( 119 | ); 120 | fileSystemSynchronizedGroups = ( 121 | ACA426E32CAF9D53003309E1 /* Sherlock */, 122 | ACA426FB2CAF9DD1003309E1 /* SherlockDockTilePlugIn */, 123 | ); 124 | name = Sherlock; 125 | packageProductDependencies = ( 126 | ); 127 | productName = Sherlock; 128 | productReference = ACA426E12CAF9D53003309E1 /* Sherlock.app */; 129 | productType = "com.apple.product-type.application"; 130 | }; 131 | ACA426F62CAF9DBA003309E1 /* SherlockDockTilePlugIn */ = { 132 | isa = PBXNativeTarget; 133 | buildConfigurationList = ACA426F82CAF9DBA003309E1 /* Build configuration list for PBXNativeTarget "SherlockDockTilePlugIn" */; 134 | buildPhases = ( 135 | ACA426F32CAF9DBA003309E1 /* Sources */, 136 | ACA426F42CAF9DBA003309E1 /* Frameworks */, 137 | ACA426F52CAF9DBA003309E1 /* Resources */, 138 | ); 139 | buildRules = ( 140 | ); 141 | dependencies = ( 142 | ); 143 | name = SherlockDockTilePlugIn; 144 | packageProductDependencies = ( 145 | ); 146 | productName = SherlockDockTilePlugIn; 147 | productReference = ACA426F72CAF9DBA003309E1 /* SherlockDockTilePlugIn.docktileplugin */; 148 | productType = "com.apple.product-type.bundle"; 149 | }; 150 | /* End PBXNativeTarget section */ 151 | 152 | /* Begin PBXProject section */ 153 | ACA426D92CAF9D53003309E1 /* Project object */ = { 154 | isa = PBXProject; 155 | attributes = { 156 | BuildIndependentTargetsInParallel = 1; 157 | LastSwiftUpdateCheck = 1600; 158 | LastUpgradeCheck = 1600; 159 | TargetAttributes = { 160 | ACA426E02CAF9D53003309E1 = { 161 | CreatedOnToolsVersion = 16.0; 162 | }; 163 | ACA426F62CAF9DBA003309E1 = { 164 | CreatedOnToolsVersion = 16.0; 165 | LastSwiftMigration = 1600; 166 | }; 167 | }; 168 | }; 169 | buildConfigurationList = ACA426DC2CAF9D53003309E1 /* Build configuration list for PBXProject "Sherlock" */; 170 | developmentRegion = en; 171 | hasScannedForEncodings = 0; 172 | knownRegions = ( 173 | en, 174 | Base, 175 | ); 176 | mainGroup = ACA426D82CAF9D53003309E1; 177 | minimizedProjectReferenceProxies = 1; 178 | preferredProjectObjectVersion = 77; 179 | productRefGroup = ACA426E22CAF9D53003309E1 /* Products */; 180 | projectDirPath = ""; 181 | projectRoot = ""; 182 | targets = ( 183 | ACA426E02CAF9D53003309E1 /* Sherlock */, 184 | ACA426F62CAF9DBA003309E1 /* SherlockDockTilePlugIn */, 185 | ); 186 | }; 187 | /* End PBXProject section */ 188 | 189 | /* Begin PBXResourcesBuildPhase section */ 190 | ACA426DF2CAF9D53003309E1 /* Resources */ = { 191 | isa = PBXResourcesBuildPhase; 192 | buildActionMask = 2147483647; 193 | files = ( 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | ACA426F52CAF9DBA003309E1 /* Resources */ = { 198 | isa = PBXResourcesBuildPhase; 199 | buildActionMask = 2147483647; 200 | files = ( 201 | ); 202 | runOnlyForDeploymentPostprocessing = 0; 203 | }; 204 | /* End PBXResourcesBuildPhase section */ 205 | 206 | /* Begin PBXSourcesBuildPhase section */ 207 | ACA426DD2CAF9D53003309E1 /* Sources */ = { 208 | isa = PBXSourcesBuildPhase; 209 | buildActionMask = 2147483647; 210 | files = ( 211 | ); 212 | runOnlyForDeploymentPostprocessing = 0; 213 | }; 214 | ACA426F32CAF9DBA003309E1 /* Sources */ = { 215 | isa = PBXSourcesBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | ); 219 | runOnlyForDeploymentPostprocessing = 0; 220 | }; 221 | /* End PBXSourcesBuildPhase section */ 222 | 223 | /* Begin XCBuildConfiguration section */ 224 | ACA426EE2CAF9D54003309E1 /* Debug */ = { 225 | isa = XCBuildConfiguration; 226 | buildSettings = { 227 | ALWAYS_SEARCH_USER_PATHS = NO; 228 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 229 | CLANG_ANALYZER_NONNULL = YES; 230 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 231 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 232 | CLANG_ENABLE_MODULES = YES; 233 | CLANG_ENABLE_OBJC_ARC = YES; 234 | CLANG_ENABLE_OBJC_WEAK = YES; 235 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 236 | CLANG_WARN_BOOL_CONVERSION = YES; 237 | CLANG_WARN_COMMA = YES; 238 | CLANG_WARN_CONSTANT_CONVERSION = YES; 239 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 240 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 241 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 242 | CLANG_WARN_EMPTY_BODY = YES; 243 | CLANG_WARN_ENUM_CONVERSION = YES; 244 | CLANG_WARN_INFINITE_RECURSION = YES; 245 | CLANG_WARN_INT_CONVERSION = YES; 246 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 247 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 248 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 249 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 250 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 251 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 252 | CLANG_WARN_STRICT_PROTOTYPES = YES; 253 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 254 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 255 | CLANG_WARN_UNREACHABLE_CODE = YES; 256 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 257 | COPY_PHASE_STRIP = NO; 258 | DEBUG_INFORMATION_FORMAT = dwarf; 259 | ENABLE_STRICT_OBJC_MSGSEND = YES; 260 | ENABLE_TESTABILITY = YES; 261 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 262 | GCC_C_LANGUAGE_STANDARD = gnu17; 263 | GCC_DYNAMIC_NO_PIC = NO; 264 | GCC_NO_COMMON_BLOCKS = YES; 265 | GCC_OPTIMIZATION_LEVEL = 0; 266 | GCC_PREPROCESSOR_DEFINITIONS = ( 267 | "DEBUG=1", 268 | "$(inherited)", 269 | ); 270 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 271 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 272 | GCC_WARN_UNDECLARED_SELECTOR = YES; 273 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 274 | GCC_WARN_UNUSED_FUNCTION = YES; 275 | GCC_WARN_UNUSED_VARIABLE = YES; 276 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 277 | MACOSX_DEPLOYMENT_TARGET = 15.0; 278 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 279 | MTL_FAST_MATH = YES; 280 | ONLY_ACTIVE_ARCH = YES; 281 | SDKROOT = macosx; 282 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 283 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 284 | }; 285 | name = Debug; 286 | }; 287 | ACA426EF2CAF9D54003309E1 /* Release */ = { 288 | isa = XCBuildConfiguration; 289 | buildSettings = { 290 | ALWAYS_SEARCH_USER_PATHS = NO; 291 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 292 | CLANG_ANALYZER_NONNULL = YES; 293 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 294 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 295 | CLANG_ENABLE_MODULES = YES; 296 | CLANG_ENABLE_OBJC_ARC = YES; 297 | CLANG_ENABLE_OBJC_WEAK = YES; 298 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 299 | CLANG_WARN_BOOL_CONVERSION = YES; 300 | CLANG_WARN_COMMA = YES; 301 | CLANG_WARN_CONSTANT_CONVERSION = YES; 302 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 303 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 304 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 305 | CLANG_WARN_EMPTY_BODY = YES; 306 | CLANG_WARN_ENUM_CONVERSION = YES; 307 | CLANG_WARN_INFINITE_RECURSION = YES; 308 | CLANG_WARN_INT_CONVERSION = YES; 309 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 310 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 311 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 312 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 313 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 314 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 315 | CLANG_WARN_STRICT_PROTOTYPES = YES; 316 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 317 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 318 | CLANG_WARN_UNREACHABLE_CODE = YES; 319 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 320 | COPY_PHASE_STRIP = NO; 321 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 322 | ENABLE_NS_ASSERTIONS = NO; 323 | ENABLE_STRICT_OBJC_MSGSEND = YES; 324 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 325 | GCC_C_LANGUAGE_STANDARD = gnu17; 326 | GCC_NO_COMMON_BLOCKS = YES; 327 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 328 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 329 | GCC_WARN_UNDECLARED_SELECTOR = YES; 330 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 331 | GCC_WARN_UNUSED_FUNCTION = YES; 332 | GCC_WARN_UNUSED_VARIABLE = YES; 333 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 334 | MACOSX_DEPLOYMENT_TARGET = 15.0; 335 | MTL_ENABLE_DEBUG_INFO = NO; 336 | MTL_FAST_MATH = YES; 337 | SDKROOT = macosx; 338 | SWIFT_COMPILATION_MODE = wholemodule; 339 | }; 340 | name = Release; 341 | }; 342 | ACA426F12CAF9D54003309E1 /* Debug */ = { 343 | isa = XCBuildConfiguration; 344 | buildSettings = { 345 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 346 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 347 | CODE_SIGN_ENTITLEMENTS = Sherlock/Sherlock.entitlements; 348 | CODE_SIGN_STYLE = Automatic; 349 | COMBINE_HIDPI_IMAGES = YES; 350 | CURRENT_PROJECT_VERSION = 1; 351 | DEVELOPMENT_TEAM = 4N3K4K49FN; 352 | ENABLE_HARDENED_RUNTIME = YES; 353 | GENERATE_INFOPLIST_FILE = YES; 354 | INFOPLIST_FILE = Sherlock/Info.plist; 355 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 356 | INFOPLIST_KEY_NSMainStoryboardFile = Main; 357 | INFOPLIST_KEY_NSPrincipalClass = NSApplication; 358 | LD_RUNPATH_SEARCH_PATHS = ( 359 | "$(inherited)", 360 | "@executable_path/../Frameworks", 361 | ); 362 | MARKETING_VERSION = 1.0; 363 | PRODUCT_BUNDLE_IDENTIFIER = com.marioaguzman.Sherlock; 364 | PRODUCT_NAME = "$(TARGET_NAME)"; 365 | SWIFT_EMIT_LOC_STRINGS = YES; 366 | SWIFT_VERSION = 5.0; 367 | }; 368 | name = Debug; 369 | }; 370 | ACA426F22CAF9D54003309E1 /* Release */ = { 371 | isa = XCBuildConfiguration; 372 | buildSettings = { 373 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 374 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 375 | CODE_SIGN_ENTITLEMENTS = Sherlock/Sherlock.entitlements; 376 | CODE_SIGN_STYLE = Automatic; 377 | COMBINE_HIDPI_IMAGES = YES; 378 | CURRENT_PROJECT_VERSION = 1; 379 | DEVELOPMENT_TEAM = 4N3K4K49FN; 380 | ENABLE_HARDENED_RUNTIME = YES; 381 | GENERATE_INFOPLIST_FILE = YES; 382 | INFOPLIST_FILE = Sherlock/Info.plist; 383 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 384 | INFOPLIST_KEY_NSMainStoryboardFile = Main; 385 | INFOPLIST_KEY_NSPrincipalClass = NSApplication; 386 | LD_RUNPATH_SEARCH_PATHS = ( 387 | "$(inherited)", 388 | "@executable_path/../Frameworks", 389 | ); 390 | MARKETING_VERSION = 1.0; 391 | PRODUCT_BUNDLE_IDENTIFIER = com.marioaguzman.Sherlock; 392 | PRODUCT_NAME = "$(TARGET_NAME)"; 393 | SWIFT_EMIT_LOC_STRINGS = YES; 394 | SWIFT_VERSION = 5.0; 395 | }; 396 | name = Release; 397 | }; 398 | ACA426F92CAF9DBA003309E1 /* Debug */ = { 399 | isa = XCBuildConfiguration; 400 | buildSettings = { 401 | CLANG_ENABLE_MODULES = YES; 402 | CODE_SIGN_STYLE = Automatic; 403 | COMBINE_HIDPI_IMAGES = YES; 404 | CURRENT_PROJECT_VERSION = 1; 405 | DEVELOPMENT_TEAM = 4N3K4K49FN; 406 | GENERATE_INFOPLIST_FILE = YES; 407 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 408 | INFOPLIST_KEY_NSPrincipalClass = SherlockDockTilePlugIn; 409 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; 410 | MARKETING_VERSION = 1.0; 411 | PRODUCT_BUNDLE_IDENTIFIER = com.marioaguzman.SherlockDockTilePlugIn; 412 | PRODUCT_NAME = "$(TARGET_NAME)"; 413 | SKIP_INSTALL = YES; 414 | SWIFT_EMIT_LOC_STRINGS = YES; 415 | SWIFT_OBJC_BRIDGING_HEADER = "SherlockDockTilePlugIn/SherlockDockTilePlugIn-Bridging-Header.h"; 416 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 417 | SWIFT_VERSION = 5.0; 418 | WRAPPER_EXTENSION = docktileplugin; 419 | }; 420 | name = Debug; 421 | }; 422 | ACA426FA2CAF9DBA003309E1 /* Release */ = { 423 | isa = XCBuildConfiguration; 424 | buildSettings = { 425 | CLANG_ENABLE_MODULES = YES; 426 | CODE_SIGN_STYLE = Automatic; 427 | COMBINE_HIDPI_IMAGES = YES; 428 | CURRENT_PROJECT_VERSION = 1; 429 | DEVELOPMENT_TEAM = 4N3K4K49FN; 430 | GENERATE_INFOPLIST_FILE = YES; 431 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 432 | INFOPLIST_KEY_NSPrincipalClass = SherlockDockTilePlugIn; 433 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; 434 | MARKETING_VERSION = 1.0; 435 | PRODUCT_BUNDLE_IDENTIFIER = com.marioaguzman.SherlockDockTilePlugIn; 436 | PRODUCT_NAME = "$(TARGET_NAME)"; 437 | SKIP_INSTALL = YES; 438 | SWIFT_EMIT_LOC_STRINGS = YES; 439 | SWIFT_OBJC_BRIDGING_HEADER = "SherlockDockTilePlugIn/SherlockDockTilePlugIn-Bridging-Header.h"; 440 | SWIFT_VERSION = 5.0; 441 | WRAPPER_EXTENSION = docktileplugin; 442 | }; 443 | name = Release; 444 | }; 445 | /* End XCBuildConfiguration section */ 446 | 447 | /* Begin XCConfigurationList section */ 448 | ACA426DC2CAF9D53003309E1 /* Build configuration list for PBXProject "Sherlock" */ = { 449 | isa = XCConfigurationList; 450 | buildConfigurations = ( 451 | ACA426EE2CAF9D54003309E1 /* Debug */, 452 | ACA426EF2CAF9D54003309E1 /* Release */, 453 | ); 454 | defaultConfigurationIsVisible = 0; 455 | defaultConfigurationName = Release; 456 | }; 457 | ACA426F02CAF9D54003309E1 /* Build configuration list for PBXNativeTarget "Sherlock" */ = { 458 | isa = XCConfigurationList; 459 | buildConfigurations = ( 460 | ACA426F12CAF9D54003309E1 /* Debug */, 461 | ACA426F22CAF9D54003309E1 /* Release */, 462 | ); 463 | defaultConfigurationIsVisible = 0; 464 | defaultConfigurationName = Release; 465 | }; 466 | ACA426F82CAF9DBA003309E1 /* Build configuration list for PBXNativeTarget "SherlockDockTilePlugIn" */ = { 467 | isa = XCConfigurationList; 468 | buildConfigurations = ( 469 | ACA426F92CAF9DBA003309E1 /* Debug */, 470 | ACA426FA2CAF9DBA003309E1 /* Release */, 471 | ); 472 | defaultConfigurationIsVisible = 0; 473 | defaultConfigurationName = Release; 474 | }; 475 | /* End XCConfigurationList section */ 476 | }; 477 | rootObject = ACA426D92CAF9D53003309E1 /* Project object */; 478 | } 479 | -------------------------------------------------------------------------------- /Sherlock.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sherlock.xcodeproj/xcuserdata/mario.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Sherlock.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | SherlockDockTilePlugIn.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sherlock/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Sherlock 4 | // 5 | // Created by Mario Guzman on 10/3/24. 6 | // 7 | 8 | import Cocoa 9 | 10 | @main 11 | class AppDelegate: NSObject, NSApplicationDelegate { 12 | 13 | 14 | 15 | 16 | func applicationDidFinishLaunching(_ aNotification: Notification) { 17 | // Insert code here to initialize your application 18 | } 19 | 20 | func applicationWillTerminate(_ aNotification: Notification) { 21 | // Insert code here to tear down your application 22 | } 23 | 24 | func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 25 | return true 26 | } 27 | 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /Sherlock/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 | -------------------------------------------------------------------------------- /Sherlock/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sherlock/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Sherlock/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | Default 529 | 530 | 531 | 532 | 533 | 534 | 535 | Left to Right 536 | 537 | 538 | 539 | 540 | 541 | 542 | Right to Left 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | Default 554 | 555 | 556 | 557 | 558 | 559 | 560 | Left to Right 561 | 562 | 563 | 564 | 565 | 566 | 567 | Right to Left 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | -------------------------------------------------------------------------------- /Sherlock/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSDockTilePlugIn 6 | SherlockDockTilePlugIn.docktileplugin 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sherlock/Sherlock.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Sherlock/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Sherlock 4 | // 5 | // Created by Mario Guzman on 10/3/24. 6 | // 7 | 8 | import Cocoa 9 | 10 | class ViewController: NSViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | // Do any additional setup after loading the view. 16 | } 17 | 18 | override var representedObject: Any? { 19 | didSet { 20 | // Update the view, if already loaded. 21 | } 22 | } 23 | 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /SherlockDockTilePlugIn/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SherlockDockTilePlugIn/Assets.xcassets/DockTile-Dark.imageset/AppDark128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioaguzman/NSDockTilePlugIn-Example/bb212cd134fd65b03f7275b11e1285b369b39628/SherlockDockTilePlugIn/Assets.xcassets/DockTile-Dark.imageset/AppDark128.png -------------------------------------------------------------------------------- /SherlockDockTilePlugIn/Assets.xcassets/DockTile-Dark.imageset/AppDark128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioaguzman/NSDockTilePlugIn-Example/bb212cd134fd65b03f7275b11e1285b369b39628/SherlockDockTilePlugIn/Assets.xcassets/DockTile-Dark.imageset/AppDark128@2x.png -------------------------------------------------------------------------------- /SherlockDockTilePlugIn/Assets.xcassets/DockTile-Dark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppDark128.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "AppDark128@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SherlockDockTilePlugIn/Assets.xcassets/DockTile.imageset/App128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioaguzman/NSDockTilePlugIn-Example/bb212cd134fd65b03f7275b11e1285b369b39628/SherlockDockTilePlugIn/Assets.xcassets/DockTile.imageset/App128.png -------------------------------------------------------------------------------- /SherlockDockTilePlugIn/Assets.xcassets/DockTile.imageset/App128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioaguzman/NSDockTilePlugIn-Example/bb212cd134fd65b03f7275b11e1285b369b39628/SherlockDockTilePlugIn/Assets.xcassets/DockTile.imageset/App128@2x.png -------------------------------------------------------------------------------- /SherlockDockTilePlugIn/Assets.xcassets/DockTile.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "App128.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "App128@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SherlockDockTilePlugIn/SherlockDockTilePlugIn-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /SherlockDockTilePlugIn/SherlockDockTilePlugIn.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SherlockDockTilePlugIn.swift 3 | // SherlockDockTilePlugIn 4 | // 5 | // Created by Mario Guzman on 10/3/24. 6 | // 7 | 8 | import AppKit 9 | import Combine 10 | 11 | class SherlockDockTilePlugIn: NSObject, NSDockTilePlugIn { 12 | 13 | private var appearancePublisher: AnyCancellable? = nil 14 | private enum DockTileImage: String { 15 | case light = "DockTile" 16 | case dark = "DockTile-Dark" 17 | } 18 | 19 | /// The associated Dock icon images will be provided in this bundle, not the host application's 20 | /// bundle. This is a convenience property to get the appropriate icon when it needs to change. 21 | private var dockTilePlugInBundle: Bundle = { 22 | Bundle(for: SherlockDockTilePlugIn.self) 23 | }() 24 | 25 | /// When you drag this app into the Dock, it will call this function, `setDockTile(_ :)` with 26 | /// a non-nil value for its `dockTile` parameter. When you drag out this app's icon out of the 27 | /// Dock, it will call this again with the `dockTile` parameter set to `nil`. 28 | /// Perform any setup when `dockTile` has a value and any cleanup when it is `nil`. 29 | func setDockTile(_ dockTile: NSDockTile?) { 30 | 31 | if let dockTile = dockTile { 32 | 33 | // A DockTile was provided by the system. Perform setup to listen 34 | // for appearance changes and system launch/termination events. 35 | 36 | // Start with an initial update to match the system immediately. 37 | updateTile(tile: dockTile) 38 | 39 | // Add a publisher for the appearance. Will get called whenever 40 | // the system appearance changes. 41 | appearancePublisher = NSApp.publisher(for: \.effectiveAppearance) 42 | .removeDuplicates() 43 | .sink(receiveValue: { appearance in 44 | self.updateTile(tile: dockTile, appearance: appearance) 45 | }) 46 | } else { 47 | 48 | // Application icon was removed from the Dock. We don't need to do 49 | // unnecessary listening and event handling since the icon is not 50 | // showing in the Dock. 51 | 52 | appearancePublisher?.cancel() 53 | appearancePublisher = nil 54 | } 55 | } 56 | 57 | /// Implement this function if you'd like to provide additional menu items for when the host 58 | /// application is not running. These will appear in addition to the system-provided options when 59 | /// the user control-clicks on the Host app's Dock icon. 60 | func dockMenu() -> NSMenu? { return nil } 61 | 62 | // MARK: - Helper Functions 63 | 64 | private func updateTile(tile: NSDockTile, appearance: NSAppearance = NSApp.effectiveAppearance) { 65 | let isLightMode = appearance.bestMatch(from: [.aqua, .darkAqua]) == .aqua 66 | let iconName: DockTileImage = isLightMode ? .light : .dark 67 | 68 | guard let image = self.dockTilePlugInBundle.image(forResource: iconName.rawValue) 69 | else { return } 70 | 71 | let imageView = NSImageView(image: image) 72 | tile.contentView = imageView 73 | tile.display() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioaguzman/NSDockTilePlugIn-Example/bb212cd134fd65b03f7275b11e1285b369b39628/images/demo.gif -------------------------------------------------------------------------------- /images/fig1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioaguzman/NSDockTilePlugIn-Example/bb212cd134fd65b03f7275b11e1285b369b39628/images/fig1.png -------------------------------------------------------------------------------- /images/fig10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioaguzman/NSDockTilePlugIn-Example/bb212cd134fd65b03f7275b11e1285b369b39628/images/fig10.png -------------------------------------------------------------------------------- /images/fig11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioaguzman/NSDockTilePlugIn-Example/bb212cd134fd65b03f7275b11e1285b369b39628/images/fig11.png -------------------------------------------------------------------------------- /images/fig12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioaguzman/NSDockTilePlugIn-Example/bb212cd134fd65b03f7275b11e1285b369b39628/images/fig12.png -------------------------------------------------------------------------------- /images/fig2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioaguzman/NSDockTilePlugIn-Example/bb212cd134fd65b03f7275b11e1285b369b39628/images/fig2.png -------------------------------------------------------------------------------- /images/fig3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioaguzman/NSDockTilePlugIn-Example/bb212cd134fd65b03f7275b11e1285b369b39628/images/fig3.png -------------------------------------------------------------------------------- /images/fig4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioaguzman/NSDockTilePlugIn-Example/bb212cd134fd65b03f7275b11e1285b369b39628/images/fig4.png -------------------------------------------------------------------------------- /images/fig5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioaguzman/NSDockTilePlugIn-Example/bb212cd134fd65b03f7275b11e1285b369b39628/images/fig5.png -------------------------------------------------------------------------------- /images/fig6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioaguzman/NSDockTilePlugIn-Example/bb212cd134fd65b03f7275b11e1285b369b39628/images/fig6.png -------------------------------------------------------------------------------- /images/fig7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioaguzman/NSDockTilePlugIn-Example/bb212cd134fd65b03f7275b11e1285b369b39628/images/fig7.png -------------------------------------------------------------------------------- /images/fig8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioaguzman/NSDockTilePlugIn-Example/bb212cd134fd65b03f7275b11e1285b369b39628/images/fig8.png -------------------------------------------------------------------------------- /images/fig9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marioaguzman/NSDockTilePlugIn-Example/bb212cd134fd65b03f7275b11e1285b369b39628/images/fig9.png --------------------------------------------------------------------------------