Haiku Drag and Drop: MIME Negotiation, Messages, and Ownership
Design Haiku drag-and-drop as a typed message exchange, with explicit MIME offers, acceptance feedback, file-reference handling, and safe asynchronous work.
Haiku drag-and-drop is not a magic file-copy operation. It is a protocol between a source view and a target view: the source advertises data in a BMessage, the target decides whether it can accept the offered representation, and the system supplies visual feedback while the pointer moves. A successful drop transfers a description of data or a reference to data; it does not automatically define who owns the payload, whether a file should be copied, or whether a lengthy operation has completed.
That distinction matters because the same mechanism serves several very different interactions: moving a file from Tracker into an application, dragging selected text between controls, handing an image to an editor, or moving an application replicant. The receiver must validate the message it actually received instead of assuming that a drag originated from a trusted application or that a familiar MIME type implies valid content.
The protocol has a source, a target, and a negotiated representation
The source constructs a message that describes one or more possible data forms. The target receives pointer-enter, pointer-move, pointer-leave, and drop events through the Interface Kit’s view and message machinery. It inspects the offered fields, gives acceptance feedback, and handles the final message only when a drop is committed. Exact event constants and helper overloads should be taken from the current Interface Kit headers; the stable design principle is to make the message contract explicit rather than infer it from screen coordinates or an application’s name.
Haiku’s programming tutorial distinguishes a simple drag from a negotiated drag. A simple drag is appropriate when the source already knows the representation and the target’s behavior is straightforward. Negotiation is useful when the target must choose among formats, when the operation can be accepted or rejected based on location, or when the source should learn which action the target will perform. Negotiation is a capability exchange, not a guarantee that the target will preserve the data or finish a later operation.
For files, the portable system-level payload is normally a B_REFS_RECEIVED message containing entry_ref values, not a path string that assumes a particular mount point. A reference identifies a directory entry within a volume context. The receiver should extract every reference with checked message APIs, verify that the expected field count and types exist, and then decide whether the operation is import, open, copy, or move. The user interface’s accepted cursor does not itself move or copy a file.
For content such as text or images, a message can advertise a MIME type and carry data appropriate to that type. Haiku’s typed-message model permits multiple fields and multiple values, so receivers must define whether they expect one item or many. A target that accepts text/plain should not treat arbitrary bytes as UTF-8 without checking the application’s documented encoding contract. A target that accepts an image representation should validate dimensions and decoded size before allocating a large bitmap.
Make MIME offers truthful and bounded
An offer is a promise about how data can be interpreted. Use registered or well-known media types when they accurately describe the content. For private application data, use a namespaced type and version the payload. Do not advertise an expensive or lossy conversion as if it were an already available representation; negotiation should not conceal unbounded work in a pointer-tracking callback.
If the source can provide both a native representation and a common interchange format, the target may select what it understands. The source should retain the data or a reproducible way to produce it until the drag finishes. Conversely, the target should copy any message field or referenced resource it needs beyond the lifetime promised by the API. Do not keep raw pointers into a temporary message or view object after the callback returns.
An offer should state cardinality and semantics. Is a list ordered? Does order matter? Are duplicate files meaningful? Does a URI represent a local object or a remote resource? Is a color profile embedded? Are line endings preserved? These are application-level questions; MIME labels alone do not settle them. Document the contract beside the code that creates and consumes the message.
Separate hover acceptance from the committed drop
Pointer movement can generate many target callbacks. Those callbacks should remain cheap: inspect the message type, destination region, and lightweight capability metadata, then provide acceptance feedback. Do not open every file, decode an image, perform a network request, or mutate persistent state each time the pointer moves. A user may hover over a target and leave without dropping anything.
At drop time, repeat validation. The final message is the authoritative input to the operation; hover-time state may be stale, the target view may have changed, and the source can disappear. Extract values defensively, reject unsupported types, enforce size and item-count limits, and provide a clear error if the actual import fails. Treat acceptance feedback as “this target can attempt this operation,” not a transaction commit.
Where the API supports reply messages or a negotiated action, define success and failure semantics in the source-target contract. A target that receives file references may open them without changing the source location. A move requires an explicit, successful source-side action after destination work succeeds. Avoid deleting the source before the new copy has been verified; cross-volume moves cannot generally be assumed to be atomic.
Keep expensive work out of the view’s event path
Importing a large file, decoding a complex image, or indexing dropped content can block the application’s looper and make the whole window appear frozen. The drop handler should validate and snapshot the request, then hand work to a worker or application-owned queue. The worker reports completion through a message to a still-live target, with a request identifier so results can be matched and stale replies discarded.
This does not mean every drop must be asynchronous. A tiny, bounded text insertion may be synchronous and simpler. The threshold should be based on known cost, not optimism. If a worker is used, give it owned data: copied references or immutable payload bytes, not pointers to view state. On window close, define whether pending work is cancelled, allowed to finish without a UI callback, or persisted as an independent operation.
Treat file references and MIME data as untrusted input
Applications can receive drops from third-party software and removable media. A path can become invalid between validation and open; the volume can unmount; permissions can change; and a symlink can resolve differently from what a user expected. Use Storage Kit APIs for the operation actually intended, check every status, and report a failure without corrupting an existing destination.
For arbitrary binary payloads, reject impossible or excessive lengths before allocation. Validate structured data before parsing nested fields. If an import writes a destination file, write to a temporary file in the destination filesystem, flush and validate it as appropriate, then rename into place. This is an application-level safety pattern, not a promise that every filesystem and failure mode provides identical durability.
Never execute dropped content merely because its name, MIME field, or icon looks familiar. Opening a document and executing a program are different operations. If the app supports executable or script imports, require an explicit action and surface the consequence to the user.
Visual feedback and keyboard-accessible alternatives
Good drag feedback communicates both acceptance and the resulting action. A highlighted row can show the destination; a cursor or status message can distinguish copy from move or link. Rejecting a drop should be visible, but should not erase the source’s selection. When a location is ambiguous, prefer a clear default and a post-drop confirmation for destructive actions.
Drag-and-drop must not be the only way to complete a task. Provide menu, toolbar, open-panel, or paste alternatives for users who cannot perform a pointer gesture and for automation. This also improves reliability: the non-drag path can share the same validated import routine, avoiding two divergent implementations.
Test the message contract, not just the animation
Test each advertised type from at least one independent source and test the receiver with malformed messages. Include zero items, many items, duplicate references, deleted files, read-only volumes, paths with non-ASCII names, unsupported MIME values, very large content, and cancellation by closing the window. Confirm that hovering does not change files and that leaving the view abandons only transient state.
For a file move, test same-volume and cross-volume cases separately. Verify that the original remains intact if destination creation fails. For asynchronous processing, close the window at each stage and confirm no worker dereferences freed view state. Check that a successful drop gives the user a completion result rather than merely returning from the initial callback.
The useful mental model is a small capability protocol: the source describes, the target evaluates, and a committed message starts an application-defined operation. MIME negotiation helps choose a representation; it does not confer trust, establish ownership, or provide exactly-once transactional semantics. Keeping those boundaries explicit produces native Haiku interactions that remain predictable when files, volumes, windows, and users behave asynchronously.
Related:
- BMessage Flattening and IPC: How Haiku Moves Typed Data Between Processes
- How to Build a Haiku Replicant That Can Live on the Desktop or Deskbar
Sources: