react-x11 2.2.1 → 2.3.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 +5 -3
- package/package.json +12 -5
- package/src/Reconciler.js +76 -11
- package/src/anchor.js +15 -5
- package/src/appcontext.js +27 -7
- package/src/cocoa/app.js +557 -0
- package/src/cocoa/bezels.js +161 -0
- package/src/cocoa/context2d.js +679 -0
- package/src/cocoa/fonts.js +541 -0
- package/src/cocoa/glarea.js +321 -0
- package/src/cocoa/globalmenu.js +149 -0
- package/src/cocoa/keymap.js +86 -0
- package/src/cocoa/native.js +51 -0
- package/src/cocoa/panehost.js +50 -0
- package/src/cocoa/panewindow.js +236 -0
- package/src/cocoa/presenter.js +837 -0
- package/src/cocoa/window.js +383 -0
- package/src/components/Button.js +68 -0
- package/src/components/Checkbox.js +43 -0
- package/src/components/Radio.js +37 -1
- package/src/components/Select.js +73 -28
- package/src/components/Slider.js +59 -0
- package/src/components/Switch.js +52 -0
- package/src/components/native.js +142 -0
- package/src/decorations.js +21 -24
- package/src/editmenu.js +23 -14
- package/src/events.js +11 -2
- package/src/frame/childmain.js +9 -0
- package/src/frame/index.js +162 -2
- package/src/glnodes.js +43 -0
- package/src/globalmenu.js +11 -3
- package/src/index.d.ts +10 -0
- package/src/nodes.js +107 -33
- package/src/paintcache.js +15 -4
- package/src/palette.js +14 -0
- package/src/styles.js +5 -0
- package/src/types/elements.d.ts +10 -0
package/README.md
CHANGED
|
@@ -79,12 +79,14 @@ three:
|
|
|
79
79
|
- **three platforms.** X11 only. macOS means XQuartz — a separate install, a
|
|
80
80
|
non-native look and no menu bar integration. Windows is out; if you need
|
|
81
81
|
Windows, use Electron or Tauri.
|
|
82
|
-
- **native Wayland.** There is no Wayland backend
|
|
83
|
-
be one; that would be a different renderer, not a flag. Ordinary
|
|
82
|
+
- **native Wayland — today.** There is no Wayland backend yet. Ordinary
|
|
84
83
|
application windows work fine on a Wayland desktop through Xwayland, which
|
|
85
84
|
is not going away — but the desktop-shell half of X11 (panel struts,
|
|
86
85
|
global key grabs, screen capture, and the window-manager example below)
|
|
87
|
-
needs a real X session.
|
|
86
|
+
needs a real X session. A native backend is researched and planned as a
|
|
87
|
+
**second target beside X11, not a migration**:
|
|
88
|
+
[docs/wayland.md](docs/wayland.md) is the RFC, from the fd transport up
|
|
89
|
+
to what the rendering would ride on.
|
|
88
90
|
- **reusing web components.** There is no DOM. Your MUI, your Tailwind and
|
|
89
91
|
your `recharts` do not come with you; the state, data-fetching, validation
|
|
90
92
|
and math libraries mostly do. [docs/ecosystem.md](docs/ecosystem.md) says
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-x11",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "react renderer with X11 as a target",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"xi2:probe": "node scripts/xi2-probe.mjs",
|
|
35
35
|
"globalmenu:host": "node scripts/globalmenu-host.mjs",
|
|
36
36
|
"devtools:proxy": "node scripts/devtools-proxy.mjs",
|
|
37
|
+
"examples:configurator": "tsx examples/configurator/index.jsx",
|
|
37
38
|
"examples:fonts": "tsx examples/fonts.jsx",
|
|
38
39
|
"examples:form": "tsx examples/form.jsx",
|
|
39
40
|
"examples:rules": "tsx examples/rules.jsx",
|
|
@@ -59,12 +60,13 @@
|
|
|
59
60
|
"stress:check": "tsx scripts/check-stress.jsx",
|
|
60
61
|
"screenshots": "tsx scripts/screenshots.jsx",
|
|
61
62
|
"screenshots:framed": "tsx scripts/screenshots-framed.jsx",
|
|
62
|
-
"bench": "tsx scripts/bench/protocol.js",
|
|
63
|
+
"bench": "node --expose-gc --import tsx scripts/bench/protocol.js",
|
|
63
64
|
"bench:pixels": "tsx scripts/bench/pixels.js",
|
|
64
65
|
"bench:frames": "tsx scripts/bench/frames.js",
|
|
65
66
|
"docs:dev": "npm --prefix website start",
|
|
66
67
|
"docs:build": "npm --prefix website run build",
|
|
67
|
-
"docs:test": "npm --prefix website test"
|
|
68
|
+
"docs:test": "npm --prefix website test",
|
|
69
|
+
"bench:presenters": "node --import tsx scripts/bench/presenters.js"
|
|
68
70
|
},
|
|
69
71
|
"repository": {
|
|
70
72
|
"type": "git",
|
|
@@ -83,12 +85,14 @@
|
|
|
83
85
|
"node": ">=20.19"
|
|
84
86
|
},
|
|
85
87
|
"dependencies": {
|
|
86
|
-
"ntk": "^8.
|
|
88
|
+
"ntk": "^8.7.0",
|
|
87
89
|
"react-reconciler": "^0.33.0",
|
|
88
90
|
"yoga-layout": "^3.2.1"
|
|
89
91
|
},
|
|
90
92
|
"optionalDependencies": {
|
|
91
|
-
"
|
|
93
|
+
"@windowkit/appkit": "^0.1.0",
|
|
94
|
+
"dbus-native": "^0.15.1",
|
|
95
|
+
"x11-dri": "^0.7.0"
|
|
92
96
|
},
|
|
93
97
|
"peerDependencies": {
|
|
94
98
|
"@babel/core": "^8.0.0",
|
|
@@ -118,6 +122,9 @@
|
|
|
118
122
|
"@babel/core": "^8.0.1",
|
|
119
123
|
"@babel/plugin-transform-react-jsx": "^8.0.1",
|
|
120
124
|
"@eslint/js": "^9.32.0",
|
|
125
|
+
"@fontsource/instrument-serif": "^5.3.0",
|
|
126
|
+
"@fontsource/inter": "^5.3.0",
|
|
127
|
+
"@fontsource/jetbrains-mono": "^5.3.0",
|
|
121
128
|
"@types/react": "^19.2.17",
|
|
122
129
|
"eslint": "^9.32.0",
|
|
123
130
|
"eslint-plugin-react": "^7.37.5",
|
package/src/Reconciler.js
CHANGED
|
@@ -571,6 +571,36 @@ async function connect(options) {
|
|
|
571
571
|
const isNtkApp = (v) =>
|
|
572
572
|
Boolean(v) && typeof v.createWindow === 'function' && typeof v.X === 'object';
|
|
573
573
|
|
|
574
|
+
/**
|
|
575
|
+
* Which display system a root that opens its own connection talks to.
|
|
576
|
+
*
|
|
577
|
+
* `'auto'` — the default — is one rule: macOS gets the native Core
|
|
578
|
+
* Animation backend, everything else gets X11 via `$DISPLAY`. (More rungs —
|
|
579
|
+
* Wayland — join this ladder, not a new one; docs/wayland.md.) On a mac
|
|
580
|
+
* without the `@windowkit/appkit` bridge installed, auto falls back to X11 so
|
|
581
|
+
* an XQuartz setup keeps working; asking for `'cocoa'` by name means the
|
|
582
|
+
* bridge is required, and its absence is an error that says how to fix it.
|
|
583
|
+
* `REACT_X11_BACKEND` overrides for A/B runs without touching code.
|
|
584
|
+
*/
|
|
585
|
+
function resolveBackend(options) {
|
|
586
|
+
const asked = options.backend ?? process.env.REACT_X11_BACKEND ?? 'auto';
|
|
587
|
+
if (asked === 'x11' || asked === 'cocoa') return asked;
|
|
588
|
+
if (asked !== 'auto') {
|
|
589
|
+
throw new Error(
|
|
590
|
+
`react-x11: unknown backend ${JSON.stringify(asked)} — expected ` +
|
|
591
|
+
"'x11', 'cocoa' or 'auto'.",
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
// Naming an X endpoint is choosing X11: a `display` or a `stream` (the
|
|
595
|
+
// tests' in-process server, a tunnel) would be silently ignored by any
|
|
596
|
+
// other backend, and an ignored connection option is worse than either
|
|
597
|
+
// answer.
|
|
598
|
+
if (options.display !== undefined || options.stream !== undefined) {
|
|
599
|
+
return 'x11';
|
|
600
|
+
}
|
|
601
|
+
return process.platform === 'darwin' ? 'cocoa' : 'x11';
|
|
602
|
+
}
|
|
603
|
+
|
|
574
604
|
// What a root that opens its own connection forwards to ntk. `stream` is
|
|
575
605
|
// how you reach a server that is not on the other end of $DISPLAY — an
|
|
576
606
|
// in-process one, a tunnel — and is what the tests connect through.
|
|
@@ -652,11 +682,24 @@ export async function createRoot(options = {}) {
|
|
|
652
682
|
}
|
|
653
683
|
const { app: borrowed, onDisconnect, ...rest } = options;
|
|
654
684
|
const owned = borrowed === undefined;
|
|
685
|
+
// Which display system this root speaks — resolved before anything runs,
|
|
686
|
+
// because the desktop-integration default below depends on it. A borrowed
|
|
687
|
+
// app already IS a backend, so the option only steers owned connections.
|
|
688
|
+
const backend = owned ? resolveBackend(rest) : null;
|
|
655
689
|
// Before anything starts, for two reasons: a bad `desktop` shape must throw
|
|
656
690
|
// with nothing in flight, like the check above it — and `startA11y()` below
|
|
657
691
|
// reads this policy, so it has to be settled before the first await, not
|
|
658
692
|
// after (src/desktopintegration.js).
|
|
659
|
-
|
|
693
|
+
//
|
|
694
|
+
// The cocoa backend's desktop is not freedesktop's: the AT-SPI bridge and
|
|
695
|
+
// the D-Bus global menu have nothing to register against there, so they
|
|
696
|
+
// default off (their macOS replacements are their own workstream —
|
|
697
|
+
// docs/macos.md). The appearance ladder stays on: its macOS rung is the
|
|
698
|
+
// one that answers. An explicit `desktop` option still wins.
|
|
699
|
+
setDesktopIntegration(
|
|
700
|
+
rest.desktop ??
|
|
701
|
+
(backend === 'cocoa' ? { a11y: false, globalMenu: false } : undefined),
|
|
702
|
+
);
|
|
660
703
|
// The connection is started first, and the order is the point rather than a
|
|
661
704
|
// detail. `loadLayout()` is only nominally asynchronous: instantiating the
|
|
662
705
|
// engine blocks the event loop for 15-50 ms before it returns its promise
|
|
@@ -669,16 +712,38 @@ export async function createRoot(options = {}) {
|
|
|
669
712
|
// synchronous block, and only where the handshake takes longer than it. It
|
|
670
713
|
// is not measurable on a Unix socket, and it is lost in the noise on a link
|
|
671
714
|
// slow enough to matter. This is the right order, not a fast one.
|
|
672
|
-
const
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
715
|
+
const connectX11 = () =>
|
|
716
|
+
connect(
|
|
717
|
+
Object.fromEntries(
|
|
718
|
+
CONNECT_OPTIONS.filter((k) => rest[k] !== undefined).map((k) => [
|
|
719
|
+
k,
|
|
720
|
+
rest[k],
|
|
721
|
+
]),
|
|
722
|
+
),
|
|
723
|
+
);
|
|
724
|
+
const cocoaAsked =
|
|
725
|
+
rest.backend === 'cocoa' || process.env.REACT_X11_BACKEND === 'cocoa';
|
|
726
|
+
const connecting = !owned
|
|
727
|
+
? Promise.resolve(borrowed)
|
|
728
|
+
: backend === 'cocoa'
|
|
729
|
+
? import('./cocoa/app.js')
|
|
730
|
+
.then(({ createCocoaApp }) => createCocoaApp(rest))
|
|
731
|
+
.catch((err) => {
|
|
732
|
+
// Asked for by name, the bridge is required and its absence is
|
|
733
|
+
// the error (it says how to install). Reached by 'auto', a mac
|
|
734
|
+
// without it falls back to X11 so an XQuartz setup keeps
|
|
735
|
+
// working — said once, because a silent fallback would look
|
|
736
|
+
// like the native backend being broken rather than absent.
|
|
737
|
+
if (cocoaAsked) throw err;
|
|
738
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
739
|
+
console.warn(
|
|
740
|
+
'react-x11: no @windowkit/appkit bridge — falling back to the ' +
|
|
741
|
+
`X11 backend. (${err.message.split('\n')[0]})`,
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
return connectX11();
|
|
745
|
+
})
|
|
746
|
+
: connectX11();
|
|
682
747
|
const layout = loadLayout();
|
|
683
748
|
const integrations = loadIntegrations(); // null when there is nothing to install
|
|
684
749
|
const [app] = await Promise.all([connecting, layout, integrations]);
|
package/src/anchor.js
CHANGED
|
@@ -28,11 +28,20 @@ import { availableArea } from './screens.js';
|
|
|
28
28
|
export function screenRect(node) {
|
|
29
29
|
if (!node?.abs) return null;
|
|
30
30
|
const origin = windowOrigin(node);
|
|
31
|
+
// Logical pixels, like every rect application code touches: this is a
|
|
32
|
+
// public export, and the rects it is compared against — `anchorRect`'s
|
|
33
|
+
// result, `anchorArea` — answer in logical too. The device-pixel sum
|
|
34
|
+
// (origin + abs) divides once, here; a caller that needs the device rect
|
|
35
|
+
// is placement math and multiplies back by `node.scale` itself. On a 1x
|
|
36
|
+
// display the two units coincide, which is how a mix survived until the
|
|
37
|
+
// first 2x backend ran the tooltip arrow into the wrong half of the
|
|
38
|
+
// bubble.
|
|
39
|
+
const s = node.scale ?? 1;
|
|
31
40
|
return {
|
|
32
|
-
x: origin.x + node.abs.x,
|
|
33
|
-
y: origin.y + node.abs.y,
|
|
34
|
-
width: node.abs.width,
|
|
35
|
-
height: node.abs.height,
|
|
41
|
+
x: (origin.x + node.abs.x) / s,
|
|
42
|
+
y: (origin.y + node.abs.y) / s,
|
|
43
|
+
width: node.abs.width / s,
|
|
44
|
+
height: node.abs.height / s,
|
|
36
45
|
};
|
|
37
46
|
}
|
|
38
47
|
|
|
@@ -136,7 +145,8 @@ export function deviceAnchorArea(node) {
|
|
|
136
145
|
const app = node?.app;
|
|
137
146
|
if (!app) return null;
|
|
138
147
|
const at = screenRect(node);
|
|
139
|
-
|
|
148
|
+
const s = node.scale ?? 1;
|
|
149
|
+
return availableArea(app, at ? { x: at.x * s, y: at.y * s } : null);
|
|
140
150
|
}
|
|
141
151
|
|
|
142
152
|
/**
|
package/src/appcontext.js
CHANGED
|
@@ -57,7 +57,16 @@ export function useApp() {
|
|
|
57
57
|
return app;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
const SUPPORTS_FEATURES = new Set([
|
|
60
|
+
const SUPPORTS_FEATURES = new Set([
|
|
61
|
+
'transparency',
|
|
62
|
+
'shaders',
|
|
63
|
+
'nativeControls',
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
// 'nativeControls' is a property of the backend, decided before the first
|
|
67
|
+
// render and never changing after — so its subscription has nothing to
|
|
68
|
+
// deliver and its snapshot is a property test.
|
|
69
|
+
const NEVER_CHANGES = () => () => {};
|
|
61
70
|
|
|
62
71
|
/**
|
|
63
72
|
* Can this **display** do something, as a value a component can branch on?
|
|
@@ -105,6 +114,13 @@ const SUPPORTS_FEATURES = new Set(['transparency', 'shaders']);
|
|
|
105
114
|
* disagree exactly when the policy did not ask. The capabilities also say
|
|
106
115
|
* *why* this is false; see docs/gl.md.
|
|
107
116
|
*
|
|
117
|
+
* `'nativeControls'` is true when this backend renders the platform's own
|
|
118
|
+
* control bezels — today the Cocoa backend, never X11. The widget set
|
|
119
|
+
* already branches on it through the theme's `controls: 'auto'` policy, so
|
|
120
|
+
* this hook is for application code composing its own controls that wants
|
|
121
|
+
* to sit beside native ones. It is a property of the backend and never
|
|
122
|
+
* changes over the app's life.
|
|
123
|
+
*
|
|
108
124
|
* Unlike `'transparency'`, which comes and goes with the compositor, this
|
|
109
125
|
* settles once and then holds still: under a policy that could pick direct,
|
|
110
126
|
* `createRoot()` waits for ntk's probe before handing the app back, so the
|
|
@@ -127,18 +143,22 @@ export function useSupports(feature) {
|
|
|
127
143
|
// side of it from disagreeing (see watchDirectGL).
|
|
128
144
|
const subscribe = useCallback(
|
|
129
145
|
(onChange) =>
|
|
130
|
-
feature === '
|
|
131
|
-
?
|
|
132
|
-
:
|
|
146
|
+
feature === 'nativeControls'
|
|
147
|
+
? NEVER_CHANGES()
|
|
148
|
+
: feature === 'shaders'
|
|
149
|
+
? watchDirectGL(app, onChange)
|
|
150
|
+
: watchCompositing(app, onChange),
|
|
133
151
|
[app, feature],
|
|
134
152
|
);
|
|
135
153
|
// a boolean, so the snapshot is stable for a given state — returning the
|
|
136
154
|
// visual object here would tear on every render
|
|
137
155
|
const snapshot = useCallback(
|
|
138
156
|
() =>
|
|
139
|
-
feature === '
|
|
140
|
-
?
|
|
141
|
-
:
|
|
157
|
+
feature === 'nativeControls'
|
|
158
|
+
? Boolean(app.nativeBezels)
|
|
159
|
+
: feature === 'shaders'
|
|
160
|
+
? hasDirectGL(app)
|
|
161
|
+
: compositingActive(app) && Boolean(argbVisual(app)),
|
|
142
162
|
[app, feature],
|
|
143
163
|
);
|
|
144
164
|
return useSyncExternalStore(subscribe, snapshot, snapshot);
|