react-x11 2.15.2 → 2.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/package.json +4 -3
- package/src/Reconciler.js +85 -22
- package/src/anchor.js +60 -18
- package/src/application.js +25 -1
- package/src/capabilities.js +349 -0
- package/src/cocoa/app.js +28 -9
- package/src/cocoa/context2d.js +139 -6
- package/src/cocoa/fonts.js +78 -0
- package/src/cocoa/presenter.js +17 -0
- package/src/cocoa/promotion.js +20 -0
- package/src/cocoa/relaunch.js +8 -3
- package/src/cocoa/symbols.js +64 -0
- package/src/cocoa/threaded.js +24 -4
- package/src/cocoa/window.js +362 -139
- package/src/components/ProgressBar.js +1 -1
- package/src/components/Slider.js +72 -39
- package/src/components/anchor.js +7 -2
- package/src/components/index.js +1 -0
- package/src/components/theme.js +32 -28
- package/src/dbusmenuexport.js +243 -0
- package/src/desktopcapabilityhooks.js +160 -0
- package/src/filedialoghooks.js +3 -5
- package/src/frame/childmain.js +8 -20
- package/src/frame/env.js +2 -10
- package/src/globalmenu.js +3 -205
- package/src/icontheme.js +240 -0
- package/src/imagesource.js +98 -3
- package/src/index.d.ts +1 -0
- package/src/index.js +11 -2
- package/src/launcher.js +235 -32
- package/src/launcherhooks.js +47 -28
- package/src/node.d.ts +7 -0
- package/src/nodes/animation.js +17 -47
- package/src/nodes/cascade.js +17 -2
- package/src/nodes/image.js +65 -2
- package/src/nodes/kinds.js +12 -0
- package/src/nodes/layout.js +5 -1
- package/src/nodes/node.js +17 -3
- package/src/nodes/paint.js +117 -0
- package/src/nodes/scope.js +259 -0
- package/src/nodes/scrollable.js +53 -6
- package/src/nodes/text.js +2 -0
- package/src/nodes/textarea.js +1 -1
- package/src/nodes/textinput.js +1 -1
- package/src/nodes/window/anchoring.js +45 -18
- package/src/nodes/window/flush.js +6 -5
- package/src/nodes/window/popup.js +10 -0
- package/src/nodes/window/size.js +40 -2
- package/src/nodes/window/window.js +41 -14
- package/src/registry.js +2 -1
- package/src/settings.js +332 -0
- package/src/statusnotifier.js +752 -0
- package/src/styles.js +212 -8
- package/src/symbols.js +200 -0
- package/src/testing/mock-app.js +10 -0
- package/src/trayhooks.js +193 -29
- package/src/types/capabilities.d.ts +139 -0
- package/src/types/components.d.ts +33 -0
- package/src/types/elements.d.ts +57 -6
- package/src/types/launcher.d.ts +50 -4
- package/src/types/style.d.ts +57 -0
- package/src/types/system.d.ts +104 -0
- package/src/types/tray.d.ts +64 -6
package/src/cocoa/fonts.js
CHANGED
|
@@ -462,11 +462,31 @@ export class CocoaFace {
|
|
|
462
462
|
}
|
|
463
463
|
}
|
|
464
464
|
|
|
465
|
+
/**
|
|
466
|
+
* The features a letter-spaced span is set with: CSS's rule, and ntk's, that
|
|
467
|
+
* the optional ligatures come off where letters are spaced — an `fi` drawn as
|
|
468
|
+
* one glyph cannot open in its middle — unless the style names them.
|
|
469
|
+
*/
|
|
470
|
+
const OPTIONAL_LIGATURES_OFF = Object.freeze({
|
|
471
|
+
liga: 0,
|
|
472
|
+
clig: 0,
|
|
473
|
+
dlig: 0,
|
|
474
|
+
hlig: 0,
|
|
475
|
+
});
|
|
476
|
+
function spacedFeatures(features, spacing) {
|
|
477
|
+
if (!spacing) return features;
|
|
478
|
+
return features
|
|
479
|
+
? { ...OPTIONAL_LIGATURES_OFF, ...features }
|
|
480
|
+
: OPTIONAL_LIGATURES_OFF;
|
|
481
|
+
}
|
|
482
|
+
|
|
465
483
|
export class CocoaFontManager {
|
|
466
484
|
/** @param native the @windowkit/appkit module; the tests hand in a fake */
|
|
467
485
|
constructor(native = loadNative()) {
|
|
468
486
|
this._native = native;
|
|
469
487
|
this._fonts = new Map(); // family|weight|italic|size -> handle
|
|
488
|
+
// handle -> features key -> the handle with those features set
|
|
489
|
+
this._featured = new WeakMap();
|
|
470
490
|
this._faces = new Map(); // family|weight|italic -> face wrapper
|
|
471
491
|
this._registered = new Map(); // lowercase family -> [{cg, weight, italic}]
|
|
472
492
|
this._byKey = new Map(); // ntk Font key -> { cg } | { ps }
|
|
@@ -585,6 +605,52 @@ export class CocoaFontManager {
|
|
|
585
605
|
return handle;
|
|
586
606
|
}
|
|
587
607
|
|
|
608
|
+
/**
|
|
609
|
+
* `handle` with OpenType features set — `fontApplyFeatures`, which
|
|
610
|
+
* `@windowkit/appkit` has from 0.11.0 — for the resolved `features` a
|
|
611
|
+
* style hands the engines, tag → value. Kept per handle, so a paragraph
|
|
612
|
+
* laid out every repaint asks the bridge for a featured font once. A
|
|
613
|
+
* bridge without the verb gets the handle back as it was
|
|
614
|
+
* (`_textAdjustments`).
|
|
615
|
+
*/
|
|
616
|
+
_withFeatures(handle, features) {
|
|
617
|
+
if (!handle || !features || !this._textAdjustments()) return handle;
|
|
618
|
+
let perHandle = this._featured.get(handle);
|
|
619
|
+
if (!perHandle) this._featured.set(handle, (perHandle = new Map()));
|
|
620
|
+
const key = JSON.stringify(features);
|
|
621
|
+
let featured = perHandle.get(key);
|
|
622
|
+
if (!featured) {
|
|
623
|
+
featured = this._native.fontApplyFeatures(handle, features);
|
|
624
|
+
perHandle.set(key, featured);
|
|
625
|
+
}
|
|
626
|
+
return featured;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Whether the bridge sets features and letter spacing at all. Both arrived
|
|
631
|
+
* in one `@windowkit/appkit` release, `fontApplyFeatures` with the span's
|
|
632
|
+
* `letterSpacing`; an older bridge takes the span option and ignores it,
|
|
633
|
+
* so the verb is what answers for both.
|
|
634
|
+
*
|
|
635
|
+
* Without them the text draws as the face has it — digits proportional,
|
|
636
|
+
* letters unspaced — and nothing else goes wrong. But a style that asked
|
|
637
|
+
* for either and got nothing is a question with no thread to pull, so
|
|
638
|
+
* development says so, once.
|
|
639
|
+
*/
|
|
640
|
+
_textAdjustments() {
|
|
641
|
+
if (typeof this._native.fontApplyFeatures === 'function') return true;
|
|
642
|
+
if (process.env.NODE_ENV !== 'production' && !this._warnedAdjustments) {
|
|
643
|
+
this._warnedAdjustments = true;
|
|
644
|
+
console.warn(
|
|
645
|
+
'react-x11: this @windowkit/appkit has no fontApplyFeatures, so ' +
|
|
646
|
+
'letterSpacing, fontVariantNumeric and fontFeatureSettings do ' +
|
|
647
|
+
'nothing on the Cocoa backend. Update @windowkit/appkit to the ' +
|
|
648
|
+
'version react-x11 lists in its optionalDependencies.',
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
|
|
588
654
|
_withVariations(handle, variations) {
|
|
589
655
|
if (
|
|
590
656
|
variations &&
|
|
@@ -882,6 +948,8 @@ export class CocoaFontManager {
|
|
|
882
948
|
sp.color ?? base.color ?? null,
|
|
883
949
|
sp.variations ?? base.variations ?? null,
|
|
884
950
|
(sp.font ?? base.font)?.key ?? null,
|
|
951
|
+
sp.features ?? base.features ?? null,
|
|
952
|
+
sp.letterSpacing ?? base.letterSpacing ?? 0,
|
|
885
953
|
]),
|
|
886
954
|
options.maxWidth,
|
|
887
955
|
options.align,
|
|
@@ -926,12 +994,22 @@ export class CocoaFontManager {
|
|
|
926
994
|
),
|
|
927
995
|
variations,
|
|
928
996
|
);
|
|
997
|
+
// Letter spacing is the typesetter's kerning attribute, and it turns
|
|
998
|
+
// the optional ligatures off the way ntk does, so the two engines
|
|
999
|
+
// agree on what a spaced word is made of (#588).
|
|
1000
|
+
let spacing = span.letterSpacing ?? base.letterSpacing ?? 0;
|
|
1001
|
+
if (spacing && !this._textAdjustments()) spacing = 0;
|
|
1002
|
+
handle = this._withFeatures(
|
|
1003
|
+
handle,
|
|
1004
|
+
spacedFeatures(span.features ?? base.features, spacing),
|
|
1005
|
+
);
|
|
929
1006
|
const color = span.color ?? base.color;
|
|
930
1007
|
if (color == null) contextInk = true;
|
|
931
1008
|
nativeSpans.push({
|
|
932
1009
|
text: t,
|
|
933
1010
|
font: handle,
|
|
934
1011
|
...(color == null ? {} : { color: parseColor(color) }),
|
|
1012
|
+
...(spacing ? { letterSpacing: spacing } : {}),
|
|
935
1013
|
});
|
|
936
1014
|
}
|
|
937
1015
|
// A width offer of zero is a question, not a degenerate layout: yoga
|
package/src/cocoa/presenter.js
CHANGED
|
@@ -480,9 +480,21 @@ export function propBoxProps(node, app, scale, parentOrigin, order) {
|
|
|
480
480
|
backgroundColor: colour(style.backgroundColor),
|
|
481
481
|
borderWidth: border / scale,
|
|
482
482
|
borderColor: colour(style.borderColor),
|
|
483
|
+
opacity: layerOpacity(style),
|
|
483
484
|
};
|
|
484
485
|
}
|
|
485
486
|
|
|
487
|
+
/**
|
|
488
|
+
* `opacity` as a layer's own. On the layer presenter a node's children are
|
|
489
|
+
* its sublayers, so this fades the subtree with it; the bitmap paths draw
|
|
490
|
+
* the group themselves and never promote a faded box.
|
|
491
|
+
*/
|
|
492
|
+
export function layerOpacity(style) {
|
|
493
|
+
const value = style?.opacity;
|
|
494
|
+
if (typeof value !== 'number' || Number.isNaN(value)) return 1;
|
|
495
|
+
return Math.min(1, Math.max(0, value));
|
|
496
|
+
}
|
|
497
|
+
|
|
486
498
|
// --- animations the render server runs ---------------------------------------
|
|
487
499
|
//
|
|
488
500
|
// The node model keeps deciding what is animating and when it ends
|
|
@@ -586,6 +598,10 @@ export class LayerAnimations {
|
|
|
586
598
|
opts.timing = EASING_CONTROL_POINTS[entry.easing];
|
|
587
599
|
opts.repeat = Infinity;
|
|
588
600
|
opts.autoreverse = entry.alternate;
|
|
601
|
+
// CA's two halves of one CSS delay: a begin time the layer waits
|
|
602
|
+
// for showing `from`, or a time offset that starts it that far in
|
|
603
|
+
if (entry.delay > 0) opts.delay = entry.delay / 1000;
|
|
604
|
+
else if (entry.delay < 0) opts.timeOffset = -entry.delay / 1000;
|
|
589
605
|
} else if (map.colour) {
|
|
590
606
|
// From where the pixels are — which is what "an interrupted
|
|
591
607
|
// transition reverses from where it got to" means here. A colour
|
|
@@ -1112,6 +1128,7 @@ export class CocoaLayerPresenter {
|
|
|
1112
1128
|
],
|
|
1113
1129
|
zPosition: order,
|
|
1114
1130
|
hidden: Boolean(node.hidden),
|
|
1131
|
+
opacity: layerOpacity(node.style),
|
|
1115
1132
|
// the layer covers the ink bounds; clipping (if any) belongs to the
|
|
1116
1133
|
// CONTENT box, which a raster self cannot express — scrolling
|
|
1117
1134
|
// containers that also raster keep clipping via a child guard below
|
package/src/cocoa/promotion.js
CHANGED
|
@@ -154,6 +154,25 @@ function insideGlArea(node) {
|
|
|
154
154
|
return false;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
const faded = (style) => style?.opacity !== undefined && !(style.opacity >= 1);
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* A box whose `opacity` is below 1, now or on its way there, is a group the
|
|
161
|
+
* bitmap composites (`NodePaint._paintGroup`) — the box and everything in it
|
|
162
|
+
* drawn once and faded as one. On a layer of its own the box and its content
|
|
163
|
+
* would be separate layers under the one opacity, fading one over the other,
|
|
164
|
+
* and an opacity the frame clock is still animating would never reach the
|
|
165
|
+
* layer at all.
|
|
166
|
+
*/
|
|
167
|
+
function fadesAsGroup(node) {
|
|
168
|
+
return (
|
|
169
|
+
faded(node.style) ||
|
|
170
|
+
faded(node._targetStyle) ||
|
|
171
|
+
Boolean(node._anim?.has('opacity')) ||
|
|
172
|
+
Boolean(node._loops?.some((loop) => loop.prop === 'opacity'))
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
157
176
|
/**
|
|
158
177
|
* Can this node be a property box on a layer at all — the static half of
|
|
159
178
|
* the answer, the same whatever the scene around it does: a plain box by
|
|
@@ -163,6 +182,7 @@ function insideGlArea(node) {
|
|
|
163
182
|
function promotableNode(node) {
|
|
164
183
|
if (node.destroyed || !plainBox(node)) return false;
|
|
165
184
|
if (insideGlArea(node)) return false;
|
|
185
|
+
if (fadesAsGroup(node)) return false;
|
|
166
186
|
if (!stylePaintsPlain(node, node._targetStyle ?? node.style)) return false;
|
|
167
187
|
if (node.isScroller?.()) return false;
|
|
168
188
|
return !paintsOutline(node);
|
package/src/cocoa/relaunch.js
CHANGED
|
@@ -181,11 +181,16 @@ export function relaunch(entry, native) {
|
|
|
181
181
|
// the package's own would be a second one the bundler never emits. The
|
|
182
182
|
// worker sets itself up when it imports react-x11 (`bootstrapWorker`,
|
|
183
183
|
// src/bootstrap.js), recognising the shared state in its workerData.
|
|
184
|
-
//
|
|
185
|
-
//
|
|
184
|
+
//
|
|
185
|
+
// No `execArgv`: a Worker given none inherits the node flags the process
|
|
186
|
+
// started with, and among them a loader like `--import tsx`, which is what
|
|
187
|
+
// lets the worker load a .jsx entry at all. Handed `process.execArgv`
|
|
188
|
+
// instead, it refuses the whole list over one V8 or process-wide flag —
|
|
189
|
+
// `--expose-gc`, `--max-old-space-size`, `--title` — which the worker
|
|
190
|
+
// would have had anyway, since those hold for every thread: it gets `gc`
|
|
191
|
+
// either way (measured, Node 20–26; Bun treats the two the same).
|
|
186
192
|
new Worker(entry, {
|
|
187
193
|
argv: process.argv.slice(2),
|
|
188
|
-
execArgv: process.execArgv,
|
|
189
194
|
env: SHARE_ENV,
|
|
190
195
|
workerData: { reactX11State: state.buffer },
|
|
191
196
|
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// SF Symbols for the Cocoa backend (#591): `app.symbols`, the provider
|
|
2
|
+
// `<image src={{ symbol }}>` asks (src/symbols.js), over `@windowkit/appkit`'s
|
|
3
|
+
// `symbolSize` and `ctxDrawSymbol` (0.12.0).
|
|
4
|
+
//
|
|
5
|
+
// A symbol is a template — its shape, in whatever colour it is drawn with —
|
|
6
|
+
// so it is drawn the way a glyph run is: in the context's fill colour, through
|
|
7
|
+
// its transform and clip. Sizes are points, which on this backend are logical
|
|
8
|
+
// pixels.
|
|
9
|
+
|
|
10
|
+
import { warnOnce } from '../symbols.js';
|
|
11
|
+
|
|
12
|
+
export class CocoaSymbols {
|
|
13
|
+
constructor(native) {
|
|
14
|
+
this.native = native;
|
|
15
|
+
this.sizes = new Map(); // name and options -> { width, height } | null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Whether the bridge draws symbols at all. Without the verbs nothing is
|
|
19
|
+
* shown and nothing else goes wrong, so development says so, once. */
|
|
20
|
+
_available() {
|
|
21
|
+
if (typeof this.native?.ctxDrawSymbol === 'function') return true;
|
|
22
|
+
warnOnce(
|
|
23
|
+
'react-x11: this @windowkit/appkit has no ctxDrawSymbol, so <image ' +
|
|
24
|
+
'src={{ symbol }}> shows nothing on the Cocoa backend. Update ' +
|
|
25
|
+
'@windowkit/appkit to the version react-x11 lists in its ' +
|
|
26
|
+
'optionalDependencies.',
|
|
27
|
+
);
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
size(name, options) {
|
|
32
|
+
if (!this._available()) return null;
|
|
33
|
+
const bridge = bridgeOptions(options);
|
|
34
|
+
const key = JSON.stringify([name, bridge]);
|
|
35
|
+
if (!this.sizes.has(key)) {
|
|
36
|
+
if (this.sizes.size > 512) this.sizes.clear();
|
|
37
|
+
this.sizes.set(key, this.native.symbolSize(name, bridge));
|
|
38
|
+
}
|
|
39
|
+
return this.sizes.get(key);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
draw(ctx, name, rect, options) {
|
|
43
|
+
if (typeof ctx.drawSymbol !== 'function' || !this._available()) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
ctx.fillStyle = options.color;
|
|
47
|
+
return ctx.drawSymbol(
|
|
48
|
+
name,
|
|
49
|
+
rect.x,
|
|
50
|
+
rect.y,
|
|
51
|
+
rect.width,
|
|
52
|
+
rect.height,
|
|
53
|
+
bridgeOptions(options),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** What the bridge is handed: the options it knows, and only those set. */
|
|
59
|
+
const bridgeOptions = ({ pointSize, weight, scale, variableValue }) => ({
|
|
60
|
+
pointSize,
|
|
61
|
+
weight,
|
|
62
|
+
...(scale === undefined ? {} : { scale }),
|
|
63
|
+
...(variableValue === undefined ? {} : { variableValue }),
|
|
64
|
+
});
|
package/src/cocoa/threaded.js
CHANGED
|
@@ -188,10 +188,23 @@ export function fdStream(fd, write = fs.writeSync) {
|
|
|
188
188
|
/**
|
|
189
189
|
* Stdout and stderr on fds 1 and 2, and a console over them — the console
|
|
190
190
|
* too, because Bun's writes past `process.stdout` altogether.
|
|
191
|
+
*
|
|
192
|
+
* A `Console` is only the writing half of the runtime's console. Node adds
|
|
193
|
+
* the inspector's methods on top — `timeStamp`, `profile`, `profileEnd`,
|
|
194
|
+
* `context`, `createTask` — and `console.Console`, so the new console takes
|
|
195
|
+
* whatever it lacks from the one it replaces, as it is. Code that loaded
|
|
196
|
+
* before this saw them: React's development reconciler asks once, as it
|
|
197
|
+
* loads, whether `console.timeStamp` is a function, and calls it on every
|
|
198
|
+
* render after, so a script importing `react-x11/test` ahead of `react-x11`
|
|
199
|
+
* threw `console.timeStamp is not a function` on its first render.
|
|
191
200
|
*/
|
|
192
|
-
export function installStdio(
|
|
193
|
-
|
|
194
|
-
|
|
201
|
+
export function installStdio(
|
|
202
|
+
proc = process,
|
|
203
|
+
global = globalThis,
|
|
204
|
+
write = fs.writeSync,
|
|
205
|
+
) {
|
|
206
|
+
const stdout = fdStream(1, write);
|
|
207
|
+
const stderr = fdStream(2, write);
|
|
195
208
|
Object.defineProperty(proc, 'stdout', {
|
|
196
209
|
configurable: true,
|
|
197
210
|
get: () => stdout,
|
|
@@ -200,10 +213,17 @@ export function installStdio(proc = process, global = globalThis) {
|
|
|
200
213
|
configurable: true,
|
|
201
214
|
get: () => stderr,
|
|
202
215
|
});
|
|
216
|
+
const runtime = global.console;
|
|
217
|
+
const ours = new Console({ stdout, stderr });
|
|
218
|
+
for (const key of Reflect.ownKeys(runtime)) {
|
|
219
|
+
if (key in ours) continue;
|
|
220
|
+
const desc = Object.getOwnPropertyDescriptor(runtime, key);
|
|
221
|
+
Object.defineProperty(ours, key, desc);
|
|
222
|
+
}
|
|
203
223
|
Object.defineProperty(global, 'console', {
|
|
204
224
|
configurable: true,
|
|
205
225
|
writable: true,
|
|
206
|
-
value:
|
|
226
|
+
value: ours,
|
|
207
227
|
});
|
|
208
228
|
}
|
|
209
229
|
|