react-x11 2.16.0 → 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.
Files changed (62) hide show
  1. package/README.md +38 -23
  2. package/package.json +3 -1
  3. package/src/Reconciler.js +82 -23
  4. package/src/a11y.js +18 -1
  5. package/src/acceleratorhooks.js +40 -6
  6. package/src/anchor.js +20 -2
  7. package/src/appcontext.js +8 -0
  8. package/src/appearance.js +36 -0
  9. package/src/{cocoa → backend}/context2d.js +27 -7
  10. package/src/capabilities.js +99 -1
  11. package/src/cocoa/app.js +204 -6
  12. package/src/cocoa/fonts.js +1 -1
  13. package/src/cocoa/overlay.js +2 -2
  14. package/src/cocoa/panewindow.js +2 -2
  15. package/src/cocoa/presenter.js +2 -2
  16. package/src/cocoa/surface.js +3 -3
  17. package/src/cocoa/window.js +2 -2
  18. package/src/events.js +21 -0
  19. package/src/foreignnodes.js +8 -3
  20. package/src/frame/index.js +30 -4
  21. package/src/glnodes.js +12 -1
  22. package/src/idle.js +59 -1
  23. package/src/index.d.ts +51 -1
  24. package/src/index.js +30 -3
  25. package/src/keysymchars.js +47 -0
  26. package/src/keysyms.d.ts +19 -1
  27. package/src/keysyms.js +107 -8
  28. package/src/launcher.js +17 -8
  29. package/src/launcherhooks.js +24 -10
  30. package/src/node.d.ts +1 -1
  31. package/src/nodes/cascade.js +9 -0
  32. package/src/nodes/node.js +6 -1
  33. package/src/nodes/window/hints.js +21 -2
  34. package/src/nodes/window/window.js +2 -2
  35. package/src/notifications.js +39 -14
  36. package/src/screens.js +159 -24
  37. package/src/taskbarhooks.js +164 -0
  38. package/src/transfer.js +20 -1
  39. package/src/trayhooks.js +1 -1
  40. package/src/types/capabilities.d.ts +32 -3
  41. package/src/types/elements.d.ts +23 -1
  42. package/src/types/events.d.ts +21 -0
  43. package/src/types/filedialog.d.ts +3 -1
  44. package/src/types/launcher.d.ts +20 -6
  45. package/src/types/taskbar.d.ts +79 -0
  46. package/src/wayland/context2d.js +1 -1
  47. package/src/wayland/xkb.js +170 -59
  48. package/src/win32/a11y.js +604 -0
  49. package/src/win32/app.js +768 -0
  50. package/src/win32/bezels.js +158 -0
  51. package/src/win32/dnd.js +283 -0
  52. package/src/win32/fonts.js +497 -0
  53. package/src/win32/glarea.js +548 -0
  54. package/src/win32/ime.js +267 -0
  55. package/src/win32/keymap.js +116 -0
  56. package/src/win32/native.js +54 -0
  57. package/src/win32/panehost.js +106 -0
  58. package/src/win32/panewindow.js +343 -0
  59. package/src/win32/shell.js +426 -0
  60. package/src/win32/surface.js +192 -0
  61. package/src/win32/window.js +659 -0
  62. package/src/windowid.js +128 -20
