react-x11 2.10.2 → 2.12.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 +278 -129
- package/package.json +10 -3
- package/src/Reconciler.js +15 -17
- package/src/a11y.js +2 -2
- package/src/anchor.js +7 -5
- package/src/bootstrap.js +14 -0
- package/src/clientmessage.js +1 -1
- package/src/cocoa/app.js +304 -49
- package/src/cocoa/bezels.js +175 -30
- package/src/cocoa/dnd.js +27 -13
- package/src/cocoa/fonts.js +3 -3
- package/src/cocoa/glarea.js +20 -3
- package/src/cocoa/main.d.ts +8 -0
- package/src/cocoa/main.js +43 -0
- package/src/cocoa/panehost.js +15 -5
- package/src/cocoa/presenter.js +13 -9
- package/src/cocoa/promotion.js +4 -7
- package/src/cocoa/relaunch.js +207 -0
- package/src/cocoa/screencolor.js +62 -0
- package/src/cocoa/threaded.js +246 -0
- package/src/cocoa/window.js +256 -42
- package/src/components/Select.js +2 -2
- package/src/components/anchor.js +3 -3
- package/src/components/native.js +12 -7
- package/src/components/theme.js +2 -2
- package/src/debug.js +1 -1
- package/src/decorations.js +1 -1
- package/src/editmenu.js +2 -2
- package/src/errors.js +46 -0
- package/src/events.js +6 -6
- package/src/foreignnodes.js +3 -2
- package/src/frames.js +2 -2
- package/src/glnodes.js +1 -1
- package/src/grid.js +1653 -0
- package/src/host.d.ts +230 -0
- package/src/host.js +11 -3
- package/src/imagesource.js +1 -1
- package/src/index.d.ts +21 -4
- package/src/index.js +9 -1
- package/src/layouts.js +721 -0
- package/src/node.d.ts +4 -2
- package/src/node.js +19 -21
- package/src/nodes/animation.js +644 -0
- package/src/nodes/box.js +21 -0
- package/src/nodes/boxpaint.js +473 -0
- package/src/nodes/canvas.js +269 -0
- package/src/nodes/cascade.js +600 -0
- package/src/nodes/damage.js +183 -0
- package/src/nodes/edithistory.js +124 -0
- package/src/nodes/editmenupopup.js +260 -0
- package/src/nodes/hittest.js +185 -0
- package/src/nodes/image.js +266 -0
- package/src/nodes/install.js +75 -0
- package/src/nodes/invalidate.js +465 -0
- package/src/nodes/kinds.js +31 -0
- package/src/nodes/layout.js +439 -0
- package/src/nodes/layouthost.js +949 -0
- package/src/nodes/node.js +868 -0
- package/src/nodes/paint.js +466 -0
- package/src/nodes/position.js +366 -0
- package/src/nodes/preedit.js +127 -0
- package/src/nodes/queries.js +330 -0
- package/src/nodes/rects.js +102 -0
- package/src/nodes/scrollable.js +891 -0
- package/src/nodes/scrollbars.js +138 -0
- package/src/nodes/scrollblit.js +1034 -0
- package/src/nodes/selectable.js +142 -0
- package/src/nodes/styling.js +225 -0
- package/src/nodes/text.js +649 -0
- package/src/nodes/textarea.js +391 -0
- package/src/nodes/textinput.js +1146 -0
- package/src/nodes/util.js +17 -0
- package/src/nodes/window/anchoring.js +161 -0
- package/src/nodes/window/capabilities.js +190 -0
- package/src/nodes/window/debugpaint.js +83 -0
- package/src/nodes/window/droptarget.js +145 -0
- package/src/nodes/window/floors.js +577 -0
- package/src/nodes/window/flush.js +334 -0
- package/src/nodes/window/hints.js +482 -0
- package/src/nodes/window/listeners.js +222 -0
- package/src/nodes/window/popup.js +71 -0
- package/src/nodes/window/size.js +591 -0
- package/src/nodes/window/window.js +945 -0
- package/src/palette.js +1 -1
- package/src/registry.js +7 -3
- package/src/screencolor.js +212 -38
- package/src/screencolorhooks.js +6 -2
- package/src/styles.js +137 -15
- package/src/svgnodes.js +2 -1
- package/src/testing/harness.js +2 -2
- package/src/textselection.js +5 -3
- package/src/trace-registry.js +1 -1
- package/src/types/components.d.ts +38 -6
- package/src/types/elements.d.ts +11 -1
- package/src/types/nodes.d.ts +33 -5
- package/src/types/screencolor.d.ts +20 -14
- package/src/types/style.d.ts +94 -3
- package/src/windowstate.js +1 -1
- package/src/yoga.js +1 -1
- package/src/nodes.js +0 -13120
package/src/palette.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Separate from `components/theme.js` because the palette is not a React
|
|
4
4
|
// concern. There are two routes into the tree — `useTheme()` through React
|
|
5
5
|
// context, and a `$token` resolved against the nearest `theme` **prop** by
|
|
6
|
-
// walking the node tree — and the second one lives in `nodes.js`, below the
|
|
6
|
+
// walking the node tree — and the second one lives in `nodes/cascade.js`, below the
|
|
7
7
|
// widget layer. Both have to agree on what "no theme was given" means, so the
|
|
8
8
|
// answer belongs underneath both of them.
|
|
9
9
|
//
|
package/src/registry.js
CHANGED
|
@@ -28,8 +28,9 @@ import {
|
|
|
28
28
|
DRAWN_KINDS,
|
|
29
29
|
CUSTOM_SEMANTIC_NAMES,
|
|
30
30
|
CUSTOM_SELF_DAMAGED,
|
|
31
|
-
|
|
32
|
-
} from './nodes.js';
|
|
31
|
+
} from './nodes/kinds.js';
|
|
32
|
+
import { Node } from './nodes/node.js';
|
|
33
|
+
import { markLayoutsHotReloadSession } from './layouts.js';
|
|
33
34
|
|
|
34
35
|
/** kind -> definition. Insertion-ordered, which is the order errors list. */
|
|
35
36
|
const registry = new Map();
|
|
@@ -54,6 +55,9 @@ let hotReloadSession = false;
|
|
|
54
55
|
/** @internal — called by react-x11/refresh; not part of the public API. */
|
|
55
56
|
export function markHotReloadSession(active = true) {
|
|
56
57
|
hotReloadSession = active;
|
|
58
|
+
// the layout and placement registries follow the same rule, for the same
|
|
59
|
+
// module-scope reason
|
|
60
|
+
markLayoutsHotReloadSession(active);
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
function assertNode(node, type) {
|
|
@@ -225,7 +229,7 @@ export function createRegisteredNode(type, props, app, hostContext) {
|
|
|
225
229
|
const definition = registry.get(type);
|
|
226
230
|
if (!definition) return undefined;
|
|
227
231
|
const node = assertNode(definition.create(props, app, hostContext), type);
|
|
228
|
-
// read by Node.insertBefore — carried on the instance so nodes
|
|
232
|
+
// read by Node.insertBefore — carried on the instance so src/nodes/ needs
|
|
229
233
|
// no import from here
|
|
230
234
|
if (!definition.childrenAllowed) node._childrenAllowed = false;
|
|
231
235
|
return node;
|
package/src/screencolor.js
CHANGED
|
@@ -1,24 +1,44 @@
|
|
|
1
1
|
// Sample one pixel from the screen — the eyedropper, through whatever this
|
|
2
2
|
// machine actually has.
|
|
3
3
|
//
|
|
4
|
-
// The file dialog's ladder again (docs/filedialog.md),
|
|
4
|
+
// The file dialog's ladder again (docs/filedialog.md), three rungs:
|
|
5
5
|
//
|
|
6
|
-
// 1. **the
|
|
6
|
+
// 1. **the system sampler** — `NSColorSampler` on the cocoa backend
|
|
7
|
+
// (src/cocoa/screencolor.js). macOS draws the loupe out of process, so
|
|
8
|
+
// the app needs no Screen Recording grant of its own and the user gets
|
|
9
|
+
// the magnifier every other Mac colour picker shows them. Found by the
|
|
10
|
+
// app carrying `colorSampler`, never by naming a backend here.
|
|
11
|
+
// 2. **the portal** — `org.freedesktop.portal.Screenshot.PickColor`. The
|
|
7
12
|
// desktop draws its own magnifier and hands back the colour, which is
|
|
8
13
|
// also the only route that works under a compositor that would refuse a
|
|
9
14
|
// root read, and the only route Wayland has at all. Needs version 2 of
|
|
10
15
|
// the Screenshot interface — XFCE ships none, GNOME and KDE ship 2 —
|
|
11
16
|
// so the gate is the interface's `version` property, not `hasService()`.
|
|
12
|
-
//
|
|
17
|
+
// 3. **X11** — grab the pointer with a crosshair, wait for the click,
|
|
13
18
|
// `GetImage` a 1×1 at it, decode by the server's own pixel layout.
|
|
14
19
|
// Reached under a bare WM, over ssh, on XQuartz: everywhere there is a
|
|
15
20
|
// display and nothing else, which is the case react-x11 exists for.
|
|
16
21
|
//
|
|
17
|
-
//
|
|
22
|
+
// Rungs 1 and 2 are the same shape — ask the system, it draws the picker,
|
|
23
|
+
// it hands back an sRGB triple — which is why the sampler goes on top of the
|
|
24
|
+
// portal rather than under the crosshair: where the OS will do this for us,
|
|
25
|
+
// it does it better, and `hexFromPortalColor()` converts for both.
|
|
26
|
+
//
|
|
27
|
+
// There is no rung to *draw*, because the thing being read — the whole
|
|
18
28
|
// screen — is precisely what an application cannot draw itself. So unlike
|
|
19
29
|
// `useFileDialog()`, `useEyedropper()` adds no rung; it adds the binding a
|
|
20
|
-
// component wants (`picking`, `supported`, the owner window) over
|
|
21
|
-
//
|
|
30
|
+
// component wants (`picking`, `supported`, the owner window) over these
|
|
31
|
+
// three.
|
|
32
|
+
//
|
|
33
|
+
// Which means the ladder really can run out, and where it does the floor has
|
|
34
|
+
// to be the typed rejection rather than a crash. A cocoa app on a bridge
|
|
35
|
+
// older than 0.9 is that place: its `app.X` is a stub with just enough on it
|
|
36
|
+
// for the modules that keep an X escape hatch to no-op (src/cocoa/app.js),
|
|
37
|
+
// so an app object is not by itself a connection that can grab a pointer and
|
|
38
|
+
// read a root window. Rung 3 is gated on the requests it is built out of,
|
|
39
|
+
// not on there being an app — the feature-detection rule `requireExtension()`
|
|
40
|
+
// already follows, and the rule rung 1 follows too, one bridge verb instead
|
|
41
|
+
// of three X requests.
|
|
22
42
|
//
|
|
23
43
|
// ## The grab is the dangerous part
|
|
24
44
|
//
|
|
@@ -42,6 +62,7 @@ import {
|
|
|
42
62
|
portalRequest,
|
|
43
63
|
portalVersion,
|
|
44
64
|
} from './portal.js';
|
|
65
|
+
import { liveApps } from './trace-registry.js';
|
|
45
66
|
import { windowIdOf } from './windowid.js';
|
|
46
67
|
|
|
47
68
|
export const SCREENSHOT_IFACE = 'org.freedesktop.portal.Screenshot';
|
|
@@ -60,10 +81,11 @@ export class NoScreenColorError extends Error {
|
|
|
60
81
|
super(
|
|
61
82
|
`react-x11: ${
|
|
62
83
|
message ??
|
|
63
|
-
'no way to sample a colour from the screen —
|
|
64
|
-
'Screenshot portal with PickColor (interface
|
|
65
|
-
'session bus, and no X connection was given for
|
|
66
|
-
'Pass `app` (from createRoot() or useApp()), or use
|
|
84
|
+
'no way to sample a colour from the screen — no cocoa app with a ' +
|
|
85
|
+
'system sampler, no Screenshot portal with PickColor (interface ' +
|
|
86
|
+
'version 2) on the session bus, and no X connection was given for ' +
|
|
87
|
+
'the fallback. Pass `app` (from createRoot() or useApp()), or use ' +
|
|
88
|
+
'useEyedropper().'
|
|
67
89
|
}`,
|
|
68
90
|
{ cause },
|
|
69
91
|
);
|
|
@@ -72,7 +94,91 @@ export class NoScreenColorError extends Error {
|
|
|
72
94
|
}
|
|
73
95
|
|
|
74
96
|
// --------------------------------------------------------------------------
|
|
75
|
-
// Rung 1: the
|
|
97
|
+
// Rung 1: the system sampler, on the cocoa backend
|
|
98
|
+
// --------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The app whose system sampler a pick should use, or null.
|
|
102
|
+
*
|
|
103
|
+
* Never a backend check: an app that can sample the screen says so by
|
|
104
|
+
* carrying `colorSampler` (src/cocoa/screencolor.js), and this asks the app
|
|
105
|
+
* the caller named — `app`, or a `parentWindow` that points at a mounted
|
|
106
|
+
* node — before asking the connections the renderer is drawing through, the
|
|
107
|
+
* rule `filePanels` and `calendars` follow. Several of those with only one
|
|
108
|
+
* showing a window is the next case (a borrowed connection stays registered
|
|
109
|
+
* after its root unmounts); genuinely several is a real null, since the
|
|
110
|
+
* sampler belongs to one process's NSApplication.
|
|
111
|
+
*/
|
|
112
|
+
function samplerApp(opts) {
|
|
113
|
+
const named = appFor(opts);
|
|
114
|
+
if (named) return named.colorSampler ? named : null;
|
|
115
|
+
const apps = liveApps().filter((one) => one.colorSampler);
|
|
116
|
+
if (apps.length <= 1) return apps[0] ?? null;
|
|
117
|
+
const showing = apps.filter((one) => (one._rootChildren ?? []).length > 0);
|
|
118
|
+
return showing.length === 1 ? showing[0] : null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Show `NSColorSampler` and wait for it.
|
|
123
|
+
*
|
|
124
|
+
* Resolves `'#rrggbb'` on a pick and `null` on a dismissal — the two
|
|
125
|
+
* outcomes every rung answers with — through the *portal's* conversion,
|
|
126
|
+
* because the bridge answers in the portal's units: sRGB in 0–1, gamut
|
|
127
|
+
* mapped from whatever space the display is in. Rejects on an abort, and on
|
|
128
|
+
* a colour with no sRGB form at all (a pattern colour), which is the
|
|
129
|
+
* bridge's one error.
|
|
130
|
+
*
|
|
131
|
+
* **An abort ends our wait, not the sampler.** AppKit has no verb to
|
|
132
|
+
* dismiss it — the session ends when the user picks or presses Escape — so
|
|
133
|
+
* where the portal rung Closes its request and the X11 rung releases its
|
|
134
|
+
* grab, this one can only stop listening: the loupe stays up, the colour
|
|
135
|
+
* that arrives afterwards is dropped, and until then the pending sample
|
|
136
|
+
* holds the event loop open the way pending I/O does. Nothing is left
|
|
137
|
+
* grabbed, which is what the abort exists to guarantee.
|
|
138
|
+
*/
|
|
139
|
+
function cocoaPick(opts, app) {
|
|
140
|
+
const signal = opts.signal;
|
|
141
|
+
if (signal?.aborted) {
|
|
142
|
+
return Promise.reject(signal.reason ?? new PortalCancelledError());
|
|
143
|
+
}
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
const onAbort = () => reject(signal.reason ?? new PortalCancelledError());
|
|
146
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
147
|
+
// No `settle()` gate, unlike the X11 rung: that one exists because a
|
|
148
|
+
// grab must be released exactly once on every path out, and an aborted
|
|
149
|
+
// pick here holds nothing — the loupe is the user's, not ours. The
|
|
150
|
+
// colour that lands after an abort reaches an already-rejected promise
|
|
151
|
+
// and is dropped by the promise itself.
|
|
152
|
+
const done = () => signal?.removeEventListener('abort', onAbort);
|
|
153
|
+
|
|
154
|
+
app.colorSampler.sample().then(
|
|
155
|
+
(color) => {
|
|
156
|
+
done();
|
|
157
|
+
// A dismissal is an ordinary outcome, not a throw — Escape on the
|
|
158
|
+
// X11 rung, the dialog's own cancel on the portal, this here.
|
|
159
|
+
if (color == null) return resolve(null);
|
|
160
|
+
const hex = hexFromPortalColor([color.r, color.g, color.b]);
|
|
161
|
+
if (!hex) {
|
|
162
|
+
return reject(
|
|
163
|
+
new Error(
|
|
164
|
+
'react-x11: the system colour sampler answered without a ' +
|
|
165
|
+
'colour — expected sRGB { r, g, b } in 0–1, got ' +
|
|
166
|
+
`${JSON.stringify(color)}.`,
|
|
167
|
+
),
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
resolve(hex);
|
|
171
|
+
},
|
|
172
|
+
(err) => {
|
|
173
|
+
done();
|
|
174
|
+
reject(err);
|
|
175
|
+
},
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// --------------------------------------------------------------------------
|
|
181
|
+
// Rung 2: the portal
|
|
76
182
|
// --------------------------------------------------------------------------
|
|
77
183
|
|
|
78
184
|
/**
|
|
@@ -131,7 +237,7 @@ async function portalCanPick(ref) {
|
|
|
131
237
|
}
|
|
132
238
|
|
|
133
239
|
// --------------------------------------------------------------------------
|
|
134
|
-
// Rung
|
|
240
|
+
// Rung 3: X11
|
|
135
241
|
// --------------------------------------------------------------------------
|
|
136
242
|
|
|
137
243
|
// x11.eventMask bits, spelled out the way xsettings.js spells its one. No
|
|
@@ -539,28 +645,87 @@ function appOf(target) {
|
|
|
539
645
|
return target.app ?? target.window?.app ?? target.root?.window?.app ?? null;
|
|
540
646
|
}
|
|
541
647
|
|
|
542
|
-
/** The
|
|
648
|
+
/** The app a pick would use, or null. Not yet: a *connection*. */
|
|
543
649
|
function appFor(opts) {
|
|
544
650
|
return opts.app ?? appOf(opts.parentWindow);
|
|
545
651
|
}
|
|
546
652
|
|
|
547
653
|
/**
|
|
548
|
-
*
|
|
654
|
+
* Can this app run the X11 rung — is its `X` a connection at all?
|
|
549
655
|
*
|
|
550
|
-
*
|
|
551
|
-
*
|
|
552
|
-
*
|
|
553
|
-
*
|
|
554
|
-
*
|
|
555
|
-
*
|
|
656
|
+
* The cocoa backend hands the renderer an app whose `X` is a stub
|
|
657
|
+
* (src/cocoa/app.js): `InternAtom` and `on` so the modules with an X escape
|
|
658
|
+
* hatch no-op cleanly, and nothing else. Reaching the rung through it used
|
|
659
|
+
* to throw `X.AllocID is not a function` out of the promise — a crash where
|
|
660
|
+
* the ladder's whole contract is a typed "not here", and one an app cannot
|
|
661
|
+
* hide a button on.
|
|
662
|
+
*
|
|
663
|
+
* So the gate is the three requests the rung is actually built out of, asked
|
|
664
|
+
* of the object rather than of `process.platform` or the backend's name:
|
|
665
|
+
* `requireExtension()`'s rule, and the one that keeps the next backend from
|
|
666
|
+
* landing here by default too.
|
|
667
|
+
*/
|
|
668
|
+
function canGrabOn(app) {
|
|
669
|
+
const X = app?.X;
|
|
670
|
+
return (
|
|
671
|
+
typeof X?.AllocID === 'function' &&
|
|
672
|
+
typeof X?.GrabPointer === 'function' &&
|
|
673
|
+
typeof X?.GetImage === 'function'
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Why the X11 rung is out of reach, for the typed rejection — an app that
|
|
679
|
+
* cannot grab and no app at all are different mistakes with different
|
|
680
|
+
* fixes, and only one of them is the caller's.
|
|
681
|
+
*/
|
|
682
|
+
function noX11Reason(app, backend) {
|
|
683
|
+
if (app) {
|
|
684
|
+
return (
|
|
685
|
+
'this tree does not render through an X connection — a cocoa-backend ' +
|
|
686
|
+
'app, for instance, whose `X` cannot grab the pointer or read a root ' +
|
|
687
|
+
"window. macOS's own sampler is the rung above, and this app does not " +
|
|
688
|
+
'carry it: `@windowkit/appkit` is older than 0.9, which has no ' +
|
|
689
|
+
'`sampleScreenColor`. Until it is updated there is no rung here — ' +
|
|
690
|
+
'`useEyedropper().supported` is false, which is the signal to leave ' +
|
|
691
|
+
'the eyedropper button undrawn (docs/macos.md).'
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
if (backend === 'x11') {
|
|
695
|
+
return (
|
|
696
|
+
"backend: 'x11' needs a connection to grab on. Pass `app` (from " +
|
|
697
|
+
'createRoot() or useApp()), or a `parentWindow` that points at a ' +
|
|
698
|
+
'mounted window.'
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
return undefined;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Which rung this machine lands on, without showing or grabbing anything.
|
|
706
|
+
*
|
|
707
|
+
* `'cocoa'` is the app carrying `colorSampler` — a cocoa-backend tree on
|
|
708
|
+
* `@windowkit/appkit` >= 0.9 — and it is asked first, so a Mac never falls
|
|
709
|
+
* through to a rung that would draw a worse picker. `'portal'` needs the
|
|
710
|
+
* Screenshot interface at version 2 — the probe reads the interface's
|
|
711
|
+
* `version` property, because `hasService()` cannot see which interfaces a
|
|
712
|
+
* portal's backends actually provide (XFCE's provides no Screenshot at
|
|
713
|
+
* all). `'x11'` needs a connection to answer with, so pass `app` (or a
|
|
714
|
+
* `parentWindow` that resolves to one) — and one that can actually grab,
|
|
715
|
+
* which a cocoa-backend app cannot. With none of the three the honest
|
|
716
|
+
* answer is `null`.
|
|
556
717
|
*
|
|
557
718
|
* Acquires a bus reference and releases it, so it is cheap but not free —
|
|
558
719
|
* `useEyedropper().supported` caches it for you.
|
|
559
720
|
*
|
|
560
|
-
* @returns {Promise<'portal'|'x11'|null>}
|
|
721
|
+
* @returns {Promise<'cocoa'|'portal'|'x11'|null>}
|
|
561
722
|
*/
|
|
562
723
|
export async function screenColorBackend(options = {}) {
|
|
563
724
|
const backend = options.backend;
|
|
725
|
+
if (!backend || backend === 'cocoa') {
|
|
726
|
+
if (samplerApp(options)) return 'cocoa';
|
|
727
|
+
if (backend === 'cocoa') return null;
|
|
728
|
+
}
|
|
564
729
|
if (!backend || backend === 'portal') {
|
|
565
730
|
const ref = await sessionBus();
|
|
566
731
|
if (ref) {
|
|
@@ -572,10 +737,23 @@ export async function screenColorBackend(options = {}) {
|
|
|
572
737
|
}
|
|
573
738
|
if (backend === 'portal') return null;
|
|
574
739
|
}
|
|
575
|
-
return appFor(options) ? 'x11' : null;
|
|
740
|
+
return canGrabOn(appFor(options)) ? 'x11' : null;
|
|
576
741
|
}
|
|
577
742
|
|
|
578
743
|
async function runPick(opts) {
|
|
744
|
+
const wantCocoa = !opts.backend || opts.backend === 'cocoa';
|
|
745
|
+
if (wantCocoa) {
|
|
746
|
+
const app = samplerApp(opts);
|
|
747
|
+
if (app) return await cocoaPick(opts, app);
|
|
748
|
+
if (opts.backend === 'cocoa') {
|
|
749
|
+
throw new NoScreenColorError(
|
|
750
|
+
"backend: 'cocoa' — no system colour sampler here: this tree does " +
|
|
751
|
+
'not render through the cocoa backend, or its `@windowkit/appkit` ' +
|
|
752
|
+
'is older than 0.9.',
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
579
757
|
const wantPortal = !opts.backend || opts.backend === 'portal';
|
|
580
758
|
if (wantPortal) {
|
|
581
759
|
const ref = await sessionBus();
|
|
@@ -598,31 +776,27 @@ async function runPick(opts) {
|
|
|
598
776
|
}
|
|
599
777
|
|
|
600
778
|
const app = appFor(opts);
|
|
601
|
-
if (app) return x11Pick(opts, app);
|
|
602
|
-
throw new NoScreenColorError(
|
|
603
|
-
opts.backend === 'x11'
|
|
604
|
-
? "backend: 'x11' needs a connection to grab on. Pass `app` (from " +
|
|
605
|
-
'createRoot() or useApp()), or a `parentWindow` that points at a ' +
|
|
606
|
-
'mounted window.'
|
|
607
|
-
: undefined,
|
|
608
|
-
);
|
|
779
|
+
if (canGrabOn(app)) return x11Pick(opts, app);
|
|
780
|
+
throw new NoScreenColorError(noX11Reason(app, opts.backend));
|
|
609
781
|
}
|
|
610
782
|
|
|
611
783
|
/**
|
|
612
|
-
* Sample one pixel from the screen: the
|
|
613
|
-
* one,
|
|
784
|
+
* Sample one pixel from the screen: the system's own picker where there is
|
|
785
|
+
* one — `NSColorSampler` on macOS, the Screenshot portal on a desktop that
|
|
786
|
+
* has it — and a crosshair grab on plain X11 everywhere else.
|
|
614
787
|
*
|
|
615
788
|
* ```js
|
|
616
789
|
* const hex = await pickScreenColor({ app });
|
|
617
790
|
* if (hex) setFill(hex); // '#rrggbb'; null means cancelled
|
|
618
791
|
* ```
|
|
619
792
|
*
|
|
620
|
-
* Resolves to **`'#rrggbb'`**, or `null` when the user cancelled (Escape,
|
|
621
|
-
* the portal dialog's own cancel) — cancelling is an
|
|
622
|
-
* should not need a `try`. Rejects with
|
|
623
|
-
*
|
|
624
|
-
* `signal` aborts the pick
|
|
625
|
-
* reported
|
|
793
|
+
* Resolves to **`'#rrggbb'`**, or `null` when the user cancelled (Escape,
|
|
794
|
+
* the portal dialog's own cancel, dismissing the sampler) — cancelling is an
|
|
795
|
+
* ordinary outcome and should not need a `try`. Rejects with
|
|
796
|
+
* {@link NoScreenColorError} when no rung is reachable, which is the signal
|
|
797
|
+
* to hide the button; `signal` aborts the pick, releasing the X11 grab or
|
|
798
|
+
* closing the portal request before the rejection is reported (the system
|
|
799
|
+
* sampler cannot be dismissed from code — see docs/eyedropper.md).
|
|
626
800
|
*
|
|
627
801
|
* In a component, reach for {@link useEyedropper} instead — it binds the
|
|
628
802
|
* connection and the owner window, and exposes `picking`/`supported` as
|
package/src/screencolorhooks.js
CHANGED
|
@@ -33,12 +33,16 @@ import { useTopLevelWindow } from './windowid.js';
|
|
|
33
33
|
*
|
|
34
34
|
* `pick()` resolves to `'#rrggbb'`, or `null` when the user cancelled. It
|
|
35
35
|
* never rejects for lack of a backend on an X11 tree — the connection this
|
|
36
|
-
* tree renders through *is* the fallback rung —
|
|
37
|
-
*
|
|
36
|
+
* tree renders through *is* the fallback rung — nor on a cocoa tree whose
|
|
37
|
+
* bridge has the system sampler (`@windowkit/appkit` >= 0.9). Where neither
|
|
38
|
+
* is true the flag is the answer: `supported` is false, and a picker that
|
|
39
|
+
* gates its eyedropper button on it simply does not draw one.
|
|
38
40
|
*
|
|
39
41
|
* The portal dialog is parented to the window this component is in, the
|
|
40
42
|
* `useFileDialog()` way: resolved at the moment the pick starts, with
|
|
41
43
|
* `parentWindow` as the override for a tree with several top-level windows.
|
|
44
|
+
* On the cocoa rung that window is what names the app whose sampler runs;
|
|
45
|
+
* the system draws the loupe over the whole screen, owned by no window.
|
|
42
46
|
*
|
|
43
47
|
* While a pick is in flight, `picking` is true and another `pick()` returns
|
|
44
48
|
* **the same promise** — a double-clicked button must not queue a second
|