react-x11 2.52.0 → 2.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/docs/README.md +160 -314
- package/docs/accessibility.md +176 -229
- package/docs/appearance.md +77 -176
- package/docs/click-to-component.md +18 -51
- package/docs/clipboard.md +40 -59
- package/docs/components.md +518 -1036
- package/docs/dbus.md +61 -133
- package/docs/debugging.md +152 -245
- package/docs/desktop-calendar.md +51 -88
- package/docs/desktop.md +222 -347
- package/docs/devtools.md +50 -167
- package/docs/drag-and-drop.md +126 -204
- package/docs/ecosystem/animation.md +91 -125
- package/docs/ecosystem/data-fetching.md +38 -53
- package/docs/ecosystem/dev-tooling.md +159 -202
- package/docs/ecosystem/forms.md +61 -97
- package/docs/ecosystem/headless.md +64 -89
- package/docs/ecosystem/i18n.md +18 -21
- package/docs/ecosystem/icons.md +93 -126
- package/docs/ecosystem/layout.md +42 -52
- package/docs/ecosystem/routing.md +18 -29
- package/docs/ecosystem/state.md +71 -88
- package/docs/ecosystem/testing.md +131 -157
- package/docs/ecosystem/theming.md +119 -165
- package/docs/ecosystem.md +145 -179
- package/docs/elements.md +1091 -1774
- package/docs/embedding.md +83 -132
- package/docs/events.md +412 -695
- package/docs/extending.md +784 -1326
- package/docs/eyedropper.md +63 -122
- package/docs/filedialog.md +71 -107
- package/docs/frame.md +195 -339
- package/docs/gl.md +65 -62
- package/docs/globalmenu.md +63 -146
- package/docs/macos.md +531 -3234
- package/docs/notifications.md +33 -47
- package/docs/packaging.md +166 -309
- package/docs/permissions.md +9 -14
- package/docs/react-features.md +81 -106
- package/docs/remote.md +32 -76
- package/docs/scale.md +72 -186
- package/docs/styling.md +329 -547
- package/docs/system.md +200 -280
- package/docs/testing.md +63 -83
- package/docs/typescript.md +6 -48
- package/docs/uri-schemes.md +67 -107
- package/docs/wayland-backend.md +200 -459
- package/docs/windows-integrations.md +75 -131
- package/docs/windows.md +343 -1367
- package/package.json +3 -2
- package/src/Reconciler.js +2 -2
- package/src/a11y.js +1 -1
- package/src/backend/context2d.js +3 -3
- package/src/bootstrap.js +1 -1
- package/src/cocoa/app.js +6 -6
- package/src/cocoa/bezels.js +1 -1
- package/src/cocoa/fonts.js +1 -1
- package/src/cocoa/globalmenu.js +1 -1
- package/src/cocoa/keymap.js +1 -1
- package/src/cocoa/panewindow.js +1 -1
- package/src/cocoa/player.js +1 -1
- package/src/cocoa/presenter.js +2 -2
- package/src/cocoa/promotion.js +1 -1
- package/src/cocoa/relaunch.js +1 -1
- package/src/cocoa/threaded.js +1 -1
- package/src/cocoa/video.js +1 -1
- package/src/cocoa/window.js +4 -4
- package/src/components/Menu.js +7 -2
- package/src/components/Switch.js +1 -1
- package/src/components/native.js +1 -1
- package/src/dnd.js +2 -2
- package/src/glnodes.js +1 -1
- package/src/grid.js +1 -1
- package/src/layouts.js +1 -1
- package/src/nodes/animation.js +1 -1
- package/src/nodes/invalidate.js +1 -1
- package/src/nodes/layouthost.js +1 -1
- package/src/nodes/video.js +19 -8
- package/src/nodes/window/droptarget.js +1 -1
- package/src/pacing.js +1 -1
- package/src/refresh/index.js +2 -1
- package/src/types/video.d.ts +5 -2
- package/src/videobackend.js +28 -0
- package/src/videoframes.js +29 -16
- package/src/wayland/app.js +27 -0
- package/src/wayland/video.js +376 -0
- package/src/win32/a11y.js +1 -1
- package/src/win32/app.js +2 -2
- package/src/win32/fonts.js +1 -1
- package/src/win32/glarea.js +4 -4
- package/src/win32/overlay.js +1 -1
- package/src/win32/panewindow.js +3 -3
- package/src/win32/shell.js +1 -1
- package/src/win32/window.js +2 -2
- package/src/windowid.js +1 -1
- package/src/xvideo.js +189 -0
- package/docs/architecture/accessibility.md +0 -470
- package/docs/architecture/animation.md +0 -707
- package/docs/architecture/container-queries.md +0 -525
- package/docs/architecture/custom-layout.md +0 -334
- package/docs/architecture/drag-and-drop.md +0 -941
- package/docs/architecture/element-layer-contents.md +0 -622
- package/docs/architecture/frame-pacing.md +0 -255
- package/docs/architecture/grid-layout.md +0 -358
- package/docs/architecture/protocol-efficiency.md +0 -357
- package/docs/architecture/video.md +0 -1149
- package/docs/architecture/windowed-regions.md +0 -854
- package/docs/glx.md +0 -137
- package/docs/wayland.md +0 -545
- package/docs/windows-embedding.md +0 -271
- package/docs/windows-gl.md +0 -221
|
@@ -1,941 +0,0 @@
|
|
|
1
|
-
# Drag and drop: XDND on both sides, and what API it deserves
|
|
2
|
-
|
|
3
|
-
Design review of [#126](https://github.com/sidorares/react-x11/issues/126)
|
|
4
|
-
("XDND drop target: accept drags from file managers"), extended to cover the
|
|
5
|
-
drag **source** half, and turned into an implementation plan.
|
|
6
|
-
|
|
7
|
-
Three questions drive it:
|
|
8
|
-
|
|
9
|
-
1. Is the API proposed in #126 the right one? (lenses: DOM familiarity,
|
|
10
|
-
interop with real XDND implementations, reuse of the npm React ecosystem)
|
|
11
|
-
2. react-x11 draws windowless controls into one window canvas. XDND is a
|
|
12
|
-
protocol between **X windows**. Where does the advertisement live, and
|
|
13
|
-
should it be lazy?
|
|
14
|
-
3. What does adding the source side cost, and how do the two halves share
|
|
15
|
-
machinery?
|
|
16
|
-
|
|
17
|
-
Checked at react-x11 `d5257ce` (master, 2026-08-02), **ntk 5.3.0**, node-x11
|
|
18
|
-
as vendored. Protocol claims are checked against two independent mirrors of
|
|
19
|
-
the XDND spec (see [§11](#11-sources)); every code reference is a real line
|
|
20
|
-
at that SHA.
|
|
21
|
-
|
|
22
|
-
> **Revision note.** The first draft was written against ntk 4.3.0. ntk 5.0.0
|
|
23
|
-
> – 5.3.0 landed while it was in review and changed four of its conclusions —
|
|
24
|
-
> one of them the headline blocker on the drag-source half. [§0.1](#01-audit-against-ntk-530)
|
|
25
|
-
> is the audit; the body below is corrected, not annotated.
|
|
26
|
-
>
|
|
27
|
-
> **What shipped, and where it diverged.** This is the design as argued, kept
|
|
28
|
-
> as the record of _why_; [drag-and-drop.md](../drag-and-drop.md) is the
|
|
29
|
-
> reference for what the API actually is. Phases 0–3 shipped in #162 and
|
|
30
|
-
> #165. Four things landed differently from the sketch below, and the
|
|
31
|
-
> reference page is authoritative on all of them: the floor is **ntk 5.4.0**,
|
|
32
|
-
> not 5.3.0 (#167 needed `clipboard.read({ time })` and `clipboard.clear`);
|
|
33
|
-
> the foreign target is resolved **per motion, uncached** — the drag-start
|
|
34
|
-
> `QueryTree` cache in §6.3/§7 is still the remote-X follow-up; the drag
|
|
35
|
-
> rides the **implicit button grab** rather than `GrabPointer` or
|
|
36
|
-
> `capturePointer`, swapping only the cursor; and `dragPreview` shipped as a
|
|
37
|
-
> **marker prop on a `<popup>` the app renders itself**, not as a node-valued
|
|
38
|
-
> prop on the source. The atom interning is also unconditional at realize
|
|
39
|
-
> rather than lazy.
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## 0. TL;DR
|
|
44
|
-
|
|
45
|
-
- **The protocol reading in #126 is sound.** Message list, field layout,
|
|
46
|
-
timestamp rule and the "reuse the clipboard read path" instinct all hold up.
|
|
47
|
-
The gaps are not in the protocol, they are in the API and in the source half.
|
|
48
|
-
- **`XdndAware` should be written unconditionally at realize time, on
|
|
49
|
-
top-level windows only** — #126 is right, and for a stronger reason than it
|
|
50
|
-
gives. It is a 4-byte property on a window that already exists; it creates
|
|
51
|
-
no X windows; and since XDND v3 the spec puts it on top-levels _only_, so
|
|
52
|
-
child windows — `<glarea>`, nested `<window>`, and any future windowed
|
|
53
|
-
region from [windowed-regions.md](windowed-regions.md) — need nothing at
|
|
54
|
-
all. **XDND is orthogonal to the windowed/windowless split.**
|
|
55
|
-
- **The lazy refcount you want is real, but it should gate _behaviour_, not
|
|
56
|
-
the property.** The protocol has its own lazy channel: the _suppression
|
|
57
|
-
rectangle_ in `XdndStatus`. A window with zero live drop targets answers the
|
|
58
|
-
first `XdndPosition` with `accept=0` and a rectangle covering the whole
|
|
59
|
-
window, and the source sends nothing more until the pointer leaves — one
|
|
60
|
-
message per window-entry for the entire drag. That beats de-advertising on
|
|
61
|
-
every axis: no property churn per commit, no mid-drag race, no
|
|
62
|
-
Offscreen/Suspense bookkeeping.
|
|
63
|
-
- **Drop the marker-props design.** #126 proposes `useDropTarget()` returning
|
|
64
|
-
opaque props carrying "an internal marker the drop router hit-tests for".
|
|
65
|
-
Not needed: `EventManager.dispatch()` already does hit test → path →
|
|
66
|
-
capture/target/bubble, keyed on ordinary `on*` props
|
|
67
|
-
([events.js:218](../../src/events.js)). Drop props should be ordinary host
|
|
68
|
-
props like every other event in the toolkit, with `useDropTarget` as a
|
|
69
|
-
thin convenience in `src/components/` — the same two-layer shape as
|
|
70
|
-
`anchorRect`/`useAnchor` and `windowIdOf`/`useWindowId`.
|
|
71
|
-
- **`dropAccept` must be declarative, and that is a latency argument, not a
|
|
72
|
-
taste argument.** With `dropAccept` as data, `XdndStatus` can be answered from the
|
|
73
|
-
ClientMessage handler without entering React. An always-advertised window
|
|
74
|
-
that stalls on a render stalls _the source application's drag cursor_ — the
|
|
75
|
-
user sees another app freeze. Declarative accept is what makes "always
|
|
76
|
-
advertise" safe.
|
|
77
|
-
- **Do not ship a fake `e.dataTransfer`.** X selections are asynchronous;
|
|
78
|
-
DOM's `getData()` is synchronous. A `dataTransfer.getData()` returning a
|
|
79
|
-
Promise fails silently into `"[object Promise]"`. Flatten the payload onto
|
|
80
|
-
the event, name the async parts async, and say why in the docs.
|
|
81
|
-
- **The two sides should share one drag session with two transports.** In-app
|
|
82
|
-
drags (reorderable lists, trees, kanban — the overwhelming majority) never
|
|
83
|
-
touch the wire: mousedown threshold, `capturePointer`, synthetic dispatch,
|
|
84
|
-
live JS payloads. XDND is engaged only when the pointer leaves our windows,
|
|
85
|
-
or when a foreign drag enters. Same handlers either way; `e.source` tells
|
|
86
|
-
them apart. This is how GTK and Qt are built, and it is the only shape that
|
|
87
|
-
respects the project's perceived-latency goal.
|
|
88
|
-
- **The source half is 3–4× the target half, but it is no longer blocked.**
|
|
89
|
-
ntk 5.3.0 landed INCR on the selection _write_ side plus arbitrary targets
|
|
90
|
-
and binary payloads ([ntk#164](https://github.com/sidorares/ntk/issues/164)),
|
|
91
|
-
which was the one hard prerequisite. `app.clipboard.write({'text/uri-list':
|
|
92
|
-
…, 'image/png': buf}, { selection: 'XdndSelection', time })` is the whole
|
|
93
|
-
data-publishing half of an XDND source, today. See [§0.1](#01-audit-against-ntk-530).
|
|
94
|
-
- **npm reuse: `react-dnd` is the one real prize.** Its core (`dnd-core`) is
|
|
95
|
-
deliberately DOM-free and its backends are pluggable, so a
|
|
96
|
-
`react-x11` backend brings `useDrag`/`useDrop` and the sortable/kanban
|
|
97
|
-
ecosystem along. `@dnd-kit` and `react-dropzone` are DOM-bound and stay
|
|
98
|
-
unusable — but react-dropzone's _hook shape_ is what React developers know
|
|
99
|
-
for file drops, and copying it costs nothing.
|
|
100
|
-
- **Testing is unusually good here, and macOS is unusually bad.** node-x11's
|
|
101
|
-
in-process JS server implements `SendEvent`, `SetSelectionOwner`,
|
|
102
|
-
`ConvertSelection`, `GetSelectionOwner`, `ChangeProperty`, `GetProperty`,
|
|
103
|
-
`QueryTree`, `TranslateCoordinates` and `GrabPointer` — so both halves are
|
|
104
|
-
testable hermetically with two connections, the `test/wm.test.js` pattern.
|
|
105
|
-
Meanwhile **XQuartz does not bridge Finder drags into X11**
|
|
106
|
-
([XQuartz#173](https://github.com/XQuartz/XQuartz/issues/173)), so the
|
|
107
|
-
headline use case cannot be demoed on this machine against Finder — only
|
|
108
|
-
X-client to X-client.
|
|
109
|
-
|
|
110
|
-
### 0.1 Audit against ntk 5.3.0
|
|
111
|
-
|
|
112
|
-
ntk went 4.3.0 → 5.3.0 in two days
|
|
113
|
-
([5.0.0](https://github.com/sidorares/ntk/releases/tag/v5.0.0) …
|
|
114
|
-
[5.3.0](https://github.com/sidorares/ntk/releases/tag/v5.3.0)). Four
|
|
115
|
-
conclusions moved.
|
|
116
|
-
|
|
117
|
-
#### Reversed — the drag-source blocker is gone
|
|
118
|
-
|
|
119
|
-
`feat(clipboard): INCR on the write side, required targets, any format`
|
|
120
|
-
([ntk#164](https://github.com/sidorares/ntk/issues/164), 5.3.0) deletes the
|
|
121
|
-
"file this before designing around it" item outright. `Clipboard` now:
|
|
122
|
-
|
|
123
|
-
- serves **arbitrary target names with binary payloads** —
|
|
124
|
-
`write({ 'text/uri-list': str, 'image/png': buf })`, strings encoded UTF-8
|
|
125
|
-
(latin-1 for `STRING`), Buffers/TypedArrays as-is;
|
|
126
|
-
- transfers **incrementally in both directions** (ICCCM 2.7.2), so the ~256 KB
|
|
127
|
-
single-request cap no longer bounds a payload;
|
|
128
|
-
- answers the three targets ICCCM 2.6.2 makes mandatory — `TARGETS`,
|
|
129
|
-
`TIMESTAMP`, `MULTIPLE` — which a v1 draft would have got wrong;
|
|
130
|
-
- takes an **ICCCM-correct timestamp**, `write(data, { time })`, explicitly
|
|
131
|
-
never `CurrentTime`. That is the same discipline XDND demands, already
|
|
132
|
-
enforced;
|
|
133
|
-
- reads back symmetrically: `targets()` enumerates offered type names, and
|
|
134
|
-
`read({ target })` returns **raw bytes**, which is exactly the shape
|
|
135
|
-
`e.getData(type)` needs.
|
|
136
|
-
|
|
137
|
-
`selection` has always been a free-form atom name, so `'XdndSelection'` needs
|
|
138
|
-
no new API at all. And the intent is explicit upstream — `clipboard.js:17`
|
|
139
|
-
reads, in the module header, that the target-map machinery exists so "HTML,
|
|
140
|
-
images and — later — XDND drags are the same machinery."
|
|
141
|
-
|
|
142
|
-
**Consequence for the plan:** Phase 0's two clipboard items collapse. The
|
|
143
|
-
"factor selection transfer out of `Clipboard` into a reusable primitive" ask
|
|
144
|
-
from #126 is satisfied by `targets()` + `read({ target })` + `write(map)`; no
|
|
145
|
-
ntk refactor is needed for either half, and Phase 3 stops being gated on an
|
|
146
|
-
ntk release cycle.
|
|
147
|
-
|
|
148
|
-
#### Improved — `on('close')` frees `on('message')` for XDND
|
|
149
|
-
|
|
150
|
-
`feat: a cancellable 'close' event for WM_DELETE_WINDOW`
|
|
151
|
-
([ntk#155](https://github.com/sidorares/ntk/issues/155), 5.3.0). Listening for
|
|
152
|
-
`'close'` self-arms: it interns `WM_PROTOCOLS`, calls
|
|
153
|
-
`addProtocol('WM_DELETE_WINDOW')`, decodes the ClientMessage, and hands over an
|
|
154
|
-
event with `preventDefault()` (`window.js:543`, `window.js:2467`).
|
|
155
|
-
|
|
156
|
-
react-x11 still does all of that by hand — `setActions()`, an `InternAtom`
|
|
157
|
-
callback, and a `wnd.on('message')` filter
|
|
158
|
-
([nodes/window/listeners.js](../../src/nodes/window/listeners.js)). So the Phase 0 item is now better
|
|
159
|
-
than "make the listener unconditional and route by `message_type`": **adopt
|
|
160
|
-
`wnd.on('close')` and delete the block**, which leaves `on('message')`
|
|
161
|
-
completely free for the XDND router with no routing logic at all. `'message'`
|
|
162
|
-
is still emitted for every ClientMessage (`window.js:511`), and
|
|
163
|
-
`_emitCloseRequest` runs after it, so the two never contend. Independently
|
|
164
|
-
worth doing: `onCloseRequest` gains cancellability.
|
|
165
|
-
|
|
166
|
-
`Window` also grew the general helpers this needs — `addProtocol` /
|
|
167
|
-
`removeProtocol` (`window.js:1459`) and `atom` / `getProperty` / `setProperty`
|
|
168
|
-
/ `deleteProperty` (`window.js:2100`) — so `XdndAware` is
|
|
169
|
-
`wnd.setProperty('XdndAware', [5], { type: 'ATOM' })` rather than a raw
|
|
170
|
-
`ChangeProperty`.
|
|
171
|
-
|
|
172
|
-
#### Weakened — the pointer-grab item is optional, not a prerequisite
|
|
173
|
-
|
|
174
|
-
`grabPointer({ cursor })` takes a cursor **XID**, and `app.cursors.get(name)`
|
|
175
|
-
mints one from the standard cursor font (`cursor.js:68`). So the drag cursor
|
|
176
|
-
_at grab time_ needs no new ntk API. Only the mid-drag swap as the accepted
|
|
177
|
-
action changes needs `ChangeActivePointerGrab`, which node-x11 exposes
|
|
178
|
-
(`corereqs.js:867`) and react-x11 can call directly. Phase 0 item downgraded
|
|
179
|
-
from prerequisite to optional convenience wrapper.
|
|
180
|
-
|
|
181
|
-
New caveat in the same area: `cursorShapes` is the X **core cursor font**, so
|
|
182
|
-
the themed XCursor names toolkits use for drag feedback (`dnd-copy`,
|
|
183
|
-
`dnd-move`, `dnd-none`) are not loadable. Feedback will be a core-font
|
|
184
|
-
approximation until ntk gains XCursor theme loading — cosmetic, but it is the
|
|
185
|
-
part of a drag the user stares at.
|
|
186
|
-
|
|
187
|
-
#### Strengthened — request buffering does _not_ cost the latency argument
|
|
188
|
-
|
|
189
|
-
`feat: one socket write per frame — buffer requests by default`
|
|
190
|
-
([ntk#141](https://github.com/sidorares/ntk/issues/141), 5.0.0) is the change
|
|
191
|
-
that looked most likely to undercut §2.4's "answer `XdndStatus` in the same
|
|
192
|
-
event-loop turn": a 64 KB output buffer with a 5 ms age gate would have added
|
|
193
|
-
up to a frame of latency to every reply.
|
|
194
|
-
|
|
195
|
-
It does not. node-x11's frame buffer has an explicit backstop —
|
|
196
|
-
"_nothing is left in the buffer when the event loop goes to poll for I/O_",
|
|
197
|
-
implemented as a `setImmediate` flush (`framebuffer.js:33-38`), alongside the
|
|
198
|
-
size cap, the 5 ms age gate and a flush before any request expecting a reply.
|
|
199
|
-
A `SendEvent` written from the ClientMessage handler therefore leaves the
|
|
200
|
-
socket at the end of the current phase, before the next poll — and now as part
|
|
201
|
-
of one write rather than one syscall per request. The claim survives with a
|
|
202
|
-
named mechanism, and the drag path got slightly cheaper.
|
|
203
|
-
|
|
204
|
-
#### Two new caveats for the source half
|
|
205
|
-
|
|
206
|
-
1. **`write()` payloads are eager.** `_encode()` serialises everything at
|
|
207
|
-
`write()` time (`clipboard.js:398`), so §6.2's lazy `dragData` thunks are
|
|
208
|
-
resolved by react-x11 **at promotion** — when it takes `XdndSelection` —
|
|
209
|
-
not when a target converts. Still worth having (an in-app drag serialises
|
|
210
|
-
_nothing_, and an external drag serialises once at promotion rather than at
|
|
211
|
-
mousedown), but the "pull-based, so a 20 MB payload is never built for a
|
|
212
|
-
drag that ends in the wastebasket" claim is now only true of drags that
|
|
213
|
-
never leave our windows. Ask ntk for lazy payload getters if that matters.
|
|
214
|
-
2. **Ownership sits on ntk's private helper window.** `Clipboard` owns every
|
|
215
|
-
selection on its hidden 1×1 window, not on the window we would name as the
|
|
216
|
-
XDND source. This is protocol-legal — the target converts `XdndSelection`
|
|
217
|
-
and the server routes to whoever owns it, wherever that is — and none of
|
|
218
|
-
the reference implementations validate owner == source window. Verify in
|
|
219
|
-
Phase 3; if a target does check, ntk needs a `window` option on `write()`.
|
|
220
|
-
Related: there was no disown API when this was written, so releasing
|
|
221
|
-
`XdndSelection` at drag end meant `app.X.SetSelectionOwner(0, sel, time)`
|
|
222
|
-
directly. **ntk 5.4.0 added `clipboard.clear(selection)`**
|
|
223
|
-
([ntk#169](https://github.com/sidorares/ntk/issues/169)) and `_disown()`
|
|
224
|
-
uses it.
|
|
225
|
-
|
|
226
|
-
#### Unchanged
|
|
227
|
-
|
|
228
|
-
Everything in §2 (advertisement, refcount, lifecycle), §3–§5 (the three
|
|
229
|
-
lenses), §6 (the API), and §8 (the test plan) is unaffected: ntk 5.x adds no
|
|
230
|
-
XDND support, no window-property policy changes, and no event-routing changes
|
|
231
|
-
that touch them. `SendClientMessage` (node-x11, `xcore.js:246`, whose own
|
|
232
|
-
comment names XDND) is still the send path.
|
|
233
|
-
|
|
234
|
-
---
|
|
235
|
-
|
|
236
|
-
## 1. What #126 gets right
|
|
237
|
-
|
|
238
|
-
Worth stating, because the rest of this document is criticism and the
|
|
239
|
-
foundation is good.
|
|
240
|
-
|
|
241
|
-
- **Message set and field layout are correct.** `XdndEnter` bit 0 →
|
|
242
|
-
`XdndTypeList`, types in `l[2..4]`; `XdndPosition` root coordinates packed
|
|
243
|
-
`(x << 16) | y` in `l[2]`; `XdndStatus` accept bit and rectangle;
|
|
244
|
-
`XdndDrop`'s timestamp in `l[2]`. Verified field-by-field against two spec
|
|
245
|
-
mirrors.
|
|
246
|
-
- **The timestamp rule is called out** — `ConvertSelection` must carry
|
|
247
|
-
`XdndDrop`'s `l[2]`, not `CurrentTime`. This is the single most commonly
|
|
248
|
-
botched part of XDND and #126 has it.
|
|
249
|
-
- **"Advertise unconditionally, answer _not accepting_"** is the right call
|
|
250
|
-
(§2 sharpens the argument).
|
|
251
|
-
- **Reusing the clipboard read path** is right — and as of ntk 5.3.0 the
|
|
252
|
-
public API is already the right shape: `targets()` enumerates offered type
|
|
253
|
-
names and `read({ selection: 'XdndSelection', target })` returns raw bytes,
|
|
254
|
-
INCR reassembly included (`clipboard.js:127`, `clipboard.js:148`).
|
|
255
|
-
- **Splitting the source off as separate work** is right. It is a much bigger
|
|
256
|
-
job (§6), and the target half is independently useful.
|
|
257
|
-
- **The infrastructure survey is accurate**: ClientMessage already routes to
|
|
258
|
-
`WindowNode` ([nodes/window/listeners.js](../../src/nodes/window/listeners.js)), hit testing and hover
|
|
259
|
-
path diffing already exist in `EventManager`, and `window._screenOrigin`
|
|
260
|
-
([nodes/window/window.js](../../src/nodes/window/window.js)) already holds the root-coordinate
|
|
261
|
-
translation `XdndPosition` needs.
|
|
262
|
-
|
|
263
|
-
Four corrections to the protocol detail before moving on:
|
|
264
|
-
|
|
265
|
-
1. **`XdndStatus` `l[1]` has a second bit.** Bit 1 means _"send
|
|
266
|
-
`XdndPosition` even while inside the rectangle"_. #126 mentions only the
|
|
267
|
-
accept bit. This bit is the difference between a dropzone that can draw an
|
|
268
|
-
insertion caret and one that goes deaf — see §2.4.
|
|
269
|
-
2. **The rectangle is not a free optimisation.** #126 proposes replying with
|
|
270
|
-
"the hit node's screen rect" on every position. For a uniform dropzone that
|
|
271
|
-
is right and saves the source a message per motion frame; for a list that
|
|
272
|
-
needs an insertion index, or a zone with edge auto-scroll, it silently
|
|
273
|
-
breaks the feature. Default must be _no suppression_; suppression is opt-in
|
|
274
|
-
(`e.freeze()`), with the one automatic exception in §2.3.
|
|
275
|
-
3. **`XdndFinished` needs a watchdog, not just a reply.** If `onDrop` awaits
|
|
276
|
-
something slow, the source blocks — that is another application's UI
|
|
277
|
-
frozen by ours. Send `XdndFinished` when the handler settles _or_ after a
|
|
278
|
-
timeout, whichever comes first, and report the timeout to the app.
|
|
279
|
-
4. **Version negotiation must be stored.** `XdndEnter`'s `l[1]` high byte
|
|
280
|
-
carries the source's version; `XdndFinished`'s flags in `l[1]`/`l[2]` are
|
|
281
|
-
v5-only. Cap replies to `min(5, sourceVersion)` and keep the source window
|
|
282
|
-
id — every reply is addressed to it, not to whoever the pointer is over.
|
|
283
|
-
|
|
284
|
-
---
|
|
285
|
-
|
|
286
|
-
## 2. Where the advertisement lives (the windowless question)
|
|
287
|
-
|
|
288
|
-
> _"our toolkit renders windowless controls inside window canvas… we need
|
|
289
|
-
> lazily Advertise on a nearest parent windowed control / De-advertise in the
|
|
290
|
-
> other direction whenever a DnD control is mounted / unmounted."_
|
|
291
|
-
|
|
292
|
-
The instinct is right — there **is** something to refcount — but the thing to
|
|
293
|
-
gate is not `XdndAware`.
|
|
294
|
-
|
|
295
|
-
### 2.1 `XdndAware` costs no windows
|
|
296
|
-
|
|
297
|
-
The concern behind the question is X window economy, which this project takes
|
|
298
|
-
seriously and rightly ([windowed-regions.md](windowed-regions.md) §3.2 is
|
|
299
|
-
where window-per-control dies). `XdndAware` is not that. It is:
|
|
300
|
-
|
|
301
|
-
- **a property, not a window** — zero `CreateWindow`, zero map/move storms,
|
|
302
|
-
zero server-side pick-tree cost;
|
|
303
|
-
- **one `ChangeProperty`**, 4 bytes of payload, no round trip once the atom is
|
|
304
|
-
interned;
|
|
305
|
-
- **on a window that exists anyway** — the top-level `<window>`.
|
|
306
|
-
|
|
307
|
-
`WindowNode.realize()` already sends `WM_NAME`, `_NET_WM_NAME`, `WM_CLASS`,
|
|
308
|
-
`_NET_WM_PID`, size hints, and optionally `WM_PROTOCOLS`, `WM_TRANSIENT_FOR`
|
|
309
|
-
and `_NET_WM_STATE`. One more 32-bit property is well under 5% of a window's
|
|
310
|
-
setup traffic and is sent once per window, ever.
|
|
311
|
-
|
|
312
|
-
### 2.2 The spec puts it on top-levels, which makes windowed regions a non-issue
|
|
313
|
-
|
|
314
|
-
XDND v3 moved `XdndAware` to **top-level windows only**; sources locate the
|
|
315
|
-
target by descending the tree with `TranslateCoordinates` and then looking for
|
|
316
|
-
`XdndAware` on the top-level (GTK and Qt both resolve the client window
|
|
317
|
-
through `WM_STATE` and walk up).
|
|
318
|
-
|
|
319
|
-
Consequences, and this is the direct answer to the question:
|
|
320
|
-
|
|
321
|
-
- **`<glarea>`'s child window: nothing.** A drag over a `<glarea>` is found
|
|
322
|
-
through the enclosing top-level and routed by our own hit test.
|
|
323
|
-
- **A future windowed a scrolling `<box>` pane, or any `<box windowed>` that never
|
|
324
|
-
ships: nothing.** The promotion decision in
|
|
325
|
-
[windowed-regions.md](windowed-regions.md) §8 is invisible to XDND. That is
|
|
326
|
-
a genuine argument _for_ that document's verdict — a feature that would have
|
|
327
|
-
had to care about `windowed` does not.
|
|
328
|
-
- **Nested `<window>` children need routing, not advertising.** react-x11
|
|
329
|
-
supports child `<window>`s inside a parent
|
|
330
|
-
([nodes/window/window.js](../../src/nodes/window/window.js)). Those are not top-levels, so they get
|
|
331
|
-
no property; messages arrive at the outer window carrying **root**
|
|
332
|
-
coordinates, and the router must descend our own window tree in JS:
|
|
333
|
-
root point → each realized `WindowNode`'s `_screenOrigin` + size → topmost
|
|
334
|
-
match in `_xStack` order → that node's `hitTest`. Cheap, local, no round
|
|
335
|
-
trips, and it is the same walk `anchorRect` already does in reverse.
|
|
336
|
-
- **`<popup>` is a top-level** (override-redirect, child of root) and _does_
|
|
337
|
-
get the property, so a drop onto an open menu or a combo panel works.
|
|
338
|
-
|
|
339
|
-
### 2.3 What the refcount should actually gate
|
|
340
|
-
|
|
341
|
-
Keep a `Set` of registered drop-target nodes **per `WindowNode`** (a `<popup>`
|
|
342
|
-
is its own root, so it gets its own). It gates three things, none of them the
|
|
343
|
-
property:
|
|
344
|
-
|
|
345
|
-
1. **The whole-window suppression fast path.** `set.size === 0` → reply to the
|
|
346
|
-
first `XdndPosition` with `accept=0`, bit 1 clear, and a rectangle covering
|
|
347
|
-
the whole window in root coordinates. The source stops sending positions
|
|
348
|
-
until the pointer leaves. **One ClientMessage per window-entry, for the
|
|
349
|
-
whole drag.** This is exactly the saving de-advertising would have bought,
|
|
350
|
-
obtained through the protocol's own mechanism.
|
|
351
|
-
2. **Atom interning.** Intern the ~15 `Xdnd*` atoms lazily, on the first
|
|
352
|
-
drop-target registration _or_ the first `XdndEnter`, whichever comes first.
|
|
353
|
-
An app with no DnD never sends the `InternAtom` batch.
|
|
354
|
-
3. **Module activation.** `src/dnd.js` stays cold. `realize()` writes one
|
|
355
|
-
property; the shared `message` listener is an atom range check.
|
|
356
|
-
|
|
357
|
-
### 2.4 Why _not_ to make the property lazy
|
|
358
|
-
|
|
359
|
-
Four reasons, in order of how much they hurt:
|
|
360
|
-
|
|
361
|
-
1. **Mid-drag mounts are invisible.** GTK's source builds its window cache at
|
|
362
|
-
_drag start_ — before our app can possibly know a drag exists. A dropzone
|
|
363
|
-
that mounts in reaction to app state during a drag would never be seen by
|
|
364
|
-
that source, and the failure is silent and unreproducible.
|
|
365
|
-
2. **Commit churn.** Route changes, tab switches and virtualised lists mount
|
|
366
|
-
and unmount dropzones constantly. A 1 → 0 → 1 refcount across a commit
|
|
367
|
-
boundary costs `DeleteProperty` + `ChangeProperty` _per commit_, which is
|
|
368
|
-
more traffic than the one write it was trying to avoid.
|
|
369
|
-
3. **Offscreen and Suspense.** `hideInstance` does not unmount
|
|
370
|
-
([Reconciler.js:408](../../src/Reconciler.js)), so a correct refcount would
|
|
371
|
-
need hidden-tracking too — state that is easy to get subtly wrong and whose
|
|
372
|
-
failure mode is "drops stopped working in this tab".
|
|
373
|
-
4. **There is no user-visible difference.** Not-advertised and
|
|
374
|
-
advertised-but-rejecting both produce the source's "no drop" cursor. The
|
|
375
|
-
lazy version buys nothing the user can see, and costs the three failures
|
|
376
|
-
above.
|
|
377
|
-
|
|
378
|
-
The one obligation `always advertise` creates: **you must always answer, and
|
|
379
|
-
answer fast.** A window that is `XdndAware` and silent freezes the _source_
|
|
380
|
-
application's drag. That obligation is why `dropAccept` has to be declarative
|
|
381
|
-
(§4.1) — the answer must be computable without waiting for React.
|
|
382
|
-
|
|
383
|
-
### 2.5 The exact lifecycle points
|
|
384
|
-
|
|
385
|
-
The codebase already answers this; `trapFocus` is the identical problem
|
|
386
|
-
(subtree-scoped declaration, root-scoped registry) and its wiring is the
|
|
387
|
-
template:
|
|
388
|
-
|
|
389
|
-
| Moment | Hook | What to do |
|
|
390
|
-
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
391
|
-
| Mount | `finalizeInitialChildren` returns `true` when the props declare drop handling ([Reconciler.js:342](../../src/Reconciler.js)) → `commitMount` ([Reconciler.js:352](../../src/Reconciler.js)) | `root._dnd.register(node)`. Must be `commitMount`, not `createInstance`: the node needs its `root`, which `_setRoot` assigns on insertion. |
|
|
392
|
-
| Update | `commitUpdate` → `applyProps` ([nodes/node.js](../../src/nodes/node.js)), next to the existing `trapFocus` branch | register/unregister on the boolean edge only |
|
|
393
|
-
| Unmount | `detachDeletedInstance` ([Reconciler.js:424](../../src/Reconciler.js)), which already calls `events.forget(instance)` | one more line: `instance.root?._dnd?.forget(instance)` |
|
|
394
|
-
| Hidden | — | do **not** track. `hitTest` already skips hidden nodes; a hidden-but-registered node costs one unsuppressed `XdndStatus` and nothing else. |
|
|
395
|
-
|
|
396
|
-
Realize-time (`WindowNode.realize()`, before `map()`): write `XdndAware = 5`
|
|
397
|
-
as `ATOM`/32 unconditionally, next to `applyWindowStates` — same reasoning as
|
|
398
|
-
the EWMH properties there, a declaration made before anyone can look.
|
|
399
|
-
|
|
400
|
-
One refactor falls out: the `message` listener is currently installed only
|
|
401
|
-
when `onCloseRequest` is set ([nodes/window/listeners.js](../../src/nodes/window/listeners.js)). Make it
|
|
402
|
-
unconditional and route by `ev.message_type` — `WM_PROTOCOLS` to the close
|
|
403
|
-
path, `Xdnd*` to the DnD router. Cheap, and `_NET_WM_PING`/`_NET_WM_SYNC` will
|
|
404
|
-
want it later.
|
|
405
|
-
|
|
406
|
-
---
|
|
407
|
-
|
|
408
|
-
## 3. Lens 1 — familiarity to the DOM API
|
|
409
|
-
|
|
410
|
-
react-x11 tracks the DOM closely and deliberately: capture/bubble
|
|
411
|
-
(`onFooCapture` then `onFoo`), `stopPropagation`/`preventDefault`, `detail`
|
|
412
|
-
click counting, `capturePointer` modelled on implicit pointer capture. Drag
|
|
413
|
-
and drop should inherit that, with three deliberate divergences.
|
|
414
|
-
|
|
415
|
-
**Keep, verbatim:**
|
|
416
|
-
|
|
417
|
-
- `onDragStart`, `onDrag`, `onDragEnd` (source) and `onDragEnter`,
|
|
418
|
-
`onDragOver`, `onDragLeave`, `onDrop` (target). Same names, same
|
|
419
|
-
enter/leave-do-not-bubble rule the hover path already implements
|
|
420
|
-
([events.js:428](../../src/events.js)).
|
|
421
|
-
- `draggable` as the prop that makes a node a source. It is the HTML
|
|
422
|
-
attribute name, and it reads correctly.
|
|
423
|
-
- `'copy' | 'move' | 'link'` action names — they map 1:1 onto
|
|
424
|
-
`XdndActionCopy`/`Move`/`Link`, which is a gift the DOM's `dropEffect`
|
|
425
|
-
vocabulary hands us for free.
|
|
426
|
-
- MIME type strings as the type vocabulary.
|
|
427
|
-
|
|
428
|
-
**Diverge, on purpose:**
|
|
429
|
-
|
|
430
|
-
1. **No `preventDefault()`-to-accept.** In the DOM, `dragover` +
|
|
431
|
-
`preventDefault()` means "suppress the browser's default of refusing the
|
|
432
|
-
drop" — the most-complained-about wart in the HTML5 DnD API, and it is
|
|
433
|
-
_meaningless here_: there is no browser and no default. Use
|
|
434
|
-
`e.accept(action)` / `e.reject()`. Explicit, discoverable, and the
|
|
435
|
-
declarative `dropAccept` prop means most code never calls either.
|
|
436
|
-
2. **No `e.dataTransfer`.** X selections are asynchronous — `ConvertSelection`
|
|
437
|
-
then wait for `SelectionNotify`, possibly through INCR chunks. DOM's
|
|
438
|
-
`getData()` is synchronous and returns a string. A `dataTransfer` whose
|
|
439
|
-
`getData()` returns a Promise is a trap that fails _silently_:
|
|
440
|
-
`"dropped: " + e.dataTransfer.getData('text/plain')` yields
|
|
441
|
-
`"dropped: [object Promise]"` with no error anywhere. Flatten onto the
|
|
442
|
-
event, name the async things async (`await e.getData(type)`), and let the
|
|
443
|
-
different name carry the different contract.
|
|
444
|
-
3. **Data may be lazy, and may be a live object.** DOM's `setData(type, str)`
|
|
445
|
-
is eager and string-only. XDND is pull-based — the target converts the
|
|
446
|
-
selection _after_ the drop — so `dragData` accepts thunks, and for in-app
|
|
447
|
-
drags it can carry a JS value that is never serialised at all. Strictly
|
|
448
|
-
better than the DOM, and it matches `react-dnd`'s model, which is what
|
|
449
|
-
React developers actually use.
|
|
450
|
-
|
|
451
|
-
---
|
|
452
|
-
|
|
453
|
-
## 4. Lens 2 — interop with real XDND implementations
|
|
454
|
-
|
|
455
|
-
This is where a plausible-looking API quietly fails in the field. Each item
|
|
456
|
-
below is a concrete requirement on the API surface, not just the plumbing.
|
|
457
|
-
|
|
458
|
-
### 4.1 `dropAccept` must be smarter than string equality
|
|
459
|
-
|
|
460
|
-
The same logical payload arrives under different names depending on who is
|
|
461
|
-
dragging:
|
|
462
|
-
|
|
463
|
-
| Drag | Types actually offered |
|
|
464
|
-
| -------------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
465
|
-
| Files from Nautilus / Thunar / Dolphin | `text/uri-list`, `text/plain`, sometimes `x-special/gnome-icon-list` |
|
|
466
|
-
| Text from a GTK app | `text/plain;charset=utf-8`, `UTF8_STRING`, `text/plain`, `STRING`, `TEXT`, `COMPOUND_TEXT` |
|
|
467
|
-
| A link from Firefox | `text/x-moz-url` (**UTF-16LE**, `url\ntitle`), `text/uri-list`, `text/plain` |
|
|
468
|
-
| A link from Chromium | `text/uri-list`, `_NETSCAPE_URL`, `text/html` |
|
|
469
|
-
|
|
470
|
-
`dropAccept: ['text/plain']` therefore misses GTK text entirely, and
|
|
471
|
-
`dropAccept: ['text/uri-list']` catches files but silently drops Firefox's
|
|
472
|
-
preferred link type. So `dropAccept` takes:
|
|
473
|
-
|
|
474
|
-
- exact MIME strings — `'image/png'`;
|
|
475
|
-
- **semantic groups** — `'files'`, `'text'`, `'uris'` — expanding through a
|
|
476
|
-
normalisation table that is part of the library, not of every app;
|
|
477
|
-
- a predicate — `(types) => types.some(...)` — for the rest.
|
|
478
|
-
|
|
479
|
-
Getting this table right _is_ the interop feature. It is also the one piece
|
|
480
|
-
that must be data rather than code, so `XdndStatus` can be answered without
|
|
481
|
-
entering React (§2.4).
|
|
482
|
-
|
|
483
|
-
### 4.2 `text/uri-list` must be parsed for the app
|
|
484
|
-
|
|
485
|
-
Per RFC 2483 it is CRLF-separated, percent-encoded, and `#`-prefixed lines are
|
|
486
|
-
comments. #126's `await e.getData('text/uri-list')` hands back the raw string
|
|
487
|
-
and every app writes the same buggy decoder. Provide instead:
|
|
488
|
-
|
|
489
|
-
```js
|
|
490
|
-
e.files; // [{ uri: 'file:///home/a/My%20Doc.pdf', path: '/home/a/My Doc.pdf' }]
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
`path` is present only for `file:` URIs whose host is empty, `localhost`, or
|
|
494
|
-
this machine — a remote `file://otherhost/...` has no local path and must not
|
|
495
|
-
pretend to. That rule is exactly the kind of thing that belongs in the library
|
|
496
|
-
once.
|
|
497
|
-
|
|
498
|
-
### 4.3 Protocol obligations that shape the API
|
|
499
|
-
|
|
500
|
-
- **Answer `XdndPosition` promptly, always.** §2.4. Declarative `dropAccept`
|
|
501
|
-
handles the common case without React; a handler-based answer runs at
|
|
502
|
-
`DiscreteEventPriority` and, if a render is in flight, replies with the last
|
|
503
|
-
known answer rather than waiting.
|
|
504
|
-
- **`XdndFinished` must always be sent.** Async `onDrop` is supported (it is
|
|
505
|
-
the natural shape — the data arrives asynchronously), so the contract is:
|
|
506
|
-
the reply goes out when the returned promise settles, or after
|
|
507
|
-
`dropTimeout` (default ~10 s), whichever is first, with a dev warning on
|
|
508
|
-
timeout. An app must not be able to hang another app's drag by forgetting an
|
|
509
|
-
`await`.
|
|
510
|
-
- **`XdndProxy` must be honoured on the source side** — read it on each
|
|
511
|
-
candidate window, send to the proxy while keeping the real window id in the
|
|
512
|
-
message fields. We never _set_ it, since only top-levels advertise (§2.2).
|
|
513
|
-
- **Version negotiation.** Store `sourceVersion` from `XdndEnter`'s high byte;
|
|
514
|
-
v5-only `XdndFinished` fields are omitted below 5.
|
|
515
|
-
- **`XdndActionAsk`** should eventually pop a menu built from the source's
|
|
516
|
-
`XdndActionList` + `XdndActionDescription` — react-x11 already has
|
|
517
|
-
`ContextMenu`. v1 narrows to `XdndActionCopy`, which the spec permits.
|
|
518
|
-
|
|
519
|
-
### 4.4 What we will not speak, and should say so
|
|
520
|
-
|
|
521
|
-
- **Motif drag-and-drop** (the older protocol). Legacy Motif and some AWT
|
|
522
|
-
builds speak only that; they will not interop. Not worth implementing.
|
|
523
|
-
- **XdndDirectSave (XDS)** — "drag out of the app to save a file into the file
|
|
524
|
-
manager". A separate protocol layered on XDND. Worth a documented "not
|
|
525
|
-
supported" line, because its absence looks like a bug.
|
|
526
|
-
- **Finder → X11 on macOS.** XQuartz does not bridge native drags into X11
|
|
527
|
-
([XQuartz#173](https://github.com/XQuartz/XQuartz/issues/173)). On this
|
|
528
|
-
machine the feature can only be exercised X-client to X-client (a GTK app
|
|
529
|
-
under XQuartz, `mwh/dragon`, or the hermetic tests in §8). Under XWayland on
|
|
530
|
-
Linux, XDND does bridge to and from native Wayland apps — which is where
|
|
531
|
-
#126's impact claim holds.
|
|
532
|
-
|
|
533
|
-
---
|
|
534
|
-
|
|
535
|
-
## 5. Lens 3 — reuse of the npm React ecosystem
|
|
536
|
-
|
|
537
|
-
| Package | Status | Why |
|
|
538
|
-
| ------------------------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
539
|
-
| **`react-dnd`** | **the prize** | `dnd-core` is deliberately DOM-free and backends are pluggable (constructor takes a manager, plus `setup()`/`teardown()`). A `react-x11` backend brings `useDrag`/`useDrop` and everything built on them. |
|
|
540
|
-
| `react-dropzone` | unusable, but _copy its shape_ | Already documented as failing twice ([ecosystem.md](../ecosystem.md)) — `<input>`, then `window is not defined` in its own mount effect. Its hook shape (`getRootProps()`, `isDragActive`, `acceptedFiles`) is nonetheless what React developers know for file drops. |
|
|
541
|
-
| `@dnd-kit/core` | unusable | Sensors are pluggable but the core measures with `getBoundingClientRect` and synthesises DOM `PointerEvent`s. A backend is not the seam it offers. |
|
|
542
|
-
| `react-beautiful-dnd` / `@hello-pangea/dnd` | unusable | DOM-only by design, and its use case (in-app reorder) is fully served by §6's local path. |
|
|
543
|
-
|
|
544
|
-
Two conclusions:
|
|
545
|
-
|
|
546
|
-
1. **Design the low-level API so a `react-dnd` backend is writable on top of
|
|
547
|
-
it.** That means the drag session must expose imperative
|
|
548
|
-
`beginDrag`/`hover`/`drop`/`endDrag` transitions and node-rect measurement,
|
|
549
|
-
not only React props — which it needs internally anyway. Nothing here is
|
|
550
|
-
extra work; it is just a constraint on where the seam goes.
|
|
551
|
-
`NativeTypes.FILE` maps onto foreign XDND drops; in-app drags never reach
|
|
552
|
-
X. The backend can ship later, in a separate package, without changing this
|
|
553
|
-
API — as long as the seam exists.
|
|
554
|
-
2. **Match `react-dropzone`'s hook ergonomics** in `useDropTarget`, including
|
|
555
|
-
the render state (`isOver`, `isAccepted`), because that is the muscle
|
|
556
|
-
memory. But go one better: react-x11 has style states, so the common case
|
|
557
|
-
needs no state at all — see `:drag-over` in §6.1.
|
|
558
|
-
|
|
559
|
-
---
|
|
560
|
-
|
|
561
|
-
## 6. The proposed API
|
|
562
|
-
|
|
563
|
-
### 6.1 Target side
|
|
564
|
-
|
|
565
|
-
Host props, on any drawn node, dispatched through the existing
|
|
566
|
-
capture/bubble machinery — no marker, no parallel router:
|
|
567
|
-
|
|
568
|
-
```jsx
|
|
569
|
-
<box
|
|
570
|
-
dropAccept={['files']} // declarative; answers XdndStatus without React
|
|
571
|
-
onDragEnter={(e) => {}} // does not bubble, like onMouseEnter
|
|
572
|
-
onDragOver={(e) => {}} // per motion, unless frozen
|
|
573
|
-
onDragLeave={(e) => {}}
|
|
574
|
-
onDrop={async (e) => setFiles(e.files)}
|
|
575
|
-
style={{
|
|
576
|
-
borderColor: '$border',
|
|
577
|
-
':drag-over': { borderColor: '$accent' }, // new style state
|
|
578
|
-
}}
|
|
579
|
-
/>
|
|
580
|
-
```
|
|
581
|
-
|
|
582
|
-
**Why `dropAccept` and not `accept`.** Prop _presence_ is what registers a
|
|
583
|
-
node as a drop target, so the name carries behaviour, and a name as generic
|
|
584
|
-
as `accept` (HTML's `<input accept>`, react-dropzone's option) could ride
|
|
585
|
-
in on a `{...spread}` and silently make a box a dropzone — on `<input
|
|
586
|
-
type=file>` the element supplies the context; on a generic `<box>` nothing
|
|
587
|
-
does. The `drop*` prefix also keeps the family discoverable next to
|
|
588
|
-
`onDrop` and mirrors the source side's `drag*` data props (`dragData`,
|
|
589
|
-
`dragActions`, `dragPreview`). Rejected alternatives: `dndAcceptTypes`
|
|
590
|
-
(abbreviation prefix foreign to the codebase, splits the family into two
|
|
591
|
-
namespaces, and `Types` over-narrows a value that can be a group or a
|
|
592
|
-
predicate), `acceptDrops` (reads boolean, takes a list), `droppable`
|
|
593
|
-
(prettiest symmetry with `draggable`, but the target needs a _filter_, and
|
|
594
|
-
with `onDrop` implying accept-anything its boolean form is boilerplate).
|
|
595
|
-
|
|
596
|
-
`:drag-over` joins `STATE_KEYS` ([styles.js:156](../../src/styles.js)) and is
|
|
597
|
-
set by the router through the same `setStyleState` path `:hover` uses
|
|
598
|
-
([events.js:429](../../src/events.js)). The whole "highlight the dropzone"
|
|
599
|
-
feature — the thing every react-dropzone tutorial spends a `useState` on —
|
|
600
|
-
becomes styling.
|
|
601
|
-
|
|
602
|
-
The event, built by `_makeEvent` ([events.js:178](../../src/events.js)) so it
|
|
603
|
-
inherits `x`/`y`/`localX`/`localY`/`stopPropagation` for free:
|
|
604
|
-
|
|
605
|
-
```
|
|
606
|
-
e.types string[] normalised type names offered by the source
|
|
607
|
-
e.has(type) boolean alias-aware ('text', 'files', or an exact MIME)
|
|
608
|
-
e.action 'copy'|'move'|'link'|'ask'|'private' what the source asked for
|
|
609
|
-
e.source 'internal'|'external'
|
|
610
|
-
e.screenX/Y number root coordinates (XdndPosition gives these natively)
|
|
611
|
-
e.accept(action?) default: the source's requested action
|
|
612
|
-
e.reject()
|
|
613
|
-
e.freeze() opt into the XdndStatus suppression rectangle
|
|
614
|
-
for this node's rect (see §1, correction 2)
|
|
615
|
-
// onDrop only:
|
|
616
|
-
e.getData(type) -> Promise<Buffer|string>
|
|
617
|
-
e.files [{ uri, path }] parsed text/uri-list (§4.2)
|
|
618
|
-
e.text string the best available text flavour
|
|
619
|
-
e.items any live JS payload — internal drags only, no serialisation
|
|
620
|
-
```
|
|
621
|
-
|
|
622
|
-
Convenience hook in `src/components/`, mirroring react-dropzone:
|
|
623
|
-
|
|
624
|
-
```jsx
|
|
625
|
-
const { dropProps, isOver, isAccepted } = useDropTarget({
|
|
626
|
-
accept: ['files'], // the hook name supplies the scope; emits `dropAccept`
|
|
627
|
-
onDrop: (e) => setFiles(e.files),
|
|
628
|
-
});
|
|
629
|
-
<box {...dropProps} style={s.zone} />;
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
### 6.2 Source side
|
|
633
|
-
|
|
634
|
-
```jsx
|
|
635
|
-
<box
|
|
636
|
-
draggable
|
|
637
|
-
dragData={{
|
|
638
|
-
'text/uri-list': () => selected.map((f) => pathToUri(f.path)), // lazy: pull-based protocol
|
|
639
|
-
'text/plain': selected.map((f) => f.name).join('\n'),
|
|
640
|
-
'application/x-myapp-row': { id: row.id }, // in-app: live object
|
|
641
|
-
}}
|
|
642
|
-
dragActions={['copy', 'move']}
|
|
643
|
-
dragPreview={<Chip label={row.name} />} // rendered into a <popup> that follows the pointer
|
|
644
|
-
onDragStart={(e) => {
|
|
645
|
-
/* e.preventDefault() cancels */
|
|
646
|
-
}}
|
|
647
|
-
onDrag={(e) => {}}
|
|
648
|
-
onDragEnd={(e) => {
|
|
649
|
-
if (e.action === 'move') remove(row.id);
|
|
650
|
-
}}
|
|
651
|
-
style={{ ':dragging': { backgroundColor: '$textMuted' } }}
|
|
652
|
-
/>
|
|
653
|
-
```
|
|
654
|
-
|
|
655
|
-
Three things to note:
|
|
656
|
-
|
|
657
|
-
- **`dragPreview` is a React node, not an image.** XDND has no drag image in
|
|
658
|
-
the protocol; every toolkit implements it as an override-redirect window
|
|
659
|
-
that follows the pointer — and react-x11 already has one, `<popup>`. So the
|
|
660
|
-
drag preview is a live component, which beats DOM's
|
|
661
|
-
`setDragImage(HTMLImageElement)` outright. The preview window must be
|
|
662
|
-
skipped when descending for the target; since we hold the pointer grab, we
|
|
663
|
-
are the only client searching, so skipping our own ids is sufficient.
|
|
664
|
-
- **Lazy `dragData` defers serialisation to promotion.** A drag that never
|
|
665
|
-
leaves our windows serialises nothing at all — it carries the live JS value.
|
|
666
|
-
A drag that goes external resolves the thunks once, when it takes
|
|
667
|
-
`XdndSelection`, because ntk's `write()` encodes eagerly
|
|
668
|
-
([§0.1](#01-audit-against-ntk-530)). Deferring all the way to the target's
|
|
669
|
-
conversion, as the pull-based protocol would allow, needs lazy payload
|
|
670
|
-
getters in ntk — worth asking for only if a real payload makes it matter.
|
|
671
|
-
- **The gesture is the existing one.** Threshold on mousedown + motion, then
|
|
672
|
-
`capturePointer` — the same mechanics `Slider` and `SplitPane` already use
|
|
673
|
-
([events.js:202](../../src/events.js)).
|
|
674
|
-
|
|
675
|
-
Hook form: `const { dragProps, isDragging } = useDragSource({ data, actions })`.
|
|
676
|
-
|
|
677
|
-
### 6.3 One session, two transports — how the halves fit together
|
|
678
|
-
|
|
679
|
-
A single `DragSession` in `src/dnd.js`, with a phase:
|
|
680
|
-
|
|
681
|
-
```
|
|
682
|
-
mousedown on draggable
|
|
683
|
-
└─ threshold exceeded ──▶ phase: 'local'
|
|
684
|
-
• no grab (the implicit button grab already delivers motion)
|
|
685
|
-
• no X traffic at all
|
|
686
|
-
• hit test + dispatch('DragOver', …) through EventManager
|
|
687
|
-
• payload is the live JS value; e.source === 'internal'
|
|
688
|
-
│
|
|
689
|
-
├─ pointer leaves all of our windows ──▶ phase: 'external'
|
|
690
|
-
│ • SetSelectionOwner(XdndSelection)
|
|
691
|
-
│ • write XdndTypeList if > 3 types
|
|
692
|
-
│ • GrabPointer with the action cursor
|
|
693
|
-
│ • build the top-level cache (one QueryTree of root + geometry)
|
|
694
|
-
│ • XdndEnter / XdndPosition / XdndStatus / XdndDrop / XdndFinished
|
|
695
|
-
│
|
|
696
|
-
└─ pointer returns over our own window ──▶ XdndLeave, back to 'local'
|
|
697
|
-
(selection ownership is kept until drag end — it is free)
|
|
698
|
-
|
|
699
|
-
foreign XdndEnter arrives ──▶ same synthetic dispatch, e.source === 'external'
|
|
700
|
-
```
|
|
701
|
-
|
|
702
|
-
Why this and not "XDND for everything":
|
|
703
|
-
|
|
704
|
-
- **Latency.** In-app reorder is the overwhelmingly common case. Routing it
|
|
705
|
-
through a pointer grab, a root-tree descent and a selection round trip would
|
|
706
|
-
put wire round trips on the drag path, against the project's stated
|
|
707
|
-
perceived-latency ordering.
|
|
708
|
-
- **Fidelity.** In-app you can drag a live JS object. Forcing serialisation
|
|
709
|
-
through MIME strings for a drag that never leaves the process is pure loss —
|
|
710
|
-
and it is precisely why `react-dnd` does not do it.
|
|
711
|
-
- **One API.** Handlers cannot tell the transports apart except by asking
|
|
712
|
-
(`e.source`), so an app written for in-app reorder gains cross-app drops by
|
|
713
|
-
adding a MIME type to `dragData`.
|
|
714
|
-
|
|
715
|
-
This is also what makes the two halves cheap together: the source's target
|
|
716
|
-
tracking (which node is under the pointer, enter/leave diffing, accept state)
|
|
717
|
-
and the target's routing are **the same code**, differing only in whether the
|
|
718
|
-
peer is a foreign window or our own subtree.
|
|
719
|
-
|
|
720
|
-
---
|
|
721
|
-
|
|
722
|
-
## 7. Implementation plan
|
|
723
|
-
|
|
724
|
-
Dependency-ordered. Phase 1 alone closes #126.
|
|
725
|
-
|
|
726
|
-
### Phase 0 — prerequisites and decisions
|
|
727
|
-
|
|
728
|
-
**No ntk release gates any of this** — 5.3.0 already ships what both halves
|
|
729
|
-
need ([§0.1](#01-audit-against-ntk-530)).
|
|
730
|
-
|
|
731
|
-
- **[react-x11]** Adopt `wnd.on('close')` for `onCloseRequest` and delete the
|
|
732
|
-
`setActions()` + `InternAtom` + `on('message')` block
|
|
733
|
-
([nodes/window/listeners.js](../../src/nodes/window/listeners.js)) — filed as
|
|
734
|
-
[#160](https://github.com/sidorares/react-x11/issues/160). That frees
|
|
735
|
-
`'message'` for the XDND router with no routing logic, and makes
|
|
736
|
-
`onCloseRequest` cancellable. The only real prerequisite, and independently
|
|
737
|
-
worth doing.
|
|
738
|
-
- **[verify in Phase 3]** That foreign targets accept `XdndSelection` owned by
|
|
739
|
-
ntk's clipboard helper window rather than by the advertised source window.
|
|
740
|
-
If one does not, ntk needs a `window` option on `write()`.
|
|
741
|
-
- **[ntk, optional]** `ChangeActivePointerGrab` wrapper for the mid-drag
|
|
742
|
-
cursor swap — `grabPointer({ cursor: app.cursors.get(name) })` already
|
|
743
|
-
covers grab time, and node-x11 exposes the request, so this is convenience.
|
|
744
|
-
- **[ntk, done in 5.4.0]** `clipboard.clear(selection)` to disown
|
|
745
|
-
`XdndSelection` at drag end
|
|
746
|
-
([ntk#169](https://github.com/sidorares/ntk/issues/169)), and a `time`
|
|
747
|
-
option on `read()`/`targets()` so a drop converts with `XdndDrop`'s
|
|
748
|
-
timestamp rather than `CurrentTime`
|
|
749
|
-
([ntk#168](https://github.com/sidorares/ntk/issues/168)). Both are wired
|
|
750
|
-
up. Still open: lazy payload getters on `write()`, if eager serialisation
|
|
751
|
-
at promotion turns out to matter.
|
|
752
|
-
- **[decision]** `:drag-over` (and later `:dragging`) join `STATE_KEYS`.
|
|
753
|
-
|
|
754
|
-
### Phase 1 — drop target (closes #126)
|
|
755
|
-
|
|
756
|
-
1. `src/dnd.js`: atom table (lazy intern), `XdndAware` writer, ClientMessage
|
|
757
|
-
decode/encode helpers, version negotiation.
|
|
758
|
-
2. `WindowNode.realize()`: write `XdndAware = 5` before `map()`.
|
|
759
|
-
3. Per-`WindowNode` drop-target registry + the three lifecycle hooks (§2.5).
|
|
760
|
-
4. Root-coordinate router: root point → owning `WindowNode` (descending our
|
|
761
|
-
own child windows, §2.2) → `hitTest` → path.
|
|
762
|
-
5. `XdndEnter`/`Position`/`Leave`/`Drop` handling; `XdndStatus` answered from
|
|
763
|
-
declarative `dropAccept` without entering React; the zero-target whole-window
|
|
764
|
-
suppression fast path; enter/leave diffing reusing the hover-path algorithm
|
|
765
|
-
([events.js:429](../../src/events.js)).
|
|
766
|
-
6. Drag event objects on top of `_makeEvent`; `:drag-over`; the `dropAccept`
|
|
767
|
-
normalisation table (§4.1); `text/uri-list` parsing (§4.2).
|
|
768
|
-
7. `XdndDrop` → `clipboard.read({ selection: 'XdndSelection', target })` with
|
|
769
|
-
the drop timestamp → `onDrop` → `XdndFinished` with the watchdog.
|
|
770
|
-
8. `useDropTarget` in `src/components/`; types in `src/types/`; docs page;
|
|
771
|
-
an `examples/` file-drop demo.
|
|
772
|
-
|
|
773
|
-
### Phase 2 — in-app drag source (no X11 at all)
|
|
774
|
-
|
|
775
|
-
9. `draggable`/`dragData`/`dragActions` props; threshold gesture over
|
|
776
|
-
`capturePointer`; `onDragStart`/`onDrag`/`onDragEnd`.
|
|
777
|
-
10. `DragSession` local phase feeding the _same_ target-side dispatch, so
|
|
778
|
-
Phase 1's handlers receive internal drags with `e.source === 'internal'`.
|
|
779
|
-
11. `dragPreview` → `<popup>` following the pointer; `:dragging`.
|
|
780
|
-
12. `useDragSource`.
|
|
781
|
-
|
|
782
|
-
At this point reorderable lists, trees and kanban work with zero wire traffic,
|
|
783
|
-
and the app-facing API is complete. Phase 3 changes no application code.
|
|
784
|
-
|
|
785
|
-
### Phase 3 — external drag source (XDND out)
|
|
786
|
-
|
|
787
|
-
13. Top-level cache: one `QueryTree` of root at drag start + geometry and
|
|
788
|
-
`XdndAware`/`XdndProxy` per candidate; refresh on root
|
|
789
|
-
`SubstructureNotify`. Per-motion target resolution is then local — the
|
|
790
|
-
same design GTK uses, and the only way to avoid round trips per motion
|
|
791
|
-
frame.
|
|
792
|
-
14. Promotion/demotion (§6.3): selection ownership, `XdndTypeList`,
|
|
793
|
-
`GrabPointer` + action cursor, `XdndEnter`/`Position`/`Leave`/`Drop`.
|
|
794
|
-
15. `SelectionRequest` service for the offered types, `TARGETS` included,
|
|
795
|
-
lazy `dragData` thunks resolved on demand; keep serving until
|
|
796
|
-
`XdndFinished` (with the "throw out extremely old data" rule from the
|
|
797
|
-
spec).
|
|
798
|
-
16. `XdndStatus` → cursor via `ChangeActivePointerGrab`; `XdndFinished` →
|
|
799
|
-
`onDragEnd` with the performed action.
|
|
800
|
-
17. `XdndProxy` honouring; skip our own preview window in the descent.
|
|
801
|
-
|
|
802
|
-
### Phase 4 — ecosystem and polish
|
|
803
|
-
|
|
804
|
-
18. `XdndActionAsk` → `ContextMenu` from `XdndActionList` /
|
|
805
|
-
`XdndActionDescription`. _Done, but not as sketched._ The two
|
|
806
|
-
properties are read and handed to the app as `e.actions` /
|
|
807
|
-
`e.actionDescriptions`, and `onDrop` settles the answer with
|
|
808
|
-
`e.accept(action)`; the menu itself stays in application code. A
|
|
809
|
-
built-in one would have to hold `XdndFinished` open for as long as it
|
|
810
|
-
was on screen, and §9's first hazard is that a slow drop freezes the
|
|
811
|
-
source application — so how long to make someone wait is the app's
|
|
812
|
-
call, not the toolkit's. Reading the properties lazily, only when a
|
|
813
|
-
position actually asks, keeps the two round trips off every other drag.
|
|
814
|
-
19. A `react-dnd` backend (separate package) over the seam from §5.
|
|
815
|
-
20. Auto-scroll on drag near a scroll container's edge — a default action in the
|
|
816
|
-
router, the same shape as the existing wheel default
|
|
817
|
-
([events.js:294](../../src/events.js)). _Done._ It hangs off
|
|
818
|
-
`_overAt`, so both transports get it from one implementation, and the
|
|
819
|
-
step re-routes rather than re-measuring. The one thing the sketch did
|
|
820
|
-
not anticipate: the step must **not** answer with an `XdndStatus`. A
|
|
821
|
-
status is the reply to a position, and the source has sent none — so
|
|
822
|
-
the fresh answer waits for the next real position instead.
|
|
823
|
-
|
|
824
|
-
---
|
|
825
|
-
|
|
826
|
-
## 8. Test plan
|
|
827
|
-
|
|
828
|
-
Unusually strong here, and worth exploiting.
|
|
829
|
-
|
|
830
|
-
- **Hermetic, two connections** — the `test/wm.test.js` pattern. node-x11's
|
|
831
|
-
in-process JS server implements `SendEvent`, `SetSelectionOwner`,
|
|
832
|
-
`ConvertSelection`, `GetSelectionOwner`, `ChangeProperty`, `GetProperty`,
|
|
833
|
-
`QueryTree`, `TranslateCoordinates` and `GrabPointer`, and its
|
|
834
|
-
`ConvertSelection` correctly turns into a `SelectionRequest` to the owner
|
|
835
|
-
(`x11/lib/xserver/properties.js:186`). So one connection can play a **fake
|
|
836
|
-
GTK source** (send `XdndEnter`/`Position`/`Drop`, own `XdndSelection`, serve
|
|
837
|
-
`text/uri-list`) and assert react-x11's `XdndStatus`/`XdndFinished` byte by
|
|
838
|
-
byte — and the mirror test can play a **fake target** against our source.
|
|
839
|
-
Both halves of the protocol are testable with no `$DISPLAY`.
|
|
840
|
-
- **Unit** — `text/uri-list` parsing (percent-encoding, CRLF, `#` comments,
|
|
841
|
-
remote hosts), the type-alias table against the recorded type lists in §4.1,
|
|
842
|
-
version negotiation, the `XdndFinished` watchdog.
|
|
843
|
-
- **Node-tree** — registry lifecycle across mount/update/unmount/hide,
|
|
844
|
-
enter/leave diffing, `:drag-over`, nested-`<window>` routing, and the
|
|
845
|
-
zero-target suppression path (assert exactly one `XdndStatus` per
|
|
846
|
-
window-entry).
|
|
847
|
-
- **Manual, and be honest about the platform** — Linux/XWayland: Nautilus,
|
|
848
|
-
Thunar, Dolphin, Firefox, Chromium, `mwh/dragon` both directions. macOS
|
|
849
|
-
XQuartz: X-client to X-client only; **Finder → X11 does not work and is not
|
|
850
|
-
our bug** ([XQuartz#173](https://github.com/XQuartz/XQuartz/issues/173)).
|
|
851
|
-
|
|
852
|
-
---
|
|
853
|
-
|
|
854
|
-
## 9. Hazards
|
|
855
|
-
|
|
856
|
-
- **A slow `onDrop` freezes the source app.** The watchdog is not optional.
|
|
857
|
-
- **Suppression rectangles are a footgun.** Freezing a zone that needed
|
|
858
|
-
per-position feedback produces a dropzone that looks broken only on slow
|
|
859
|
-
movements. Default off; `e.freeze()` opt-in; document the insertion-caret
|
|
860
|
-
case explicitly.
|
|
861
|
-
- **The source's window cache goes stale.** Windows move and stack during a
|
|
862
|
-
drag. Refresh on `SubstructureNotify`, and re-descend on `ConfigureNotify`
|
|
863
|
-
rather than trusting the cache for the whole gesture.
|
|
864
|
-
- **Our own preview window is under the pointer.** Skip our ids in the
|
|
865
|
-
descent (safe, because the grab makes us the only client searching).
|
|
866
|
-
- **Timestamps.** Using `CurrentTime` for `ConvertSelection` mostly works and
|
|
867
|
-
then fails against sources that validate — the worst failure profile there
|
|
868
|
-
is. Thread `XdndDrop`'s `l[2]` through. _Done:_ `read({ time })` landed in
|
|
869
|
-
ntk 5.4.0 and `_getData` passes the drop timestamp.
|
|
870
|
-
- **Selection ownership outlives the drag.** ntk's `Clipboard` holds a
|
|
871
|
-
selection until someone else takes it. An `XdndSelection` still owned after
|
|
872
|
-
the drag ends is mostly harmless, but a stale offer answered after
|
|
873
|
-
`XdndFinished` is the case the spec warns about ("throw out extremely old
|
|
874
|
-
data"); release it explicitly at drag end. _Done:_ `_disown()` calls
|
|
875
|
-
`clipboard.clear('XdndSelection')` (ntk 5.4.0), which releases with the
|
|
876
|
-
acquisition timestamp — so a drag that already lost the selection to
|
|
877
|
-
another client leaves it alone, where `CurrentTime` would have taken it
|
|
878
|
-
back from them.
|
|
879
|
-
- **Grabs versus `<popup>`.** Menus already take pointer grabs
|
|
880
|
-
(`window.js:2017`). A drag beginning inside an open menu must not fight
|
|
881
|
-
them; establish that the drag grab replaces the menu grab, and that closing
|
|
882
|
-
the menu does not release ours.
|
|
883
|
-
|
|
884
|
-
---
|
|
885
|
-
|
|
886
|
-
## 10. Answering the three questions directly
|
|
887
|
-
|
|
888
|
-
1. **Is #126's API right?** The protocol plan is; the API needs three changes:
|
|
889
|
-
ordinary host props instead of marker-carrying spread props (the existing
|
|
890
|
-
dispatcher already does the work), declarative `dropAccept` promoted from a
|
|
891
|
-
convenience to the mechanism that makes always-advertising safe, and no
|
|
892
|
-
`dataTransfer` façade over an asynchronous transfer. Plus parsed `files`,
|
|
893
|
-
a smarter `dropAccept` vocabulary, and the `XdndFinished` watchdog.
|
|
894
|
-
2. **Should the advertisement be lazy?** No — but the refcount you were
|
|
895
|
-
reaching for is real; point it at the suppression rectangle instead of at
|
|
896
|
-
the property. You get the same saving, with no race, no churn, and no
|
|
897
|
-
Offscreen bookkeeping. And because `XdndAware` lives on top-levels only,
|
|
898
|
-
none of this depends on the windowed/windowless decision from
|
|
899
|
-
[windowed-regions.md](windowed-regions.md) — including a `<box windowed>`
|
|
900
|
-
that document recommends never shipping.
|
|
901
|
-
3. **How well do the two sides work together?** Very well, if the drag session
|
|
902
|
-
is one object with two transports rather than two features. Target-side
|
|
903
|
-
routing and source-side target tracking are the same enter/leave/accept
|
|
904
|
-
state machine; in-app drags reuse it with no X11 at all; and an app written
|
|
905
|
-
against Phase 2 gains cross-application drops in Phase 3 without touching
|
|
906
|
-
its code. With ntk 5.3.0 the source half is no longer gated on an upstream
|
|
907
|
-
release ([§0.1](#01-audit-against-ntk-530)) — the only argument left for
|
|
908
|
-
splitting it out of #126 is size, not dependency.
|
|
909
|
-
|
|
910
|
-
---
|
|
911
|
-
|
|
912
|
-
## 11. Sources
|
|
913
|
-
|
|
914
|
-
- XDND protocol specification —
|
|
915
|
-
[freedesktop mirror](https://www.accum.se/~vatten/XDND.html) (v5 field
|
|
916
|
-
layouts, `XdndStatus` flag bits, `XdndFinished` v5 additions) and the
|
|
917
|
-
[tkDND copy](https://users.iit.demokritos.gr/~petasis/Tcl/tkDND/XDND_Protocol/xdnd.html)
|
|
918
|
-
(versions 0–4, property semantics, target non-response rules). Fields were
|
|
919
|
-
cross-checked between the two; they agree.
|
|
920
|
-
- [Drag-and-Drop Protocol for the X Window System](https://www.freedesktop.org/wiki/Specifications/XDND/)
|
|
921
|
-
— canonical home of the spec.
|
|
922
|
-
- [XQuartz#173 — drag and drop from Mac to X11 not working](https://github.com/XQuartz/XQuartz/issues/173)
|
|
923
|
-
- [`dnd-core`](https://github.com/react-dnd/dnd-core) — the DOM-free core
|
|
924
|
-
behind react-dnd's pluggable backends.
|
|
925
|
-
- [`mwh/dragon`](https://github.com/mwh/dragon) — a small XDND source/target,
|
|
926
|
-
useful as a manual test peer.
|
|
927
|
-
- ntk releases audited in §0.1:
|
|
928
|
-
[5.0.0](https://github.com/sidorares/ntk/releases/tag/v5.0.0) (buffered
|
|
929
|
-
requests), [5.2.0](https://github.com/sidorares/ntk/releases/tag/v5.2.0)
|
|
930
|
-
(`frameInFlight`),
|
|
931
|
-
[5.3.0](https://github.com/sidorares/ntk/releases/tag/v5.3.0) —
|
|
932
|
-
[ntk#164](https://github.com/sidorares/ntk/issues/164) (INCR write, any
|
|
933
|
-
format), [ntk#163](https://github.com/sidorares/ntk/issues/163) (selection
|
|
934
|
-
watch), [ntk#155](https://github.com/sidorares/ntk/issues/155) (cancellable
|
|
935
|
-
`close`).
|
|
936
|
-
- In-tree: [events.js](../../src/events.js), [nodes/window/droptarget.js](../../src/nodes/window/droptarget.js),
|
|
937
|
-
[Reconciler.js](../../src/Reconciler.js), [styles.js](../../src/styles.js),
|
|
938
|
-
[windowed-regions.md](windowed-regions.md), [ecosystem.md](../ecosystem.md);
|
|
939
|
-
ntk 5.3.0 `lib/clipboard.js`, `lib/window.js`, `lib/cursor.js`,
|
|
940
|
-
`lib/index.js`; node-x11 `lib/framebuffer.js`,
|
|
941
|
-
`lib/xserver/properties.js`, `lib/xcore.js`.
|