Haiku Tracker Add-Ons: Selection Messages, Menu Hooks, and Lifetime
Build Tracker add-ons around the verified process_refs ABI, optional menu callbacks, selected entry refs, and the host-process lifetime boundary.
A Tracker add-on is not just an executable placed in a menu. In Haiku R1/beta6, Tracker discovers add-on images under its Tracker add-on paths, offers them in a context menu, loads their code, and calls a small C-linkage interface. The most important entry point is process_refs(entry_ref directory, BMessage* refs, void* reserved). It receives the selected filesystem references and the containing directory. Two additional hooks, populate_menu and message_received, support a richer menu interaction. Understanding which hook owns which moment prevents an add-on from assuming that a menu click is an ordinary application launch.
The public TrackerAddOn.h header declares these three functions but says little about their behavioral contract. For that, the release-tagged Tracker implementation and bundled add-ons are useful primary evidence. This article uses the r1beta6 source as a concrete revision, not a promise that private Tracker implementation details will remain stable forever. Compile and test against the SDK and Tracker version you actually deploy.
Start with the required selection path
Tracker’s normal add-on invocation loads the image, looks up the symbol named process_refs, and calls it on an add-on worker thread. The function receives an entry_ref for the current directory, a BMessage* whose what is B_REFS_RECEIVED, and a reserved pointer currently passed as null. The message contains zero or more refs fields for the selected items; code must not assume that exactly one item was selected.
A minimal contract-oriented skeleton is intentionally small:
#include <Entry.h>
#include <Message.h>
#include <TrackerAddOn.h>
extern "C" void
process_refs(entry_ref directory, BMessage* refs, void* reserved)
{
if (refs == nullptr)
return;
entry_ref selected;
for (int32 index = 0;
refs->FindRef("refs", index, &selected) == B_OK;
++index) {
// Validate this ref and perform the add-on's bounded operation.
}
}
The body is a sketch, not a useful add-on by itself. directory and reserved are unused in the fragment. A real implementation should check refs->what, validate each selected entry, account for a symlink or a file disappearing after the menu was built, and report errors in a way appropriate to Tracker’s UI. Do not delete the BMessage* or keep a raw pointer to it after returning. Tracker owns the message passed into its call; copy data needed by an asynchronous job into separately owned storage before the function returns.
The directory argument identifies the folder from which the add-on was invoked, not the first selected item. Haiku’s bundled TrackerAddOnAppLaunch.h notes that this reference may be invalid when launched from a query. Test it before resolving a path. An add-on that wants to launch a separate application can use that helper as a reference design: it adds dir_ref to the received message and uses BRoster to launch an application with the selected refs. That is a different lifecycle from doing all work inside Tracker’s process; do not claim the two models have identical crash isolation.
Treat menu hooks as a separate protocol
The header also declares populate_menu(BMessage*, BMenu*, BHandler*) and message_received(BMessage*). Tracker probes for populate_menu when building an add-on submenu. In the R1/beta6 implementation, it creates a B_TRACKER_ADDON_MESSAGE containing the selected refs, dir_ref, a Tracker view messenger, and the add-on’s own addon_ref. It loads the image, calls populate_menu, then unloads that image when the call returns. A custom submenu item may later cause Tracker to load the add-on again and call message_received on a worker thread.
That unload/reload boundary is easy to overlook. Do not store static pointers or an image-local object in a menu item and expect them to survive until the submenu command fires. Put the command’s durable, serializable state into a BMessage instead and validate it when message_received runs. Avoid treating TrackerViewToken or other Tracker-internal fields as a stable public extension API merely because current source adds them. The public header exposes the callbacks; the implementation fields are evidence of current behavior, not a compatibility guarantee.
populate_menu runs as part of menu construction. It should be fast and avoid blocking I/O, network calls, or a modal operation that leaves Tracker’s UI waiting. message_received and process_refs are invoked via worker threads in the release-tagged source, but both execute code loaded into Tracker’s process. A crash or memory corruption in the add-on can therefore affect Tracker. Long work should use clear cancellation and ownership, or be delegated to a separately launched application when process isolation is required.
Understand discovery and supported types
Tracker calls BPathFinder::FindPaths(B_FIND_PATH_ADD_ONS_DIRECTORY, "Tracker", ...) to enumerate add-on locations, rather than relying on one hard-coded absolute directory. It filters candidates by executable status, resolves symlinks, and reads supported MIME types from BAppFileInfo where available. Those supported types influence where an add-on appears in the menu; they are not proof that the selected file’s contents are safe or parseable.
An add-on can support a broad file type for discoverability and still reject an individual file after opening it. Conversely, incorrect MIME metadata may hide an otherwise valid add-on from the expected context. Check the installed artifact’s executable permission, package path, metadata, and actual Tracker menu in a disposable user account. Do not use an add-on name collision as a deployment strategy: the current loader de-duplicates by name while scanning paths, making search order an unreliable contract to lean on.
Keep file operations deliberate. entry_ref identifies a filesystem entry, but files can be renamed or replaced between selection and processing. Reopen and validate each target, avoid writing until its type and intended destination are known, and do not assume a metadata-declared MIME type means a file is trustworthy. If an add-on modifies files, test it first on copies with a backup; a context-menu action can affect several selected entries at once.
Build a release-specific acceptance test
Test no selection, one file, multiple files, a directory, a symlink, a missing or renamed target, and invocation from a query. Confirm whether the directory reference is valid in each context. For a menu-capable add-on, populate the menu twice, trigger an item after the first image has been unloaded, and verify that the follow-up message contains all state needed by message_received. Deliberately test a missing callback symbol to see how Tracker surfaces loading errors in the target release.
Then test a clean uninstall or upgrade. Tracker watches add-on directories and may rebuild menus, but an in-flight callback can still be executing while a package changes. Arrange deployment so users do not replace an image underneath active work, and verify the next menu invocation after the change. Do not promise hot-reload behavior beyond what the tested Tracker build demonstrates.
The safe mental model is a small callback ABI hosted by Tracker, not an independent plugin service. process_refs handles selected refs; optional menu hooks add a second dispatch path; and every call crosses an image and ownership boundary. Code that makes those boundaries explicit is easier to test than an add-on that only works when a single file is selected on a quiet desktop.
Related:
- Haiku BAppFileInfo: Executable Signatures, Supported Types, and Version Data
- Haiku find_directory and BPathFinder: Resolve Paths at Runtime
Sources: