react-x11 2.42.0 → 2.44.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-x11",
3
- "version": "2.42.0",
3
+ "version": "2.44.0",
4
4
  "description": "react renderer with X11 as a target",
5
5
  "main": "./src/index.js",
6
6
  "files": [
@@ -104,7 +104,7 @@
104
104
  "ical.js": "^2.2.1",
105
105
  "image-in-browser": "^3.6.0",
106
106
  "linebreak": "^1.1.0",
107
- "ntk": "^8.23.0",
107
+ "ntk": "^8.23.2",
108
108
  "react-reconciler": "^0.34.0",
109
109
  "yoga-layout": "^3.2.1"
110
110
  },
package/src/cocoa/app.js CHANGED
@@ -44,6 +44,7 @@ import { CocoaPlayer, canPlay } from './player.js';
44
44
  import { CocoaSurface } from './surface.js';
45
45
  import { CocoaSymbols } from './symbols.js';
46
46
  import { CocoaWindow, FRAME_SLACK_MS } from './window.js';
47
+ import { utiFromMime, wire } from './dnd.js';
47
48
  import { decodeKey, modifierMask } from './keymap.js';
48
49
  import { loadNative } from './native.js';
49
50
  import { requestAppKit, threadedChannel } from './threaded.js';
@@ -409,13 +410,53 @@ export class CocoaApp {
409
410
  // (docs/clipboard.md, "Two clipboards").
410
411
  const isClipboard = (selection = 'CLIPBOARD') => selection === 'CLIPBOARD';
411
412
  this.clipboard = {
413
+ // A write is one pasteboard item with every flavour under its UTI —
414
+ // dnd.js's table, then the system's — the way a drag offers its
415
+ // payload. Text alone keeps pasteboardWriteText, the verb every
416
+ // bridge has; anything else needs pasteboardWrite (windowkit/appkit
417
+ // #119), and a bridge without it keeps the text and says, once, what
418
+ // it dropped — an image that silently never reaches the pasteboard
419
+ // looks like a Copy that did nothing.
412
420
  write: (data, { selection } = {}) => {
413
421
  if (!isClipboard(selection)) return Promise.resolve();
422
+ if (typeof data === 'string') {
423
+ native.pasteboardWriteText(data);
424
+ return Promise.resolve();
425
+ }
426
+ const item = {};
427
+ for (const [type, value] of Object.entries(data ?? {})) {
428
+ const uti = utiFromMime(type, native);
429
+ if (uti && value != null && !(uti in item)) item[uti] = wire(value);
430
+ }
431
+ const TEXT = 'public.utf8-plain-text';
432
+ const raw = item[TEXT];
414
433
  const text =
415
- typeof data === 'string'
416
- ? data
417
- : (data?.UTF8_STRING ?? data?.STRING ?? '');
418
- native.pasteboardWriteText(String(text));
434
+ raw == null
435
+ ? ''
436
+ : typeof raw === 'string'
437
+ ? raw
438
+ : Buffer.from(raw).toString('utf8');
439
+ const others = Object.keys(item).filter((uti) => uti !== TEXT);
440
+ if (others.length === 0) {
441
+ native.pasteboardWriteText(text);
442
+ } else if (typeof native.pasteboardWrite === 'function') {
443
+ native.pasteboardWrite(item);
444
+ } else {
445
+ if (
446
+ !this._warnedPasteboardFlavours &&
447
+ process.env.NODE_ENV !== 'production'
448
+ ) {
449
+ this._warnedPasteboardFlavours = true;
450
+ console.warn(
451
+ `react-x11: clipboard.write() kept only the text — this ` +
452
+ `@windowkit/appkit has no pasteboardWrite, so ` +
453
+ `${others.join(', ')} did not reach the pasteboard. ` +
454
+ 'Update @windowkit/appkit to a release with pasteboardWrite ' +
455
+ '(windowkit/appkit#119).',
456
+ );
457
+ }
458
+ native.pasteboardWriteText(text);
459
+ }
419
460
  return Promise.resolve();
420
461
  },
421
462
  clear: (selection) => {
package/src/cocoa/dnd.js CHANGED
@@ -208,7 +208,7 @@ export function readPayload(native, types, carried = null) {
208
208
 
209
209
  /** A value as the pasteboard carries it: strings and bytes as they are,
210
210
  * anything else as JSON — the same rule the X transport applies. */
211
- const wire = (value) =>
211
+ export const wire = (value) =>
212
212
  typeof value === 'string' ||
213
213
  Buffer.isBuffer(value) ||
214
214
  ArrayBuffer.isView(value) ||
package/src/node.d.ts CHANGED
@@ -220,6 +220,24 @@ export interface Context2D {
220
220
  dw: number,
221
221
  dh: number,
222
222
  ): void;
223
+ /**
224
+ * Draw an image through a projection, in one draw: its pixel (u, v) lands
225
+ * where the 3×3 `matrix` — row-major, `[a, b, c, d, e, f, g, h, i]` — takes
226
+ * it, ((a·u + b·v + c) / w, (d·u + e·v + f) / w) with
227
+ * w = g·u + h·v + i, and the current transform takes that; clipped and
228
+ * faded by `globalAlpha` as `drawImage` is. A plane turned out of the page
229
+ * — CSS `rotateY()` in a `perspective` — is one draw where it would be
230
+ * hundreds of tiles each drawn through the affine matrix nearest it.
231
+ * Answers false, drawing nothing, where a corner of the image has w ≤ 0,
232
+ * behind the viewer.
233
+ *
234
+ * The Wayland context only. A GPU interpolates texture coordinates in
235
+ * perspective; XRender on glamor divides at the corners and folds the
236
+ * image along its diagonal, and CoreGraphics and Direct2D draw an image
237
+ * through an affine matrix alone. Test for it with `'drawImageProjected'
238
+ * in ctx`, and tile where it is absent.
239
+ */
240
+ drawImageProjected?(image: unknown, matrix: readonly number[]): boolean;
223
241
  createLinearGradient(
224
242
  x0: number,
225
243
  y0: number,
@@ -65,6 +65,8 @@ const MODE_TEXTURE = 1;
65
65
  const MODE_GLYPH = 2;
66
66
  const MODE_RRECT = 3;
67
67
  const MODE_GRADIENT = 4;
68
+ /** A texture read through a projection: `vParams` is (s/w, t/w, 1/w). */
69
+ const MODE_PROJECTIVE = 5;
68
70
 
69
71
  /**
70
72
  * The stencil buffer, shared between two uses: bit 7 is the clip (set where
@@ -114,7 +116,9 @@ const FRAG = `
114
116
  precision mediump float;
115
117
  varying vec2 vUV;
116
118
  varying vec4 vColor;
117
- varying vec4 vParams;
119
+ // highp: in MODE_PROJECTIVE these are texture coordinates over w, divided
120
+ // per fragment, and half floats would put a large texture a pixel out
121
+ varying highp vec4 vParams;
118
122
  uniform int uMode;
119
123
  uniform int uSwizzle;
120
124
  uniform sampler2D uTex;
@@ -156,6 +160,10 @@ void main() {
156
160
  else if (w < 0.0) a = 1.0 - smoothstep(w, -w, d);
157
161
  else a = 1.0 - smoothstep(-0.5, 0.5, d);
158
162
  gl_FragColor = vColor * a;
163
+ } else if (uMode == 5) {
164
+ // Interpolated across the quad in the plane of the screen, s/w, t/w and
165
+ // 1/w are each exact, and so is their quotient: a texture in perspective.
166
+ gl_FragColor = vColor * sampleTex(vParams.xy / vParams.z);
159
167
  } else {
160
168
  // Gradients are evaluated per fragment from its device position, so any
161
169
  // shape — a cover quad over a path included — can be filled with one.
@@ -1450,6 +1458,56 @@ export class WaylandContext2D {
1450
1458
  );
1451
1459
  }
1452
1460
 
1461
+ /**
1462
+ * Draw `img` through a projection, as one quad: its pixel (u, v) lands
1463
+ * where the 3×3 `m` — row-major, `[a, b, c, d, e, f, g, h, i]` — takes it,
1464
+ * ((a·u + b·v + c) / w, (d·u + e·v + f) / w) with w = g·u + h·v + i, and
1465
+ * the current transform takes that. Clipped and faded by `globalAlpha` as
1466
+ * `drawImage` is. Each corner carries its texture coordinates over its w,
1467
+ * which are exact anywhere across the quad, and the fragment divides them
1468
+ * back: the image in perspective, where a quad's own interpolation would
1469
+ * fold it along its diagonal. That fold is what RENDER on glamor draws for
1470
+ * a projective picture transform, and why X11 has no such call.
1471
+ *
1472
+ * Draws nothing and answers false where a corner has w ≤ 0, behind the
1473
+ * viewer: the caller draws the part in front another way.
1474
+ */
1475
+ drawImageProjected(img, m) {
1476
+ this._claim();
1477
+ const src = this._textureFor(img);
1478
+ if (!src) return false;
1479
+ const [a, b, c, d, e, f, g, h, i] = m;
1480
+ const W = src.w;
1481
+ const H = src.h;
1482
+ const ctm = this._m;
1483
+ const pos = new Array(8);
1484
+ const params = new Array(16);
1485
+ const us = [0, W, W, 0];
1486
+ const vs = [0, 0, H, H];
1487
+ for (let k = 0; k < 4; k++) {
1488
+ const u = us[k];
1489
+ const v = vs[k];
1490
+ const w = g * u + h * v + i;
1491
+ if (!(w > 1e-9)) return false;
1492
+ const x = (a * u + b * v + c) / w;
1493
+ const y = (d * u + e * v + f) / w;
1494
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return false;
1495
+ pos[k * 2] = applyX(ctm, x, y);
1496
+ pos[k * 2 + 1] = applyY(ctm, x, y);
1497
+ const s = u / W;
1498
+ // a render target's row 0 is the bottom of what was drawn into it
1499
+ const t = src.flipY ? 1 - v / H : v / H;
1500
+ params[k * 4] = s / w;
1501
+ params[k * 4 + 1] = t / w;
1502
+ params[k * 4 + 2] = 1 / w;
1503
+ params[k * 4 + 3] = 0;
1504
+ }
1505
+ this._setMode(MODE_PROJECTIVE, src.tex, src.swizzle);
1506
+ const alpha = this.globalAlpha;
1507
+ this._quad(pos, ZERO8, [alpha, alpha, alpha, alpha], params);
1508
+ return true;
1509
+ }
1510
+
1453
1511
  /**
1454
1512
  * Write pixels, ignoring the transform and the clip (the canvas rule).
1455
1513
  * `dirtyX/Y/Width/Height` select part of the data, as the spec has it.
@@ -1926,7 +1984,8 @@ export class WaylandContext2D {
1926
1984
  }
1927
1985
  }
1928
1986
 
1929
- /** Two triangles from four corners, in order TL, TR, BR, BL. */
1987
+ /** Two triangles from four corners, in order TL, TR, BR, BL. `params` is
1988
+ * one vec4 for every corner, or sixteen numbers, a vec4 a corner. */
1930
1989
  _quad(pos, uv, color, params) {
1931
1990
  // Buffering is taking the device, not a step before it: the quad is GL
1932
1991
  // work that has been decided on and not yet issued, and the handover in
@@ -1935,9 +1994,11 @@ export class WaylandContext2D {
1935
1994
  this._claim();
1936
1995
  if (this._n + 6 > MAX_VERTS) this._flush();
1937
1996
  const v = this._verts;
1997
+ const each = params.length === 16 ? 4 : 0;
1938
1998
  let o = this._n * STRIDE;
1939
1999
  for (let k = 0; k < 6; k++) {
1940
2000
  const i = TRI[k];
2001
+ const p = i * each;
1941
2002
  v[o++] = pos[i * 2];
1942
2003
  v[o++] = pos[i * 2 + 1];
1943
2004
  v[o++] = uv[i * 2];
@@ -1946,10 +2007,10 @@ export class WaylandContext2D {
1946
2007
  v[o++] = color[1];
1947
2008
  v[o++] = color[2];
1948
2009
  v[o++] = color[3];
1949
- v[o++] = params[0];
1950
- v[o++] = params[1];
1951
- v[o++] = params[2];
1952
- v[o++] = params[3];
2010
+ v[o++] = params[p];
2011
+ v[o++] = params[p + 1];
2012
+ v[o++] = params[p + 2];
2013
+ v[o++] = params[p + 3];
1953
2014
  }
1954
2015
  this._n += 6;
1955
2016
  this.shapeStats.quads++;
@@ -2081,6 +2142,7 @@ export class WaylandContext2D {
2081
2142
 
2082
2143
  const UV_UNIT = Object.freeze([0, 0, 1, 0, 1, 1, 0, 1]);
2083
2144
  const ZERO4 = Object.freeze([0, 0, 0, 0]);
2145
+ const ZERO8 = Object.freeze([0, 0, 0, 0, 0, 0, 0, 0]);
2084
2146
 
2085
2147
  // ---- geometry helpers ------------------------------------------------------------
2086
2148
 
@@ -70,7 +70,8 @@ export function releaseDevice(gl) {
70
70
  * The owner is the only context that can be holding anything (see above), so
71
71
  * flushing it is flushing the device. Call before *reading* a target's pixels
72
72
  * behind the contexts' backs — a framebuffer blit, a scroll copy — the way
73
- * `releaseDevice` is called after writing them.
73
+ * `releaseDevice` is called after writing them, and before deleting a target
74
+ * a buffered draw may still read (`WaylandSurface.destroy`).
74
75
  */
75
76
  export function flushDevice(gl) {
76
77
  const device = shared(gl) ? devices.get(gl) : null;
@@ -25,6 +25,7 @@
25
25
  // back by hand either.
26
26
 
27
27
  import { WaylandContext2D } from './context2d.js';
28
+ import { flushDevice } from './device.js';
28
29
  import { GLTarget } from './target.js';
29
30
 
30
31
  export class WaylandSurface {
@@ -130,6 +131,15 @@ export class WaylandSurface {
130
131
  if (this._destroyed) return;
131
132
  this._destroyed = true;
132
133
  this.app.makeCurrent();
134
+ // A draw that reads this surface may still be buffered in the context
135
+ // that made it — a window's quad, to be drawn in one call with the rest
136
+ // of its batch — and a texture deleted under a draw not yet issued is
137
+ // sampled as GLES samples a texture that is not there: opaque black. A
138
+ // surface painted for one paint, drawn, and destroyed in a `finally`
139
+ // drew as a black box (`<Html>`'s transformed boxes too large to keep).
140
+ // The owner is the only context holding anything (device.js), so
141
+ // flushing it issues every draw that could read this one.
142
+ flushDevice(this.target.gl);
133
143
  this._ctx?.destroy();
134
144
  this._ctx = null;
135
145
  this.target.destroy();