react-x11 2.16.1 → 2.17.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 +38 -23
- package/package.json +3 -1
- package/src/Reconciler.js +82 -23
- package/src/a11y.js +18 -1
- package/src/appcontext.js +8 -0
- package/src/appearance.js +36 -0
- package/src/{cocoa → backend}/context2d.js +27 -7
- package/src/capabilities.js +99 -1
- package/src/cocoa/app.js +8 -4
- package/src/cocoa/fonts.js +1 -1
- package/src/cocoa/overlay.js +2 -2
- package/src/cocoa/panewindow.js +2 -2
- package/src/cocoa/presenter.js +2 -2
- package/src/cocoa/surface.js +3 -3
- package/src/cocoa/window.js +2 -2
- package/src/events.js +21 -0
- package/src/foreignnodes.js +8 -3
- package/src/frame/index.js +30 -4
- package/src/glnodes.js +12 -1
- package/src/idle.js +59 -1
- package/src/index.d.ts +41 -0
- package/src/index.js +30 -3
- package/src/launcher.js +17 -8
- package/src/launcherhooks.js +24 -10
- package/src/node.d.ts +1 -1
- package/src/nodes/cascade.js +9 -0
- package/src/nodes/node.js +6 -1
- package/src/nodes/window/hints.js +21 -2
- package/src/nodes/window/window.js +2 -2
- package/src/notifications.js +39 -14
- package/src/taskbarhooks.js +164 -0
- package/src/transfer.js +20 -1
- package/src/trayhooks.js +1 -1
- package/src/types/capabilities.d.ts +32 -3
- package/src/types/elements.d.ts +23 -1
- package/src/types/events.d.ts +16 -0
- package/src/types/launcher.d.ts +20 -6
- package/src/types/taskbar.d.ts +79 -0
- package/src/wayland/context2d.js +1 -1
- package/src/win32/a11y.js +604 -0
- package/src/win32/app.js +768 -0
- package/src/win32/bezels.js +158 -0
- package/src/win32/dnd.js +283 -0
- package/src/win32/fonts.js +497 -0
- package/src/win32/glarea.js +548 -0
- package/src/win32/ime.js +267 -0
- package/src/win32/keymap.js +116 -0
- package/src/win32/native.js +54 -0
- package/src/win32/panehost.js +106 -0
- package/src/win32/panewindow.js +343 -0
- package/src/win32/shell.js +426 -0
- package/src/win32/surface.js +192 -0
- package/src/win32/window.js +659 -0
- package/src/windowid.js +66 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// Native control bezels — the visual styles engine's own pixels for the core
|
|
2
|
+
// controls, cached as surfaces the ordinary 2d paint path blits. This is the
|
|
3
|
+
// Windows counterpart of src/cocoa/bezels.js, and it answers the same
|
|
4
|
+
// `app.nativeBezels` contract: interaction, focus and keyboard stay the shared
|
|
5
|
+
// component implementation, and only the *bezel* is asked of the system.
|
|
6
|
+
//
|
|
7
|
+
// ## Light only, and why that is the honest answer
|
|
8
|
+
//
|
|
9
|
+
// Windows exposes the **dark** common controls through undocumented uxtheme
|
|
10
|
+
// ordinals. The public route is `SetWindowTheme(hwnd, 'DarkMode_CFD')`, and
|
|
11
|
+
// measured on Windows 11 build 26200 it reaches COMBOBOX and nothing else:
|
|
12
|
+
// BUTTON, the checkbox, the radio and the trackbar thumb all draw
|
|
13
|
+
// pixel-identical to their light selves (test/bezels.js in the bridge is that
|
|
14
|
+
// measurement, and it fails if a future build changes the answer).
|
|
15
|
+
//
|
|
16
|
+
// A light button on a dark window is worse than a drawn one, so this store is
|
|
17
|
+
// installed only while the desktop is light. In dark the widget set draws its
|
|
18
|
+
// own bezels — which still follow the system, because the palette's accent is
|
|
19
|
+
// the accent the appearance ladder read out of DWM.
|
|
20
|
+
//
|
|
21
|
+
// ## Nothing here is asked twice
|
|
22
|
+
//
|
|
23
|
+
// A bezel is a surface keyed by everything that changes its pixels. The Cocoa
|
|
24
|
+
// store has a `_drawLater` path because its bridge answers on another thread;
|
|
25
|
+
// here every draw is synchronous on the JS thread, so `get` always answers and
|
|
26
|
+
// the `onReady` callback is never needed.
|
|
27
|
+
|
|
28
|
+
/** The kinds the widget set asks for. `switch` is absent on purpose: the
|
|
29
|
+
* theme engine has no toggle-switch part — the WinUI one is not in it — so
|
|
30
|
+
* the component keeps drawing its own rather than being handed something that
|
|
31
|
+
* is not a switch. */
|
|
32
|
+
const KINDS = ['push', 'checkbox', 'radio', 'popup', 'slider'];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The heights a control is laid out at. A checkbox and a radio have a real
|
|
36
|
+
* intrinsic size and the theme reports it (13x13 at 96 dpi), so those are
|
|
37
|
+
* asked. A button and a combo box size to their content, and the theme's
|
|
38
|
+
* answer for them is a *minimum* — 13x11 for a push button, which as a
|
|
39
|
+
* footprint would be a button nobody could read — so the conventional Windows
|
|
40
|
+
* metrics are used instead.
|
|
41
|
+
*/
|
|
42
|
+
const HEIGHTS = {
|
|
43
|
+
push: { regular: 24, small: 20 },
|
|
44
|
+
popup: { regular: 24, small: 20 },
|
|
45
|
+
slider: { regular: 22, small: 18 },
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export class Win32Bezels {
|
|
49
|
+
constructor(native, scale = 1) {
|
|
50
|
+
this._native = native;
|
|
51
|
+
this._scale = scale;
|
|
52
|
+
/** key -> { surface, sx, sy, sw, sh } */
|
|
53
|
+
this._cache = new Map();
|
|
54
|
+
/** `${kind}:${controlSize}` -> { width, height } | null */
|
|
55
|
+
this._natural = new Map();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Dropped when the appearance changes, because every cached bezel was
|
|
59
|
+
* drawn in the old one. `cascade.js` calls this. */
|
|
60
|
+
clear() {
|
|
61
|
+
for (const entry of this._cache.values()) {
|
|
62
|
+
this._native.releaseSurface(entry.surface);
|
|
63
|
+
}
|
|
64
|
+
this._cache.clear();
|
|
65
|
+
this._natural.clear();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
natural(kind, controlSize = 'regular') {
|
|
69
|
+
const key = `${kind}:${controlSize}`;
|
|
70
|
+
if (this._natural.has(key)) return this._natural.get(key);
|
|
71
|
+
|
|
72
|
+
let answer = null;
|
|
73
|
+
if (KINDS.includes(kind)) {
|
|
74
|
+
const themed = this._native.bezelNatural(kind, false);
|
|
75
|
+
if (themed) {
|
|
76
|
+
const height = HEIGHTS[kind]?.[controlSize];
|
|
77
|
+
answer = height
|
|
78
|
+
? { width: themed.width, height }
|
|
79
|
+
: { width: themed.width, height: themed.height };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
this._natural.set(key, answer);
|
|
83
|
+
return answer;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Windows parts carry no translucent shadow band above or below the body,
|
|
87
|
+
* the way an AppKit push button does — the bezel is its own footprint. */
|
|
88
|
+
shadow() {
|
|
89
|
+
return { top: 0, bottom: 0 };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The bezel for these parameters at this size, as a surface and the rect
|
|
94
|
+
* inside it. Answers null for a kind with no part, which the component reads
|
|
95
|
+
* as "draw your own".
|
|
96
|
+
*/
|
|
97
|
+
get(params, width, height) {
|
|
98
|
+
const w = Math.max(1, Math.round(width));
|
|
99
|
+
const h = Math.max(1, Math.round(height));
|
|
100
|
+
if (!KINDS.includes(params.kind)) return null;
|
|
101
|
+
// A dark request should never reach here — the store is uninstalled in
|
|
102
|
+
// dark — but a palette pinned dark under a light desktop can ask, and a
|
|
103
|
+
// light bezel is not the answer to it.
|
|
104
|
+
if (params.appearance === 'dark') return null;
|
|
105
|
+
|
|
106
|
+
const key = [
|
|
107
|
+
params.kind,
|
|
108
|
+
params.controlSize ?? 'regular',
|
|
109
|
+
params.state ? 1 : 0,
|
|
110
|
+
params.pressed ? 1 : 0,
|
|
111
|
+
params.enabled === false ? 0 : 1,
|
|
112
|
+
params.isDefault ? 1 : 0,
|
|
113
|
+
w,
|
|
114
|
+
h,
|
|
115
|
+
].join(':');
|
|
116
|
+
const cached = this._cache.get(key);
|
|
117
|
+
if (cached) return cached;
|
|
118
|
+
|
|
119
|
+
const surface = this._native.createSurface(w, h, 1);
|
|
120
|
+
const drawn = this._native.bezelDraw(
|
|
121
|
+
surface,
|
|
122
|
+
params.kind,
|
|
123
|
+
{
|
|
124
|
+
enabled: params.enabled !== false,
|
|
125
|
+
pressed: Boolean(params.pressed),
|
|
126
|
+
checked: Boolean(params.state),
|
|
127
|
+
isDefault: Boolean(params.isDefault),
|
|
128
|
+
dark: false,
|
|
129
|
+
},
|
|
130
|
+
0,
|
|
131
|
+
0,
|
|
132
|
+
w,
|
|
133
|
+
h,
|
|
134
|
+
);
|
|
135
|
+
if (!drawn) {
|
|
136
|
+
this._native.releaseSurface(surface);
|
|
137
|
+
this._cache.set(key, null);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const entry = { surface, sx: 0, sy: 0, sw: w, sh: h };
|
|
142
|
+
this._cache.set(key, entry);
|
|
143
|
+
return entry;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The store, or null where native bezels would be worse than drawn ones:
|
|
149
|
+
* under a classic theme with no visual styles at all, and in dark, where the
|
|
150
|
+
* parts this backend can reach are light-only (see the header).
|
|
151
|
+
*/
|
|
152
|
+
export function createBezels(native, { colorScheme, scale = 1 } = {}) {
|
|
153
|
+
if (typeof native.themesActive !== 'function' || !native.themesActive()) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
if (colorScheme === 'dark') return null;
|
|
157
|
+
return new Win32Bezels(native, scale);
|
|
158
|
+
}
|
package/src/win32/dnd.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// Drag and drop on the win32 backend — the OLE transport over
|
|
2
|
+
// @windowkit/win32: `IDropTarget` on every window that has a `dropAccept`
|
|
3
|
+
// under it, `DoDragDrop` out of one. The `dropAccept` / `onDrag*` / `dragData`
|
|
4
|
+
// prop contract is the tree's (src/dnd.js); what this file owns is the
|
|
5
|
+
// translation between the shell's vocabulary and `DropSession`/`DragSession`'s,
|
|
6
|
+
// the same job src/cocoa/dnd.js does for AppKit and src/wayland/dnd.js for the
|
|
7
|
+
// data device.
|
|
8
|
+
//
|
|
9
|
+
// ## The answer, and when it is given
|
|
10
|
+
//
|
|
11
|
+
// AppKit asks its questions on the thread the tree lives on and wants the
|
|
12
|
+
// answer from inside the callback. The shell asks on the bridge's UI thread
|
|
13
|
+
// and wants it before `IDropTarget::DragOver` returns — and this tree is on
|
|
14
|
+
// another thread, so "from inside the callback" is not available.
|
|
15
|
+
//
|
|
16
|
+
// The bridge splits the difference by what each question is for (src/dnd.cc):
|
|
17
|
+
// a motion's answer picks a cursor and is allowed to be one motion stale, so
|
|
18
|
+
// the native returns the previous answer and never waits; a drop's answer
|
|
19
|
+
// decides whether the source deletes its original, so the native waits for
|
|
20
|
+
// this file to call `dropResponse`. Both arrive here as ordinary events and
|
|
21
|
+
// both are answered the same way — the difference is entirely on the other
|
|
22
|
+
// side, and the only thing it asks of this file is that the drop is answered
|
|
23
|
+
// **synchronously in the event handler**, which `DropSession.localDrop`
|
|
24
|
+
// already is.
|
|
25
|
+
//
|
|
26
|
+
// ## Types
|
|
27
|
+
//
|
|
28
|
+
// The shell has no registration step: a window that is a drop target is
|
|
29
|
+
// offered everything, and what it accepts is decided per motion. So
|
|
30
|
+
// `refreshTypes` has nothing to register — it turns the target on when any
|
|
31
|
+
// `dropAccept` exists under the window and off when the last one goes, which
|
|
32
|
+
// is what keeps the shell from offering a cursor over a window that would
|
|
33
|
+
// refuse everything.
|
|
34
|
+
//
|
|
35
|
+
// Clipboard formats map to MIME in the bridge. `text/uri-list` is CF_HDROP,
|
|
36
|
+
// which is what a Explorer file drag arrives as, and anything the table does
|
|
37
|
+
// not name is a registered format named by the MIME string — two react-x11
|
|
38
|
+
// apps agree on it for free, like an X11 atom, and nothing else will.
|
|
39
|
+
//
|
|
40
|
+
// ## The source
|
|
41
|
+
//
|
|
42
|
+
// `beginDrag` resolves every type's data **up front** and hands the bytes
|
|
43
|
+
// over, then `DoDragDrop` runs its modal loop on the UI thread. That is a
|
|
44
|
+
// real difference from the cocoa transport, which keeps a thunk as a promise
|
|
45
|
+
// the bridge redeems when the receiver asks: here a thunk is called at the
|
|
46
|
+
// start of the drag instead. The reason is the thread split — a data object
|
|
47
|
+
// that asked JS for its bytes from inside the modal loop would be reaching
|
|
48
|
+
// across it at the one moment the UI thread cannot wait — and the cost is
|
|
49
|
+
// that a `dragData` thunk is not lazy on this backend.
|
|
50
|
+
//
|
|
51
|
+
// What the split buys back is the thing it was built for: JS keeps rendering
|
|
52
|
+
// for the whole drag. On the cocoa backend AppKit owns the thread from the
|
|
53
|
+
// threshold to the drop and no frame clock ticks in between.
|
|
54
|
+
const DEBUG = process.env.REACT_X11_WIN32_DEBUG === '1';
|
|
55
|
+
|
|
56
|
+
import {
|
|
57
|
+
TEXT_TARGETS,
|
|
58
|
+
TYPE_GROUPS,
|
|
59
|
+
parseUriList,
|
|
60
|
+
resolveType,
|
|
61
|
+
} from '../transfer.js';
|
|
62
|
+
|
|
63
|
+
/** Bytes for the wire: a string goes as UTF-8, anything else as it is. */
|
|
64
|
+
function wire(value) {
|
|
65
|
+
if (value == null) return '';
|
|
66
|
+
if (typeof value === 'string') return value;
|
|
67
|
+
if (Buffer.isBuffer(value)) return value;
|
|
68
|
+
if (ArrayBuffer.isView(value)) {
|
|
69
|
+
return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
70
|
+
}
|
|
71
|
+
return String(value);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* What `beginDrag` hands the bridge: every type with its bytes already
|
|
76
|
+
* resolved, and the actions the gesture allows.
|
|
77
|
+
*/
|
|
78
|
+
export function dragSpec(session) {
|
|
79
|
+
const items = [];
|
|
80
|
+
for (const type of session.types) {
|
|
81
|
+
const raw = session._resolve(type);
|
|
82
|
+
if (raw == null) continue;
|
|
83
|
+
items.push({ type, data: wire(raw) });
|
|
84
|
+
}
|
|
85
|
+
return { items, actions: session.actions ?? ['copy'] };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* One window's drop side: the four events turned into `DropSession` calls,
|
|
90
|
+
* with the answer sent back before the handler returns.
|
|
91
|
+
*/
|
|
92
|
+
export class Win32DropTransport {
|
|
93
|
+
constructor(wnd, session, node) {
|
|
94
|
+
this.wnd = wnd;
|
|
95
|
+
this.session = session;
|
|
96
|
+
this.node = node;
|
|
97
|
+
this._enabled = null;
|
|
98
|
+
this.refreshTypes();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The shell registers no types, so this is the on/off switch: a window with
|
|
103
|
+
* nothing that would accept a drop is not a drop target at all, and the
|
|
104
|
+
* shell shows the "no" cursor over it without asking anybody.
|
|
105
|
+
*/
|
|
106
|
+
refreshTypes() {
|
|
107
|
+
const wanted =
|
|
108
|
+
(this.node._dndConcreteTypes?.() ?? []).length > 0 ||
|
|
109
|
+
this.node._dnd != null;
|
|
110
|
+
if (wanted === this._enabled) return;
|
|
111
|
+
this._enabled = wanted;
|
|
112
|
+
if (DEBUG) console.error(`[win32] dropTarget ${this.wnd.id} -> ${wanted}`);
|
|
113
|
+
this.wnd._native.dropTargetEnable(this.wnd.id, wanted);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Register again now that there is an HWND to register on.
|
|
118
|
+
*
|
|
119
|
+
* The tree mounts its `dropAccept`s in the turn the window is asked for,
|
|
120
|
+
* and this bridge creates a window asynchronously — `createWindow` answers
|
|
121
|
+
* with an id and the HWND appears later. So the first registration lands
|
|
122
|
+
* on a window that does not exist yet and does nothing, quietly, and the
|
|
123
|
+
* only symptom is a window the shell never offers a drag to.
|
|
124
|
+
*/
|
|
125
|
+
reattach() {
|
|
126
|
+
this._enabled = null;
|
|
127
|
+
this.refreshTypes();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
handle(ev) {
|
|
131
|
+
if (DEBUG)
|
|
132
|
+
console.error(`[win32] ${ev.type} ${ev.a},${ev.b} "${ev.text ?? ''}"`);
|
|
133
|
+
switch (ev.type) {
|
|
134
|
+
case 'drag-enter':
|
|
135
|
+
case 'drag-over':
|
|
136
|
+
return this._over(ev);
|
|
137
|
+
case 'drag-leave':
|
|
138
|
+
return this._leave();
|
|
139
|
+
case 'drag-drop':
|
|
140
|
+
return this._drop(ev);
|
|
141
|
+
default:
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** The live drag in this process, when this drop is our own gesture coming
|
|
147
|
+
* back over one of our windows. */
|
|
148
|
+
_local() {
|
|
149
|
+
return this.wnd.app._activeDrag ?? null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
_types(ev) {
|
|
153
|
+
return String(ev.text ?? '')
|
|
154
|
+
.split('\n')
|
|
155
|
+
.filter(Boolean);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** What the shell says the source allows, in the tree's vocabulary. The
|
|
159
|
+
* order is the preference: a source offering both copy and move means
|
|
160
|
+
* move, which is what every other transport reports. */
|
|
161
|
+
_actions(effects) {
|
|
162
|
+
const allowed = [];
|
|
163
|
+
if (effects & 2) allowed.push('move');
|
|
164
|
+
if (effects & 1) allowed.push('copy');
|
|
165
|
+
if (effects & 4) allowed.push('link');
|
|
166
|
+
return allowed;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
_offer(ev) {
|
|
170
|
+
const drag = this._local();
|
|
171
|
+
if (drag) return drag._offer();
|
|
172
|
+
const actions = this._actions(ev.c ?? 0);
|
|
173
|
+
return {
|
|
174
|
+
types: this._types(ev),
|
|
175
|
+
action: actions[0] ?? 'copy',
|
|
176
|
+
source: 'external',
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The answer, and which question it answers.
|
|
182
|
+
*
|
|
183
|
+
* `forDrop` is not a detail: the bridge's UI thread is blocked inside
|
|
184
|
+
* `IDropTarget::Drop` waiting for one, and the answers to the last few
|
|
185
|
+
* motions are still arriving behind it — JS is a queue or two back by
|
|
186
|
+
* then. Without the flag the first of those would release the wait, and
|
|
187
|
+
* the drop would be decided by a motion's answer.
|
|
188
|
+
*/
|
|
189
|
+
_answer(answer, forDrop = false) {
|
|
190
|
+
this.wnd._native.dropResponse(
|
|
191
|
+
this.wnd.id,
|
|
192
|
+
Boolean(answer.accepted ?? answer.handled),
|
|
193
|
+
answer.action ?? 'copy',
|
|
194
|
+
forDrop,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
_over(ev) {
|
|
199
|
+
const drag = this._local();
|
|
200
|
+
const offer = this._offer(ev);
|
|
201
|
+
const answer = this.session.localOver(ev.a, ev.b, offer, Date.now());
|
|
202
|
+
if (drag) {
|
|
203
|
+
drag.accepted = answer.accepted;
|
|
204
|
+
if (answer.accepted) drag.currentAction = answer.action;
|
|
205
|
+
}
|
|
206
|
+
this._answer(answer);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
_leave() {
|
|
210
|
+
const drag = this._local();
|
|
211
|
+
this.session.localLeave();
|
|
212
|
+
if (drag) drag.accepted = false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
_drop(ev) {
|
|
216
|
+
const drag = this._local();
|
|
217
|
+
const offer = this._offer(ev);
|
|
218
|
+
// The bridge read every format while the data object was alive; this asks
|
|
219
|
+
// it for the ones the tree wants. A local drop keeps `e.items` by
|
|
220
|
+
// reference instead, so an in-app drop never round-trips through bytes.
|
|
221
|
+
const extras = drag ? drag._dropExtras() : this._payload(offer.types);
|
|
222
|
+
let outcome = { handled: false, action: null };
|
|
223
|
+
try {
|
|
224
|
+
outcome = this.session.localDrop(offer, extras, Date.now());
|
|
225
|
+
} catch (err) {
|
|
226
|
+
if (DEBUG) console.error(`[win32] localDrop threw: ${err?.stack ?? err}`);
|
|
227
|
+
throw err;
|
|
228
|
+
} finally {
|
|
229
|
+
// Answered in a `finally` because the bridge's UI thread is blocked
|
|
230
|
+
// inside `IDropTarget::Drop` waiting for it. A handler that throws must
|
|
231
|
+
// not leave it there for the whole two-second backstop — the pointer is
|
|
232
|
+
// captured for as long as the drop is open.
|
|
233
|
+
this._answer(outcome, true);
|
|
234
|
+
}
|
|
235
|
+
if (DEBUG) console.error(`[win32] drop outcome ${JSON.stringify(outcome)}`);
|
|
236
|
+
if (drag && outcome.handled) drag.currentAction = outcome.action;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* What the bridge read at the drop, in the shape `localDrop` takes — the
|
|
241
|
+
* same four fields the cocoa transport builds from a pasteboard.
|
|
242
|
+
*
|
|
243
|
+
* Textual types come back as strings and everything else as the bytes the
|
|
244
|
+
* source put on the clipboard: a `text/uri-list` that was decoded from
|
|
245
|
+
* CF_HDROP is text by the time it gets here, and an
|
|
246
|
+
* `application/x-myapp-thing` is whatever its owner wrote.
|
|
247
|
+
*/
|
|
248
|
+
_payload(types) {
|
|
249
|
+
const values = {};
|
|
250
|
+
for (const type of types) {
|
|
251
|
+
const bytes = this.wnd._native.dropData(type);
|
|
252
|
+
if (!bytes) continue;
|
|
253
|
+
values[type] = this._textual(type) ? bytes.toString('utf8') : bytes;
|
|
254
|
+
}
|
|
255
|
+
if (
|
|
256
|
+
values['text/plain;charset=utf-8'] !== undefined &&
|
|
257
|
+
values['text/plain'] === undefined
|
|
258
|
+
) {
|
|
259
|
+
values['text/plain'] = values['text/plain;charset=utf-8'];
|
|
260
|
+
}
|
|
261
|
+
const best = TEXT_TARGETS.find((t) => values[t] !== undefined);
|
|
262
|
+
return {
|
|
263
|
+
items: values,
|
|
264
|
+
files: values['text/uri-list']
|
|
265
|
+
? parseUriList(values['text/uri-list'])
|
|
266
|
+
: [],
|
|
267
|
+
text: best ? values[best] : undefined,
|
|
268
|
+
// A promise, like the cocoa transport's: the payload is already in
|
|
269
|
+
// hand, and answering synchronously here would make this the one
|
|
270
|
+
// backend where `await e.getData(…)` was not needed.
|
|
271
|
+
getData: (type) =>
|
|
272
|
+
Promise.resolve(values[resolveType(type, types)] ?? null),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
_textual(type) {
|
|
277
|
+
return (
|
|
278
|
+
TEXT_TARGETS.includes(type) ||
|
|
279
|
+
TYPE_GROUPS.files.includes(type) ||
|
|
280
|
+
type.startsWith('text/')
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|