// lesson: the-clipboard

The Clipboard

Copy and paste feels like the simplest feature in the editor. On Windows it nearly is: OpenClipboard, SetClipboardData(CF_UNICODETEXT, hglobal) โ€” the OS keeps a copy in a central store, and your process can exit without the data vanishing. macOS's NSPasteboard is the same central- store idea with a richer type system (writeObjects:, UTIs for types).

X11 did something completely different, and famously so: there is no clipboard. There is no central store at all. What X has is selections โ€” a protocol for live negotiation between clients, standardized in the ICCCM. Understanding it will finally explain two Linux folklore facts: why middle-click paste exists, and why your copied text disappears when you close the app you copied it from.

Selections: copy is a claim, paste is a conversation

When you "copy" in an X11 app, no data moves anywhere. The app simply tells the server: I own the selection named CLIPBOARD now (that's xcb_set_selection_owner; the name is an atom โ€” lesson 3's interned strings, back again). Whoever owned it before receives a SelectionClear event: you've been dethroned; release your buffered data. That's the entire copy operation โ€” a claim, one previous owner notified. (There are several selections co-existing: CLIPBOARD for explicit Ctrl+C/Ctrl+V, PRIMARY for select-then-middle-click โ€” the same protocol, different atom, which is how those two paste channels stay independent.)

Paste is where the data actually moves, and it's a four-step conversation between the requestor (the app being pasted into) and the owner (the app holding the copied data):

  1. The requestor asks the server to deliver a SelectionRequest to the owner: "convert selection CLIPBOARD to target UTF8_STRING, and put the result in property P on my window W."
  2. The owner receives the event, looks at the requested target, and โ€” if it can โ€” writes the converted bytes into property P on window W (properties are the server-side key-value storage from lesson 3, so the server briefly holds the data in transit).
  3. The owner sends back a SelectionNotify: "done โ€” look in property P." Refusal has a shape too: a SelectionNotify with property None (atom 0) means "I can't produce that target".
  4. The requestor reads and deletes the property.

Before asking for text, well-behaved requestors first ask for the special target TARGETS: "what formats can you offer?" The owner answers with a list of atoms โ€” UTF8_STRING, maybe text/html, maybe image/png from a screenshot tool. That's the negotiation that lets "paste" mean rich text between word processors but plain text into a terminal. Your editor's owner side supports exactly two targets: TARGETS itself, and UTF8_STRING.

Two consequences of this design, now explicable:

  • Data dies with its owner. The owner is the store. Close the app and there is nobody left to answer SelectionRequests. (Clipboard- manager daemons fix this by watching for ownership changes and immediately requesting a copy for themselves โ€” becoming a hidden second app that never exits.)
  • Nothing transfers until paste. Copying a 100 MB selection costs nothing until someone asks for it โ€” lazy, like the piece table.

One more protocol wrinkle you should recognize by name: server properties have practical size limits, so for large transfers the owner writes the property with type INCR instead of the requested target โ€” "this will arrive in chunks" โ€” and the two clients then ping-pong property writes and deletions until a zero-length write says done. We model the decision (small enough to send directly, or INCR?) without the chunk loop.

The seam stays honest through all of this: the editor core calls Platform::clipboard_write(text) and clipboard_read(). The Win32 backend implements those in six lines; the X11 backend runs this whole conversation โ€” and the state machine at its heart is this lesson's first challenge, pure logic, no server needed.

The editing side: cut, copy, paste as document operations

Independent of transport, the three commands are selection-and-text algebra with firm conventions users rely on:

  • Copy with a non-empty selection stores its text; the selection stays (you can copy, then keep typing to replace โ€” er, no: copy does not collapse the selection; watch any editor). Copy with an empty selection is a no-op that must not clear the clipboard โ€” nothing is more rage-inducing than losing a clipboard to a stray Ctrl+C.
  • Cut = copy + delete selection + caret collapses where the text was.
  • Paste replaces the selection (if any) with the clipboard, caret landing after the inserted text, selection collapsed. Pasting an empty clipboard is a no-op โ€” it must not silently delete a selection.

โ€บ Own the Selection

15 pts

Implement the owner side of the X11 selection protocol as a pure state machine. (Your real backend feeds it xcb_selection_request_event_ts and turns its answers into xcb_send_event + xcb_change_property calls; every rule below is straight from the ICCCM.)

  • copy(text): become the owner and store the data.
  • on_clear(): SelectionClear arrived โ€” another client claimed the selection. Stop owning; release the stored data (text() becomes empty).
  • on_request(req, out_targets, out_data) returns the SelectionNotify to send:
    • Not the owner (never copied, or cleared): refuse โ€” property 0, regardless of target.
    • Target::Targets: set *out_targets = {Targets, Utf8String} and succeed with the requested property echoed back.
    • Target::Utf8String: if the data is larger than kIncrThreshold bytes, succeed with incr = true and leave out_data alone (the chunk loop would follow); otherwise write the data to *out_data and succeed normally.
    • Target::Other (text/html, images... things we can't produce): refuse โ€” property 0.
    • Every notify carries the requestor's window ID back.

Log in to submit a solution and earn points.

โ€บ Cut, Copy, Paste

12 pts

Implement the three commands as pure functions over an editing state. sel_begin <= sel_end is guaranteed (the normalized view from lesson 11).

  • do_copy: non-empty selection โ†’ clipboard becomes the selected text, state untouched. Empty selection โ†’ everything untouched.
  • do_cut: non-empty selection โ†’ clipboard becomes the selected text, the selection's bytes are removed, caret collapses to sel_begin. Empty โ†’ no-op.
  • do_paste: non-empty clipboard โ†’ selection (possibly empty) is replaced by the clipboard text, caret collapses to just after it, clipboard unchanged. Empty clipboard โ†’ no-op.

Log in to submit a solution and earn points.