react-x11 2.12.0 → 2.14.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 +12 -8
- package/src/Reconciler.js +38 -35
- package/src/appcontext.js +59 -30
- package/src/cocoa/app.js +11 -0
- package/src/cocoa/glarea.js +4 -2
- package/src/cocoa/overlay.js +159 -0
- package/src/cocoa/promotion.js +13 -0
- package/src/components/Select.js +8 -2
- package/src/embedding.js +31 -0
- package/src/events.js +80 -14
- package/src/foreignnodes.js +56 -3
- package/src/glnodes.js +171 -40
- package/src/gloverlay.js +383 -0
- package/src/host.d.ts +0 -1
- package/src/index.d.ts +19 -1
- package/src/node.d.ts +12 -1
- package/src/nodes/boxpaint.js +9 -0
- package/src/nodes/preedit.js +64 -14
- package/src/nodes/window/flush.js +35 -0
- package/src/nodes/window/window.js +9 -0
- package/src/scale.js +52 -22
- package/src/screencolor.js +104 -17
- package/src/types/elements.d.ts +15 -13
- package/src/wayland/app.js +560 -0
- package/src/wayland/backendwindow.js +1123 -0
- package/src/wayland/clipboard.js +326 -0
- package/src/wayland/connection.js +482 -0
- package/src/wayland/context2d.js +2133 -0
- package/src/wayland/decorations.js +476 -0
- package/src/wayland/dmabuf.js +89 -0
- package/src/wayland/dnd.js +581 -0
- package/src/wayland/fdutil.js +108 -0
- package/src/wayland/framestyle.js +257 -0
- package/src/wayland/glarea.js +371 -0
- package/src/wayland/glcontext.js +415 -0
- package/src/wayland/glyphatlas.js +237 -0
- package/src/wayland/input.js +417 -0
- package/src/wayland/keysymnames.js +35 -0
- package/src/wayland/layershell.js +363 -0
- package/src/wayland/outputs.js +601 -0
- package/src/wayland/protocols/cursor-shape-v1.json +1 -0
- package/src/wayland/protocols/ext-idle-notify-v1.json +1 -0
- package/src/wayland/protocols/ext-image-capture-source-v1.json +1 -0
- package/src/wayland/protocols/ext-image-copy-capture-v1.json +1 -0
- package/src/wayland/protocols/fractional-scale-v1.json +1 -0
- package/src/wayland/protocols/index.json +127 -0
- package/src/wayland/protocols/keyboard-shortcuts-inhibit-unstable-v1.json +1 -0
- package/src/wayland/protocols/linux-dmabuf-v1.json +1 -0
- package/src/wayland/protocols/pointer-constraints-unstable-v1.json +1 -0
- package/src/wayland/protocols/presentation-time.json +1 -0
- package/src/wayland/protocols/primary-selection-unstable-v1.json +1 -0
- package/src/wayland/protocols/relative-pointer-unstable-v1.json +1 -0
- package/src/wayland/protocols/tablet-v2.json +1 -0
- package/src/wayland/protocols/text-input-unstable-v3.json +1 -0
- package/src/wayland/protocols/viewporter.json +1 -0
- package/src/wayland/protocols/wayland.json +1 -0
- package/src/wayland/protocols/wlr-layer-shell-unstable-v1.json +1 -0
- package/src/wayland/protocols/wlr-screencopy-unstable-v1.json +1 -0
- package/src/wayland/protocols/xdg-activation-v1.json +1 -0
- package/src/wayland/protocols/xdg-decoration-unstable-v1.json +1 -0
- package/src/wayland/protocols/xdg-output-unstable-v1.json +1 -0
- package/src/wayland/protocols/xdg-shell.json +1 -0
- package/src/wayland/protocols/xdg-toplevel-icon-v1.json +1 -0
- package/src/wayland/readback.js +99 -0
- package/src/wayland/screencopy.js +584 -0
- package/src/wayland/seat.js +584 -0
- package/src/wayland/shm.js +226 -0
- package/src/wayland/ssd.js +106 -0
- package/src/wayland/surface.js +123 -0
- package/src/wayland/swapchain.js +411 -0
- package/src/wayland/tablet.js +522 -0
- package/src/wayland/target.js +263 -0
- package/src/wayland/text.js +113 -0
- package/src/wayland/textinput.js +671 -0
- package/src/wayland/touch.js +284 -0
- package/src/wayland/window.js +827 -0
- package/src/wayland/xkb.js +425 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-x11",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.0",
|
|
4
4
|
"description": "react renderer with X11 as a target",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
"examples:appearance": "tsx examples/appearance.jsx",
|
|
21
21
|
"examples:animation": "tsx examples/animation.jsx",
|
|
22
22
|
"examples:simple": "tsx examples/simple.jsx",
|
|
23
|
+
"examples:wayland-gl": "node examples/wayland-gl.mjs",
|
|
24
|
+
"examples:wayland-ui": "node examples/wayland-ui.mjs",
|
|
23
25
|
"examples:simple-nojsx": "node examples/simple-nojsx.js",
|
|
24
26
|
"examples:xeyes": "tsx examples/xeyes.jsx",
|
|
25
27
|
"examples:dashboard": "tsx examples/dashboard.jsx",
|
|
@@ -60,6 +62,7 @@
|
|
|
60
62
|
"examples:frame": "tsx examples/frame.jsx",
|
|
61
63
|
"labs:text-baseline": "tsx examples/labs/text-baseline.jsx",
|
|
62
64
|
"labs:direct-gl": "tsx examples/labs/direct-gl.jsx",
|
|
65
|
+
"labs:gl-overlay": "tsx examples/labs/gl-overlay.jsx",
|
|
63
66
|
"examples:viewer3d": "tsx examples/viewer3d.jsx",
|
|
64
67
|
"examples:transparent": "tsx examples/transparent.jsx",
|
|
65
68
|
"examples:windows": "tsx examples/windows.jsx",
|
|
@@ -99,20 +102,21 @@
|
|
|
99
102
|
"ical.js": "^2.2.1",
|
|
100
103
|
"linebreak": "^1.1.0",
|
|
101
104
|
"ntk": "^8.7.0",
|
|
102
|
-
"react-reconciler": "^0.
|
|
105
|
+
"react-reconciler": "^0.34.0",
|
|
103
106
|
"yoga-layout": "^3.2.1"
|
|
104
107
|
},
|
|
105
108
|
"optionalDependencies": {
|
|
106
109
|
"@windowkit/appkit": "^0.10.0",
|
|
110
|
+
"@windowkit/wayland": "^3.1.1",
|
|
107
111
|
"dbus-native": "^0.15.1",
|
|
108
|
-
"x11-dri": "^0.
|
|
112
|
+
"x11-dri": "^0.9.0"
|
|
109
113
|
},
|
|
110
114
|
"peerDependencies": {
|
|
111
115
|
"@babel/core": "^8.0.0",
|
|
112
116
|
"@babel/plugin-transform-react-jsx": "^8.0.0",
|
|
113
117
|
"hot-module-replacement": "^4.0.1",
|
|
114
|
-
"react": "^19.
|
|
115
|
-
"react-refresh": "^0.
|
|
118
|
+
"react": "^19.3.0",
|
|
119
|
+
"react-refresh": "^0.19.0"
|
|
116
120
|
},
|
|
117
121
|
"peerDependenciesMeta": {
|
|
118
122
|
"@babel/core": {
|
|
@@ -145,9 +149,9 @@
|
|
|
145
149
|
"hot-module-replacement": "^4.0.1",
|
|
146
150
|
"katex": "^0.18.1",
|
|
147
151
|
"prettier": "^3.6.0",
|
|
148
|
-
"react": "^19.
|
|
149
|
-
"react-devtools-core": "^
|
|
150
|
-
"react-refresh": "^0.
|
|
152
|
+
"react": "^19.3.0",
|
|
153
|
+
"react-devtools-core": "^8.0.0",
|
|
154
|
+
"react-refresh": "^0.19.0",
|
|
151
155
|
"tsx": "^4.23.1",
|
|
152
156
|
"typescript": "^7.0.2",
|
|
153
157
|
"ws": "^8.21.1"
|
package/src/Reconciler.js
CHANGED
|
@@ -138,17 +138,18 @@ const HostConfig = {
|
|
|
138
138
|
return {
|
|
139
139
|
isInsideText: false,
|
|
140
140
|
isInsideSvg: false,
|
|
141
|
-
isInside3d: false,
|
|
142
141
|
};
|
|
143
142
|
},
|
|
144
143
|
|
|
145
144
|
getChildHostContext(parentHostContext, type) {
|
|
145
|
+
// A `<glarea>`'s children are drawn nodes like any box's — 2D content
|
|
146
|
+
// above the surface (src/gloverlay.js) — so it opens no context of its
|
|
147
|
+
// own. A scene graph over the surface is `@react-x11/components/three`,
|
|
148
|
+
// with a reconciler of its own.
|
|
146
149
|
return {
|
|
147
150
|
isInsideText: parentHostContext.isInsideText || type === 'text',
|
|
148
151
|
// <svg> children are declarative SVG elements, not react-x11 nodes
|
|
149
152
|
isInsideSvg: parentHostContext.isInsideSvg || type === 'svg',
|
|
150
|
-
// inside <glarea> the children are scene nodes, not drawn nodes
|
|
151
|
-
isInside3d: parentHostContext.isInside3d || type === 'glarea',
|
|
152
153
|
};
|
|
153
154
|
},
|
|
154
155
|
|
|
@@ -194,17 +195,6 @@ const HostConfig = {
|
|
|
194
195
|
'<text> spans and strings are.',
|
|
195
196
|
);
|
|
196
197
|
}
|
|
197
|
-
if (hostContext.isInside3d) {
|
|
198
|
-
// `<glarea>` is a leaf here: it owns the surface, the frame clock and
|
|
199
|
-
// the swap, and `onDraw` is the escape hatch. A *scene graph* over it
|
|
200
|
-
// — meshes, materials, lights, post-processing, on either backend —
|
|
201
|
-
// is `@react-x11/components/three`, which brings its own reconciler.
|
|
202
|
-
throw new Error(
|
|
203
|
-
`react-x11: <${type}> is not an element — <glarea> takes no ` +
|
|
204
|
-
'children. Draw through `onDraw`, or use ' +
|
|
205
|
-
'`@react-x11/components/three` for a scene graph. See docs/gl.md.',
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
198
|
let node;
|
|
209
199
|
switch (type) {
|
|
210
200
|
case 'window':
|
|
@@ -463,6 +453,10 @@ const HostConfig = {
|
|
|
463
453
|
},
|
|
464
454
|
startSuspendingCommit() {},
|
|
465
455
|
suspendInstance() {},
|
|
456
|
+
// react-reconciler 0.34 calls this on every commit whose lanes are all
|
|
457
|
+
// transition, retry or idle lanes, whether or not the tree has a
|
|
458
|
+
// <ViewTransition>. There is no view transition here to wait on.
|
|
459
|
+
suspendOnActiveViewTransition() {},
|
|
466
460
|
waitForCommitToBeReady() {
|
|
467
461
|
return null;
|
|
468
462
|
},
|
|
@@ -584,11 +578,11 @@ const isNtkApp = (v) =>
|
|
|
584
578
|
*/
|
|
585
579
|
function resolveBackend(options) {
|
|
586
580
|
const asked = options.backend ?? process.env.REACT_X11_BACKEND ?? 'auto';
|
|
587
|
-
if (asked === 'x11' || asked === 'cocoa') return asked;
|
|
581
|
+
if (asked === 'x11' || asked === 'cocoa' || asked === 'wayland') return asked;
|
|
588
582
|
if (asked !== 'auto') {
|
|
589
583
|
throw new Error(
|
|
590
584
|
`react-x11: unknown backend ${JSON.stringify(asked)} — expected ` +
|
|
591
|
-
"'x11', 'cocoa' or 'auto'.",
|
|
585
|
+
"'x11', 'cocoa', 'wayland' or 'auto'.",
|
|
592
586
|
);
|
|
593
587
|
}
|
|
594
588
|
// Naming an X endpoint is choosing X11: a `display` or a `stream` (the
|
|
@@ -725,25 +719,34 @@ export async function createRoot(options = {}) {
|
|
|
725
719
|
rest.backend === 'cocoa' || process.env.REACT_X11_BACKEND === 'cocoa';
|
|
726
720
|
const connecting = !owned
|
|
727
721
|
? Promise.resolve(borrowed)
|
|
728
|
-
: backend === '
|
|
729
|
-
?
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
)
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
722
|
+
: backend === 'wayland'
|
|
723
|
+
? // Opt-in only, never through 'auto': X11 stays the default on Linux
|
|
724
|
+
// because the remote case (docs/remote.md) is the flagship reason
|
|
725
|
+
// this project exists and Wayland has no network transparency. The
|
|
726
|
+
// backend is opt-in; docs/wayland-backend.md says what it does
|
|
727
|
+
// and does not do.
|
|
728
|
+
import('./wayland/app.js').then(({ createWaylandApp }) =>
|
|
729
|
+
createWaylandApp(rest),
|
|
730
|
+
)
|
|
731
|
+
: backend === 'cocoa'
|
|
732
|
+
? import('./cocoa/app.js')
|
|
733
|
+
.then(({ createCocoaApp }) => createCocoaApp(rest))
|
|
734
|
+
.catch((err) => {
|
|
735
|
+
// Asked for by name, the bridge is required and its absence is
|
|
736
|
+
// the error (it says how to install). Reached by 'auto', a mac
|
|
737
|
+
// without it falls back to X11 so an XQuartz setup keeps
|
|
738
|
+
// working — said once, because a silent fallback would look
|
|
739
|
+
// like the native backend being broken rather than absent.
|
|
740
|
+
if (cocoaAsked) throw err;
|
|
741
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
742
|
+
console.warn(
|
|
743
|
+
'react-x11: no @windowkit/appkit bridge — falling back to the ' +
|
|
744
|
+
`X11 backend. (${err.message.split('\n')[0]})`,
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
return connectX11();
|
|
748
|
+
})
|
|
749
|
+
: connectX11();
|
|
747
750
|
const layout = loadLayout();
|
|
748
751
|
const integrations = loadIntegrations(); // null when there is nothing to install
|
|
749
752
|
const [app] = await Promise.all([connecting, layout, integrations]);
|
package/src/appcontext.js
CHANGED
|
@@ -26,7 +26,9 @@ import {
|
|
|
26
26
|
compositingActive,
|
|
27
27
|
watchCompositing,
|
|
28
28
|
} from './compositing.js';
|
|
29
|
+
import { canEmbed } from './embedding.js';
|
|
29
30
|
import { hasDirectGL, watchDirectGL } from './glbackend.js';
|
|
31
|
+
import { canOverlay } from './gloverlay.js';
|
|
30
32
|
|
|
31
33
|
const AppContext = createContext(null);
|
|
32
34
|
|
|
@@ -57,17 +59,29 @@ export function useApp() {
|
|
|
57
59
|
return app;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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.
|
|
62
|
+
// 'nativeControls', 'embedding' and 'glOverlay' are properties of the
|
|
63
|
+
// backend, decided before the first render and never changing after — so
|
|
64
|
+
// their subscription has nothing to deliver and their snapshot is a property
|
|
65
|
+
// test.
|
|
69
66
|
const NEVER_CHANGES = () => () => {};
|
|
70
67
|
|
|
68
|
+
// What `useSupports` watches and reads, per feature. `read` answers a
|
|
69
|
+
// boolean, so the snapshot is stable for a given state — returning the
|
|
70
|
+
// visual object for 'transparency' would tear on every render.
|
|
71
|
+
const FEATURES = {
|
|
72
|
+
transparency: {
|
|
73
|
+
watch: watchCompositing,
|
|
74
|
+
read: (app) => compositingActive(app) && Boolean(argbVisual(app)),
|
|
75
|
+
},
|
|
76
|
+
shaders: { watch: watchDirectGL, read: hasDirectGL },
|
|
77
|
+
nativeControls: {
|
|
78
|
+
watch: NEVER_CHANGES,
|
|
79
|
+
read: (app) => Boolean(app.nativeBezels),
|
|
80
|
+
},
|
|
81
|
+
embedding: { watch: NEVER_CHANGES, read: canEmbed },
|
|
82
|
+
glOverlay: { watch: NEVER_CHANGES, read: canOverlay },
|
|
83
|
+
};
|
|
84
|
+
|
|
71
85
|
/**
|
|
72
86
|
* Can this **display** do something, as a value a component can branch on?
|
|
73
87
|
*
|
|
@@ -127,40 +141,55 @@ const NEVER_CHANGES = () => () => {};
|
|
|
127
141
|
* first render already reads the final answer. A policy raised after
|
|
128
142
|
* connecting has missed that probe, and re-renders its readers when it
|
|
129
143
|
* settles rather than leaving them with two different answers.
|
|
144
|
+
*
|
|
145
|
+
* `'embedding'` is true when this connection can take another process's
|
|
146
|
+
* window into its own — the X11 backend; never Cocoa, which has no such
|
|
147
|
+
* primitive, and never the headless mock. It is the question to ask before
|
|
148
|
+
* rendering a `<foreign>`, which refuses with one `onError` where the answer
|
|
149
|
+
* is no:
|
|
150
|
+
*
|
|
151
|
+
* ```jsx
|
|
152
|
+
* const embedding = useSupports('embedding');
|
|
153
|
+
* <box style={{ flexGrow: 1 }}>
|
|
154
|
+
* {embedding ? <foreign onReady={spawnInto} /> : <text>X11 only</text>}
|
|
155
|
+
* </box>
|
|
156
|
+
* ```
|
|
157
|
+
*
|
|
158
|
+
* Like `'nativeControls'`, it is a property of the backend and never changes.
|
|
159
|
+
*
|
|
160
|
+
* `'glOverlay'` is true when the children of a `<glarea>` are drawn above its
|
|
161
|
+
* GL surface on this connection — laid out in its box, painted on panes over
|
|
162
|
+
* the surface, hit before it. Both backends draw them; what differs is
|
|
163
|
+
* translucency, composited by Core Animation on the Cocoa backend and opaque
|
|
164
|
+
* on X11 (docs/elements.md says exactly how). It is the question to ask
|
|
165
|
+
* before handing a surface its HUD rather than drawing that some other way:
|
|
166
|
+
*
|
|
167
|
+
* ```jsx
|
|
168
|
+
* const overlay = useSupports('glOverlay');
|
|
169
|
+
* <glarea onDraw={drawMap}>{overlay && <Legend />}</glarea>
|
|
170
|
+
* ```
|
|
171
|
+
*
|
|
172
|
+
* A property of the backend too, and it never changes.
|
|
130
173
|
*/
|
|
131
174
|
export function useSupports(feature) {
|
|
132
175
|
const app = useApp();
|
|
133
|
-
|
|
176
|
+
const spec = Object.hasOwn(FEATURES, feature) ? FEATURES[feature] : null;
|
|
177
|
+
if (!spec) {
|
|
134
178
|
throw new Error(
|
|
135
179
|
`react-x11: useSupports(${JSON.stringify(feature)}) — unknown feature ` +
|
|
136
|
-
`(expected one of ${
|
|
180
|
+
`(expected one of ${Object.keys(FEATURES).join(', ')})`,
|
|
137
181
|
);
|
|
138
182
|
}
|
|
139
|
-
//
|
|
183
|
+
// Every feature goes through the same store, so the hooks below run in the
|
|
140
184
|
// same order whatever is being asked about. Where compositing comes and
|
|
141
185
|
// goes for as long as the app runs, the backend settles at most once — and
|
|
142
186
|
// watching that one moment is what keeps two components rendered either
|
|
143
187
|
// side of it from disagreeing (see watchDirectGL).
|
|
144
188
|
const subscribe = useCallback(
|
|
145
|
-
(onChange) =>
|
|
146
|
-
|
|
147
|
-
? NEVER_CHANGES()
|
|
148
|
-
: feature === 'shaders'
|
|
149
|
-
? watchDirectGL(app, onChange)
|
|
150
|
-
: watchCompositing(app, onChange),
|
|
151
|
-
[app, feature],
|
|
152
|
-
);
|
|
153
|
-
// a boolean, so the snapshot is stable for a given state — returning the
|
|
154
|
-
// visual object here would tear on every render
|
|
155
|
-
const snapshot = useCallback(
|
|
156
|
-
() =>
|
|
157
|
-
feature === 'nativeControls'
|
|
158
|
-
? Boolean(app.nativeBezels)
|
|
159
|
-
: feature === 'shaders'
|
|
160
|
-
? hasDirectGL(app)
|
|
161
|
-
: compositingActive(app) && Boolean(argbVisual(app)),
|
|
162
|
-
[app, feature],
|
|
189
|
+
(onChange) => spec.watch(app, onChange),
|
|
190
|
+
[app, spec],
|
|
163
191
|
);
|
|
192
|
+
const snapshot = useCallback(() => spec.read(app), [app, spec]);
|
|
164
193
|
return useSyncExternalStore(subscribe, snapshot, snapshot);
|
|
165
194
|
}
|
|
166
195
|
|
package/src/cocoa/app.js
CHANGED
|
@@ -26,6 +26,7 @@ import { setScreensForTests } from '../screens.js';
|
|
|
26
26
|
import { setScaleForTests } from '../scale.js';
|
|
27
27
|
import { BezelStore } from './bezels.js';
|
|
28
28
|
import { CocoaGLArea, cocoaGLConfig, resolveCocoaGLRuntime } from './glarea.js';
|
|
29
|
+
import { CocoaOverlayPane } from './overlay.js';
|
|
29
30
|
import { CocoaDockMenu } from './dock.js';
|
|
30
31
|
import { CocoaGlobalMenuExport } from './globalmenu.js';
|
|
31
32
|
import { CocoaStatusItem } from './statusitem.js';
|
|
@@ -418,6 +419,16 @@ export class CocoaApp {
|
|
|
418
419
|
return cocoaGLConfig(this, spec);
|
|
419
420
|
}
|
|
420
421
|
|
|
422
|
+
/**
|
|
423
|
+
* The pane a `<glarea>`'s children are drawn on (src/gloverlay.js): one
|
|
424
|
+
* transparent layer above the surface's, which Core Animation composites —
|
|
425
|
+
* so the overlay blends with the GL frame here, where an X11 pane is an
|
|
426
|
+
* opaque child window. Having this at all is how the overlay knows.
|
|
427
|
+
*/
|
|
428
|
+
createOverlayPane(attributes) {
|
|
429
|
+
return new CocoaOverlayPane(this, attributes);
|
|
430
|
+
}
|
|
431
|
+
|
|
421
432
|
/**
|
|
422
433
|
* The Frame host seam (src/frame/index.js): a pane's composited region
|
|
423
434
|
* in this window. Its presence is what routes <Frame> to the shared-
|
package/src/cocoa/glarea.js
CHANGED
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
// `destroy()`, `requestAnimationFrame`. On X11 that child is a real X
|
|
15
15
|
// window stacked above the parent's drawing; here it is a sublayer of the
|
|
16
16
|
// window's root layer with a high zPosition — the same "GL sits above the
|
|
17
|
-
// 2D" semantics, by the same mechanism the platform gives us.
|
|
17
|
+
// 2D" semantics, by the same mechanism the platform gives us. The layer
|
|
18
|
+
// takes no input: pointer events are the NSWindow's, and the window's hit
|
|
19
|
+
// test answers the surface for a point over it (`GlAreaNode.hitSurface`),
|
|
20
|
+
// which is where X11's event propagation ends up too.
|
|
18
21
|
//
|
|
19
22
|
// ## The API ladder
|
|
20
23
|
//
|
|
@@ -142,7 +145,6 @@ export class CocoaGLArea {
|
|
|
142
145
|
this.scale = this.parent.scale ?? app.scale ?? 1;
|
|
143
146
|
this.destroyed = false;
|
|
144
147
|
this._reactX11Node = null;
|
|
145
|
-
this.onWheel = options.onWheel ?? null;
|
|
146
148
|
this.layer = this._native.createLayer();
|
|
147
149
|
this._native.addSublayer(this.parent._layer, this.layer);
|
|
148
150
|
this.rect = null;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// The pane a `<glarea>`'s children are drawn on, on the Cocoa backend: a
|
|
2
|
+
// transparent bitmap layer over the surface (src/gloverlay.js).
|
|
3
|
+
//
|
|
4
|
+
// A sublayer of the window's root layer like the surface's own
|
|
5
|
+
// (src/cocoa/glarea.js), one step above it: the GL layer sits at zPosition
|
|
6
|
+
// 1e7, over everything both presenters put on the root layer, and this one
|
|
7
|
+
// at 1e7 + 1, over that. Core Animation composites it, so what the children
|
|
8
|
+
// leave transparent shows the GL frame, and a translucent fill, an
|
|
9
|
+
// antialiased edge or a shadow blends with it — the one backend where the
|
|
10
|
+
// overlay is not opaque. Every surface's GL layer shares the one zPosition,
|
|
11
|
+
// so where two surfaces overlap, both overlays are above both frames.
|
|
12
|
+
//
|
|
13
|
+
// It speaks the verbs of a window the overlay drives on X11 — `setState`,
|
|
14
|
+
// `map`, `unmap`, `getContext`, `destroy` — plus `present`, which puts what
|
|
15
|
+
// was painted on the layer: ntk blits an X window's backing store on its
|
|
16
|
+
// own, where a layer's contents are a copy the bitmap has to be pushed to.
|
|
17
|
+
import { CocoaContext2D } from './context2d.js';
|
|
18
|
+
|
|
19
|
+
export const OVERLAY_Z = 1e7 + 1;
|
|
20
|
+
|
|
21
|
+
export class CocoaOverlayPane {
|
|
22
|
+
constructor(app, options) {
|
|
23
|
+
this.app = app;
|
|
24
|
+
this.parent = options.parent;
|
|
25
|
+
this._native = app._native;
|
|
26
|
+
this.scale = this.parent.scale ?? app.scale ?? 1;
|
|
27
|
+
this.destroyed = false;
|
|
28
|
+
this.layer = this._native.createLayer();
|
|
29
|
+
this._native.addSublayer(this.parent._layer, this.layer);
|
|
30
|
+
this.rect = null;
|
|
31
|
+
this._surface = null;
|
|
32
|
+
this._surfaceSize = null;
|
|
33
|
+
this._gen = 0;
|
|
34
|
+
this._ctx = null;
|
|
35
|
+
this._dirty = false;
|
|
36
|
+
// Hidden until something is on it: a layer shows its contents from the
|
|
37
|
+
// moment it is added, and before the first present there are none to
|
|
38
|
+
// show, only whatever the render server makes of that.
|
|
39
|
+
this._presented = false;
|
|
40
|
+
this._hidden = false;
|
|
41
|
+
this.setState({
|
|
42
|
+
x: options.x ?? 0,
|
|
43
|
+
y: options.y ?? 0,
|
|
44
|
+
width: options.width ?? 1,
|
|
45
|
+
height: options.height ?? 1,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get width() {
|
|
50
|
+
return this.rect?.width ?? 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get height() {
|
|
54
|
+
return this.rect?.height ?? 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Geometry in device px, the unit the overlay's rects are in. */
|
|
58
|
+
setState(rect) {
|
|
59
|
+
if (this.destroyed) return;
|
|
60
|
+
this.rect = rect;
|
|
61
|
+
const s = this.scale;
|
|
62
|
+
this._setLayerProps({
|
|
63
|
+
frame: [rect.x / s, rect.y / s, rect.width / s, rect.height / s],
|
|
64
|
+
zPosition: OVERLAY_Z,
|
|
65
|
+
hidden: this._hidden || !this._presented,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
map() {
|
|
70
|
+
this._hidden = false;
|
|
71
|
+
if (this._presented) this._setLayerProps({ hidden: false });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
unmap() {
|
|
75
|
+
this._hidden = true;
|
|
76
|
+
this._setLayerProps({ hidden: true });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Implicit animations off, for the reason `CocoaGLArea._setLayerProps`
|
|
80
|
+
* gives: no frame's transaction covers a layer that is not a presenter's. */
|
|
81
|
+
_setLayerProps(props) {
|
|
82
|
+
if (this.destroyed) return;
|
|
83
|
+
const native = this._native;
|
|
84
|
+
native.txBegin({ disableActions: true });
|
|
85
|
+
try {
|
|
86
|
+
native.setLayerProps(this.layer, props);
|
|
87
|
+
} finally {
|
|
88
|
+
native.txCommit();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The bitmap, the pane's size — a new size is a new bitmap, cleared, and
|
|
93
|
+
* the old one freed now rather than by the handle's finalizer. */
|
|
94
|
+
_ensureSurface() {
|
|
95
|
+
const w = Math.max(1, this.rect?.width ?? 1);
|
|
96
|
+
const h = Math.max(1, this.rect?.height ?? 1);
|
|
97
|
+
const size = this._surfaceSize;
|
|
98
|
+
if (!this._surface || size.width !== w || size.height !== h) {
|
|
99
|
+
this._release();
|
|
100
|
+
this._surface = this._native.createSurface(w, h, this.scale);
|
|
101
|
+
this._native.ctxClearRect(this._surface, 0, 0, w, h);
|
|
102
|
+
this._surfaceSize = { width: w, height: h };
|
|
103
|
+
this._gen++;
|
|
104
|
+
}
|
|
105
|
+
return this._surface;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
_release() {
|
|
109
|
+
const surface = this._surface;
|
|
110
|
+
this._surface = null;
|
|
111
|
+
if (surface && typeof this._native.releaseSurface === 'function') {
|
|
112
|
+
this._native.releaseSurface(surface);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
getContext() {
|
|
117
|
+
if (!this._ctx) {
|
|
118
|
+
this._ctx = new CocoaContext2D(
|
|
119
|
+
this._native,
|
|
120
|
+
() => this._ensureSurface(),
|
|
121
|
+
() => {
|
|
122
|
+
this._ensureSurface();
|
|
123
|
+
return this._gen;
|
|
124
|
+
},
|
|
125
|
+
);
|
|
126
|
+
this._ctx._fonts = this.app.fonts;
|
|
127
|
+
this._ctx._onDirty = () => {
|
|
128
|
+
this._dirty = true;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return this._ctx;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** What was painted, onto the layer — a copy, so the bitmap is free to be
|
|
135
|
+
* painted again at once. */
|
|
136
|
+
present() {
|
|
137
|
+
if (this.destroyed || !this._dirty || !this._surface) return;
|
|
138
|
+
this._dirty = false;
|
|
139
|
+
const native = this._native;
|
|
140
|
+
native.txBegin({ disableActions: true });
|
|
141
|
+
try {
|
|
142
|
+
native.surfaceToLayer(this._surface, this.layer);
|
|
143
|
+
if (!this._presented) {
|
|
144
|
+
this._presented = true;
|
|
145
|
+
if (!this._hidden) native.setLayerProps(this.layer, { hidden: false });
|
|
146
|
+
}
|
|
147
|
+
} finally {
|
|
148
|
+
native.txCommit();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
destroy() {
|
|
153
|
+
if (this.destroyed) return;
|
|
154
|
+
this.destroyed = true;
|
|
155
|
+
this._release();
|
|
156
|
+
this._ctx = null;
|
|
157
|
+
this._native.removeFromSuperlayer(this.layer);
|
|
158
|
+
}
|
|
159
|
+
}
|
package/src/cocoa/promotion.js
CHANGED
|
@@ -142,6 +142,18 @@ function paintsSomething(node) {
|
|
|
142
142
|
return Boolean(node.isScroller?.());
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Inside a `<glarea>`: drawn on a pane above the surface (src/gloverlay.js).
|
|
147
|
+
* A layer of its own would sit on the root layer *under* the GL layer, so a
|
|
148
|
+
* promoted node there would vanish behind the surface it is drawn over.
|
|
149
|
+
*/
|
|
150
|
+
function insideGlArea(node) {
|
|
151
|
+
for (let n = node.parent; n && !n.isWindow; n = n.parent) {
|
|
152
|
+
if (n.isGlArea) return true;
|
|
153
|
+
}
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
145
157
|
/**
|
|
146
158
|
* Can this node be a property box on a layer at all — the static half of
|
|
147
159
|
* the answer, the same whatever the scene around it does: a plain box by
|
|
@@ -150,6 +162,7 @@ function paintsSomething(node) {
|
|
|
150
162
|
*/
|
|
151
163
|
function promotableNode(node) {
|
|
152
164
|
if (node.destroyed || !plainBox(node)) return false;
|
|
165
|
+
if (insideGlArea(node)) return false;
|
|
153
166
|
if (!stylePaintsPlain(node, node._targetStyle ?? node.style)) return false;
|
|
154
167
|
if (node.isScroller?.()) return false;
|
|
155
168
|
return !paintsOutline(node);
|
package/src/components/Select.js
CHANGED
|
@@ -354,11 +354,17 @@ export function Select({
|
|
|
354
354
|
);
|
|
355
355
|
const chosenRowTop = MENU_BORDER + metrics.pad + chosen * metrics.row;
|
|
356
356
|
const top = triggerCapsBottom - chosenRowTop - rowCapsBottom;
|
|
357
|
+
// `top` is where the menu starts against the trigger's own top, so it
|
|
358
|
+
// goes in as an `offset` back past the trigger's bottom — not as an `at`.
|
|
359
|
+
// Tracking asks whether `at` is still in view, and a menu opened over a
|
|
360
|
+
// trigger near the window's top edge starts above that edge: the popup
|
|
361
|
+
// is a real window and may, but the rect asked about was then "scrolled
|
|
362
|
+
// out", and the menu closed on the next unrelated layout pass (#552).
|
|
363
|
+
// What has to stay visible is the trigger.
|
|
357
364
|
return {
|
|
358
365
|
placement: 'bottom',
|
|
359
|
-
offset:
|
|
366
|
+
offset: top - triggerHeight,
|
|
360
367
|
flip: false,
|
|
361
|
-
at: { x: 0, y: top, width: node ? node.abs.width / scale : 0, height: 0 },
|
|
362
368
|
alignOffset: -metrics.hang.left,
|
|
363
369
|
height,
|
|
364
370
|
width,
|
package/src/embedding.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Whether a connection can take another process's window into its own.
|
|
2
|
+
//
|
|
3
|
+
// `<foreign>` asks before it builds a socket, and `useSupports('embedding')`
|
|
4
|
+
// asks for a component deciding whether to render one. One function for
|
|
5
|
+
// both, because the two have to agree: a hook that said yes over an element
|
|
6
|
+
// that then refused, or the reverse, is the bug this closes (issue #531).
|
|
7
|
+
//
|
|
8
|
+
// A feature test on the connection, not a question about which backend this
|
|
9
|
+
// is — and not "is there an `X`", which is true everywhere: the Cocoa app
|
|
10
|
+
// carries an X stub for the modules with an X escape hatch, and the headless
|
|
11
|
+
// mock carries the same one. Both also have a `createWindow` that takes a
|
|
12
|
+
// parent, which is how `<foreign>` used to get as far as a socket over them.
|
|
13
|
+
// So each part of embedding is asked about by name: a container window to
|
|
14
|
+
// hold the client, `ReparentWindow` to move somebody else's window into it,
|
|
15
|
+
// and the save set, which keeps that window alive if this process dies
|
|
16
|
+
// holding it — the promise docs/embedding.md makes about a window we do not
|
|
17
|
+
// own.
|
|
18
|
+
//
|
|
19
|
+
// A leaf module on purpose, for glbackend.js's reason: it imports nothing of
|
|
20
|
+
// ours, so both `appcontext.js` and the node layer can use it without the
|
|
21
|
+
// two importing each other.
|
|
22
|
+
|
|
23
|
+
/** Can `app` host another client's window inside one of its own? */
|
|
24
|
+
export function canEmbed(app) {
|
|
25
|
+
const X = app?.X;
|
|
26
|
+
return (
|
|
27
|
+
typeof app?.createWindow === 'function' &&
|
|
28
|
+
typeof X?.ReparentWindow === 'function' &&
|
|
29
|
+
typeof X?.ChangeSaveSet === 'function'
|
|
30
|
+
);
|
|
31
|
+
}
|