react-x11 2.40.0 → 2.41.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/package.json +2 -2
- package/src/cocoa/app.js +19 -8
- package/src/cocoa/keymap.js +69 -12
- package/src/cocoa/panehost.js +44 -12
- package/src/cocoa/paneplayer.js +101 -24
- package/src/cocoa/panewindow.js +325 -44
- package/src/cocoa/window.js +9 -1
- package/src/events.js +12 -0
- package/src/frame/index.js +63 -17
- package/src/nodes/textarea.js +85 -15
- package/src/nodes/textinput.js +14 -3
- package/src/nodes/textkeys.js +322 -0
- package/src/types/events.d.ts +6 -2
- package/src/win32/fonts.js +521 -44
- package/src/win32/panewindow.js +25 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-x11",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.41.0",
|
|
4
4
|
"description": "react renderer with X11 as a target",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"yoga-layout": "^3.2.1"
|
|
110
110
|
},
|
|
111
111
|
"optionalDependencies": {
|
|
112
|
-
"@windowkit/appkit": "^0.
|
|
112
|
+
"@windowkit/appkit": "^0.25.0",
|
|
113
113
|
"@windowkit/wayland": "^3.1.1",
|
|
114
114
|
"@windowkit/win32": "^0.0.7",
|
|
115
115
|
"dbus-native": "^0.16.0",
|
package/src/cocoa/app.js
CHANGED
|
@@ -290,7 +290,9 @@ export class CocoaApp {
|
|
|
290
290
|
// the text controls' Undo/Cut/Copy/Paste/Select All answer ⌘ rather
|
|
291
291
|
// than ⌃, and the edit menu prints them the way a Mac menu does. The
|
|
292
292
|
// menu bar already read a chord's `Control` as ⌘ (cocoa/globalmenu.js);
|
|
293
|
-
// this is the same rule for everything else that reads one.
|
|
293
|
+
// this is the same rule for everything else that reads one. Text is
|
|
294
|
+
// edited the Mac way with it too: ⌥ moves by word, ⌘ to the ends of a
|
|
295
|
+
// line and the document, ⌃ is Emacs's (src/nodes/textkeys.js).
|
|
294
296
|
this.primaryModifier = 'Super';
|
|
295
297
|
|
|
296
298
|
// AppKit-rendered control bezels. Its *presence* is the capability:
|
|
@@ -934,8 +936,9 @@ export class CocoaApp {
|
|
|
934
936
|
|
|
935
937
|
/**
|
|
936
938
|
* The pane's end of the frame channel (childmain hands it over,
|
|
937
|
-
* feature-detected so the X11 pane path never notices): geometry
|
|
938
|
-
*
|
|
939
|
+
* feature-detected so the X11 pane path never notices): geometry, input
|
|
940
|
+
* and the host's word that it has looked a present up come in,
|
|
941
|
+
* pane-present and pane-cursor go out. `players` is the
|
|
939
942
|
* host's word that it plays the pane's `<video src>`s, which makes
|
|
940
943
|
* `createPlayer` here — before the pane's first render, which asks.
|
|
941
944
|
*/
|
|
@@ -963,7 +966,12 @@ export class CocoaApp {
|
|
|
963
966
|
}
|
|
964
967
|
const pane = this._paneWindow ?? [...this._windows.values()][0];
|
|
965
968
|
if (!pane) return;
|
|
966
|
-
if (msg?.type === 'pane-
|
|
969
|
+
if (msg?.type === 'pane-shown') {
|
|
970
|
+
// the host has looked up this window's presents to `seq`, so the
|
|
971
|
+
// buffers they named may go (`CocoaPaneWindow._releaseRing`)
|
|
972
|
+
const wnd = msg.window != null ? this._windows.get(msg.window) : pane;
|
|
973
|
+
wnd?._shown?.(msg.seq);
|
|
974
|
+
} else if (msg?.type === 'pane-rect') {
|
|
967
975
|
pane.setPaneSize(msg.width, msg.height, msg.scale, msg.screen);
|
|
968
976
|
this._flushPaneOutbox();
|
|
969
977
|
this._afterInput();
|
|
@@ -1210,11 +1218,12 @@ export class CocoaApp {
|
|
|
1210
1218
|
const surfaces = [];
|
|
1211
1219
|
for (const entry of queue) {
|
|
1212
1220
|
// A window whose last flip has not reached its layer yet (threaded
|
|
1213
|
-
// mode's fence, `CocoaWindow.
|
|
1214
|
-
//
|
|
1215
|
-
//
|
|
1221
|
+
// mode's fence, `CocoaWindow.frameHeld`), or a pane with no buffer the
|
|
1222
|
+
// host is done with (`CocoaPaneWindow.frameHeld`), waits before its
|
|
1223
|
+
// clock is asked, or the clock would count a frame that did not run.
|
|
1224
|
+
// The release is an event — a batch, a `pane-shown` — and it ticks
|
|
1216
1225
|
// again.
|
|
1217
|
-
if (entry.wnd?.
|
|
1226
|
+
if (entry.wnd?.frameHeld?.()) {
|
|
1218
1227
|
this._rafQueue.push(entry);
|
|
1219
1228
|
continue;
|
|
1220
1229
|
}
|
|
@@ -1560,6 +1569,8 @@ export class CocoaApp {
|
|
|
1560
1569
|
keysym: decoded.keysym,
|
|
1561
1570
|
baseKeysym: decoded.baseKeysym,
|
|
1562
1571
|
codepoint: decoded.codepoint,
|
|
1572
|
+
// more than one character, which no code point carries (keymap.js)
|
|
1573
|
+
text: decoded.text,
|
|
1563
1574
|
buttons: modifierMask(ev),
|
|
1564
1575
|
group: 0,
|
|
1565
1576
|
time: ev.time,
|
package/src/cocoa/keymap.js
CHANGED
|
@@ -5,8 +5,39 @@
|
|
|
5
5
|
// have fixed keysyms looked up here by kVK_* virtual key code. NSEvent's
|
|
6
6
|
// three character views map onto the three keysym roles events.js reads:
|
|
7
7
|
//
|
|
8
|
-
// charsBase (modifiers stripped)
|
|
9
|
-
//
|
|
8
|
+
// charsBase (modifiers stripped) -> baseKeysym — what chords match
|
|
9
|
+
// chars (every modifier applied) -> keysym / codepoint — what was
|
|
10
|
+
// typed, unless ⌘ or ⌃ is held
|
|
11
|
+
// charsShifted (only Shift applied) -> keysym / codepoint of a ⌘ or
|
|
12
|
+
// ⌃ chord, which types nothing
|
|
13
|
+
//
|
|
14
|
+
// **⌥ types on a Mac.** It is the layout's third and fourth level: ⌥S is ß on
|
|
15
|
+
// a US keyboard, and on a German one ⌥L is @, ⌥5 [ and ⌥8 { — the only way
|
|
16
|
+
// to type them. `charsShifted` is `charactersIgnoringModifiers`, which drops
|
|
17
|
+
// ⌥ along with everything but Shift, so reading the typed character there
|
|
18
|
+
// made ⌥S type `s` and left a German keyboard no @ at all. `chars` is
|
|
19
|
+
// `characters`, the layout's own answer with ⌥, Shift and Caps Lock applied —
|
|
20
|
+
// Caps Lock is the other thing `charsShifted` leaves out, so a field typed
|
|
21
|
+
// lower case with it on. It is also exactly the DOM's `key` on macOS: ⌥S is
|
|
22
|
+
// `ß`, ⌘S is `s`.
|
|
23
|
+
//
|
|
24
|
+
// There is no input method behind this. The bridge's view swallows
|
|
25
|
+
// `keyDown:` without `interpretKeyEvents:` and is no `NSTextInputClient`
|
|
26
|
+
// (docs/macos.md, "IME"), so these fields are all the text there is. Two
|
|
27
|
+
// consequences, both handled below:
|
|
28
|
+
//
|
|
29
|
+
// - **A dead key types nothing.** ⌥E on a US layout leaves `chars` empty, and
|
|
30
|
+
// the key it is waiting for decides what the accent becomes. Typing the
|
|
31
|
+
// key's own letter, which is what reading `charsShifted` did, put an `e` in
|
|
32
|
+
// the field. Whether the next key's `chars` carries the composed `é` is the
|
|
33
|
+
// window server's business — an NSEvent built in-process does not carry the
|
|
34
|
+
// state, and a real keyboard was not measured — so nothing here depends on
|
|
35
|
+
// it: a composed character is taken as typed, and a plain one is the
|
|
36
|
+
// accent lost rather than a letter too many.
|
|
37
|
+
// - **A key can type more than one character** — the accent and a letter it
|
|
38
|
+
// does not combine with, `´s`, where the layout composes. No code point
|
|
39
|
+
// carries two, so the whole string rides `text` and events.js commits it
|
|
40
|
+
// the way it commits an input method's text.
|
|
10
41
|
//
|
|
11
42
|
// Function and editing keys type Unicode private-use characters (U+F700…),
|
|
12
43
|
// which must never leak out as code points; the kVK table wins over the
|
|
@@ -48,30 +79,56 @@ const VK_KEYSYMS = new Map([
|
|
|
48
79
|
[111, 0xffc9], // F12
|
|
49
80
|
]);
|
|
50
81
|
|
|
51
|
-
|
|
82
|
+
// The block NSEvent's function-key constants live in (NSUpArrowFunctionKey is
|
|
83
|
+
// U+F700). Not the whole U+F700–U+F8FF range Apple reserves: U+F8FF is the
|
|
84
|
+
// Apple logo, which ⌥⇧K types and the system font draws.
|
|
85
|
+
const FUNCTION_KEY = (cp) => cp >= 0xf700 && cp <= 0xf7ff;
|
|
86
|
+
|
|
87
|
+
const printable = (cp) => cp != null && cp >= 0x20 && cp !== 0x7f;
|
|
52
88
|
|
|
53
89
|
function keysymFromChars(chars) {
|
|
54
90
|
if (!chars) return 0;
|
|
55
91
|
const cp = chars.codePointAt(0);
|
|
56
|
-
if (cp
|
|
92
|
+
if (!printable(cp) || FUNCTION_KEY(cp)) return 0;
|
|
57
93
|
return keysymOf(String.fromCodePoint(cp));
|
|
58
94
|
}
|
|
59
95
|
|
|
60
96
|
/**
|
|
61
|
-
* The
|
|
62
|
-
* @windowkit/appkit payload
|
|
97
|
+
* The key facts events.js reads off a native key event, from the raw
|
|
98
|
+
* @windowkit/appkit payload: `keysym`, `baseKeysym` and `codepoint`, and
|
|
99
|
+
* `text` when the key typed more than one character.
|
|
63
100
|
*/
|
|
64
101
|
export function decodeKey(ev) {
|
|
65
102
|
const fixed = VK_KEYSYMS.get(ev.keyCode);
|
|
66
103
|
if (fixed) {
|
|
67
104
|
return { keysym: fixed, baseKeysym: fixed, codepoint: undefined };
|
|
68
105
|
}
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
106
|
+
const baseKeysym =
|
|
107
|
+
keysymFromChars(ev.charsBase) || keysymFromChars(ev.charsShifted);
|
|
108
|
+
// A ⌘ or ⌃ chord types nothing, and is named by its key: ⌘⌥S is a chord
|
|
109
|
+
// on S, not on ß. (`chars` for ⌃S is a control character besides.)
|
|
110
|
+
// `chars` is absent from a bridge older than the field, and from a test's
|
|
111
|
+
// event that only says which key it was.
|
|
112
|
+
const chord = ev.command || ev.control;
|
|
113
|
+
const typed = chord ? ev.charsShifted : (ev.chars ?? ev.charsShifted);
|
|
114
|
+
const points = typed ? Array.from(typed) : [];
|
|
115
|
+
if (points.length > 1) {
|
|
116
|
+
return {
|
|
117
|
+
keysym: keysymFromChars(ev.charsShifted) || baseKeysym,
|
|
118
|
+
baseKeysym,
|
|
119
|
+
codepoint: undefined,
|
|
120
|
+
text: typed,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const keysym = keysymFromChars(typed);
|
|
124
|
+
const cp = typed?.codePointAt(0);
|
|
125
|
+
const codepoint = printable(cp) && !FUNCTION_KEY(cp) ? cp : undefined;
|
|
126
|
+
return {
|
|
127
|
+
// a dead key typed nothing; the key it sits on is what a chord names
|
|
128
|
+
keysym: keysym || keysymFromChars(ev.charsShifted) || baseKeysym,
|
|
129
|
+
baseKeysym,
|
|
130
|
+
codepoint,
|
|
131
|
+
};
|
|
75
132
|
}
|
|
76
133
|
|
|
77
134
|
/** AppKit modifier booleans -> the X-style state mask events carry. */
|
package/src/cocoa/panehost.js
CHANGED
|
@@ -172,16 +172,20 @@ class PaneLayer {
|
|
|
172
172
|
* A pane-present landed: scan out of the named shared surface.
|
|
173
173
|
*
|
|
174
174
|
* A present names a buffer the pane may since have retired. The channel
|
|
175
|
-
* is a queue
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
175
|
+
* is a queue, so a pane-rect that changes the pane's size (or its scale —
|
|
176
|
+
* the host window moved to another display during startup, which is how
|
|
177
|
+
* this was first hit) can cross a present already in flight: the pane
|
|
178
|
+
* rebuilds its ring on that rect (`CocoaPaneWindow._ensureSurface`)
|
|
179
|
+
* before the host has looked the id up. The id is all the present
|
|
180
|
+
* carries, and the system gives a freed id to the next surface made, so
|
|
181
|
+
* the pane keeps such a buffer until the host says it has looked it up
|
|
182
|
+
* (`pane-shown`, which `CocoaPaneHost` sends once it has) — the lookup finds
|
|
183
|
+
* the frame the present named, a size behind, and the full frame on the
|
|
184
|
+
* fresh ring is queued behind it. Where the buffer is gone anyway — a
|
|
185
|
+
* pane that freed it for want of an answer, or one that died with
|
|
186
|
+
* presents in the channel — the present is dropped, and the layer keeps
|
|
187
|
+
* the frame it already holds a reference to. Nothing else here throws;
|
|
188
|
+
* any other error is the bug it says it is.
|
|
185
189
|
*/
|
|
186
190
|
present(iosurfaceId, size = null) {
|
|
187
191
|
if (this.destroyed) return false;
|
|
@@ -260,6 +264,28 @@ export class CocoaPaneHost extends PaneLayer {
|
|
|
260
264
|
this._players = new Map();
|
|
261
265
|
}
|
|
262
266
|
|
|
267
|
+
/**
|
|
268
|
+
* The pane's own present, scanned out (`PaneLayer.present`), and the pane
|
|
269
|
+
* told: whatever the buffer it named holds is the layer's now, or was
|
|
270
|
+
* dropped, so the pane may free it (`CocoaPaneWindow._releaseRing`).
|
|
271
|
+
*/
|
|
272
|
+
present(iosurfaceId, size = null) {
|
|
273
|
+
const shown = super.present(iosurfaceId, size);
|
|
274
|
+
this._acknowledge(size?.seq, null);
|
|
275
|
+
return shown;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** `pane-shown`: the host has looked up the presents to `seq` of the
|
|
279
|
+
* pane's own window, or of the one `window` names. */
|
|
280
|
+
_acknowledge(seq, window) {
|
|
281
|
+
if (this.destroyed || seq == null) return;
|
|
282
|
+
this._send?.({
|
|
283
|
+
type: 'pane-shown',
|
|
284
|
+
seq,
|
|
285
|
+
...(window != null && { window }),
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
263
289
|
/**
|
|
264
290
|
* A `pane-player` message: one of the pane's `<video src>`s made, steered
|
|
265
291
|
* or let go of its player. A player that cannot be made says so the way
|
|
@@ -375,9 +401,15 @@ export class CocoaPaneHost extends PaneLayer {
|
|
|
375
401
|
/** A pane-present naming one of the pane's other windows. */
|
|
376
402
|
presentWindow(msg) {
|
|
377
403
|
const entry = this._windows.get(msg.window);
|
|
378
|
-
if (!entry || this.destroyed)
|
|
404
|
+
if (!entry || this.destroyed) {
|
|
405
|
+
this._acknowledge(msg.seq, msg.window);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
379
408
|
entry.layer.setRect({ x: 0, y: 0, width: msg.width, height: msg.height });
|
|
380
|
-
|
|
409
|
+
const shown = entry.layer.present(msg.id);
|
|
410
|
+
// its ring is the pane's own ring, retired the same way
|
|
411
|
+
this._acknowledge(msg.seq, msg.window);
|
|
412
|
+
if (!shown) return;
|
|
381
413
|
entry.presented = true;
|
|
382
414
|
// a transparent window's shadow is the shape of what it shows
|
|
383
415
|
entry.wnd._shadowAfterFlip?.();
|
package/src/cocoa/paneplayer.js
CHANGED
|
@@ -18,6 +18,15 @@
|
|
|
18
18
|
// other: the host writes the buffer after the one it last named, so the one
|
|
19
19
|
// the pane is told about is not written again for two frames — some 66ms at
|
|
20
20
|
// 30fps, against a pane that copies within a display frame of hearing.
|
|
21
|
+
//
|
|
22
|
+
// A buffer is named by its IOSurface id, and the system gives a freed id to
|
|
23
|
+
// the next surface anyone makes, in either process. So the pane answers
|
|
24
|
+
// each frame once it has looked the buffer up (`seen`), the host keeps a
|
|
25
|
+
// buffer of a ring it retired until the frame naming it is answered
|
|
26
|
+
// (`HostedPlayer._freeRing`), and the pane keeps nothing a lookup found at
|
|
27
|
+
// another size than the frame says (`PanePlayer._lookUp`) — the pane's own
|
|
28
|
+
// presents, the other way round, are kept the same way
|
|
29
|
+
// (`CocoaPaneWindow._releaseRing`).
|
|
21
30
|
|
|
22
31
|
import { PlayerState } from './player.js';
|
|
23
32
|
|
|
@@ -113,27 +122,13 @@ export class PanePlayer extends PlayerState {
|
|
|
113
122
|
}
|
|
114
123
|
|
|
115
124
|
/** The host named a buffer with a new frame in it. */
|
|
116
|
-
_frame({ surface, width, height, time }) {
|
|
125
|
+
_frame({ surface, width, height, time, seq }) {
|
|
117
126
|
if (this.released) return;
|
|
118
|
-
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
) {
|
|
124
|
-
this._dropBuffers();
|
|
125
|
-
this._bufferSize = { width, height };
|
|
126
|
-
}
|
|
127
|
-
let handle = this._buffers.get(surface);
|
|
128
|
-
if (!handle) {
|
|
129
|
-
try {
|
|
130
|
-
handle = this._native.surfaceFromIOSurfaceID(surface, 1).handle;
|
|
131
|
-
} catch {
|
|
132
|
-
// freed before it got here: the next frame is in another buffer
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
this._buffers.set(surface, handle);
|
|
136
|
-
}
|
|
127
|
+
const handle = this._lookUp(surface, width, height);
|
|
128
|
+
// this frame's lookup is done, whatever it found: the host may free the
|
|
129
|
+
// buffer, which it kept for it (`HostedPlayer._freeRing`)
|
|
130
|
+
if (seq != null) this._post({ op: 'seen', seq });
|
|
131
|
+
if (!handle) return;
|
|
137
132
|
this._shown = { handle, width, height, time };
|
|
138
133
|
this._fresh = true;
|
|
139
134
|
// a playing `<video>` asks every display frame; a paused one asks when
|
|
@@ -148,6 +143,40 @@ export class PanePlayer extends PlayerState {
|
|
|
148
143
|
}
|
|
149
144
|
}
|
|
150
145
|
|
|
146
|
+
/**
|
|
147
|
+
* The buffer the host named, looked up the first time it is named; null
|
|
148
|
+
* where the lookup found none, or found a surface of another size than
|
|
149
|
+
* the frame, which is not the frame's buffer but whatever the id has gone
|
|
150
|
+
* to since (`HostedPlayer._freeRing` says how) — never kept, and never
|
|
151
|
+
* copied out of.
|
|
152
|
+
*/
|
|
153
|
+
_lookUp(surface, width, height) {
|
|
154
|
+
// a ring of another size is a new ring: the old one's buffers are the
|
|
155
|
+
// host's to free, and these handles only keep them alive
|
|
156
|
+
if (
|
|
157
|
+
this._bufferSize?.width !== width ||
|
|
158
|
+
this._bufferSize?.height !== height
|
|
159
|
+
) {
|
|
160
|
+
this._dropBuffers();
|
|
161
|
+
this._bufferSize = { width, height };
|
|
162
|
+
}
|
|
163
|
+
const known = this._buffers.get(surface);
|
|
164
|
+
if (known) return known;
|
|
165
|
+
let found;
|
|
166
|
+
try {
|
|
167
|
+
found = this._native.surfaceFromIOSurfaceID(surface, 1);
|
|
168
|
+
} catch {
|
|
169
|
+
// freed before it got here: the next frame is in another buffer
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
if (found.width !== width || found.height !== height) {
|
|
173
|
+
this._native.releaseSurface?.(found.handle);
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
this._buffers.set(surface, found.handle);
|
|
177
|
+
return found.handle;
|
|
178
|
+
}
|
|
179
|
+
|
|
151
180
|
/** As `CocoaPlayer.copyFrame`: the newest frame into `surface`, or null
|
|
152
181
|
* when the host has sent nothing since the last copy. */
|
|
153
182
|
copyFrame(surface) {
|
|
@@ -212,6 +241,12 @@ export class HostedPlayer {
|
|
|
212
241
|
this._next = 0;
|
|
213
242
|
this._want = 0;
|
|
214
243
|
this._pull = null;
|
|
244
|
+
// Each frame's number, the newest the pane has looked up (`seen`), and
|
|
245
|
+
// the buffers of retired rings a frame it has not looked up yet names:
|
|
246
|
+
// freed as it does (`_freeRing`).
|
|
247
|
+
this._seq = 0;
|
|
248
|
+
this._seenSeq = 0;
|
|
249
|
+
this._retired = [];
|
|
215
250
|
this._off = this.player._subscribe((ev) => this._event(ev));
|
|
216
251
|
}
|
|
217
252
|
|
|
@@ -221,6 +256,7 @@ export class HostedPlayer {
|
|
|
221
256
|
if (player.released) return;
|
|
222
257
|
if (msg.op === 'set') player.set(msg.settings);
|
|
223
258
|
else if (msg.op === 'seek') player.seek(msg.seconds);
|
|
259
|
+
else if (msg.op === 'seen') this._seen(msg.seq);
|
|
224
260
|
else if (msg.op === 'release') this.release();
|
|
225
261
|
}
|
|
226
262
|
|
|
@@ -274,6 +310,9 @@ export class HostedPlayer {
|
|
|
274
310
|
);
|
|
275
311
|
if (got?.written) {
|
|
276
312
|
this._next = (this._next + 1) % RING;
|
|
313
|
+
// the buffer is named in the channel until the pane says otherwise
|
|
314
|
+
// (`_freeRing`)
|
|
315
|
+
buffer.namedSeq = ++this._seq;
|
|
277
316
|
this._send({
|
|
278
317
|
type: 'pane-player-frame',
|
|
279
318
|
player: this.id,
|
|
@@ -281,6 +320,7 @@ export class HostedPlayer {
|
|
|
281
320
|
width: got.width,
|
|
282
321
|
height: got.height,
|
|
283
322
|
time: got.time,
|
|
323
|
+
seq: this._seq,
|
|
284
324
|
});
|
|
285
325
|
if (!player.playing) this._want = 0;
|
|
286
326
|
} else if (got?.width > 0 && got?.height > 0) {
|
|
@@ -316,13 +356,46 @@ export class HostedPlayer {
|
|
|
316
356
|
return this._ring[this._next];
|
|
317
357
|
}
|
|
318
358
|
|
|
359
|
+
/**
|
|
360
|
+
* Let the ring go — all but a buffer a frame names that the pane has not
|
|
361
|
+
* looked up yet. The pane finds a buffer by its IOSurface id, and the
|
|
362
|
+
* system hands a freed id to the next surface anyone makes: the ring of
|
|
363
|
+
* the new size this same call is about to make, the pane's own ring,
|
|
364
|
+
* another process's surface. A frame still in the channel named the id,
|
|
365
|
+
* not the surface, so the pane copied out of whatever had it by the time
|
|
366
|
+
* it looked — the next ring's buffer, unwritten, shown until the next
|
|
367
|
+
* frame came, which for a paused player is the next seek. So that buffer
|
|
368
|
+
* is kept (`_retired`) until the pane says it has looked the frame up
|
|
369
|
+
* (`seen`, `_seen`). One, as a rule, since the pane answers as it reads,
|
|
370
|
+
* and never more than a ring's worth: a pane that stops answering has the
|
|
371
|
+
* oldest freed anyway, the one it is likeliest to have looked up already.
|
|
372
|
+
*/
|
|
319
373
|
_freeRing() {
|
|
374
|
+
const ring = this._ring;
|
|
375
|
+
this._ring = [];
|
|
320
376
|
const release = this._native.releaseSurface;
|
|
321
|
-
if (typeof release
|
|
322
|
-
|
|
323
|
-
|
|
377
|
+
if (typeof release !== 'function') return;
|
|
378
|
+
for (const buffer of ring) {
|
|
379
|
+
if (buffer.namedSeq > this._seenSeq) this._retired.push(buffer);
|
|
380
|
+
else release.call(this._native, buffer.handle);
|
|
324
381
|
}
|
|
325
|
-
this.
|
|
382
|
+
while (this._retired.length > RING) {
|
|
383
|
+
release.call(this._native, this._retired.shift().handle);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** The pane has looked up every frame up to `seq`: the retired buffers
|
|
388
|
+
* they named go. */
|
|
389
|
+
_seen(seq) {
|
|
390
|
+
if (!(seq > this._seenSeq)) return;
|
|
391
|
+
this._seenSeq = seq;
|
|
392
|
+
if (this._retired.length === 0) return;
|
|
393
|
+
const release = this._native.releaseSurface;
|
|
394
|
+
this._retired = this._retired.filter((buffer) => {
|
|
395
|
+
if (buffer.namedSeq > seq) return true;
|
|
396
|
+
release.call(this._native, buffer.handle);
|
|
397
|
+
return false;
|
|
398
|
+
});
|
|
326
399
|
}
|
|
327
400
|
|
|
328
401
|
release() {
|
|
@@ -332,5 +405,9 @@ export class HostedPlayer {
|
|
|
332
405
|
this._want = 0;
|
|
333
406
|
this._syncPull();
|
|
334
407
|
this._freeRing();
|
|
408
|
+
// and what it kept: the pane's player is gone, or the pane is
|
|
409
|
+
for (const buffer of this._retired.splice(0)) {
|
|
410
|
+
this._native.releaseSurface(buffer.handle);
|
|
411
|
+
}
|
|
335
412
|
}
|
|
336
413
|
}
|