// 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):
- 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." - 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).
- 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". - 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 ptsImplement 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 thankIncrThresholdbytes, succeed withincr = trueand leaveout_dataalone (the chunk loop would follow); otherwise write the data to*out_dataand succeed normally.Target::Other(text/html, images... things we can't produce): refuse โ property0.- Every notify carries the requestor's window ID back.
- Not the owner (never copied, or cleared): refuse โ property
Log in to submit a solution and earn points.
โบ Cut, Copy, Paste
12 ptsImplement 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 tosel_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.