react-x11 2.18.4 → 2.18.6
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 +3 -3
- package/src/Reconciler.js +9 -2
- package/src/backend/context2d.js +90 -20
- package/src/cocoa/app.js +1 -1
- package/src/cocoa/fonts.js +2 -1
- package/src/cocoa/presenter.js +1 -1
- package/src/decorations.js +1 -1
- package/src/fonts.js +1 -1
- package/src/foreignnodes.js +6 -1
- package/src/glbackend.js +1 -1
- package/src/glnodes.js +1 -1
- package/src/gloverlay.js +48 -25
- package/src/imagesource.js +4 -1
- package/src/node.d.ts +3 -0
- package/src/nodes/boxpaint.js +5 -3
- package/src/nodes/image.js +3 -3
- package/src/nodes/invalidate.js +31 -2
- package/src/nodes/layout.js +4 -4
- package/src/nodes/node.js +15 -3
- package/src/nodes/paint.js +1 -1
- package/src/nodes/scrollable.js +5 -5
- package/src/nodes/scrollblit.js +12 -2
- package/src/nodes/text.js +1 -1
- package/src/nodes/window/capabilities.js +1 -1
- package/src/nodes/window/flush.js +27 -18
- package/src/ntk.js +8 -31
- package/src/ntkroot.js +50 -0
- package/src/offscreen.js +33 -0
- package/src/paintcache.js +11 -7
- package/src/refresh/loader.js +23 -4
- package/src/screencolor.js +1 -1
- package/src/styles.js +1 -1
- package/src/svgnodes.js +1 -1
- package/src/symbols.js +4 -2
- package/src/trayhooks.js +1 -1
- package/src/wayland/context2d.js +2 -1
- package/src/wayland/decorations.js +1 -1
- package/src/win32/a11y.js +66 -6
- package/src/win32/app.js +83 -9
- package/src/win32/fonts.js +27 -8
- package/src/win32/overlay.js +244 -0
- package/src/win32/panewindow.js +6 -0
- package/src/win32/window.js +71 -1
package/src/nodes/paint.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// window, background first.
|
|
4
4
|
|
|
5
5
|
import { isPlaced } from '../layouts.js';
|
|
6
|
-
import { Surface } from '../
|
|
6
|
+
import { Surface } from '../offscreen.js';
|
|
7
7
|
import { isPaintedColor } from './boxpaint.js';
|
|
8
8
|
import { DAMAGE_SLOP } from './damage.js';
|
|
9
9
|
import { DRAWN_KINDS } from './kinds.js';
|
package/src/nodes/scrollable.js
CHANGED
|
@@ -301,11 +301,11 @@ export const Scrollable = (Base) =>
|
|
|
301
301
|
// for a `<glarea>` child's move that this pane rides (issue
|
|
302
302
|
// #644): its box claimed nothing, and a pane laid out again may
|
|
303
303
|
// have moved rows the move's copy carries where they were.
|
|
304
|
-
if (clamped || layoutDiff.ride) outer(vp);
|
|
304
|
+
if (clamped || layoutDiff.ride) outer(vp, this);
|
|
305
305
|
} else {
|
|
306
|
-
layoutDiff.sink = (rect) => {
|
|
306
|
+
layoutDiff.sink = (rect, node) => {
|
|
307
307
|
const clipped = intersectRects(rect, vp);
|
|
308
|
-
if (clipped) outer(clipped);
|
|
308
|
+
if (clipped) outer(clipped, node);
|
|
309
309
|
};
|
|
310
310
|
}
|
|
311
311
|
this._claimThumbs(thumbsWere, outer);
|
|
@@ -863,8 +863,8 @@ export const Scrollable = (Base) =>
|
|
|
863
863
|
if (sameThumb(before, after)) continue;
|
|
864
864
|
// a pixel of slop for the rounded corners' antialiasing, as the
|
|
865
865
|
// blit's own thumb repair takes
|
|
866
|
-
if (before) sink(insetRect(before, -1));
|
|
867
|
-
if (after) sink(insetRect(after, -1));
|
|
866
|
+
if (before) sink(insetRect(before, -1), this);
|
|
867
|
+
if (after) sink(insetRect(after, -1), this);
|
|
868
868
|
}
|
|
869
869
|
}
|
|
870
870
|
|
package/src/nodes/scrollblit.js
CHANGED
|
@@ -970,12 +970,22 @@ export class WindowScrollBlit {
|
|
|
970
970
|
if (ancestors.has(child)) {
|
|
971
971
|
// on the path down: its solid background under the viewport is
|
|
972
972
|
// translation-invariant, its border ring and corners are not.
|
|
973
|
-
// The widest side is conservative for a non-uniform border
|
|
973
|
+
// The widest side is conservative for a non-uniform border.
|
|
974
|
+
//
|
|
975
|
+
// Rounded, because that is where the ring ends for what is
|
|
976
|
+
// inside it: at a fractional scale a 1px border is 1.25 or 1.5
|
|
977
|
+
// device pixels, and layout puts the children at the rounded
|
|
978
|
+
// inner edge — so the column the ring reaches a quarter of the
|
|
979
|
+
// way into is a child's, painted over whole. Tested exactly, that
|
|
980
|
+
// column refused every blit under a bordered box at 125%, the
|
|
981
|
+
// scale most Windows laptops run at.
|
|
974
982
|
const bw = resolveBorderWidths(
|
|
975
983
|
child.style ?? EMPTY_STYLE,
|
|
976
984
|
child.direction,
|
|
977
985
|
);
|
|
978
|
-
const ring = Math.
|
|
986
|
+
const ring = Math.round(
|
|
987
|
+
Math.max(bw.top, bw.right, bw.bottom, bw.left),
|
|
988
|
+
);
|
|
979
989
|
if (ring > 0 && !rectContains(insetRect(child.abs, ring), vp)) {
|
|
980
990
|
return false;
|
|
981
991
|
}
|
package/src/nodes/text.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// selection paints, and the glyph strip.
|
|
3
3
|
|
|
4
4
|
import { localTextStyleChanged, TEXT_REMEASURE } from '../styles.js';
|
|
5
|
-
import { cssColorStraight } from 'ntk';
|
|
5
|
+
import { cssColorStraight } from 'ntk/color';
|
|
6
6
|
import { hooks as a11yHooks } from '../a11y.js';
|
|
7
7
|
import { codePointAtOffset, codeUnitOffsets } from '../textrange.js';
|
|
8
8
|
import { NO_DAMAGE } from './damage.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// What a window can paint: compositing and ARGB transparency, the window
|
|
2
2
|
// background, and the capabilities `@supports` blocks read.
|
|
3
3
|
|
|
4
|
-
import { cssColorStraight } from 'ntk';
|
|
4
|
+
import { cssColorStraight } from 'ntk/color';
|
|
5
5
|
import {
|
|
6
6
|
argbVisual,
|
|
7
7
|
compositingActive,
|
|
@@ -6,7 +6,7 @@ import { resolveFramePolicy } from '../../pacing.js';
|
|
|
6
6
|
import { paintCacheFor } from '../../paintcache.js';
|
|
7
7
|
import { hooks as traceHooks } from '../../trace-registry.js';
|
|
8
8
|
import { now } from '../animation.js';
|
|
9
|
-
import { FULL_DAMAGE, layoutDiff
|
|
9
|
+
import { FULL_DAMAGE, layoutDiff } from '../damage.js';
|
|
10
10
|
import { BLIT_POISONED } from '../scrollblit.js';
|
|
11
11
|
import { debugPaint } from './debugpaint.js';
|
|
12
12
|
|
|
@@ -204,12 +204,10 @@ export class WindowFlush {
|
|
|
204
204
|
(panes && this._paneDamage !== FULL_DAMAGE)
|
|
205
205
|
) {
|
|
206
206
|
const cap = this._damageRectCap();
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
if (panes) this._addPaneDamage(rect);
|
|
207
|
+
// each to the lists that paint the node that moved
|
|
208
|
+
// (`_claimLayoutMove`): a move inside a `<glarea>` is its panes'
|
|
209
|
+
layoutDiff.sink = (rect, node) => {
|
|
210
|
+
if (this._claimLayoutMove(rect, node, cap)) layoutMoved = true;
|
|
213
211
|
};
|
|
214
212
|
}
|
|
215
213
|
try {
|
|
@@ -259,14 +257,8 @@ export class WindowFlush {
|
|
|
259
257
|
if (sv && !sv._recordBlitClaim(after)) {
|
|
260
258
|
sv._pendingBlitFrom = BLIT_POISONED;
|
|
261
259
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
this._damage,
|
|
265
|
-
after,
|
|
266
|
-
this._damageRectCap(),
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
if (panes) this._addPaneDamage(after);
|
|
260
|
+
// to the lists that paint it, as the layout diff's claims go
|
|
261
|
+
this._claimLayoutMove(after, node, this._damageRectCap());
|
|
270
262
|
}
|
|
271
263
|
this._reflowed.clear();
|
|
272
264
|
} else if (this._reflowed.size) {
|
|
@@ -326,9 +318,26 @@ export class WindowFlush {
|
|
|
326
318
|
// list was still taken (its bookkeeping is what keeps the two paths one
|
|
327
319
|
// code) and is simply not consumed; the presenter diffs at the layer.
|
|
328
320
|
if (typeof this.window.presentFrame === 'function') {
|
|
329
|
-
//
|
|
330
|
-
|
|
331
|
-
|
|
321
|
+
// A presenter whose frame is the ordinary paint walk — a pass per
|
|
322
|
+
// damage rect into a context, the Windows window's — paints through
|
|
323
|
+
// the paint cache as an ntk window does, and says so
|
|
324
|
+
// (`usesPaintCache`). Without it every `<canvas cacheKey>` drew
|
|
325
|
+
// afresh in every pass that reached it, and a shadow, which is only
|
|
326
|
+
// ever baked into the cache (issue #413), was not drawn at all. The
|
|
327
|
+
// retained layer presenter paints nodes onto layers of their own and
|
|
328
|
+
// keeps to itself.
|
|
329
|
+
const cache = this.window.usesPaintCache
|
|
330
|
+
? (this._paintCache ??= paintCacheFor(this.app))
|
|
331
|
+
: null;
|
|
332
|
+
cache?.beginFrame();
|
|
333
|
+
try {
|
|
334
|
+
// the panes are no presenter's: they paint from their damage either
|
|
335
|
+
// way
|
|
336
|
+
if (this._overlaid.size !== 0) this._paintOverlays(paneDamage);
|
|
337
|
+
this.window.presentFrame(this, damage);
|
|
338
|
+
} finally {
|
|
339
|
+
cache?.endFrame();
|
|
340
|
+
}
|
|
332
341
|
this.app._reactX11Startup?.painted();
|
|
333
342
|
return true;
|
|
334
343
|
}
|
package/src/ntk.js
CHANGED
|
@@ -22,43 +22,20 @@
|
|
|
22
22
|
// `@react-x11/components` (`<Markdown>`, `<Formula>`). `SvgView` is still
|
|
23
23
|
// here; a drawing is not a document.
|
|
24
24
|
//
|
|
25
|
-
// One name is not a plain re-export. `Surface`
|
|
25
|
+
// One name is not a plain re-export. `Surface` (src/offscreen.js) asks the app it is
|
|
26
26
|
// handed for the implementation, because ntk's own is a pixmap and a
|
|
27
27
|
// Picture — an X connection's — and a component allocates its buffer
|
|
28
28
|
// without knowing which backend it was mounted on. This subpath is where a
|
|
29
29
|
// drawing-adjacent name gets its backend-neutral answer; the X-only names
|
|
30
30
|
// (`createClient`, `Pixmap`, `Picture`, `XEmbedSocket`) stay X-only.
|
|
31
|
-
import
|
|
31
|
+
import * as ntk from 'ntk';
|
|
32
|
+
|
|
33
|
+
import { adoptNtk } from './ntkroot.js';
|
|
32
34
|
|
|
33
35
|
export * from 'ntk';
|
|
34
36
|
export { default } from 'ntk';
|
|
37
|
+
export { Surface } from './offscreen.js';
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
* An app that makes its own surfaces answers `createSurface(options)` —
|
|
40
|
-
* the Cocoa app does, over a CG bitmap (src/cocoa/surface.js), the Wayland
|
|
41
|
-
* app over a GL render target (src/wayland/surface.js) — and an ntk
|
|
42
|
-
* connection has no such method and gets ntk's pixmap. The result is
|
|
43
|
-
* whichever implementation answered, not an instance of this class: the
|
|
44
|
-
* contract is the shape — `width`/`height`, `getContext('2d')`, `render`,
|
|
45
|
-
* `clear`, `copyWithin`, `destroy`, and `ctx.drawImage(surface, …)` —
|
|
46
|
-
* (docs/extending.md "Scrolling the pixels, not just the offset"), and
|
|
47
|
-
* nothing needs `instanceof`.
|
|
48
|
-
*
|
|
49
|
-
* Part of that shape is that a context may be **held**: ntk tells a caller
|
|
50
|
-
* doing many draws to take `getContext('2d')` once rather than one per
|
|
51
|
-
* frame, and that holds on every backend here, including the ones where a
|
|
52
|
-
* surface is a GPU target sharing a device with the window (#566). Draws
|
|
53
|
-
* through a held context land in the surface whenever they are made, and
|
|
54
|
-
* leave nothing for the caller to restore; `render(fn)` is the scoped form,
|
|
55
|
-
* for a caller that wants a frame's clean transform and clip.
|
|
56
|
-
*/
|
|
57
|
-
export class Surface {
|
|
58
|
-
constructor(app, options) {
|
|
59
|
-
if (typeof app?.createSurface === 'function') {
|
|
60
|
-
return app.createSurface(options);
|
|
61
|
-
}
|
|
62
|
-
return new NtkSurface(app, options);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
39
|
+
// what src/ntkroot.js would load, already loaded: handed over rather than
|
|
40
|
+
// imported twice
|
|
41
|
+
adoptNtk(ntk);
|
package/src/ntkroot.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// ntk's package root — the X11 client, ntk's windows and their contexts, the
|
|
2
|
+
// GL rungs, fontconfig and the rest of what the X11 backend draws with —
|
|
3
|
+
// loaded where an X connection is made rather than wherever react-x11 is
|
|
4
|
+
// imported.
|
|
5
|
+
//
|
|
6
|
+
// Only that backend needs it. The Cocoa, Windows and Wayland backends use a
|
|
7
|
+
// colour parser, the shadow arithmetic, the image and SVG decoders and the
|
|
8
|
+
// font parser of ntk's, each from a subpath of its own (`ntk/color`,
|
|
9
|
+
// `ntk/shadow-math`, `ntk/image`, `ntk/svg`, `ntk/font`, …), and importing
|
|
10
|
+
// the root for them was ~210 ms of module loading on Windows before the first
|
|
11
|
+
// window of an app that never opens an X connection.
|
|
12
|
+
//
|
|
13
|
+
// `createRoot` loads it on the X11 path, before the connection, and for an
|
|
14
|
+
// ntk app it is handed rather than makes — an app that could only have come
|
|
15
|
+
// from ntk's own `createClient`, so the import is already done and this is a
|
|
16
|
+
// lookup. `react-x11/ntk` hands over the root it imports. After that, the
|
|
17
|
+
// code that only ever runs against an X connection reads it synchronously.
|
|
18
|
+
let root = null;
|
|
19
|
+
let loading = null;
|
|
20
|
+
|
|
21
|
+
/** Load ntk's root, once; resolves to the module. */
|
|
22
|
+
export function loadNtk() {
|
|
23
|
+
return (loading ??= import('ntk').then((mod) => (root = mod)));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** A root imported some other way (`react-x11/ntk`): the same module, so
|
|
27
|
+
* nothing is loaded twice by taking it. */
|
|
28
|
+
export function adoptNtk(mod) {
|
|
29
|
+
root ??= mod;
|
|
30
|
+
loading ??= Promise.resolve(mod);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** ntk's root, or null while no X connection has been made. */
|
|
34
|
+
export function ntkRoot() {
|
|
35
|
+
return root;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* ntk's root, for code that only runs against an X connection — which
|
|
40
|
+
* `createRoot` has loaded it for. Throws where it is not: reaching for an X
|
|
41
|
+
* resource on a backend that has none is the mistake, and this says so
|
|
42
|
+
* rather than failing inside ntk on an app that is not its own.
|
|
43
|
+
*/
|
|
44
|
+
export function x11Ntk(what) {
|
|
45
|
+
if (root) return root;
|
|
46
|
+
throw new Error(
|
|
47
|
+
`react-x11: ${what} needs an X connection, and this process has none ` +
|
|
48
|
+
'(ntk loads with the X11 backend, in createRoot).',
|
|
49
|
+
);
|
|
50
|
+
}
|
package/src/offscreen.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// An offscreen surface on whichever backend an app is — what the paint cache,
|
|
2
|
+
// a group's opacity and the symbol icons draw into, and what `react-x11/ntk`
|
|
3
|
+
// hands a component as `Surface`.
|
|
4
|
+
//
|
|
5
|
+
// An app that makes its own surfaces answers `createSurface(options)` — the
|
|
6
|
+
// Cocoa app over a CG bitmap (src/cocoa/surface.js), the Windows app over a
|
|
7
|
+
// Direct2D bitmap, the Wayland app over a GL render target
|
|
8
|
+
// (src/wayland/surface.js) — and an X connection has no such method and gets
|
|
9
|
+
// ntk's pixmap, from ntk's root, which only an X connection loads
|
|
10
|
+
// (src/ntkroot.js). The result is whichever implementation answered, not an
|
|
11
|
+
// instance of this class: the contract is the shape — `width`/`height`,
|
|
12
|
+
// `getContext('2d')`, `render`, `clear`, `copyWithin`, `destroy`, and
|
|
13
|
+
// `ctx.drawImage(surface, …)` — (docs/extending.md "Scrolling the pixels, not
|
|
14
|
+
// just the offset"), and nothing needs `instanceof`.
|
|
15
|
+
//
|
|
16
|
+
// Part of that shape is that a context may be **held**: ntk tells a caller
|
|
17
|
+
// doing many draws to take `getContext('2d')` once rather than one per frame,
|
|
18
|
+
// and that holds on every backend here, including the ones where a surface is
|
|
19
|
+
// a GPU target sharing a device with the window (#566). Draws through a held
|
|
20
|
+
// context land in the surface whenever they are made, and leave nothing for
|
|
21
|
+
// the caller to restore; `render(fn)` is the scoped form, for a caller that
|
|
22
|
+
// wants a frame's clean transform and clip.
|
|
23
|
+
import { x11Ntk } from './ntkroot.js';
|
|
24
|
+
|
|
25
|
+
export class Surface {
|
|
26
|
+
constructor(app, options) {
|
|
27
|
+
if (typeof app?.createSurface === 'function') {
|
|
28
|
+
return app.createSurface(options);
|
|
29
|
+
}
|
|
30
|
+
const { Surface: NtkSurface } = x11Ntk('an offscreen surface');
|
|
31
|
+
return new NtkSurface(app, options);
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/paintcache.js
CHANGED
|
@@ -42,13 +42,13 @@
|
|
|
42
42
|
// this package pins, and a named import of something absent is a *load-time*
|
|
43
43
|
// SyntaxError — the whole renderer, not just the cache. Same shape as the
|
|
44
44
|
// `typeof wnd?.scrollRegion !== 'function'` guard for ntk without #139.
|
|
45
|
-
import * as ntk from 'ntk';
|
|
46
45
|
|
|
47
46
|
// The surface this cache draws into is `react-x11/ntk`'s: ntk's pixmap on
|
|
48
47
|
// an X connection, the app's own (`app.createSurface`) on a backend that
|
|
49
48
|
// makes them — the Cocoa backend's CG bitmap. One import, so the cache
|
|
50
49
|
// names neither.
|
|
51
|
-
import {
|
|
50
|
+
import { ntkRoot } from './ntkroot.js';
|
|
51
|
+
import { Surface } from './offscreen.js';
|
|
52
52
|
|
|
53
53
|
/** Stale pixels are undebuggable, and every other optimization here has an
|
|
54
54
|
* escape hatch — see NO_SCROLL_BLIT. */
|
|
@@ -415,15 +415,19 @@ export class PaintCache {
|
|
|
415
415
|
}
|
|
416
416
|
}
|
|
417
417
|
|
|
418
|
-
/** Whether the installed ntk can make the surfaces this needs
|
|
419
|
-
*
|
|
420
|
-
|
|
418
|
+
/** Whether the installed ntk can make the surfaces this needs on an X
|
|
419
|
+
* connection — ntk's root, which only an X connection loads
|
|
420
|
+
* (src/ntkroot.js). Older ones cannot, and the answer is simply that nothing
|
|
421
|
+
* is cached there. */
|
|
422
|
+
export const paintCacheSupported = () =>
|
|
423
|
+
typeof ntkRoot()?.Surface === 'function';
|
|
421
424
|
|
|
422
425
|
/** Whether `app` can make an offscreen surface at all: an X connection
|
|
423
426
|
* with the Render extension (ntk's pixmap surface), or a backend with a
|
|
424
427
|
* surface of its own — the Cocoa app's `createSurface` (docs/macos.md). */
|
|
425
428
|
const canMakeSurfaces = (app) =>
|
|
426
|
-
Boolean(app?.display?.Render)
|
|
429
|
+
(Boolean(app?.display?.Render) && paintCacheSupported()) ||
|
|
430
|
+
typeof app?.createSurface === 'function';
|
|
427
431
|
|
|
428
432
|
/**
|
|
429
433
|
* The cache for an app, created on first use. Null when caching is off, when
|
|
@@ -432,6 +436,6 @@ const canMakeSurfaces = (app) =>
|
|
|
432
436
|
* there must paint live.
|
|
433
437
|
*/
|
|
434
438
|
export function paintCacheFor(app) {
|
|
435
|
-
if (DISABLED || !
|
|
439
|
+
if (DISABLED || !canMakeSurfaces(app)) return null;
|
|
436
440
|
return (app._paintCache ??= new PaintCache(app));
|
|
437
441
|
}
|
package/src/refresh/loader.js
CHANGED
|
@@ -326,6 +326,28 @@ function buildTransformer({ babel, jsxTransform, refreshTransform }, options) {
|
|
|
326
326
|
|
|
327
327
|
let registered = false;
|
|
328
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Which files stay out of the hot graph: every package in node_modules,
|
|
331
|
+
* react-x11's own src/, and whatever the app's `ignore` names.
|
|
332
|
+
*
|
|
333
|
+
* Compared with forward slashes, whichever the path arrives with: on
|
|
334
|
+
* Windows a file path's separators are backslashes, and a forward-slash
|
|
335
|
+
* test let every package in node_modules into the hot graph — where the
|
|
336
|
+
* HMR layer's rewrite of a package's modules could leave one reading an
|
|
337
|
+
* import before it was bound (entities, under htmlparser2).
|
|
338
|
+
*/
|
|
339
|
+
export function hotIgnoreFor(ignore, srcDir = SRC_DIR) {
|
|
340
|
+
const src = srcDir.replaceAll('\\', '/');
|
|
341
|
+
return (path) => {
|
|
342
|
+
const slashed = path.replaceAll('\\', '/');
|
|
343
|
+
return (
|
|
344
|
+
slashed.includes('/node_modules/') ||
|
|
345
|
+
slashed.startsWith(src) ||
|
|
346
|
+
(ignore ? ignore(path) === true : false)
|
|
347
|
+
);
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
329
351
|
/**
|
|
330
352
|
* Register the hot-reload loader. `react-x11/refresh/register` calls this
|
|
331
353
|
* with no options; a tool that needs the seams writes its own two-line
|
|
@@ -362,10 +384,7 @@ export async function registerRefresh(options = {}) {
|
|
|
362
384
|
const { ignore } = validated;
|
|
363
385
|
registered = true;
|
|
364
386
|
|
|
365
|
-
const hotIgnore = (
|
|
366
|
-
path.includes('/node_modules/') ||
|
|
367
|
-
path.startsWith(SRC_DIR) ||
|
|
368
|
-
(ignore ? ignore(path) === true : false);
|
|
387
|
+
const hotIgnore = hotIgnoreFor(ignore);
|
|
369
388
|
|
|
370
389
|
nodeModule.registerHooks({
|
|
371
390
|
load(url, context, nextLoad) {
|
package/src/screencolor.js
CHANGED
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
// and losing the grab to someone else is detected (the server says so, with
|
|
57
57
|
// a LeaveNotify of mode Ungrab) rather than waited out.
|
|
58
58
|
|
|
59
|
-
import { pixelLayout, toStraightRgba } from 'ntk';
|
|
59
|
+
import { pixelLayout, toStraightRgba } from 'ntk/imagedata';
|
|
60
60
|
|
|
61
61
|
import { sessionBus } from './bus.js';
|
|
62
62
|
import { XK_ESCAPE, XK_KP_ENTER, XK_RETURN, XK_SPACE } from './keysyms.js';
|
package/src/styles.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Numbers are pixels; strings like '50%' / 'auto' pass through to yoga.
|
|
4
4
|
// The engine is ours (`./yoga.js`) — the enum tables below are built at
|
|
5
5
|
// module scope, which is what that module's synchronous half is for.
|
|
6
|
-
import { cssColorStraight } from 'ntk';
|
|
6
|
+
import { cssColorStraight } from 'ntk/color';
|
|
7
7
|
|
|
8
8
|
import { parseBoxShadow, parseLinearGradient } from './decorations.js';
|
|
9
9
|
import {
|
package/src/svgnodes.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// `<Formula>`), composed from public host elements. `<svg>` stays here
|
|
11
11
|
// because it is not a document — it is a drawing, and one node is the right
|
|
12
12
|
// grain for it.
|
|
13
|
-
import
|
|
13
|
+
import SvgView from 'ntk/svg';
|
|
14
14
|
|
|
15
15
|
import { Node } from './nodes/node.js';
|
|
16
16
|
import { intrinsicSize } from './nodes/layout.js';
|
package/src/symbols.js
CHANGED
|
@@ -17,11 +17,13 @@
|
|
|
17
17
|
// the same bargain a tray icon's name makes — so an app that runs on both
|
|
18
18
|
// picks its names by platform, as it does for the tray.
|
|
19
19
|
|
|
20
|
-
import { cssColorStraight
|
|
20
|
+
import { cssColorStraight } from 'ntk/color';
|
|
21
|
+
import { decodeImage } from 'ntk/image';
|
|
22
|
+
import SvgView from 'ntk/svg';
|
|
21
23
|
import * as nodeFs from 'node:fs';
|
|
22
24
|
|
|
23
25
|
import { IconTheme } from './icontheme.js';
|
|
24
|
-
import { Surface } from './
|
|
26
|
+
import { Surface } from './offscreen.js';
|
|
25
27
|
import { xsettings } from './xsettings.js';
|
|
26
28
|
|
|
27
29
|
/** A `fontWeight` as the number both icon systems configure with. */
|
package/src/trayhooks.js
CHANGED
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
// frame is not available for anything that has to ask the desktop.
|
|
55
55
|
|
|
56
56
|
import { useEffect, useRef, useState } from 'react';
|
|
57
|
-
import { decodeImage } from 'ntk';
|
|
57
|
+
import { decodeImage } from 'ntk/image';
|
|
58
58
|
|
|
59
59
|
import { useAppOrNull } from './appcontext.js';
|
|
60
60
|
import { currentRegistration } from './application.js';
|
package/src/wayland/context2d.js
CHANGED
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
import { createRequire } from 'node:module';
|
|
43
43
|
import path from 'node:path';
|
|
44
44
|
import { pathToFileURL } from 'node:url';
|
|
45
|
-
import {
|
|
45
|
+
import { Image as NtkImage } from 'ntk/image';
|
|
46
|
+
import { Path2D } from 'ntk/path';
|
|
46
47
|
import { glDevice } from './device.js';
|
|
47
48
|
import { GlyphAtlas } from './glyphatlas.js';
|
|
48
49
|
import { GLTarget } from './target.js';
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
// buttons do what they say. input.js routes; this answers where things are.
|
|
33
33
|
|
|
34
34
|
import fs from 'node:fs';
|
|
35
|
-
import { Path2D } from 'ntk';
|
|
35
|
+
import { Path2D } from 'ntk/path';
|
|
36
36
|
import { RESIZE_EDGE, TOPLEVEL_STATE } from './window.js';
|
|
37
37
|
import { DEFAULTS, parseButtonLayout, titleFont } from './framestyle.js';
|
|
38
38
|
|
package/src/win32/a11y.js
CHANGED
|
@@ -36,6 +36,22 @@ import { onApp } from '../trace-registry.js';
|
|
|
36
36
|
|
|
37
37
|
const TRACE = process.env.REACT_X11_TRACE_A11Y === '1';
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* How often commits may push the mirror, in ms. A commit that follows a
|
|
41
|
+
* quiet spell pushes at once; a stream of them — a pan, a drag, a value
|
|
42
|
+
* ticking — pushes once per interval, the last of them catching up.
|
|
43
|
+
*
|
|
44
|
+
* UIA's clients are almost always listening on a desktop Windows (the
|
|
45
|
+
* touch keyboard, the text services and others ask for the tree), so the
|
|
46
|
+
* push is not the rare case the listening gate makes it elsewhere: a walk
|
|
47
|
+
* of the window and a marshalled diff, on every commit. For a graph pane
|
|
48
|
+
* whose every item moves on every step of a pan that was 1.6 ms of each
|
|
49
|
+
* frame. A screen reader reads positions on demand and does not need
|
|
50
|
+
* sixty of them a second; a focus change, which it is waiting on, still
|
|
51
|
+
* pushes at once (`hooks.focus`), as does an announcement.
|
|
52
|
+
*/
|
|
53
|
+
const COMMIT_PUSH_MS = 100;
|
|
54
|
+
|
|
39
55
|
// --------------------------------------------------------------------------
|
|
40
56
|
// The one translation that is Windows' own
|
|
41
57
|
// --------------------------------------------------------------------------
|
|
@@ -300,6 +316,9 @@ export class Win32Accessibility {
|
|
|
300
316
|
this._pushed = new Set();
|
|
301
317
|
this._unsubscribes = [];
|
|
302
318
|
this.dead = false;
|
|
319
|
+
/** When a commit last pushed, and the push a stream of commits owes. */
|
|
320
|
+
this._lastCommitPush = -Infinity;
|
|
321
|
+
this._trailing = null;
|
|
303
322
|
}
|
|
304
323
|
|
|
305
324
|
_idOf(node) {
|
|
@@ -388,15 +407,29 @@ export class Win32Accessibility {
|
|
|
388
407
|
* commit and paying nothing — the same shape as the AT-SPI bridge's "no
|
|
389
408
|
* bus, no work".
|
|
390
409
|
*
|
|
410
|
+
* *Nobody* is asked per window where the bridge can say (`uiaActive`):
|
|
411
|
+
* whether a client has read this window's tree in the last few seconds.
|
|
412
|
+
* UIA's own answer, `UiaClientsAreListening`, is whether any client on
|
|
413
|
+
* the desktop is subscribed to anything, and on a Windows running the
|
|
414
|
+
* touch keyboard or the text services that is always yes — so every
|
|
415
|
+
* commit walked and diffed the window for a mirror nobody read. A client
|
|
416
|
+
* that comes back after a quiet spell is noticed on its first read and
|
|
417
|
+
* asks for a fresh push (`wanted`). `global` keeps the desktop-wide answer
|
|
418
|
+
* for a focus change and an announcement: a client subscribed to focus
|
|
419
|
+
* events reads the focused element the moment it hears of it, before
|
|
420
|
+
* any read of ours could say it was there.
|
|
421
|
+
*
|
|
391
422
|
* With one exception, which is what makes the gate safe: **every window is
|
|
392
423
|
* pushed once regardless.** A client's first `WM_GETOBJECT` has to find a
|
|
393
424
|
* tree, and it arrives before anything in this process knows a client
|
|
394
425
|
* exists. After that first push the mirror is live, and a client attaching
|
|
395
426
|
* later asks for a fresh one (`uia-wanted`).
|
|
396
427
|
*/
|
|
397
|
-
flush({ force = false } = {}) {
|
|
428
|
+
flush({ force = false, global = false } = {}) {
|
|
398
429
|
if (this.dead || this._dirty.size === 0) return;
|
|
399
|
-
const
|
|
430
|
+
const perWindow =
|
|
431
|
+
!force && !global && typeof this._native.uiaActive === 'function';
|
|
432
|
+
const listening = force || (!perWindow && this._native.uiaListening());
|
|
400
433
|
const windows = [...this._dirty];
|
|
401
434
|
this._dirty.clear();
|
|
402
435
|
for (const win of windows) {
|
|
@@ -409,12 +442,37 @@ export class Win32Accessibility {
|
|
|
409
442
|
this._dirty.add(win);
|
|
410
443
|
continue;
|
|
411
444
|
}
|
|
412
|
-
if (
|
|
445
|
+
if (
|
|
446
|
+
!listening &&
|
|
447
|
+
this._pushed.has(wnd.id) &&
|
|
448
|
+
!(perWindow && this._native.uiaActive(wnd.id))
|
|
449
|
+
) {
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
413
452
|
this._pushed.add(wnd.id);
|
|
414
453
|
this._push(win);
|
|
415
454
|
}
|
|
416
455
|
}
|
|
417
456
|
|
|
457
|
+
/** A commit's push, at most once per `COMMIT_PUSH_MS`. */
|
|
458
|
+
_commitFlush() {
|
|
459
|
+
if (this.dead || this._dirty.size === 0) return;
|
|
460
|
+
const now = performance.now();
|
|
461
|
+
const wait = this._lastCommitPush + COMMIT_PUSH_MS - now;
|
|
462
|
+
if (wait <= 0) {
|
|
463
|
+
this._lastCommitPush = now;
|
|
464
|
+
this.flush();
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (this._trailing) return;
|
|
468
|
+
this._trailing = setTimeout(() => {
|
|
469
|
+
this._trailing = null;
|
|
470
|
+
this._lastCommitPush = performance.now();
|
|
471
|
+
this.flush();
|
|
472
|
+
}, wait);
|
|
473
|
+
this._trailing.unref?.();
|
|
474
|
+
}
|
|
475
|
+
|
|
418
476
|
/** A window's HWND exists now, so what is owed for it can be pushed. */
|
|
419
477
|
windowReady(wnd) {
|
|
420
478
|
const win = this.toplevels.find((w) => w.window === wnd);
|
|
@@ -546,12 +604,12 @@ export class Win32Accessibility {
|
|
|
546
604
|
// Pushed now rather than at the next commit: a focus change is what a
|
|
547
605
|
// screen reader is waiting for, and the event has to follow an update
|
|
548
606
|
// that already carries the new state or the client reads the old one.
|
|
549
|
-
this.flush();
|
|
607
|
+
this.flush({ global: true });
|
|
550
608
|
const wnd = this._windowOf(win);
|
|
551
609
|
if (wnd && next) this._native.uiaFocusChanged(wnd.id, this._idOf(next));
|
|
552
610
|
};
|
|
553
611
|
hooks.windowFocus = (win) => this._dirty.add(win);
|
|
554
|
-
hooks.commit = () => this.
|
|
612
|
+
hooks.commit = () => this._commitFlush();
|
|
555
613
|
hooks.announce = (text, opts) => {
|
|
556
614
|
const win =
|
|
557
615
|
this.toplevels.find((w) => w.events?.windowFocused) ??
|
|
@@ -562,7 +620,7 @@ export class Win32Accessibility {
|
|
|
562
620
|
// The window must be in the mirror before anything can be announced
|
|
563
621
|
// from it, which on the first announcement of a session it may not be.
|
|
564
622
|
this._dirty.add(win);
|
|
565
|
-
this.flush();
|
|
623
|
+
this.flush({ global: true });
|
|
566
624
|
return Boolean(
|
|
567
625
|
this._native.uiaAnnounce(wnd.id, String(text), !opts?.assertive),
|
|
568
626
|
);
|
|
@@ -579,6 +637,8 @@ export class Win32Accessibility {
|
|
|
579
637
|
bury() {
|
|
580
638
|
if (this.dead) return;
|
|
581
639
|
this.dead = true;
|
|
640
|
+
if (this._trailing) clearTimeout(this._trailing);
|
|
641
|
+
this._trailing = null;
|
|
582
642
|
for (const key of Object.keys(hooks)) hooks[key] = null;
|
|
583
643
|
for (const unsubscribe of this._unsubscribes) unsubscribe();
|
|
584
644
|
this._unsubscribes = [];
|