How to add menu items to dock item when app not running

Well, it's all in the title.

I want to make a URL history list available on the dock item of my macOS app.

I found NSDockTilePlugin but nothing on how to implement it.

Can anyone help?

Answered by DTS Engineer in 800839022

Hi Kevin. No I'm not sure but… what I want to provide for my app is a context menu on the dock icon that shows the last 10(?) folder URLs that were browsed from the app - just as Xcode shows the latest projects without having to open Xcode.

OK. SO, this is an example of why it's important to start with "what you want", not "how do I do". Within the system, there are often multiple mechanism that provide overlapping functionality, making it possible to create exactly the same interface experience through COMPLETELY different implementation paths. Even worse, you'll sometimes find that the more complicated path is more visible/documented simply because it's more complicated, so it took more words to explain.

In any case, the Xcode menu you're seeing is actually common across most (all?) of our document based apps. That menu isn't actually managed by the app directly, but is actually controlled through NSDocumentController. It's possible you'll need to subclass NSDocumentController to get this working the way you want, but my initial read of things is that all you'd need to is call NSDocumentController.noteNewRecentDocumentURL as your app browses and NSApplicationDelegate.application(_:open:) to receive the "open" (when the user opens your URL).

Shifting over that my previous answer:

I tried this but it didn't show the menu before the app was running.

Yes. What I described was how your app can customize it's dock while it's actually running. Up above I talked about how very similar interface can actually be implemented through TOTALLY different API paths and, if you look closely, you can actually see this in Xcode's menu behavior.

When Xcode is running, you'll notice that it include an additional section called "Open Developer Tools". That's Xcode using "applicationDockMenu(_:)" to customize it's dock menu. However, when you quit Xcode that menu disappears. That's because Xcode isn't doing anything "special", it's simply falling back on the standard "Recent Document" behavior the system provides through NSDocumentController.

This is what I want, but with the list instead of just one and before the app has started. This seems to automatically take the title bar caption, which I set to a representative text of the URL.

My guess is that you're currently using a single NSDocument, which the system is then picking up "automatically" through NSDocumentController. You should be able to use NSDocumentController to make this work exactly the way you want.

If NSDockTilePlugin is "deprecated", where can I find how to implement this "app extension" that you mention?

Clarifying what I said earlier, the issue isn't that NSDockTilePlugin is formally documented (it isn't). The problem is that it's built on the old, inherently unsafe, plugin model which App Extensions replaced. The biggest immediate issue is that app store apps are NOT allowed to use it. More broadly, we've slowly been removing every plugin that used the old architecture, which means it isn't something I would assume would "just work" forever.

Have I explained myself clearly enough? If not, please let me know.

This is for an app that has been out there for several years and I just got a user request.

So, the right answer for you is to use NSDocumentController. However, the NSDockTilePlugin documentation actually describes what's required, it just assumes a lot of background information which isn't NEARLY was well known as it used to be. Breaking it down in details:

"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."

...

"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(_:) is invoked with nil."

What it's talking about there is a specific example of what we documented in "Loadable Bundles in Cocoa". That document has a more complete background, but a loadable bundle is basically just a directory that:

  • Uses the standard bundle structure.

  • Includes the key "NSPrincipalClass" in it's Info.plist with a class name in it.

  • Has a loadable MachO file that implements that calls embedded as it's executable target.

  • That class implement the two "NSDockTilePlugIn" protocol methods.

Xcode's "Bundle" target is what you'd start with to create the target, but you'd then need to add your own source files into.

The system then finds that bundle plugin by looking at the "NSDockTilePlugIn" in your apps Info.plist:

"The name of the plugin is indicated by a NSDockTilePlugIn key in the application's Info.plist file."

If you want a concrete example of how that's structured, take a look inside "System Setting.app"*.

*You could also look at Calendar.app, but it's dock tile plug is buried inside "Resources" and no one should be doing that.

As I said, I can't recommend making one but that's what's involved if you wanted to.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Well, it's all in the title.

I want to make a URL history list available on the dock item of my macOS app.

I found NSDockTilePlugin but nothing on how to implement it.

Can anyone help?

First off, are you sure you actually want a "NSDockTilePlugin"? NSDockTilePlugin is specifically how an app that ISN'T running can update it's dock icon. For example, it's how "Calendar" is able to show the correct date on it's icon, even when you aren't running. Unfortunately, the dock plugin API is an old loadable plugin API, NOT a modern app extension. They're not allowed in App store apps and are not something I'd recommend creating today.

HOWEVER, let me go back to what you said hear:

I want to make a URL history list available on the dock item of my macOS app.

The key words above where "how an app that ISN'T running". If your app is running, then it has full control of everything it "puts" in the Dock. Your app icon is controlled through NSApplication.dockTile and your windows through NSWindow.dockTile. The NSDockTile class itself is pretty straightforward. It has convenience methods to control badging and the badge label, or you can customize the content yourself by adding a your own view into it's contentView. As a side note, you can also change your running dock icon directly using NSApplication.applicationIconImage.

