react-x11 2.47.0 → 2.47.2
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/nodes/window/listeners.js +7 -6
- package/src/nodes/window/size.js +56 -1
- package/src/nodes/window/window.js +11 -4
- package/src/wayland/glcontext.js +11 -3
- package/src/wayland/swapchain.js +93 -10
- package/src/wayland/window.js +27 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-x11",
|
|
3
|
-
"version": "2.47.
|
|
3
|
+
"version": "2.47.2",
|
|
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.28.0",
|
|
113
113
|
"@windowkit/wayland": "^3.1.1",
|
|
114
114
|
"@windowkit/win32": "^0.0.9",
|
|
115
115
|
"dbus-native": "^0.16.0",
|
|
@@ -41,12 +41,13 @@ export class WindowListeners {
|
|
|
41
41
|
// is theirs. Growing it back under a user who has just made it smaller
|
|
42
42
|
// is the one behaviour worse than not fitting the content.
|
|
43
43
|
//
|
|
44
|
-
// Checked against
|
|
45
|
-
// because our own configures come back as echoes, and
|
|
46
|
-
// them would otherwise read as the user taking over on
|
|
47
|
-
// re-fit
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
// Checked against the sizes we asked for rather than ntk's
|
|
45
|
+
// `ev.resized`, because our own configures come back as echoes, and
|
|
46
|
+
// every one of them would otherwise read as the user taking over on
|
|
47
|
+
// the first re-fit — and against all of them still in flight, not
|
|
48
|
+
// only the latest, because a move echo or an earlier re-fit's can land
|
|
49
|
+
// after the record moved on (#929, `_ownSize`).
|
|
50
|
+
if (this._requestedSize && !this._ownSize(ev)) {
|
|
50
51
|
this._userSized = true;
|
|
51
52
|
}
|
|
52
53
|
// Where the window sits on screen decides where popups anchored to it
|
package/src/nodes/window/size.js
CHANGED
|
@@ -46,6 +46,10 @@ const NO_SCOPED_FLOORS = process.env.REACT_X11_NO_SCOPED_FLOORS === '1';
|
|
|
46
46
|
// …and for the width pass reading only the extents a floor is written from
|
|
47
47
|
// (`markUnreadLeaves`): with it set, every leaf is shaped at no width again.
|
|
48
48
|
const NO_UNREAD_WIDTHS = process.env.REACT_X11_NO_UNREAD_WIDTHS === '1';
|
|
49
|
+
// How many sizes an `'auto'` window keeps as asked for and not yet echoed
|
|
50
|
+
// (`_askSize`). Echoes come back within a round trip, so a re-fit a frame
|
|
51
|
+
// is a handful at most.
|
|
52
|
+
const SIZES_IN_FLIGHT = 8;
|
|
49
53
|
|
|
50
54
|
/** Take a node's extents off, as `collectFloorStale` does a dirty child's:
|
|
51
55
|
* both are measured again, and the minimum yoga holds may no longer be
|
|
@@ -995,7 +999,7 @@ export class WindowSize {
|
|
|
995
999
|
if (asked && next.width === asked.width && next.height === asked.height) {
|
|
996
1000
|
return;
|
|
997
1001
|
}
|
|
998
|
-
this.
|
|
1002
|
+
this._askSize({ width: next.width, height: next.height });
|
|
999
1003
|
// Asked for, not assumed. `window.width` stays what the server last said
|
|
1000
1004
|
// until the ConfigureNotify lands, and this frame lays out against that
|
|
1001
1005
|
// — the echo brings `needsLayout` and an unbounded repaint with it (see
|
|
@@ -1038,6 +1042,57 @@ export class WindowSize {
|
|
|
1038
1042
|
);
|
|
1039
1043
|
}
|
|
1040
1044
|
|
|
1045
|
+
/**
|
|
1046
|
+
* Record a size this window has asked the backend for. `_requestedSize`
|
|
1047
|
+
* is the latest, which `_refit()` compares against to ask only once; the
|
|
1048
|
+
* echoes still to come are kept beside it, because every one of them is
|
|
1049
|
+
* ours and none has to be the latest (`_ownSize`).
|
|
1050
|
+
*/
|
|
1051
|
+
_askSize(size) {
|
|
1052
|
+
this._requestedSize = size;
|
|
1053
|
+
const flight = (this._sizesInFlight ??= []);
|
|
1054
|
+
flight.push(size);
|
|
1055
|
+
// A backend that never echoes — a mock — would grow this for ever, and
|
|
1056
|
+
// a size asked for this many re-fits ago is not coming back.
|
|
1057
|
+
if (flight.length > SIZES_IN_FLIGHT) flight.shift();
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Whether a resize event's size is one this window chose, as opposed to
|
|
1062
|
+
* the user or the window manager choosing it — the question that ends an
|
|
1063
|
+
* `'auto'` window's tracking for good. Settles the record either way.
|
|
1064
|
+
*
|
|
1065
|
+
* Not "is it the latest size asked for" (#929). Two kinds of echo are ours
|
|
1066
|
+
* and carry another size. A **move** reports the size the window has, and
|
|
1067
|
+
* when it reached the server before a resize that was already asked for —
|
|
1068
|
+
* a popup re-anchored by the same re-fit, an `x` prop moving with the
|
|
1069
|
+
* content — it lands after the record moved on. And a re-fit asked for
|
|
1070
|
+
* before the last one was answered has its echo arrive with the size it
|
|
1071
|
+
* asked for, not the one asked for since. So an event is ours when its
|
|
1072
|
+
* size is the one the server last reported (nothing was resized at all:
|
|
1073
|
+
* a move, or a request the manager refused), or one still in flight —
|
|
1074
|
+
* which spends it, and every one asked for before it, since the server
|
|
1075
|
+
* answers in order. Anything else is somebody else's size: the user and
|
|
1076
|
+
* a window manager can only ever *change* it.
|
|
1077
|
+
*/
|
|
1078
|
+
_ownSize(ev) {
|
|
1079
|
+
const is = (size) =>
|
|
1080
|
+
size && size.width === ev.width && size.height === ev.height;
|
|
1081
|
+
const confirmed = this._confirmedSize;
|
|
1082
|
+
this._confirmedSize = { width: ev.width, height: ev.height };
|
|
1083
|
+
// the confirmed size first: a size that came round again (A → B → A)
|
|
1084
|
+
// is in flight too, and spending it there would spend B with it
|
|
1085
|
+
if (is(confirmed)) return true;
|
|
1086
|
+
const flight = this._sizesInFlight ?? [];
|
|
1087
|
+
const at = flight.findIndex(is);
|
|
1088
|
+
if (at < 0) {
|
|
1089
|
+
flight.length = 0;
|
|
1090
|
+
return false;
|
|
1091
|
+
}
|
|
1092
|
+
flight.splice(0, at + 1);
|
|
1093
|
+
return true;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1041
1096
|
/** One layout pass at the window's size, with the content floors it
|
|
1042
1097
|
* needs: fresh ones when something changed them, the ones in hand during
|
|
1043
1098
|
* a live resize, none when nothing moved them. */
|
|
@@ -271,6 +271,10 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
271
271
|
// the only thing that ever does is the user dragging an edge.
|
|
272
272
|
this._requestedSize = null;
|
|
273
273
|
this._userSized = false;
|
|
274
|
+
// what the server last reported, and the sizes asked for since that it
|
|
275
|
+
// has not echoed yet: both are ours when an echo carries them (_ownSize)
|
|
276
|
+
this._confirmedSize = null;
|
|
277
|
+
this._sizesInFlight = [];
|
|
274
278
|
// the last `WM_NORMAL_HINTS` struct written and the size it was written
|
|
275
279
|
// at, so that a bound measured every frame is only *sent* on the frames
|
|
276
280
|
// it moves (see _sendSizeHints for why the size is part of it)
|
|
@@ -409,6 +413,7 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
409
413
|
// resize echo is compared against, so it has to be the size the echo
|
|
410
414
|
// will carry (#586, `_snapSize`).
|
|
411
415
|
this._requestedSize = { width: wnd.width, height: wnd.height };
|
|
416
|
+
this._confirmedSize = this._requestedSize;
|
|
412
417
|
// Now that the visual is known: settle the capabilities, re-resolve any
|
|
413
418
|
// `@supports` block against them, and start following the compositor.
|
|
414
419
|
// Before the first paint, and before children realize against it.
|
|
@@ -862,10 +867,12 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
862
867
|
const geo = scaleWindowGeometry(newProps, this.scale);
|
|
863
868
|
if (sizeChanged) {
|
|
864
869
|
this._userSized = false;
|
|
865
|
-
this.
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
870
|
+
this._askSize(
|
|
871
|
+
this._snapSize({
|
|
872
|
+
width: isAutoSize(geo.width) ? wnd.width : geo.width,
|
|
873
|
+
height: isAutoSize(geo.height) ? wnd.height : geo.height,
|
|
874
|
+
}),
|
|
875
|
+
);
|
|
869
876
|
}
|
|
870
877
|
if (geometryChanged) {
|
|
871
878
|
if (typeof wnd.setState === 'function') {
|
package/src/wayland/glcontext.js
CHANGED
|
@@ -255,7 +255,11 @@ export class WaylandGLContext {
|
|
|
255
255
|
if (this.destroyed) return null;
|
|
256
256
|
const w = this.window.bufferWidth;
|
|
257
257
|
const h = this.window.bufferHeight;
|
|
258
|
-
|
|
258
|
+
// a window whose viewport shows part of a buffer keeps its buffers
|
|
259
|
+
// through a resize that fits them (`surfaceFor`'s slack)
|
|
260
|
+
const surface = this.chain.surfaceFor(w, h, {
|
|
261
|
+
slack: this.window.showsPartOfBuffer === true,
|
|
262
|
+
});
|
|
259
263
|
this._surface = surface;
|
|
260
264
|
this.gpu.makeCurrent(surface);
|
|
261
265
|
let resized = false;
|
|
@@ -321,11 +325,12 @@ export class WaylandGLContext {
|
|
|
321
325
|
// next buffer might be missing. Then the GPU work is settled (optionally
|
|
322
326
|
// through a fence rather than a stall) before the swap hands it over.
|
|
323
327
|
const rects = this.chain.blitHint(damage);
|
|
328
|
+
// into the top left of a buffer that may be larger than the window
|
|
324
329
|
this.backing.blitTo(
|
|
325
330
|
null,
|
|
326
331
|
rects === 'all' ? null : rects,
|
|
327
|
-
this.chain.
|
|
328
|
-
this.chain.
|
|
332
|
+
this.chain.allocWidth,
|
|
333
|
+
this.chain.allocHeight,
|
|
329
334
|
);
|
|
330
335
|
await this._settleGpu();
|
|
331
336
|
|
|
@@ -338,6 +343,9 @@ export class WaylandGLContext {
|
|
|
338
343
|
// is EGL_BAD_SURFACE (0x300d): the second window of examples/windows.jsx.
|
|
339
344
|
this.gpu.makeCurrent(this._surface);
|
|
340
345
|
const presented = await this.chain.swap(damage);
|
|
346
|
+
// the part of the buffer just attached that is the window — only with a
|
|
347
|
+
// buffer, since the one shown otherwise is the last, of its own size
|
|
348
|
+
if (presented) win.showBufferPart?.(this.chain.width, this.chain.height);
|
|
341
349
|
// `swap` attached and damaged but did not commit; and when nothing went
|
|
342
350
|
// out — every buffer busy — the frame request still needs a commit to be
|
|
343
351
|
// delivered. Either way, exactly one commit.
|
package/src/wayland/swapchain.js
CHANGED
|
@@ -59,6 +59,20 @@ import { importDmabuf, DmabufRefused } from './dmabuf.js';
|
|
|
59
59
|
/** How many frames may be in the compositor's hands at once. */
|
|
60
60
|
const DEFAULT_MAX_IN_FLIGHT = 2;
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Buffers are made a multiple of this larger than the window where they
|
|
64
|
+
* may be (`slack`), so that a resize a few pixels at a time — a window edge
|
|
65
|
+
* dragged — shows a different part of the same buffers rather than making
|
|
66
|
+
* new ones at every step.
|
|
67
|
+
*/
|
|
68
|
+
const SLACK_STEP = 128;
|
|
69
|
+
/** A visible area this many times smaller than the buffers makes them
|
|
70
|
+
* again, smaller: a window shrunk far is not to hold its old size's
|
|
71
|
+
* memory. */
|
|
72
|
+
const SLACK_SHRINK = 4;
|
|
73
|
+
/** Frames at one size before buffers grown for a drag are cut back to it. */
|
|
74
|
+
const SLACK_SETTLE = 60;
|
|
75
|
+
|
|
62
76
|
/**
|
|
63
77
|
* Damage sets are kept small: past a handful of rectangles the bookkeeping
|
|
64
78
|
* costs more than the full-surface copy it is trying to avoid, and the
|
|
@@ -92,8 +106,14 @@ export class WaylandSwapchain {
|
|
|
92
106
|
this.gbm = null;
|
|
93
107
|
/** `${generation}:${key}` -> { wlBuffer, busy, damage, key, generation } */
|
|
94
108
|
this.buffers = new Map();
|
|
109
|
+
/** the part of each buffer shown: the window's buffer size */
|
|
95
110
|
this.width = 0;
|
|
96
111
|
this.height = 0;
|
|
112
|
+
/** the buffers' own size, at least the part shown (`slack`) */
|
|
113
|
+
this.allocWidth = 0;
|
|
114
|
+
this.allocHeight = 0;
|
|
115
|
+
/** frames since the size shown last changed */
|
|
116
|
+
this._sameSize = 0;
|
|
97
117
|
/** set once the compositor has refused a tiled buffer */
|
|
98
118
|
this.linear = false;
|
|
99
119
|
/** swaps since a buffer was last seen for the first time */
|
|
@@ -111,14 +131,66 @@ export class WaylandSwapchain {
|
|
|
111
131
|
/**
|
|
112
132
|
* Point the chain at a size.
|
|
113
133
|
*
|
|
134
|
+
* With `slack` — where the window has a viewport, whose source rectangle
|
|
135
|
+
* shows part of a buffer — the buffers are made larger than the size and
|
|
136
|
+
* kept while it fits in them: dragging a window's edge used to make the
|
|
137
|
+
* chain's buffers again at every step, and import each one into the
|
|
138
|
+
* compositor, a dozen requests and a GPU allocation a step. Without it,
|
|
139
|
+
* the buffers are the size exactly, as a surface with no viewport shows
|
|
140
|
+
* all of its buffer.
|
|
141
|
+
*
|
|
142
|
+
* @param {number} width
|
|
143
|
+
* @param {number} height
|
|
144
|
+
* @param {{ slack?: boolean }} [opts]
|
|
114
145
|
* @returns the GBM surface to draw into
|
|
115
146
|
*/
|
|
116
|
-
surfaceFor(width, height) {
|
|
147
|
+
surfaceFor(width, height, { slack = false } = {}) {
|
|
117
148
|
const w = Math.max(1, Math.round(width));
|
|
118
149
|
const h = Math.max(1, Math.round(height));
|
|
119
|
-
if (!this.gbm)
|
|
120
|
-
|
|
150
|
+
if (!this.gbm) {
|
|
151
|
+
const [aw, ah] = slack ? slackFor(w, h, 1) : [w, h];
|
|
152
|
+
this._create(aw, ah);
|
|
153
|
+
this.width = w;
|
|
154
|
+
this.height = h;
|
|
155
|
+
return this.gbm;
|
|
156
|
+
}
|
|
157
|
+
if (w === this.width && h === this.height) {
|
|
158
|
+
// A size held a while is given buffers its own size again, where a
|
|
159
|
+
// drag left them larger than it needs.
|
|
160
|
+
if (
|
|
161
|
+
++this._sameSize === SLACK_SETTLE &&
|
|
162
|
+
(this.allocWidth !== w || this.allocHeight !== h)
|
|
163
|
+
) {
|
|
164
|
+
const [aw, ah] = slack ? slackFor(w, h, 1) : [w, h];
|
|
165
|
+
if (aw !== this.allocWidth || ah !== this.allocHeight)
|
|
166
|
+
this._resize(aw, ah);
|
|
167
|
+
}
|
|
168
|
+
return this.gbm;
|
|
169
|
+
}
|
|
170
|
+
this._sameSize = 0;
|
|
171
|
+
if (
|
|
172
|
+
slack &&
|
|
173
|
+
w <= this.allocWidth &&
|
|
174
|
+
h <= this.allocHeight &&
|
|
175
|
+
w * h * SLACK_SHRINK >= this.allocWidth * this.allocHeight
|
|
176
|
+
) {
|
|
177
|
+
// The buffers hold it: show another part of them. What each holds is
|
|
178
|
+
// of the old size, so all of it is owed to it.
|
|
179
|
+
this.width = w;
|
|
180
|
+
this.height = h;
|
|
181
|
+
for (const entry of this.buffers.values()) entry.damage = 'all';
|
|
182
|
+
return this.gbm;
|
|
183
|
+
}
|
|
184
|
+
const growing = w > this.allocWidth || h > this.allocHeight;
|
|
185
|
+
const [aw, ah] = slack ? slackFor(w, h, growing ? 1.25 : 1) : [w, h];
|
|
186
|
+
this._resize(aw, ah);
|
|
187
|
+
this.width = w;
|
|
188
|
+
this.height = h;
|
|
189
|
+
return this.gbm;
|
|
190
|
+
}
|
|
121
191
|
|
|
192
|
+
/** Give the chain buffers of this size, keeping its GBM surface. */
|
|
193
|
+
_resize(w, h) {
|
|
122
194
|
// In place: the handle stays valid, the context keeps its GL objects and
|
|
123
195
|
// stays current. Buffers from the old generation are gone, so their
|
|
124
196
|
// `wl_buffer`s go with them — the compositor may still be showing one, so
|
|
@@ -132,14 +204,13 @@ export class WaylandSwapchain {
|
|
|
132
204
|
{ cause: err },
|
|
133
205
|
),
|
|
134
206
|
);
|
|
135
|
-
return
|
|
207
|
+
return;
|
|
136
208
|
}
|
|
137
209
|
this._dropBuffers();
|
|
138
|
-
this.
|
|
139
|
-
this.
|
|
210
|
+
this.allocWidth = w;
|
|
211
|
+
this.allocHeight = h;
|
|
140
212
|
this.inFlight = 0;
|
|
141
213
|
this._swapsSinceNew = 0;
|
|
142
|
-
return this.gbm;
|
|
143
214
|
}
|
|
144
215
|
|
|
145
216
|
_create(w, h) {
|
|
@@ -157,8 +228,8 @@ export class WaylandSwapchain {
|
|
|
157
228
|
{ cause: err },
|
|
158
229
|
);
|
|
159
230
|
}
|
|
160
|
-
this.
|
|
161
|
-
this.
|
|
231
|
+
this.allocWidth = w;
|
|
232
|
+
this.allocHeight = h;
|
|
162
233
|
this._swapsSinceNew = 0;
|
|
163
234
|
return this.gbm;
|
|
164
235
|
}
|
|
@@ -285,7 +356,8 @@ export class WaylandSwapchain {
|
|
|
285
356
|
this.policy.linearFallback &&
|
|
286
357
|
!this.linear
|
|
287
358
|
) {
|
|
288
|
-
|
|
359
|
+
// the buffers again, at the size they were: the part shown stays
|
|
360
|
+
const { allocWidth: width, allocHeight: height } = this;
|
|
289
361
|
this.linear = true;
|
|
290
362
|
this._dropBuffers();
|
|
291
363
|
try {
|
|
@@ -409,3 +481,14 @@ export function unionDamage(a, b) {
|
|
|
409
481
|
const merged = [...a, ...b];
|
|
410
482
|
return merged.length > MAX_RECTS ? 'all' : merged;
|
|
411
483
|
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Buffers for a size, with room to grow: each side rounded up to a
|
|
487
|
+
* multiple of `SLACK_STEP`, after `grow` — 1.25 for a size outgrowing the
|
|
488
|
+
* buffers it had, so a window dragged wider makes a few sets of buffers
|
|
489
|
+
* rather than one a bucket.
|
|
490
|
+
*/
|
|
491
|
+
function slackFor(w, h, grow) {
|
|
492
|
+
const up = (v) => Math.ceil((v * grow) / SLACK_STEP) * SLACK_STEP;
|
|
493
|
+
return [up(w), up(h)];
|
|
494
|
+
}
|
package/src/wayland/window.js
CHANGED
|
@@ -182,6 +182,8 @@ export class WaylandWindow extends EventEmitter {
|
|
|
182
182
|
this._onConfigured = resolve;
|
|
183
183
|
});
|
|
184
184
|
this._viewport = null;
|
|
185
|
+
/** the part of the buffer the viewport shows, once one is attached */
|
|
186
|
+
this._shownPart = null;
|
|
185
187
|
this._fractional = null;
|
|
186
188
|
this._geometry = null;
|
|
187
189
|
/** the xdg-decoration object, where the compositor has the protocol (ssd.js) */
|
|
@@ -545,6 +547,31 @@ export class WaylandWindow extends EventEmitter {
|
|
|
545
547
|
}
|
|
546
548
|
}
|
|
547
549
|
|
|
550
|
+
/**
|
|
551
|
+
* Whether the surface shows a part of its buffer, the top left
|
|
552
|
+
* `bufferWidth` by `bufferHeight` of it, rather than all of it: where the
|
|
553
|
+
* compositor has a viewport to say so. The buffer may then be larger than
|
|
554
|
+
* the window, and is kept as the window is resized within it.
|
|
555
|
+
*/
|
|
556
|
+
get showsPartOfBuffer() {
|
|
557
|
+
return this._viewport !== null;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Show the top left `width` by `height` of the buffer the next commit
|
|
562
|
+
* attaches (`showsPartOfBuffer`). Only with a commit that attaches one at
|
|
563
|
+
* least that large: a source rectangle past the buffer shown is a protocol
|
|
564
|
+
* error, and a commit with no new buffer — every buffer busy, a configure
|
|
565
|
+
* acked — shows the old one. In buffer pixels, the buffer scale being 1.
|
|
566
|
+
*/
|
|
567
|
+
showBufferPart(width, height) {
|
|
568
|
+
if (!this._viewport) return;
|
|
569
|
+
const shown = this._shownPart;
|
|
570
|
+
if (shown && shown.width === width && shown.height === height) return;
|
|
571
|
+
this._shownPart = { width, height };
|
|
572
|
+
this._viewport.$.set_source(0, 0, width, height);
|
|
573
|
+
}
|
|
574
|
+
|
|
548
575
|
/** The size the backing buffer has to be for the current scale. */
|
|
549
576
|
get bufferWidth() {
|
|
550
577
|
return Math.max(1, Math.round(this.width * this.scale));
|