@@ -0,0 +1,267 @@
1
+ // The input method, as the tree sees it.
2
+ //
3
+ // The bridge answers the IMM32 messages and hands over four events
4
+ // (windows/src/ime.cc); this turns them into the composition events the
5
+ // renderer already has — `CompositionStart`, `CompositionUpdate`,
6
+ // `CompositionEnd` — so that a `<textinput>` on Windows composes through
7
+ // exactly the machinery a `<textinput>` on Wayland composes through, and an
8
+ // application that handles one handles both.
9
+ //
10
+ // The order is the one `src/wayland/textinput.js` `_apply` fixes, because it
11
+ // is the order the events are defined in rather than a detail of either
12
+ // protocol: **the commit first, then the new preedit.** One
13
+ // WM_IME_COMPOSITION can carry both, and together they mean "this text is
14
+ // settled, and this is what is still being typed".
15
+ //
16
+ // The other half is telling the IME where to put its candidate list. The
17
+ // preedit is drawn in the field (src/nodes/preedit.js), so the candidate list
18
+ // is the only thing the IME still draws, and it has to sit at the caret
19
+ // rather than in the corner of the screen. `sync()` is what keeps it there:
20
+ // the frame loop calls it after each tick, when the layout the caret is read
21
+ // off is the one just painted.
22
+ import { runWithPriority, DiscreteEventPriority } from '../priority.js';
23
+
24
+ const TRACE = process.env.REACT_X11_TRACE_IME === '1';
25
+
26
+ /** How many code points `units` UTF-16 code units cover.
27
+ *
28
+ * The bridge counts in code units because that is what IMM32 hands it, and
29
+ * the tree counts in code points. JS strings are UTF-16, so slicing at the
30
+ * bridge's offset and counting the result is the whole conversion — and it is
31
+ * done here rather than there because here is where the string is. */
32
+ function codePointsIn(text, units) {
33
+ if (!(units > 0)) return 0;
34
+ return Array.from(text.slice(0, units)).length;
35
+ }
36
+
37
+ export class Win32InputMethod {
38
+ constructor(app) {
39
+ this.app = app;
40
+ this._native = app._native;
41
+ /** The field the IME is enabled for: `{ wnd, node, rect }`, or null. */
42
+ this.active = null;
43
+ /** Whether the tree has heard a `CompositionStart` it is owed an end for. */
44
+ this.composing = false;
45
+ }
46
+
47
+ // --- what the tree says ---------------------------------------------------
48
+
49
+ /**
50
+ * The focused text control of a window, or null.
51
+ *
52
+ * The duck type is `defaultComposition` + `textCaretRect`, which is what
53
+ * `<textinput>` and `<textarea>` implement and what a registered element
54
+ * with a caret of its own would implement to be reached the same way —
55
+ * the same test `src/wayland/textinput.js` makes, deliberately.
56
+ *
57
+ * A `sensitive` field answers null: a password is not offered to an input
58
+ * method, whose word history is one more place it must not reach. Windows
59
+ * does the same for its own password boxes.
60
+ */
61
+ _focusedTextControl(wnd) {
62
+ const node = wnd._reactX11Node?.events?.focusManager?.focused ?? null;
63
+ if (!node || node.destroyed) return null;
64
+ if (node.root?.window !== wnd) return null;
65
+ if (typeof node.defaultComposition !== 'function') return null;
66
+ if (typeof node.textCaretRect !== 'function') return null;
67
+ if (node.composes === false) return null;
68
+ if (node.props?.sensitive) return null;
69
+ return node;
70
+ }
71
+
72
+ /**
73
+ * Where the caret is, in the window's client pixels — which is the space
74
+ * the tree is already in on this backend (a mouse event's `x` reaches the
75
+ * tree exactly as the bridge sent it), so there is no scale to divide out
76
+ * and no frame inset to add. A field whose text has no layout yet offers
77
+ * its content box, which at least puts the list under the field.
78
+ */
79
+ _caretRect(wnd, node) {
80
+ let rect = null;
81
+ try {
82
+ const chars = Array.from(node.value ?? '');
83
+ const caret = Number.isInteger(node._caret)
84
+ ? Math.max(0, Math.min(node._caret, chars.length))
85
+ : chars.length;
86
+ rect = node.textCaretRect(caret);
87
+ } catch {
88
+ rect = null;
89
+ }
90
+ if (!rect) {
91
+ const box = node.contentBox?.() ?? node.abs;
92
+ if (!box) return null;
93
+ rect = { x: box.x, y: box.y, width: 0, height: box.height };
94
+ }
95
+ return {
96
+ x: Math.round(rect.x),
97
+ y: Math.round(rect.y),
98
+ width: Math.max(1, Math.round(rect.width ?? 0)),
99
+ height: Math.max(1, Math.round(rect.height ?? 0)),
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Bring the IME up to date about a window's focused field.
105
+ *
106
+ * Called at the end of every frame the window ran, which is cheap when
107
+ * nothing changed: one duck-type check and four comparisons. Nothing is
108
+ * sent to the UI thread unless the field or the caret actually moved.
109
+ */
110
+ sync(wnd) {
111
+ if (this._destroyed || !wnd || wnd._destroyed) return;
112
+ const node = this._focusedTextControl(wnd);
113
+ if (node !== this.active?.node) {
114
+ if (this.active) this._disable();
115
+ if (node) this._enable(wnd, node);
116
+ return;
117
+ }
118
+ if (node) this._place(wnd, node);
119
+ }
120
+
121
+ _enable(wnd, node) {
122
+ this.active = { wnd, node, rect: null };
123
+ this.composing = false;
124
+ this._native.imeEnable(wnd.id, true);
125
+ this._place(wnd, node);
126
+ if (TRACE) trace(`enable <${node.kind}>`);
127
+ }
128
+
129
+ /**
130
+ * Take the IME off this window, and end the composition the tree is owed.
131
+ *
132
+ * The field's own blur has already cleared the preedit it was drawing; what
133
+ * is owed is the `CompositionEnd` an application that saw the start is
134
+ * waiting for, with no data — which is how an abandoned composition has
135
+ * always ended.
136
+ */
137
+ _disable() {
138
+ const active = this.active;
139
+ this.active = null;
140
+ if (!active) return;
141
+ if (this.composing) {
142
+ this.composing = false;
143
+ const node = active.node;
144
+ const events = node.destroyed ? null : node.root?.events;
145
+ if (events) {
146
+ runWithPriority(DiscreteEventPriority, () =>
147
+ events._composition('End', node, '', null),
148
+ );
149
+ }
150
+ }
151
+ this._native.imeEnable(active.wnd.id, false);
152
+ if (TRACE) trace('disable');
153
+ }
154
+
155
+ /** Move the candidate list to the caret, if it is not already there. */
156
+ _place(wnd, node) {
157
+ const rect = this._caretRect(wnd, node);
158
+ const was = this.active?.rect;
159
+ if (!rect) return;
160
+ if (
161
+ was &&
162
+ was.x === rect.x &&
163
+ was.y === rect.y &&
164
+ was.width === rect.width &&
165
+ was.height === rect.height
166
+ ) {
167
+ return;
168
+ }
169
+ if (this.active) this.active.rect = rect;
170
+ this._native.imeCaret(wnd.id, rect.x, rect.y, rect.width, rect.height);
171
+ }
172
+
173
+ // --- what the input method says -------------------------------------------
174
+
175
+ /**
176
+ * One `ime-*` event from the bridge.
177
+ *
178
+ * Every one is dispatched at `DiscreteEventPriority`: a composition is a
179
+ * keystroke's worth of user intent and must not be batched behind a
180
+ * transition, exactly as the Wayland transport dispatches its own.
181
+ */
182
+ handle(event, wnd) {
183
+ const active = this.active;
184
+ if (!active || active.wnd !== wnd) return;
185
+ const node = active.node;
186
+ if (node.destroyed) return;
187
+ const events = node.root?.events;
188
+ if (!events) return;
189
+
190
+ // There is no key behind an input method's commit, which is what a
191
+ // handler reading `nativeEvent` needs to be told.
192
+ const native = { type: 'ime' };
193
+
194
+ switch (event.type) {
195
+ case 'ime-start':
196
+ // The renderer's `CompositionStart` is raised lazily, with the first
197
+ // text — the same as every other backend, so that a composition that
198
+ // begins and ends with nothing in it never reaches an application.
199
+ // This only clears a flag a previous session could have left.
200
+ if (TRACE) trace('start');
201
+ return;
202
+
203
+ case 'ime-commit': {
204
+ const text = event.text ?? '';
205
+ if (!text) return;
206
+ if (TRACE) trace(`commit ${JSON.stringify(text)}`);
207
+ runWithPriority(DiscreteEventPriority, () => {
208
+ if (!this.composing) events._composition('Start', node, '', native);
209
+ this.composing = false;
210
+ // `defaultComposition` inserts it the way a typed character is
211
+ // inserted — through `_insert`, with `maxLength`, `onChange` and
212
+ // the undo run all in play.
213
+ events._composition('End', node, text, native);
214
+ });
215
+ return;
216
+ }
217
+
218
+ case 'ime-preedit': {
219
+ const text = event.text ?? '';
220
+ if (TRACE)
221
+ trace(`preedit ${JSON.stringify(text)} ${event.a}..${event.b}`);
222
+ runWithPriority(DiscreteEventPriority, () => {
223
+ if (text) {
224
+ if (!this.composing) {
225
+ events._composition('Start', node, '', native);
226
+ this.composing = true;
227
+ }
228
+ // The clause the IME is converting, which is what it wants drawn
229
+ // as the selection inside the preedit.
230
+ const cursor = {
231
+ cursorBegin: codePointsIn(text, event.a),
232
+ cursorEnd: codePointsIn(text, event.b),
233
+ };
234
+ events._composition('Update', node, text, native, cursor);
235
+ } else if (this.composing) {
236
+ this.composing = false;
237
+ events._composition('End', node, '', native);
238
+ }
239
+ });
240
+ return;
241
+ }
242
+
243
+ case 'ime-end':
244
+ // Usually nothing to do: the commit or an emptied preedit has already
245
+ // ended it. An IME that cancels without either lands here.
246
+ if (!this.composing) return;
247
+ if (TRACE) trace('end');
248
+ runWithPriority(DiscreteEventPriority, () => {
249
+ this.composing = false;
250
+ events._composition('End', node, '', native);
251
+ });
252
+ return;
253
+
254
+ default:
255
+ return;
256
+ }
257
+ }
258
+
259
+ destroy() {
260
+ this._destroyed = true;
261
+ this.active = null;
262
+ }
263
+ }
264
+
265
+ function trace(line) {
266
+ process.stderr.write(`react-x11 win32: ime ${line}\n`);
267
+ }
@@ -0,0 +1,116 @@
1
+ // Windows key events -> the renderer's keysym vocabulary (src/keysyms.js).
2
+ //
3
+ // The rule mirrors X's own, as the Cocoa map does: a Latin-1 keysym is its
4
+ // code point, anything else printable is 0x01000000 + code point, and the
5
+ // editing and navigation keys have fixed keysyms looked up here by virtual
6
+ // key code.
7
+ //
8
+ // The bridge has already done the part only it can do — asking the active
9
+ // layout what the key types, once with the modifiers that are down and once
10
+ // with none (src/win32.cc, EmitKey). That gives the two character roles
11
+ // events.js reads:
12
+ //
13
+ // baseCodepoint (nothing held) -> baseKeysym — what chords match
14
+ // codepoint (Shift/AltGr held) -> keysym / codepoint — what was typed
15
+ //
16
+ // A key that types nothing reports 0 for both, and the table below is what
17
+ // gives it a keysym at all. Tab is the one to keep an eye on: it *does* type
18
+ // a character on Windows (U+0009), and letting the character rule have it
19
+ // would make the keysym 9 instead of XK_Tab — which is why the table wins.
20
+ import { keysymOf, MOD } from '../keysyms.js';
21
+
22
+ // Virtual key codes -> X keysyms, for keys whose character is a control code
23
+ // or nothing at all.
24
+ const VK_KEYSYMS = new Map([
25
+ [0x08, 0xff08], // Backspace
26
+ [0x09, 0xff09], // Tab
27
+ [0x0d, 0xff0d], // Return
28
+ [0x13, 0xff13], // Pause
29
+ [0x14, 0xffe5], // Caps Lock
30
+ [0x1b, 0xff1b], // Escape
31
+ [0x21, 0xff55], // Page Up
32
+ [0x22, 0xff56], // Page Down
33
+ [0x23, 0xff57], // End
34
+ [0x24, 0xff50], // Home
35
+ [0x25, 0xff51], // Left
36
+ [0x26, 0xff52], // Up
37
+ [0x27, 0xff53], // Right
38
+ [0x28, 0xff54], // Down
39
+ [0x2c, 0xfd1d], // Print Screen -> XK_3270_PrintScreen
40
+ [0x2d, 0xff63], // Insert
41
+ [0x2e, 0xffff], // Delete
42
+ [0x5b, 0xffeb], // Left Super
43
+ [0x5c, 0xffec], // Right Super
44
+ [0x5d, 0xff67], // Menu
45
+ [0x90, 0xff7f], // Num Lock
46
+ [0x91, 0xff14], // Scroll Lock
47
+ [0xa0, 0xffe1], // Left Shift
48
+ [0xa1, 0xffe2], // Right Shift
49
+ [0xa2, 0xffe3], // Left Control
50
+ [0xa3, 0xffe4], // Right Control
51
+ [0xa4, 0xffe9], // Left Alt
52
+ [0xa5, 0xffea], // Right Alt
53
+ // The keypad, which has keysyms of its own so that a caret can tell
54
+ // KP_Left from Left even when Num Lock is off.
55
+ [0x6a, 0xffaa], // KP_Multiply
56
+ [0x6b, 0xffab], // KP_Add
57
+ [0x6d, 0xffad], // KP_Subtract
58
+ [0x6e, 0xffae], // KP_Decimal
59
+ [0x6f, 0xffaf], // KP_Divide
60
+ ]);
61
+
62
+ // F1..F24 are contiguous on both sides, so they are a range rather than 24
63
+ // table entries.
64
+ const VK_F1 = 0x70;
65
+ const XK_F1 = 0xffbe;
66
+
67
+ // The keypad digits, likewise: VK_NUMPAD0..9 -> XK_KP_0..9.
68
+ const VK_NUMPAD0 = 0x60;
69
+ const XK_KP_0 = 0xffb0;
70
+
71
+ function fixedKeysym(vk) {
72
+ const known = VK_KEYSYMS.get(vk);
73
+ if (known) return known;
74
+ if (vk >= VK_F1 && vk <= VK_F1 + 23) return XK_F1 + (vk - VK_F1);
75
+ if (vk >= VK_NUMPAD0 && vk <= VK_NUMPAD0 + 9)
76
+ return XK_KP_0 + (vk - VK_NUMPAD0);
77
+ return 0;
78
+ }
79
+
80
+ const keysymFromCodepoint = (cp) =>
81
+ cp && cp >= 0x20 ? keysymOf(String.fromCodePoint(cp)) : 0;
82
+
83
+ /**
84
+ * The three key facts events.js reads off a native key event, from the
85
+ * bridge's payload: `a` the virtual key, `b` what it typed, `c` what it
86
+ * would type with nothing held.
87
+ */
88
+ export function decodeKey(event) {
89
+ const vk = event.a;
90
+ const fixed = fixedKeysym(vk);
91
+ if (fixed) {
92
+ // A key with a fixed keysym reports no code point even when Windows
93
+ // says it types one. Tab and Return type U+0009 and U+000D, and letting
94
+ // those through would insert a control character into every text field
95
+ // the moment a caret was in one.
96
+ return { keysym: fixed, baseKeysym: fixed, codepoint: undefined };
97
+ }
98
+ const keysym = keysymFromCodepoint(event.b);
99
+ const baseKeysym = keysymFromCodepoint(event.c) || keysym;
100
+ return {
101
+ keysym: keysym || baseKeysym,
102
+ baseKeysym,
103
+ codepoint: event.b ? event.b : undefined,
104
+ };
105
+ }
106
+
107
+ /** The bridge's modifier bits -> the X-style state mask events carry. */
108
+ export function modifierMask(bits) {
109
+ let mask = 0;
110
+ if (bits & 1) mask |= MOD.Shift;
111
+ if (bits & 2) mask |= MOD.Control;
112
+ if (bits & 4) mask |= MOD.Alt;
113
+ if (bits & 8) mask |= MOD.Super;
114
+ if (bits & 16) mask |= MOD.Lock;
115
+ return mask;
116
+ }
@@ -0,0 +1,54 @@
1
+ // The bridge to @windowkit/win32 — the Windows backend's native half.
2
+ // Resolved lazily so that importing react-x11 anywhere else costs nothing;
3
+ // `createRoot` only walks in here once the backend decision has landed on
4
+ // 'win32'. That laziness is what lets the package be an *optional* dependency:
5
+ // off Windows npm skips it (its `os` field is win32-only) and nothing here
6
+ // ever asks for it.
7
+ //
8
+ // Resolution order: `REACT_X11_WIN32_PATH` (a checkout, for development), then
9
+ // the installed `@windowkit/win32`. This mirrors src/cocoa/native.js down to
10
+ // the loader, including why `createRequire` is made from `process.execPath`
11
+ // when there is no module URL — a single-executable build (docs/packaging.md
12
+ // tier 3) is CommonJS, and esbuild leaves `import.meta` an empty object there.
13
+ import { createRequire } from 'node:module';
14
+
15
+ const PACKAGE = '@windowkit/win32';
16
+
17
+ let require = null;
18
+ function load(spec) {
19
+ require ??= createRequire(import.meta.url ?? process.execPath);
20
+ return require(spec);
21
+ }
22
+
23
+ let cached = null;
24
+
25
+ export function loadNative() {
26
+ if (cached) return cached;
27
+ if (process.platform !== 'win32') {
28
+ throw new Error(
29
+ "react-x11: the 'win32' backend is Windows-only. On this platform use " +
30
+ 'the X11 backend (the default when DISPLAY is set), or pass ' +
31
+ "createRoot({ backend: 'x11' }).",
32
+ );
33
+ }
34
+ const tried = [];
35
+ const path = process.env.REACT_X11_WIN32_PATH;
36
+ for (const spec of [path, PACKAGE].filter(Boolean)) {
37
+ try {
38
+ const mod = load(spec);
39
+ cached = mod.native ?? mod;
40
+ return cached;
41
+ } catch (err) {
42
+ tried.push(` ${spec}: ${err.message}`);
43
+ }
44
+ }
45
+ throw new Error(
46
+ `react-x11: the win32 backend needs the ${PACKAGE} native bridge and ` +
47
+ 'none could be loaded:\n' +
48
+ tried.join('\n') +
49
+ `\nInstall it with \`npm install ${PACKAGE}\` (Windows; prebuilds ship ` +
50
+ 'for x64 and ARM64, and building from source needs Visual Studio with ' +
51
+ 'the "Desktop development with C++" workload). To use X11 instead, set ' +
52
+ "DISPLAY and pass createRoot({ backend: 'x11' }).",
53
+ );
54
+ }
@@ -0,0 +1,106 @@
1
+ // The host's half of a Windows frame pane: one visual over the window's
2
+ // content — the same stacking a `<glarea>` gets — whose content is the
3
+ // buffer the pane process is drawing into. The pane owns its buffers and its
4
+ // drawing; this side owns the visual, the layout and the input. CPU
5
+ // offloading, not isolation (docs/frame.md).
6
+ //
7
+ // The seam is `setRect` / `present` / `destroy`, the same three methods the
8
+ // Cocoa host implements, and the difference between the two backends lives
9
+ // entirely inside `present`. There a Cocoa host is told a **different**
10
+ // IOSurface every frame and points its layer at each one; here it is told
11
+ // the **same** composition surface handle every time, because the pane's
12
+ // swapchain flips inside that one handle. So this one attaches once and
13
+ // recognises the handle ever after — after which the pane's frames arrive
14
+ // with nothing at all crossing the process boundary.
15
+ export class Win32PaneHost {
16
+ constructor(app, wnd) {
17
+ this.app = app;
18
+ this.wnd = wnd;
19
+ this._native = app._native;
20
+ this.destroyed = false;
21
+ /** The bridge's id for the attached visual; 0 until the first present,
22
+ * because there is no buffer to attach before the pane names one. */
23
+ this._view = 0;
24
+ /** The handle that view was opened from, so the same one arriving again
25
+ * is recognised rather than re-opened. */
26
+ this._handle = 0;
27
+ this._rect = null;
28
+ }
29
+
30
+ /**
31
+ * Where the pane goes, in the host window's device pixels — the node's
32
+ * `abs`. The size is a **clip**, not a scale: the pane draws at whatever
33
+ * size it was last told over the channel, and the two disagree for the
34
+ * length of a round trip whenever the box changes. Bounding it here is
35
+ * what keeps a pane that has not caught up from painting over its
36
+ * neighbours (`paneSetRect`, windows/src/pane.cc).
37
+ */
38
+ setRect(rect) {
39
+ if (this.destroyed || !rect) return;
40
+ const prev = this._rect;
41
+ if (
42
+ prev &&
43
+ prev.x === rect.x &&
44
+ prev.y === rect.y &&
45
+ prev.width === rect.width &&
46
+ prev.height === rect.height
47
+ ) {
48
+ return;
49
+ }
50
+ this._rect = { ...rect };
51
+ this._place();
52
+ }
53
+
54
+ _place() {
55
+ const rect = this._rect;
56
+ if (!this._view || !rect) return;
57
+ this._native.paneSetRect(
58
+ this._view,
59
+ Math.round(rect.x),
60
+ Math.round(rect.y),
61
+ Math.max(0, Math.round(rect.width)),
62
+ Math.max(0, Math.round(rect.height)),
63
+ );
64
+ }
65
+
66
+ /**
67
+ * The pane named the buffer its frames are in.
68
+ *
69
+ * On the first one this opens the handle and hangs it in the window's
70
+ * visual tree; on every one after it there is nothing to do, and saying so
71
+ * quickly is the point — the compositor is already scanning out of
72
+ * whatever the pane last presented, so a frame reaches the screen without
73
+ * this method being called at all.
74
+ *
75
+ * A *different* handle means a different pane behind the same host, which
76
+ * is what a restart looks like from here: drop the old view and open the
77
+ * new one, rather than leaving the window showing a buffer whose process
78
+ * has gone.
79
+ */
80
+ present(handle) {
81
+ if (this.destroyed || !handle) return;
82
+ if (handle === this._handle && this._view) return;
83
+ if (this._view) {
84
+ this._native.paneDetach(this._view);
85
+ this._view = 0;
86
+ }
87
+ const view = this._native.paneAttach(this.wnd.id, handle);
88
+ if (!view) {
89
+ // Nothing was shown and nothing was lost; the pane will say so again
90
+ // with its next pane-rect, and a host window not yet composed is the
91
+ // one way this happens.
92
+ return;
93
+ }
94
+ this._handle = handle;
95
+ this._view = view;
96
+ this._place();
97
+ }
98
+
99
+ destroy() {
100
+ if (this.destroyed) return;
101
+ this.destroyed = true;
102
+ if (this._view) this._native.paneDetach(this._view);
103
+ this._view = 0;
104
+ this._handle = 0;
105
+ }
106
+ }