# The OS clipboard is the file clipboard

> Why Ctrl+C in cwdio pastes in Explorer and an Explorer cut pastes here as a move: what a file copy is, why Electron cannot write it, plus a clipboard-wedge bug.

- Canonical: https://cwdio.com/blog/the-os-clipboard-is-the-file-clipboard/
- Updated: 2026-08-15
- Product: Cwdio File Manager (cwdio) — https://cwdio.com

A file manager whose <kbd>Ctrl+C</kbd> cannot be pasted in Explorer is not finished. Until August 2026,
cwdio's was exactly that: the clipboard was a variable inside a React hook. This is how it became the real
Windows file clipboard — and the one rule learned along the way that applies to any program touching the
clipboard from a process it might kill. Retold from ADR 0031.

## What a Windows file copy actually is

Not text. When Explorer copies files it puts three formats on the clipboard together:

| Format | Carries |
| --- | --- |
| `CF_HDROP` (standard format 15) | a `DROPFILES` header and a double-null-terminated list of paths |
| `Preferred DropEffect` (registered) | a DWORD: copy (Explorer writes 5, `COPY\|LINK`) or move (2) |
| `FileNameW` / `FileName` (registered) | a legacy single path, also published |

The second one is what makes a cut a cut. And it is the only channel through which cwdio can hand another
application a **move**: native drag-out (ADR 0030) is copy-only by Electron's construction and never reports
where the drop landed.

## Ruled out: Electron alone

Measured on Electron 42, not inferred:

- `clipboard.writeBuffer("CF_HDROP", …)` registers the *string* `"CF_HDROP"` as a custom format instead of
  using standard format 15. The shell never sees it.
- Two formats cannot coexist through `writeBuffer`: every call clears the clipboard first, so writing the
  path list and then the effect leaves only the effect.
- Electron can read exactly one path back (`FileNameW`); a multi-file copy from Explorer is unreadable.

So writing the file clipboard, and reading its full path list, needs an out-of-process helper. Observation,
however, is free: `availableFormats()` contains `text/uri-list` if and only if real files are on the
clipboard (plain text, URLs, bookmarks and HTML all report otherwise), and the effect and any owner marker
can be read with `readBuffer`. That split shaped everything: **enabling Paste costs nothing; only a real
copy, cut or paste spawns a process.**

## What shipped

**`cwdio-clipboard.exe`** — a one-shot C# helper compiled by the in-box .NET Framework `csc.exe`, the same
no-toolchain pattern four earlier helpers use — with two verbs: `read`, and `write <copy|move> [token]`. It
calls `SetDataObject(data, copy: true)`, which flushes the payload so it survives the process exiting. Copy,
cut and paste are human-scale events, so a short-lived process per event costs nothing and leaves no state
to go stale.

**Ownership is a token, not a flag.** Every write stamps a fresh nonce into a registered
`cwdio-clipboard-owner` format. On paste, the renderer compares it with the token it last wrote:

- match → the clipboard still holds our payload, so paste the **in-app rows**, which carry each row's real
  URI — a copy from an `sftp://` or `archive://` panel still works;
- anything else → foreign; paste the OS path list.

It has to be a token because cwdio is multi-window and each window owns its own in-app clipboard. A bare
"is this cwdio's?" marker would make window B recognise window A's payload as its own, consult its own
empty clipboard and paste nothing. With a token, A's payload reads as foreign in B and pastes from the path
list — cross-window copy/paste works *because* of the distinction.

**A selection with no OS paths still writes the token, with an empty list.** A remote selection has nothing
to hand the shell, but the copy must still *replace* the clipboard, or a stale Explorer payload would
outrank the files you just copied and <kbd>Ctrl+V</kbd> would paste the wrong thing.

**A consumed cut is retired**, as Explorer does after a cut-paste — by writing an empty owned payload, and
only after the paste was actually dispatched. Both clipboards, in-app and OS, retire through one gate; during
development the in-app half kept an older gate and Explorer could paste a spent cut whose sources had
already moved. That is now an end-to-end test, not a memory.

**Foreign paths are canonicalised to `file://` URIs** so the "paste into its own folder" guard compares
like with like, and a foreign selection that spans folders — an Explorer search result — is transferred one
source directory at a time so a move's undo returns each item to its own origin.

## Never hold the clipboard open

The most transferable lesson in the ADR. The obvious way to retire a cut is to empty the clipboard, and both
obvious implementations are wrong:

- Raw `OpenClipboard` → `EmptyClipboard` → `CloseClipboard` holds the clipboard open across our own code,
  and `EmptyClipboard` blocks while it notifies the previous owner. Kill the helper on a timeout inside that
  window and the clipboard is left open with no live owner — **wedged for the whole machine**. Every
  application's <kbd>Ctrl+C</kbd> stops working until Explorer is restarted. This happened twice on the
  development machine and was first misdiagnosed as an environment problem.
- `Clipboard.Clear()` is `SetDataObject(new DataObject())` with `copy: false`, so the empty object is never
  flushed and is owned by a process that exits immediately. Observable state becomes a race — which showed
  up as a flaky test.

So there is **no clear operation at all**. Every mutation goes through the one flushed, self-contained
`SetDataObject(data, copy: true)`; "retire this payload" is just a write with an empty list. One primitive,
one failure mode, verified deterministic over repeated rounds while Electron's main thread was deliberately
blocked — the exact condition that deadlocked the raw version.

## What it deliberately does not do

- It does not run on macOS or Linux yet. There the file clipboard is **absent, not broken**: the handlers
  no-op rather than reject, or every <kbd>Ctrl+C</kbd> would raise an error for a capability the build never
  claimed. A *missing helper on Windows* does report, because there the copy really did fail to reach
  Explorer.
- It does not implement "Performed DropEffect", so after cwdio pastes an Explorer cut, Explorer's source view
  stays dimmed until it notices the files are gone. Retiring the clipboard covers the correctness half; the
  cosmetic half self-resolves.
- It does not stage remote or in-archive selections to a temp folder so they can leave the app. That is the
  same limitation as drag-out, for the same reason, and the same future mechanism would serve both.

Every mechanic was measured before it was built: a separate process reads the helper's write back intact;
Explorer's own Paste verb accepted a folder, a name with a space and `日本語 файл.txt`; PowerShell's
`Set-Clipboard -Path` and `Get-Clipboard -Format FileDropList` interoperate in both directions. The
user-facing summary is on the [Windows integration page](/features/windows-integration/).

## Frequently asked questions

**Does Ctrl+C in cwdio replace what I had on the Windows clipboard?** Yes — as any copy on Windows does. Before ADR 0031 the file clipboard lived only inside cwdio and left the system clipboard alone; now a copy or cut writes the OS clipboard so other applications can paste it. It is the point of the feature and a behaviour change worth knowing.

**Can I cut in Explorer and paste in cwdio as a move?** Yes. cwdio reads the Preferred DropEffect format Explorer writes, so an Explorer cut arrives as a move and an Explorer copy as a copy. The other direction works too, including from a second cwdio window.

**Why does the file clipboard only work on Windows?** The Windows helper writes CF_HDROP; macOS and Linux need their own equivalents, which are not built yet. There the file clipboard is absent rather than broken: Ctrl+C keeps working inside cwdio and simply does not reach other applications, with no error toast.