Finally, on the menu side, dock menus are actually handled through the applicationDockMenu() delegate method. Typically you'd create the menu object in advance and return in on demand, but you can actually build/update the menu on demand. Just be aware that any significant delay will be noticeable to the user.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

First off, are you sure you actually want a "NSDockTilePlugin"? NSDockTilePlugin is specifically how an app that ISN'T running can update it's dock icon

Hi Kevin. No I'm not sure but… what I want to provide for my app is a context menu on the dock icon that shows the last 10(?) folder URLs that were browsed from the app - just as Xcode shows the latest projects without having to open Xcode.

Finally, on the menu side, dock menus are actually handled through the delegate method.

I tried this but it didn't show the menu before the app was running.

This is what I want, but with the list instead of just one and before the app has started. This seems to automatically take the title bar caption, which I set to a representative text of the URL.

If NSDockTilePlugin is "deprecated", where can I find how to implement this "app extension" that you mention?

Have I explained myself clearly enough? If not, please let me know.

This is for an app that has been out there for several years and I just got a user request.

Hi Kevin. No I'm not sure but… what I want to provide for my app is a context menu on the dock icon that shows the last 10(?) folder URLs that were browsed from the app - just as Xcode shows the latest projects without having to open Xcode.

OK. SO, this is an example of why it's important to start with "what you want", not "how do I do". Within the system, there are often multiple mechanism that provide overlapping functionality, making it possible to create exactly the same interface experience through COMPLETELY different implementation paths. Even worse, you'll sometimes find that the more complicated path is more visible/documented simply because it's more complicated, so it took more words to explain.

In any case, the Xcode menu you're seeing is actually common across most (all?) of our document based apps. That menu isn't actually managed by the app directly, but is actually controlled through NSDocumentController. It's possible you'll need to subclass NSDocumentController to get this working the way you want, but my initial read of things is that all you'd need to is call NSDocumentController.noteNewRecentDocumentURL as your app browses and NSApplicationDelegate.application(_:open:) to receive the "open" (when the user opens your URL).

Shifting over that my previous answer:

I tried this but it didn't show the menu before the app was running.

Yes. What I described was how your app can customize it's dock while it's actually running. Up above I talked about how very similar interface can actually be implemented through TOTALLY different API paths and, if you look closely, you can actually see this in Xcode's menu behavior.

When Xcode is running, you'll notice that it include an additional section called "Open Developer Tools". That's Xcode using "applicationDockMenu(_:)" to customize it's dock menu. However, when you quit Xcode that menu disappears. That's because Xcode isn't doing anything "special", it's simply falling back on the standard "Recent Document" behavior the system provides through NSDocumentController.

This is what I want, but with the list instead of just one and before the app has started. This seems to automatically take the title bar caption, which I set to a representative text of the URL.

My guess is that you're currently using a single NSDocument, which the system is then picking up "automatically" through NSDocumentController. You should be able to use NSDocumentController to make this work exactly the way you want.

If NSDockTilePlugin is "deprecated", where can I find how to implement this "app extension" that you mention?

Clarifying what I said earlier, the issue isn't that NSDockTilePlugin is formally documented (it isn't). The problem is that it's built on the old, inherently unsafe, plugin model which App Extensions replaced. The biggest immediate issue is that app store apps are NOT allowed to use it. More broadly, we've slowly been removing every plugin that used the old architecture, which means it isn't something I would assume would "just work" forever.

Have I explained myself clearly enough? If not, please let me know.

This is for an app that has been out there for several years and I just got a user request.

So, the right answer for you is to use NSDocumentController. However, the NSDockTilePlugin documentation actually describes what's required, it just assumes a lot of background information which isn't NEARLY was well known as it used to be. Breaking it down in details:

"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."

...

"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(_:) is invoked with nil."

What it's talking about there is a specific example of what we documented in "Loadable Bundles in Cocoa". That document has a more complete background, but a loadable bundle is basically just a directory that:

  • Uses the standard bundle structure.

  • Includes the key "NSPrincipalClass" in it's Info.plist with a class name in it.

  • Has a loadable MachO file that implements that calls embedded as it's executable target.

  • That class implement the two "NSDockTilePlugIn" protocol methods.

Xcode's "Bundle" target is what you'd start with to create the target, but you'd then need to add your own source files into.

The system then finds that bundle plugin by looking at the "NSDockTilePlugIn" in your apps Info.plist:

"The name of the plugin is indicated by a NSDockTilePlugIn key in the application's Info.plist file."

If you want a concrete example of how that's structured, take a look inside "System Setting.app"*.

*You could also look at Calendar.app, but it's dock tile plug is buried inside "Resources" and no one should be doing that.

As I said, I can't recommend making one but that's what's involved if you wanted to.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

How to add menu items to dock item when app not running
 
 
Q