Drag, drop & DOBJ¶
The picture under the pointer is feedback. The typed object is the event.
This page is about moving things between programs: drag and drop, copy and paste, and the typed object, DOBJ, that carries them. After reading it you will know how to make a window accept drops, how to start a drag, what a dropped item looks like to your code, and why a move never deletes the original until the destination has it.
Good drag and drop feels physical, but its real job is meaning: move this paragraph, copy that chart, link this live range, put this file in the Trash. d/BASIC treats those as operations on objects, not coordinates followed by a hopeful paste.
The same model works at three distances:
- inside one application, where stable object identities can stay local;
- between applications, where a typed, transactional pasteboard (the system's clipboard-like exchange area) carries the item in one or more forms; and
- across the host boundary, where the d/OS runtime, the program that runs your compiled code, bridges those forms to macOS, Windows, Linux or a browser.
At the rich end of that path is DOBJ, the typed object a program hands to another program when you drag, drop, copy or paste. DOBJ is the preferred form for structured objects: first in the sender's list of forms when it preserves the real meaning, followed by honest fallbacks such as styled or plain text.
The NeXTSTEP lesson¶
NeXTSTEP made drag and drop feel deep because it was not a trick of moving a screenshot. The drag was built around a pasteboard: a source could offer several typed forms, a destination declared what it understood, and the destination acted on the data, not on the drag image.
d/OS, the operating system that brings modern software to 8-bit home computers such as the Atari 800, the Commodore 64 and the Apple II, carries that lineage forward in four rules:
- clipboard and drag and drop use one typed pasteboard with different verbs;
- one item may offer several forms of the same meaning;
- source and destination agree on type and operation before a drop commits; and
- object graphs cross as portable values, never as pointers into one program's memory.
The through-line is visible in the original NeXTSTEP Concepts manual and AppKit's later documentation of pasteboard items with multiple representations and dragging sources. d/OS adds exact schema identity, revision binding, bounded records, a receipt for every transaction (the host's record of exactly what was delivered), and a policy of declining anything it cannot check.
One pasteboard, two verbs¶
Copy and paste and drag and drop enter the same typed exchange system, but they do not trample each other:
- the General pasteboard holds the ordinary clipboard;
- every drag gets its own separate transient pasteboard;
- a committed item cannot change and is stamped with a version number; and
- ending a drag retires its transient item without touching the clipboard.
An item may carry up to four forms, in the order the sender prefers. Common tags are:
| Tag | Meaning | Typical use |
|---|---|---|
DOBJ |
a d/OS object, identified together with the package that defines it | range, chart, document block, style, scene object |
STYL |
styled text in d/OS's standard form | rich text that does not need a domain schema |
TEXT |
plain text | universal readable fallback |
PATH |
a path reference the host supervises | Filer and native OS exchange |
FILE |
file/content representation | import, attach, snapshot, or conversion |
Order matters. A d/Calc selection might offer DOBJ first, then STYL, then tab-separated TEXT. A target that understands the exact range schema receives the living structure; a plain editor receives the truthful text. The target cannot reorder the sender's list to pick a weaker reading.
PATH and FILE go through the supervised file boundary. Importing their bytes as a durable document with revision history crosses deliberately into Database & Data Intents; a drag tag alone grants neither.
DOBJ: the premier domain object¶
DOBJ means d/OS object, but it is not "dump this class's memory". It is a system-wide, language-neutral interchange format shared by d/BASIC, native applications, the second, smaller runtime written for the 6502 (the processor in the Atari 800, the Commodore 64 and the Apple II), packages, pasteboards and the bridges to other operating systems.
Three related forms do the work:
| Form | Carries |
|---|---|
DSCH |
the standard schema: fields, kinds, widths and referenced types |
DOBJ |
a 64-byte envelope, the standard type name, an identity and the value itself |
DGRF |
a bounded class-object graph with stable serialization identities and relationships |
A DOBJ type is identified by the installed package ID, its kind, its standard type name and the SHA-256 of its schema. The receiver resolves that exact identity before reading a single byte. A four-letter DOBJ tag on its own proves nothing.
Values, not machine artifacts¶
DOBJ keeps the language's meaning while dropping the accidents of the runtime:
INTEGER,LONG,DECIMAL,SINGLEandDOUBLEhave fixed widths and byte order;- a
TYPEbecomes an exact packed image of its value; - a
CLASSbecomes a graph whose references are serialization IDs; - strings are counted bytes, not native pointers or NUL-terminated buffers;
- shared and cyclic relationships can be represented without duplicating an object; and
- no handle from the compiled program, implementation reference, allocator token, window ID, callback or host pointer crosses the boundary.
One accepted form is limited to 1 MiB, and one class graph to 256 objects. A smaller system may advertise a lower limit. It may not quietly truncate an object or reinterpret its schema.
Premier does not mean compulsory¶
DOBJ should come first when it is the most truthful expression of the selection. It should not be manufactured when the data is simply text, nor forced on a destination that does not know the schema.
| Source | Premier representation | Honest fallbacks |
|---|---|---|
| d/Calc range | range DOBJ | styled table, TSV text, snapshot |
| d/Write block | rich-block DOBJ | STYL, then TEXT |
| d/Present object | scene-fragment DOBJ | image snapshot, accessible text |
| d/File object set | typed object-set DOBJ | projected table or readable text |
| style | style DOBJ | style name/summary text |
| ordinary sentence | STYL or TEXT |
TEXT |
This is the useful NeXTSTEP idea, made stricter: offer the richest object the receiver can genuinely understand, and always keep a human-readable fallback when one is truthful.
The three d/BASIC drag constructs¶
The source-level surface stays small:
WINDOW ACCEPTS "DOBJ", "STYL", "TEXT"
ON DROP SUB (D AS DROPITEM)
SELECT CASE D.KIND$
CASE "DOBJ"
CALL ImportDomainObject(D.HANDLE)
CASE "STYL"
CALL InsertStyledText(D.HANDLE)
CASE "TEXT"
CALL InsertText(D.TEXT$)
END SELECT
END SUB
DRAG Selection
WINDOW ACCEPTSdeclares which forms the target accepts. It is checked before the program runs, so a drag passing over the window can be answered without waking the application each time.ON DROPruns once for a drop that has been negotiated and committed.DROPITEMpresents the accepted form as a small, bounded language value. The operation and the source's settlement stay session metadata inside the host-written receipt, not extra fields an application could forge.DRAG valuestarts a drag at the source. ATYPEorCLASSvalue uses its supervised DOBJ mapping; standard values use their natural form.
DROPITEM has four fields:
| Field | Meaning |
|---|---|
KIND$ |
the agreed tag: TEXT, STYL, PATH, FILE, or DOBJ |
TEXT$ |
complete text value when KIND$ = "TEXT" |
PATH$ |
the supervised path or reference when the accepted form is PATH |
HANDLE |
an opaque lease on the payload, valid only inside the handler, for structured or binary data; pass it to the matching typed reader, never do pointer arithmetic on it or keep it |
A window that accepts drops but has no drop handler is a contradiction the compiler catches. It rejects the program instead of advertising a target that cannot finish the job.
For exact transactional control, import the d/Works libraries (d/Works is the office suite being written in d/BASIC: d/Write, d/Calc, d/Present):
dworks.interchange owns checked wrappers for publishing to the pasteboard, listing, negotiating, reading, status, acknowledgement and DOBJ schema lookup. dworks.drag adds the host-owned drag-session calls without making ordinary copy and paste depend on drag support.
The low-level entry points are grouped by the stage they advance:
| Stage | d/Works procedures |
|---|---|
| publish an item | WorksPasteboardQueryCaps, WorksPasteboardBegin, WorksPasteboardWriteExact, WorksPasteboardCommit, WorksPasteboardAbort |
| discover and read | WorksPasteboardInspect, WorksPasteboardList, WorksPasteboardNegotiate, WorksPasteboardRead |
| settle publication | WorksPasteboardStatus, WorksPasteboardAcknowledge |
| resolve DOBJ schema | WorksDobjSchemaLength, WorksDobjSchemaRead |
| open and negotiate drag | WorksDragQueryCaps, WorksDragBegin, WorksDragNegotiate, WorksDragAccept, WorksDragReject |
| settle a drop | WorksDragDestinationCommit, WorksDragAuthorizeMove, WorksDragSourceCommit, WorksDragStatus, WorksDragCancel, WorksDragAcknowledge |
These wrappers use one-dimensional INTEGER arrays as checked byte records; each element holds two little-endian bytes. UUIDs, revisions, digests and receipts stay as exact groups of words instead of being squeezed into a LONG. Higher-level libraries build and decode the arrays so application code can speak in objects.
The transaction in calls¶
When a library needs to publish several forms explicitly, the sequence is short even though the records are exact. This assumes the library has already prepared each checked request and chunk array:
CALL WorksPasteboardBegin(BeginItem(), Transaction())
CALL WorksPasteboardWriteExact(DobjBytes, DobjCarrier())
CALL WorksPasteboardWriteExact(StyledBytes, StyledCarrier())
CALL WorksPasteboardWriteExact(TextBytes, TextCarrier())
CALL WorksPasteboardCommit(Transaction())
CALL WorksPasteboardStatus(Transaction(), PublicationReceipt())
' DRAG_BEGIN freezes the committed item into the new session.
CALL WorksDragBegin(Opening(), Session())
CALL WorksPasteboardAcknowledge(PublicationReceipt())
Each write names the transaction, the index of the form and the chunk offset; a chunk carries at most 256 bytes, so a large DOBJ takes several writes. Keep the publication receipt open until WorksDragBegin has frozen the item. Acknowledging it earlier retires the readable publication; holding it after a successful begin needlessly keeps the board slot.
Inside one application¶
Reordering within one app does not need to serialize a card just to move it between two panels. Keep the model operation local:
SUB Board.MoveCard (CardId AS LONG, ExpectedRevision AS LONG, NewColumn AS INTEGER)
IF THIS.Revision <> ExpectedRevision THEN EXIT SUB
' Validate the stable identity, record undo, and commit one model change.
END SUB
The application owns object identity, insertion rules, undo and the meaning of the drop. The compositor, the part of d/OS that draws all the windows together, still owns pointer capture, the drag ghost, target routing and the accept-or-decline feedback.
A runtime may take a shortcut on this same-app path, but it must keep the externally visible behaviour: the same offered types, the same copy-or-move decision, the same validation, the same undo result and the same failure behaviour as the cross-app route.
Between d/OS applications¶
A cross-app drag is a small transaction, not a long callback:
- Stage. The source creates a transient pasteboard item with an identity, a revision, the allowed operations (
COPY,MOVEor both) and its ordered forms. - Begin. The host stamps the source's identity; an application cannot impersonate another.
- Negotiate. A candidate target supplies the types it accepts, the operations it allows, the drop zone, its base revision and its interpretation. The first form in the sender's order that both sides accept wins; when both sides allow both operations,
COPYwins so deletion is never chosen by ambiguity. - Show. The compositor draws a ghost and honest accept, insert or decline feedback.
- Commit destination. The target checks the complete form in private, applies one model transaction, and returns its exact result and revision against the frozen binding.
- Finish move. Only after the destination succeeds does the host issue a one-use authorization. The source deletes its own exact selection and returns its new revision and a digest of what it deleted.
- Acknowledge. Final receipts are acknowledged exactly once; a cancel retires the session without a half-drop.
For COPY, step 6 disappears. For MOVE, it is the safety hinge: dropping a file, a paragraph or a mail message cannot erase the source just because a destination looked willing for one frame.
The final receipt is a delivery receipt. It proves that the exact destination received the agreed bytes and that a move had the source's consent; it does not pretend the host understands a d/Write document well enough to certify that a paragraph was inserted. Each app remains the authority for its own model. When source and target are different applications, both acknowledge the result before the session slot is reused.
LINK is not a secret third low-level operation. It is the destination's interpretation of an accepted copy: the destination commits a durable link plus a usable snapshot.
Out into macOS, Windows, Linux, and the browser¶
Working with native systems is the runtime's job. A d/BASIC program never imports AppKit, COM, Wayland, X11 or a browser DataTransfer object.
| Host | Runtime bridge |
|---|---|
| macOS | maps one d/OS item onto one AppKit pasteboard item, with native text and file types plus a declared DOBJ type; checks AppKit's change marker before acknowledging the publication |
| Windows | maps the forms onto the native data-object and clipboard contract while keeping d/OS type order and copy-or-move meaning |
| Linux | maps onto the active Wayland or X11 drag and data-offer route; session mechanics stay outside BASIC |
| browser | offers only the web data types and permissions the sandbox allows; export or import it cannot support is declined plainly |
| d/OS display | keeps the typed pasteboard native and lets the composed scene own the ghost, cursor and target feedback |
On a Mac, for example, a d/Works rich block can publish DOBJ, styled text and plain text on one NSPasteboardItem. Another d/OS application may choose DOBJ. A native text editor may choose public.utf8-plain-text. Finder may offer a file URL or a file promise, which the runtime translates into a supervised PATH or FILE form before d/BASIC sees it. The private types stay distinct (org.d-os.pasteboard.text.v1, org.d-os.pasteboard.styl.v1 and org.d-os.pasteboard.dobj.v1), so a native label cannot silently change d/OS meaning.
The bridge is deliberately lopsided where it has to be. The Mac import path accepts a stable, complete UTF-8 string; it does not relabel RTF, an arbitrary archive or private native bytes as STYL or DOBJ. Export re-checks the committed d/OS bytes, publishes every form present together, and matches the final AppKit change marker before acknowledging the d/OS item. AppKit does not promise that ownership changes atomically across every process, so the marker and the durable match tell exactly the truth the host can prove.
This is the same upgrade-or-lower rule as every other intent (an intent is a named request the machine carries out however it can): use the richest honest local route, keep the meaning, and decline what cannot be represented safely.
A real DOBJ in d/BASIC¶
The standard d/Write fragment library is a compact example of the pattern:
IMPORT dworks.dwrite_dobj
DIM Fragment AS WorksDwriteFragment
DIM Encoded(0 TO (WORKS_DWRITE_FRAGMENT_DOBJ_BYTES \ 2) - 1) AS INTEGER
Fragment = NEW WorksDwriteFragment()
Fragment.Text = "A SMALL OBJECT WITH A LARGE TRAVEL ITINERARY."
IF WorksDwriteFragmentEncode(Fragment, Encoded()) = 0 THEN STOP
' Encoded() is now the complete canonical DOBJ representation to stage.
That fragment contains text and bounded formatting spans. It contains no document handle, selection, caret, window state, callback or native object. The pasteboard does not need to understand d/Write; the installed schema and the receiving library do.
Trash is a serious drop target¶
Trash is the standard test of honest move semantics. A drop into Trash is staged deletion, not immediate destruction:
- the target accepts a type and a move;
- the destination's staging succeeds first;
- the source deletes only after the final authorization;
- the trashed object stays restorable; and
- emptying Trash purges each object only after its own backend operation succeeds.
If restoring or purging one item fails, that item and everything after it stay recoverable. A mail message, a Filer object and an app-defined DOBJ can all use the same target because Trash negotiates typed objects rather than memorizing every application.
Rules that keep a drop honest¶
- Use stable object IDs and exact source revisions.
- Put DOBJ first only when it is the most truthful form.
- Offer
TEXTor another readable fallback when it keeps the meaning. - Never treat a tag, a filename or a successful parse as proof of the schema.
- Check a complete DOBJ in private before exposing typed objects.
- Never serialize handles, pointers, callbacks or native object identities.
- Commit the destination before authorizing deletion at the source.
- Keep drag pasteboards transient and the General clipboard intact.
- Let the compositor own capture and feedback; let the application own meaning and undo.
- Let the runtime bridge native systems without leaking their APIs into BASIC.
Next: Imports & Libraries →
See also: Widgets · Intents & Services · Imports & Libraries · Bytecode & Delivery