react-x11 2.52.0 → 2.53.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.
Files changed (112) hide show
  1. package/README.md +10 -10
  2. package/docs/README.md +160 -314
  3. package/docs/accessibility.md +176 -229
  4. package/docs/appearance.md +77 -176
  5. package/docs/click-to-component.md +18 -51
  6. package/docs/clipboard.md +40 -59
  7. package/docs/components.md +518 -1036
  8. package/docs/dbus.md +61 -133
  9. package/docs/debugging.md +152 -245
  10. package/docs/desktop-calendar.md +51 -88
  11. package/docs/desktop.md +222 -347
  12. package/docs/devtools.md +50 -167
  13. package/docs/drag-and-drop.md +126 -204
  14. package/docs/ecosystem/animation.md +91 -125
  15. package/docs/ecosystem/data-fetching.md +38 -53
  16. package/docs/ecosystem/dev-tooling.md +159 -202
  17. package/docs/ecosystem/forms.md +61 -97
  18. package/docs/ecosystem/headless.md +64 -89
  19. package/docs/ecosystem/i18n.md +18 -21
  20. package/docs/ecosystem/icons.md +93 -126
  21. package/docs/ecosystem/layout.md +42 -52
  22. package/docs/ecosystem/routing.md +18 -29
  23. package/docs/ecosystem/state.md +71 -88
  24. package/docs/ecosystem/testing.md +131 -157
  25. package/docs/ecosystem/theming.md +119 -165
  26. package/docs/ecosystem.md +145 -179
  27. package/docs/elements.md +1091 -1774
  28. package/docs/embedding.md +83 -132
  29. package/docs/events.md +412 -695
  30. package/docs/extending.md +784 -1326
  31. package/docs/eyedropper.md +63 -122
  32. package/docs/filedialog.md +71 -107
  33. package/docs/frame.md +195 -339
  34. package/docs/gl.md +65 -62
  35. package/docs/globalmenu.md +63 -146
  36. package/docs/macos.md +531 -3234
  37. package/docs/notifications.md +33 -47
  38. package/docs/packaging.md +166 -309
  39. package/docs/permissions.md +9 -14
  40. package/docs/react-features.md +81 -106
  41. package/docs/remote.md +32 -76
  42. package/docs/scale.md +72 -186
  43. package/docs/styling.md +329 -547
  44. package/docs/system.md +200 -280
  45. package/docs/testing.md +63 -83
  46. package/docs/typescript.md +6 -48
  47. package/docs/uri-schemes.md +67 -107
  48. package/docs/wayland-backend.md +200 -459
  49. package/docs/windows-integrations.md +75 -131
  50. package/docs/windows.md +343 -1367
  51. package/package.json +3 -2
  52. package/src/Reconciler.js +2 -2
  53. package/src/a11y.js +1 -1
  54. package/src/backend/context2d.js +3 -3
  55. package/src/bootstrap.js +1 -1
  56. package/src/cocoa/app.js +6 -6
  57. package/src/cocoa/bezels.js +1 -1
  58. package/src/cocoa/fonts.js +1 -1
  59. package/src/cocoa/globalmenu.js +1 -1
  60. package/src/cocoa/keymap.js +1 -1
  61. package/src/cocoa/panewindow.js +1 -1
  62. package/src/cocoa/player.js +1 -1
  63. package/src/cocoa/presenter.js +2 -2
  64. package/src/cocoa/promotion.js +1 -1
  65. package/src/cocoa/relaunch.js +1 -1
  66. package/src/cocoa/threaded.js +1 -1
  67. package/src/cocoa/video.js +1 -1
  68. package/src/cocoa/window.js +4 -4
  69. package/src/components/Menu.js +7 -2
  70. package/src/components/Switch.js +1 -1
  71. package/src/components/native.js +1 -1
  72. package/src/dnd.js +2 -2
  73. package/src/glnodes.js +1 -1
  74. package/src/grid.js +1 -1
  75. package/src/layouts.js +1 -1
  76. package/src/nodes/animation.js +1 -1
  77. package/src/nodes/invalidate.js +1 -1
  78. package/src/nodes/layouthost.js +1 -1
  79. package/src/nodes/video.js +19 -8
  80. package/src/nodes/window/droptarget.js +1 -1
  81. package/src/pacing.js +1 -1
  82. package/src/refresh/index.js +2 -1
  83. package/src/types/video.d.ts +5 -2
  84. package/src/videobackend.js +28 -0
  85. package/src/videoframes.js +29 -16
  86. package/src/wayland/app.js +27 -0
  87. package/src/wayland/video.js +376 -0
  88. package/src/win32/a11y.js +1 -1
  89. package/src/win32/app.js +2 -2
  90. package/src/win32/fonts.js +1 -1
  91. package/src/win32/glarea.js +4 -4
  92. package/src/win32/overlay.js +1 -1
  93. package/src/win32/panewindow.js +3 -3
  94. package/src/win32/shell.js +1 -1
  95. package/src/win32/window.js +2 -2
  96. package/src/windowid.js +1 -1
  97. package/src/xvideo.js +189 -0
  98. package/docs/architecture/accessibility.md +0 -470
  99. package/docs/architecture/animation.md +0 -707
  100. package/docs/architecture/container-queries.md +0 -525
  101. package/docs/architecture/custom-layout.md +0 -334
  102. package/docs/architecture/drag-and-drop.md +0 -941
  103. package/docs/architecture/element-layer-contents.md +0 -622
  104. package/docs/architecture/frame-pacing.md +0 -255
  105. package/docs/architecture/grid-layout.md +0 -358
  106. package/docs/architecture/protocol-efficiency.md +0 -357
  107. package/docs/architecture/video.md +0 -1149
  108. package/docs/architecture/windowed-regions.md +0 -854
  109. package/docs/glx.md +0 -137
  110. package/docs/wayland.md +0 -545
  111. package/docs/windows-embedding.md +0 -271
  112. package/docs/windows-gl.md +0 -221
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-x11",
3
- "version": "2.52.0",
3
+ "version": "2.53.0",
4
4
  "description": "react renderer with X11 as a target",
5
5
  "main": "./src/index.js",
6
6
  "bin": {
@@ -72,6 +72,7 @@
72
72
  "labs:direct-gl": "tsx examples/labs/direct-gl.jsx",
73
73
  "labs:gl-overlay": "tsx examples/labs/gl-overlay.jsx",
74
74
  "examples:viewer3d": "tsx examples/viewer3d.jsx",
75
+ "examples:video": "tsx examples/video.jsx",
75
76
  "examples:transparent": "tsx examples/transparent.jsx",
76
77
  "examples:windows": "tsx examples/windows.jsx",
77
78
  "examples:wm": "tsx examples/wm.jsx",
@@ -110,7 +111,7 @@
110
111
  "ical.js": "^2.2.1",
111
112
  "image-in-browser": "^3.6.0",
112
113
  "linebreak": "^1.1.0",
113
- "ntk": "^8.23.2",
114
+ "ntk": "^8.24.0",
114
115
  "react-reconciler": "^0.34.0",
115
116
  "yoga-layout": "^3.2.1"
116
117
  },
package/src/Reconciler.js CHANGED
@@ -678,7 +678,7 @@ const isNtkApp = (v) =>
678
678
  *
679
679
  * `'auto'` — the default — is one rule: macOS gets the native Core
680
680
  * Animation backend, everything else gets X11 via `$DISPLAY`. (More rungs —
681
- * Wayland — join this ladder, not a new one; docs/wayland.md.) On a mac
681
+ * Wayland — join this ladder, not a new one; design/wayland.md.) On a mac
682
682
  * without the `@windowkit/appkit` bridge installed, auto falls back to X11 so
683
683
  * an XQuartz setup keeps working; asking for `'cocoa'` by name means the
684
684
  * bridge is required, and its absence is an error that says how to fix it.
@@ -809,7 +809,7 @@ export async function createRoot(options = {}) {
809
809
  // The cocoa backend's desktop is not freedesktop's: the AT-SPI bridge and
810
810
  // the D-Bus global menu have nothing to register against there, so they
811
811
  // default off (their macOS replacements are their own workstream —
812
- // docs/macos.md). The appearance ladder stays on: its macOS rung is the
812
+ // design/macos.md). The appearance ladder stays on: its macOS rung is the
813
813
  // one that answers. An explicit `desktop` option still wins.
814
814
  setDesktopIntegration(
815
815
  rest.desktop ??
package/src/a11y.js CHANGED
@@ -1289,7 +1289,7 @@ export function startA11y(app) {
1289
1289
  // presence *is* the capability — the same rule the rest of the library
1290
1290
  // uses. The Windows backend installs one, because UI Automation asks
1291
1291
  // synchronously and has to be answered from a pushed copy rather than from
1292
- // the live tree (docs/windows.md §Accessibility); AT-SPI's asynchronous
1292
+ // the live tree (design/windows.md §Accessibility); AT-SPI's asynchronous
1293
1293
  // questions are answered from the tree itself, below.
1294
1294
  if (typeof app?.startAccessibility === 'function') {
1295
1295
  startPromise = Promise.resolve()
@@ -1,7 +1,7 @@
1
1
  // A canvas-shaped 2d context over a **verb table** — the drawing dialect
2
2
  // every native backend answers, and the one place it is written down.
3
3
  //
4
- // This is macos.md §"The split" step 4, taken when a second native backend
4
+ // This is design/macos.md §"The split" step 4, taken when a second native backend
5
5
  // started: the class takes its `native` as a constructor argument and calls
6
6
  // nothing else, so one wrapper drives @windowkit/appkit's CoreGraphics verbs
7
7
  // and @windowkit/win32's Direct2D verbs alike. A bridge that lacks an
@@ -16,7 +16,7 @@
16
16
  //
17
17
  // On the surface presenter this is the whole drawing path; on the layer
18
18
  // presenter it stays as the fallback every painted-code node (<canvas>,
19
- // <svg>, registered elements) rasters through — docs/macos.md §"Custom
19
+ // <svg>, registered elements) rasters through — design/macos.md §"Custom
20
20
  // drawing on a layer tree".
21
21
  //
22
22
  // The native surface holds the real graphics state (paths, CTM, clip); this
@@ -322,7 +322,7 @@ const NON_RECT = Symbol('non-rectangular clip');
322
322
  * linear in it: 14ms, 29ms, 56ms, 113ms. The driver is the subpath count,
323
323
  * not the vertex count — one 26,000-vertex subpath strokes in 4ms where
324
324
  * two thousand 13-vertex ones take 208. The full table, the two shapes
325
- * left whole and why, are in docs/macos.md
325
+ * left whole and why, are in design/macos.md
326
326
  * §"Stroking a path with many subpaths".
327
327
  *
328
328
  * Note the X11 context wants the opposite — there a stroke is an a8
package/src/bootstrap.js CHANGED
@@ -4,7 +4,7 @@
4
4
  // an app on macOS moves onto a worker (src/cocoa/relaunch.js), and on that
5
5
  // worker the same import sets it up. A Windows backend's UI thread will be
6
6
  // the addon's own, and X11 has none, so neither needs this step
7
- // (docs/windows.md §"Three shapes on Windows"). Imported first by
7
+ // (design/windows.md §"Three shapes on Windows"). Imported first by
8
8
  // src/index.js, so that it runs before the rest of the package.
9
9
  import { isMainThread, workerData } from 'node:worker_threads';
10
10
 
package/src/cocoa/app.js CHANGED
@@ -5,7 +5,7 @@
5
5
  // that stands in for the X socket.
6
6
  //
7
7
  // Two run loops. By default Node's loop is master and a timer pumps AppKit
8
- // (docs/macos.md §"Input and the run loop", option 1): input waits for the
8
+ // (design/macos.md §"Input and the run loop", option 1): input waits for the
9
9
  // pump, and AppKit's modal loops (live resize, menu tracking, a drag) stall
10
10
  // JS timers — but not the delegate callbacks, which is why live resize
11
11
  // still relayouts: the resize event flushes the frame synchronously on its
@@ -13,7 +13,7 @@
13
13
  // Under the launcher (`node --import react-x11/cocoa-main`, src/cocoa/
14
14
  // main.js) the app runs on a worker instead, AppKit keeps the main thread
15
15
  // in a real `[NSApp run]`, and this object is fed by the bridge's batches
16
- // (`_routeBatch`) with no pump at all (docs/macos.md §"JS on a worker").
16
+ // (`_routeBatch`) with no pump at all (design/macos.md §"JS on a worker").
17
17
  import { isMainThread } from 'node:worker_threads';
18
18
 
19
19
  import { cssColorStraight } from 'ntk/color';
@@ -64,11 +64,11 @@ const PUMP_INTERVAL_MS = 8;
64
64
  // The edge moves when the delegate returns, so the budget is how long a
65
65
  // slow frame may hold the drag back. 50 covers what a large tree costs —
66
66
  // the Cocoa `resize` cell measures 28ms a tick at 3,662 nodes
67
- // (docs/macos.md) — with room, where the bridge's own test met every tick
67
+ // (design/macos.md) — with room, where the bridge's own test met every tick
68
68
  // in 3.1ms against 3ms of layout. Past three 60Hz frames an edge that waits
69
69
  // reads as the drag stuttering, so a tree slower than that lets the edge
70
70
  // go first and its frame land after (Flutter's Windows embedder waits up
71
- // to 100, docs/windows.md §"Resize"). `createRoot({ cocoa: { resizeWait } })`
71
+ // to 100, design/windows.md §"Resize: where the threads meet"). `createRoot({ cocoa: { resizeWait } })`
72
72
  // sets it; 0 is no handshake at all.
73
73
  const RESIZE_WAIT_MS = 50;
74
74
  // What `createRoot({ cocoa: { paneStretch } })` and
@@ -272,7 +272,7 @@ export class CocoaApp {
272
272
  }
273
273
 
274
274
  // 'surface' (the measured default) or 'layers' — the retained CALayer
275
- // presenter, opt-in while docs/macos.md's measure-first gate is open.
275
+ // presenter, opt-in while design/macos.md's measure-first gate is open.
276
276
  this._presenterMode =
277
277
  options.cocoa?.presenter ??
278
278
  process.env.REACT_X11_COCOA_PRESENTER ??
@@ -391,7 +391,7 @@ export class CocoaApp {
391
391
 
392
392
  // the global-menu exports, newest active: the macOS menu bar is one per
393
393
  // app, so the most recent MenuBar owns it (per-focused-window switching
394
- // is the follow-up — docs/macos.md §Menus)
394
+ // is the follow-up — design/macos.md §Menus)
395
395
  this._globalMenus = [];
396
396
  this._activeGlobalMenu = null;
397
397
 
@@ -1,5 +1,5 @@
1
1
  // Native control bezels — AppKit's own pixels for the core controls, cached
2
- // as surfaces the ordinary 2d paint path can blit (docs/macos.md §Native
2
+ // as surfaces the ordinary 2d paint path can blit (design/macos.md §Native
3
3
  // controls). The mechanism is offscreen NSCell/NSControl rendering, the
4
4
  // WebKit/Gecko form-control technique: interaction, focus and keyboard stay
5
5
  // the shared component implementation, and only the *bezel* — the pixels of
@@ -1,6 +1,6 @@
1
1
  // The Cocoa text engine: CoreText behind the same `app.fonts` contract ntk's
2
2
  // FontManager answers on the X11 backend. The renderer touches exactly this
3
- // surface (docs/macos.md §"Text: the engine contract"):
3
+ // surface (design/macos.md §"Text: the engine contract"):
4
4
  //
5
5
  // fonts.layout(spans, base, { maxWidth, align, lineHeight, maxLines,
6
6
  // overflow, direction })
@@ -1,4 +1,4 @@
1
- // The macOS menu bar as a global-menu export (docs/macos.md §Menus).
1
+ // The macOS menu bar as a global-menu export (design/macos.md §Menus).
2
2
  //
3
3
  // The Linux global menu was built for exactly this moment: the item
4
4
  // vocabulary is data, and the pure snapshot/IdAllocator machinery in
@@ -23,7 +23,7 @@
23
23
  //
24
24
  // There is no input method behind this. The bridge's view swallows
25
25
  // `keyDown:` without `interpretKeyEvents:` and is no `NSTextInputClient`
26
- // (docs/macos.md, "IME"), so these fields are all the text there is. Two
26
+ // (design/macos.md, "IME"), so these fields are all the text there is. Two
27
27
  // consequences, both handled below:
28
28
  //
29
29
  // - **A dead key types nothing.** ⌥E on a US layout leaves `chars` empty, and
@@ -1,4 +1,4 @@
1
- // The pane process's "window" on the Cocoa backend — docs/frame.md's child
1
+ // The pane process's "window" on the Cocoa backend — design/frame.md's child
2
2
  // half, with the X server replaced by shared memory.
3
3
  //
4
4
  // On X11 a pane renders into a real window of its own and the host embeds
@@ -1,4 +1,4 @@
1
- // `<video src>` on macOS (docs/architecture/video.md §3.2, §9): a file or URL
1
+ // `<video src>` on macOS (design/architecture/video.md §3.2, §9): a file or URL
2
2
  // played by AVFoundation through @windowkit/appkit's player verbs — the
3
3
  // platform's decoder, audio, seeking and HLS — with the picture on the
4
4
  // player's own AVPlayerLayer.
@@ -1,4 +1,4 @@
1
- // The retained layer presenter — Tier L of docs/macos.md: one CALayer per
1
+ // The retained layer presenter — Tier L of design/macos.md: one CALayer per
2
2
  // drawn node, React commits landing as property sets inside a single
3
3
  // disabled-actions CATransaction per frame, the WindowServer compositing.
4
4
  //
@@ -350,7 +350,7 @@ export function stylePaintsPlain(node, style = node.style ?? {}) {
350
350
  /**
351
351
  * Style property → the key path on a PropBox's own layer, for the
352
352
  * animations the presenter takes off the frame clock and hands to the
353
- * render server (docs/architecture/animation.md §4.2). Only what the layer
353
+ * render server (design/architecture/animation.md §4.2). Only what the layer
354
354
  * expresses as a property of itself qualifies: a colour on `<text>` is a
355
355
  * re-raster per frame, a layout property moves the siblings, and both stay
356
356
  * on the JS loop. `scaled` values go out in points, as `_syncPropBox`
@@ -36,7 +36,7 @@
36
36
  // overlapped, hidden, clipped, faded or non-plain returns to the bitmap in
37
37
  // the frame that finds it, the animation handed back to the clock — and a
38
38
  // loop the clock runs is offered again once the scene would take it
39
- // (`wouldTake`), where it is in its cycle. docs/macos.md §"Layer
39
+ // (`wouldTake`), where it is in its cycle. design/macos.md §"Layer
40
40
  // promotion" is the account.
41
41
  import { now } from '../nodes/animation.js';
42
42
  import { BoxNode } from '../nodes/box.js';
@@ -1,4 +1,4 @@
1
- // Threaded mode by default (docs/macos.md §"JS on a worker"). On macOS the
1
+ // Threaded mode by default (design/macos.md §"JS on a worker"). On macOS the
2
2
  // process main thread is AppKit's — the only thread it lets own a window —
3
3
  // and while the app's JS runs there, every menu, drag, live resize and
4
4
  // modal panel stops it. So the first import of react-x11 on the main thread
@@ -1,4 +1,4 @@
1
- // Threaded mode's worker half (docs/macos.md §"JS on a worker: a UI thread
1
+ // Threaded mode's worker half (design/macos.md §"JS on a worker: a UI thread
2
2
  // of the bridge's own"): with the launcher (src/cocoa/main.js) holding the
3
3
  // process main thread in the bridge's `runMain()`, the app's JS runs on a
4
4
  // `worker_threads` Worker, and this module is what that worker needs to
@@ -1,4 +1,4 @@
1
- // A video's frames on a layer of their own (docs/architecture/video.md §7):
1
+ // A video's frames on a layer of their own (design/architecture/video.md §7):
2
2
  // what the surface presenter's sprites (src/cocoa/sprites.js) do with a part
3
3
  // whose `contents` is a `VideoFrames` sink instead of a `paint`.
4
4
  //
@@ -39,7 +39,7 @@ const FENCE_TIMEOUT_MS = 100;
39
39
  // (`_takeBack`). Three, a `CAMetalLayer`'s depth, is what an animation
40
40
  // mostly needs; an input answered between two of its frames puts two flips
41
41
  // inside one refresh, and four kept every write of a clicked 120Hz animation
42
- // off a held buffer (docs/macos.md, "Measured: the swapchain and the
42
+ // off a held buffer (design/macos.md, "Measured: the swapchain and the
43
43
  // WindowServer's hold").
44
44
  const MAX_BUFFERS = 4;
45
45
  // Past this many rects, what a buffer missed is copied whole: a buffer left
@@ -235,14 +235,14 @@ export class CocoaWindow {
235
235
  // change.
236
236
  this.damageRectCap = 16;
237
237
 
238
- // The retained layer presenter (docs/macos.md Tier L), behind
238
+ // The retained layer presenter (design/macos.md Tier L), behind
239
239
  // REACT_X11_COCOA_PRESENTER=layers while the surface path is the
240
240
  // measured default. Its hooks exist only in this mode, so the feature
241
241
  // detection in src/nodes/ keeps the surface path byte-identical; the
242
242
  // scroll blit is shadowed off because a layer frame has no backing
243
243
  // bitmap to blit. The last two are the animation seam: a transition or
244
244
  // a loop the presenter takes runs in the render server and schedules no
245
- // frames here (docs/architecture/animation.md §4).
245
+ // frames here (design/architecture/animation.md §4).
246
246
  if (app._presenterMode === 'layers') {
247
247
  this._presenter = new CocoaLayerPresenter(this);
248
248
  this.presentFrame = (windowNode) => this._presenter.frame(windowNode);
@@ -659,7 +659,7 @@ export class CocoaWindow {
659
659
  * A new size retires the chain, released on the spot (`_releaseBacking`):
660
660
  * left to the handles' finalizers, a forty-tick drag of the two-buffer
661
661
  * chain this used to be held 800MB until a collection happened to run —
662
- * the `rss +80MB` docs/macos.md measured. The layer keeps its own
662
+ * the `rss +80MB` design/macos.md measured. The layer keeps its own
663
663
  * reference to whichever IOSurface it is still showing, so the free is
664
664
  * safe while that frame is on glass. A new chain starts with the one
665
665
  * buffer its first frame paints: a live-resize tick paints its size once,
@@ -984,7 +984,9 @@ function useMenuAccelerators(
984
984
  *
985
985
  * The wrapper is focusable and takes focus when the menu opens, because the
986
986
  * popup is override-redirect and never gets focus itself — the same trick
987
- * `Select` uses to keep arrow keys working while its menu is open.
987
+ * `Select` uses to keep arrow keys working while its menu is open. It is not
988
+ * a Tab stop: it has no role or name, so Tab landing on it would announce
989
+ * nothing, and the controls inside are stops of their own.
988
990
  */
989
991
  export function ContextMenu({
990
992
  items = [],
@@ -1087,7 +1089,10 @@ export function ContextMenu({
1087
1089
  'box',
1088
1090
  {
1089
1091
  ref,
1090
- focusable: true,
1092
+ // focusable by a press and by `focus()` above, but out of the Tab
1093
+ // cycle: a stop here is a nameless "filler" around content whose own
1094
+ // controls are already reachable
1095
+ tabIndex: -1,
1091
1096
  onMouseDown: (ev) => {
1092
1097
  if (ev.button !== 3 && rect) close();
1093
1098
  },
@@ -98,7 +98,7 @@ export function Switch({
98
98
  );
99
99
 
100
100
  // NSSwitch's own pixels, on/off as two cached bezels. The knob slide is
101
- // the fidelity this loses for now (docs/macos.md flags the switch as the
101
+ // the fidelity this loses for now (design/macos.md flags the switch as the
102
102
  // one control worth revisiting if that disappoints); the press keeps its
103
103
  // answer as the wash, since NSSwitch renders no pressed state offscreen.
104
104
  if (nativeControls) {
@@ -1,4 +1,4 @@
1
- // The native half of the widget set (docs/macos.md §Native controls).
1
+ // The native half of the widget set (design/macos.md §Native controls).
2
2
  //
3
3
  // House rule: **default native, seam out.** On a backend that renders the
4
4
  // platform's own control bezels — today the Cocoa backend — the core
package/src/dnd.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // XDND drag and drop, the drop-target half: react-x11 windows accept drags
2
2
  // from other X11 applications (file managers, editors, browsers).
3
3
  //
4
- // Design: docs/architecture/drag-and-drop.md; protocol: XDND version 5.
4
+ // Design: design/architecture/drag-and-drop.md; protocol: XDND version 5.
5
5
  // The advertisement (`XdndAware`) is a 4-byte property written
6
6
  // unconditionally on every top-level window at realize time — it creates no
7
7
  // X windows, so it is orthogonal to the windowed/windowless split.
@@ -1080,7 +1080,7 @@ export class DropSession {
1080
1080
  // ---------------------------------------------------------------------------
1081
1081
  // The drag source.
1082
1082
  //
1083
- // One drag session, two transports (docs/architecture/drag-and-drop.md §6.3):
1083
+ // One drag session, two transports (design/architecture/drag-and-drop.md §6.3):
1084
1084
  // while the pointer is over this process's own windows the drag is local —
1085
1085
  // no wire, live JS payloads, the DropSession above delivering to the same
1086
1086
  // handlers with `e.source === 'internal'`. The moment it leaves them, the
package/src/glnodes.js CHANGED
@@ -95,7 +95,7 @@ export function glxConfig(app, spec) {
95
95
  process.platform === 'win32'
96
96
  ? 'react-x11: <glarea> is not built on the win32 backend yet — it ' +
97
97
  'needs ANGLE (EGL and GLES over Direct3D 11), which ' +
98
- 'docs/windows.md plans as an optional dependency the way ' +
98
+ 'design/windows.md plans as an optional dependency the way ' +
99
99
  'x11-dri is on X11. Everything else on this backend works ' +
100
100
  'without it; for GL content today, use the X11 backend.'
101
101
  : 'react-x11: <glarea> needs ntk >= 3.6.0 (app.chooseGLConfig)',
package/src/grid.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // CSS grid, built in (docs/styling.md "Grid"; the design record is
2
- // docs/architecture/grid-layout.md). A box with `display: 'grid'` — or
2
+ // design/architecture/grid-layout.md). A box with `display: 'grid'` — or
3
3
  // `layout: 'grid'`, the same request under the name every layout goes by —
4
4
  // arranges its children by CSS Grid's placement and track sizing algorithms
5
5
  // (css-grid-2 §8.5 and §12), inside the pass that lays the window out. It is
package/src/layouts.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Layout algorithms and positions from outside the renderer — the two seams
2
- // docs/architecture/custom-layout.md is the design record for.
2
+ // design/architecture/custom-layout.md is the design record for.
3
3
  //
4
4
  // A **layout** arranges a box's children. It is asked how big the box is for
5
5
  // the room on offer, and then where each child goes in the room the box got.
@@ -310,7 +310,7 @@ export class NodeAnimation {
310
310
  // loop-stop rule and `sameAnimation` all see it — but it contributes no
311
311
  // value to the style, is skipped by the tick, and keeps the node out of the
312
312
  // window's animating set. The X11 path has none of these hooks and is
313
- // byte-identical (docs/architecture/animation.md §4).
313
+ // byte-identical (design/architecture/animation.md §4).
314
314
 
315
315
  _offload(prop, entry) {
316
316
  const wnd = this.root?.window;
@@ -361,7 +361,7 @@ export class WindowInvalidate {
361
361
  }
362
362
  // A retained presenter keeps a per-node diff instead of damage rects,
363
363
  // and this is the one channel every change already announces itself on
364
- // (docs/macos.md §"One renderer, two presenters"). Feature-detected: an
364
+ // (design/macos.md §"One renderer, two presenters"). Feature-detected: an
365
365
  // ntk window has no ear here and the X11 path is byte-identical. A
366
366
  // presenter that answers `true` has taken the claim onto a layer of its
367
367
  // own (src/cocoa/promotion.js): the bitmap owes nothing for it, and the
@@ -2,7 +2,7 @@
2
2
  // in layouts.js arranges its children itself. Each child is a yoga root of
3
3
  // its own, and the host is a measured leaf in its parent — so what the
4
4
  // algorithm decides is on screen in the frame that asked.
5
- // docs/architecture/custom-layout.md is the design record.
5
+ // design/architecture/custom-layout.md is the design record.
6
6
 
7
7
  import {
8
8
  createLayoutNode,
@@ -1,6 +1,6 @@
1
1
  // <video>: frames an application decodes (`frames`, a VideoFrames sink), or a
2
2
  // file or URL the platform plays (`src`, where there is a player), fitted
3
- // into the node's box (docs/architecture/video.md §6–7).
3
+ // into the node's box (design/architecture/video.md §6–7).
4
4
  //
5
5
  // A drawn node like `<image>`: it joins yoga, it is in the paint order and it
6
6
  // is hit like anything else — which is the whole point. A video in a form
@@ -18,9 +18,10 @@
18
18
  // - **drawn** — everywhere else, and in every frame the presenter declines
19
19
  // the part (something is painted over it, it fades, a rounded card does
20
20
  // not hold it): the newest frame is copied into a surface of the node's
21
- // own at paint, natively where the backend can (`app.writeVideoFrame`) and
22
- // through `frameToRGBA` where it cannot, and composited at the picture's
23
- // rect like an `<image>` whose pixels changed. A push claims the picture.
21
+ // own at paint, natively where the backend can (`writeVideoFrame`,
22
+ // src/videobackend.js) and through `frameToRGBA` where it cannot, and
23
+ // composited at the picture's rect like an `<image>` whose pixels
24
+ // changed. A push claims the picture.
24
25
  //
25
26
  // `src` needs a platform player, which this backend has when
26
27
  // `useSupports('mediaPlayback')` says so — the app's `createPlayer`, on macOS
@@ -41,6 +42,7 @@ import {
41
42
  } from '../imagesource.js';
42
43
  import { canPlayMedia, NoMediaPlaybackError } from '../mediaplayback.js';
43
44
  import { Surface } from '../offscreen.js';
45
+ import { videoBackendOf } from '../videobackend.js';
44
46
  import { frameToRGBA, isVideoFrames } from '../videoframes.js';
45
47
  import { fitRect, objectFitOf } from './fit.js';
46
48
  import { intrinsicSize } from './layout.js';
@@ -592,10 +594,18 @@ export class VideoNode extends Node {
592
594
  }
593
595
 
594
596
  _upload(surface, sink, frame) {
595
- // natively where the backend converts in its own code (the Cocoa
596
- // bridge's writeVideoSurface: a copy for BGRA, VideoToolbox for YCbCr,
597
- // colour-matched to what a layer shows)
598
- if (this.app?.writeVideoFrame?.(surface, sink, frame)) return;
597
+ // natively where the backend converts in its own code: the Cocoa
598
+ // bridge's writeVideoSurface (a copy for BGRA, VideoToolbox for YCbCr,
599
+ // colour-matched to what a layer shows), the Wayland context's shader,
600
+ // an Xv port on X11 (src/videobackend.js)
601
+ if (
602
+ videoBackendOf(this.app)?.writeVideoFrame?.(surface, sink, frame, this)
603
+ ) {
604
+ // a frame converted in JavaScript earlier — the first, on X11, before
605
+ // the window was mapped — left a buffer the native path does not need
606
+ this._rgba = null;
607
+ return;
608
+ }
599
609
  const w = sink.width;
600
610
  const h = sink.height;
601
611
  if (this._rgba?.width !== w || this._rgba?.height !== h) {
@@ -632,6 +642,7 @@ export class VideoNode extends Node {
632
642
  this._surface = null;
633
643
  this._rgba = null;
634
644
  this._drawn = -1;
645
+ if (surface) videoBackendOf(this.app)?.releaseVideoFrame?.(surface);
635
646
  try {
636
647
  surface?.destroy();
637
648
  } catch {
@@ -1,4 +1,4 @@
1
- // The window's half of XDND (docs/architecture/drag-and-drop.md): the drop
1
+ // The window's half of XDND (design/architecture/drag-and-drop.md): the drop
2
2
  // targets registered under it and the types they accept.
3
3
 
4
4
  import {
package/src/pacing.js CHANGED
@@ -49,7 +49,7 @@
49
49
  // Off by default: `'display'` is the built-in, and paints every frame the
50
50
  // clock gives, after React's own batching — a UI answers its input as
51
51
  // quickly as it can unless it says otherwise. `'adaptive'` is what a window
52
- // that streams asks for. docs/architecture/frame-pacing.md is the account.
52
+ // that streams asks for. design/architecture/frame-pacing.md is the account.
53
53
 
54
54
  export const DEFAULT_FRAME_RATE = 'display';
55
55
 
@@ -1,6 +1,7 @@
1
1
  // react-x11/refresh — the runtime half of state-preserving hot reload
2
2
  // (Fast Refresh). The loader half is ./loader.js; `react-x11/refresh/register`
3
- // wires both up for `node --import`. See docs/ecosystem/dev-tooling.md.
3
+ // wires both up for `node --import`. See docs/ecosystem/dev-tooling.md, and
4
+ // design/ecosystem/dev-tooling.md §"How it reloads" for the loader's rules.
4
5
  //
5
6
  // Import order is what this module exists to own:
6
7
  //
@@ -69,8 +69,11 @@ export class VideoFrames {
69
69
  readonly colorSpace: VideoColorSpace;
70
70
  readonly range: VideoRange;
71
71
  /** This display's formats, cheapest first: what a decoder that can choose
72
- * should emit (`ffmpeg -pix_fmt nv12`). NV12 leads where a frame goes on
73
- * a layer as it is — macOS — and BGRA wherever a 2D context draws it. */
72
+ * should emit (`ffmpeg -pix_fmt nv12`). NV12 leads wherever something
73
+ * native takes it as it is — a layer on macOS, a shader on Wayland, an Xv
74
+ * port on X11 — and BGRA where a frame is converted in JavaScript. On X11
75
+ * it is BGRA, and not kept, until the port has answered, a few round
76
+ * trips after the first sink is made. */
74
77
  readonly preferredFormats: readonly VideoFormat[];
75
78
  /** Frames pushed so far. */
76
79
  readonly version: number;
@@ -0,0 +1,28 @@
1
+ // Who writes a `<video>`'s frames natively on an app, if anyone — the seam
2
+ // `VideoFrames.preferredFormats` and the drawn presentation ask through
3
+ // (design/architecture/video.md §6.3).
4
+ //
5
+ // The app itself where its backend has the verbs: the Cocoa app over the
6
+ // bridge's video surfaces, the Wayland app over its own GL context. On X11
7
+ // the app is ntk's, which knows nothing of sinks, so the answer is the Xv
8
+ // writer react-x11 keeps for the connection (src/xvideo.js). A feature test,
9
+ // not a question about which backend this is — `typeof app.xv` is an ntk
10
+ // with XVideo discovery. Null where nothing will, and every frame is
11
+ // converted in JavaScript (`frameToRGBA`, src/videoframes.js).
12
+ import { xvVideoFor } from './xvideo.js';
13
+
14
+ /**
15
+ * `{ videoFormats(), writeVideoFrame(surface, sink, frame, node),
16
+ * releaseVideoFrame?(surface), prepare?(sink) }` for `app`, or null.
17
+ */
18
+ export function videoBackendOf(app) {
19
+ if (!app) return null;
20
+ if (
21
+ typeof app.writeVideoFrame === 'function' ||
22
+ typeof app.videoFormats === 'function'
23
+ ) {
24
+ return app;
25
+ }
26
+ if (typeof app.xv === 'function') return xvVideoFor(app);
27
+ return null;
28
+ }
@@ -1,5 +1,5 @@
1
1
  // `VideoFrames`: a sink an application pushes decoded video frames into,
2
- // and a `<video frames>` shows (docs/architecture/video.md §6.3).
2
+ // and a `<video frames>` shows (design/architecture/video.md §6.3).
3
3
  //
4
4
  // react-x11 decodes nothing. Where the bytes come from — an `ffmpeg` child
5
5
  // writing rawvideo to a pipe, a WASM decoder, an addon over libmpv — is the
@@ -33,12 +33,16 @@
33
33
  // a decoder that can choose (`ffmpeg -pix_fmt`); one that cannot still
34
34
  // works, with the conversion charged where it happens.
35
35
  //
36
- // Pure: no node, no backend. The app is asked for its formats
37
- // (`app.videoFormats()`, the Cocoa app's answer from the bridge) and nothing
38
- // else.
36
+ // No node and no backend: the one question asked of the display is its
37
+ // formats, through `videoBackendOf` (src/videobackend.js) — the Cocoa app's
38
+ // answer from the bridge, the Wayland app's own, and on X11 the Xv writer's,
39
+ // which a sink starts asking for when it is made.
40
+
41
+ import { videoBackendOf } from './videobackend.js';
39
42
 
40
43
  export const VIDEO_FORMATS = Object.freeze(['NV12', 'I420', 'BGRA']);
41
44
  const COLOR_SPACES = ['bt709', 'bt601', 'bt2020'];
45
+ const BGRA_ONLY = Object.freeze(['BGRA']);
42
46
  const RANGES = ['video', 'full'];
43
47
  // The largest side a frame may have: a surface's limit on every backend, and
44
48
  // a number past which a size is a bug rather than a video.
@@ -209,22 +213,30 @@ export class VideoFrames {
209
213
  this._frame = null;
210
214
  this._listeners = new Set();
211
215
  this._preferred = null;
216
+ // On X11 the formats are a port's, and asking for one is round trips:
217
+ // started here, so they are known by the time a decoder that waits for
218
+ // anything at all — a probe of the file, a spawn — reads them.
219
+ videoBackendOf(app)?.prepare?.(this);
212
220
  }
213
221
 
214
222
  /**
215
223
  * This display's formats, cheapest first: what a decoder that can choose
216
- * should emit. NV12 first where a frame can go on a layer as it is
217
- * (Cocoa); BGRA first wherever a 2D context draws it.
224
+ * should emit. YCbCr first wherever something native takes it as it is —
225
+ * a layer on Cocoa, a shader on Wayland, an Xv port on X11 — and BGRA
226
+ * first where a frame is converted in JavaScript on its way to a 2D
227
+ * context. On X11 the answer is BGRA until the port has answered, a few
228
+ * round trips after the first sink is made, and is not kept until then.
218
229
  */
219
230
  get preferredFormats() {
220
- if (this._preferred === null) {
221
- const asked = this.app?.videoFormats?.();
222
- this._preferred = Object.freeze(
223
- Array.isArray(asked) && asked.length !== 0
224
- ? asked.filter((f) => VIDEO_FORMATS.includes(f))
225
- : ['BGRA'],
226
- );
227
- }
231
+ if (this._preferred !== null) return this._preferred;
232
+ const asked = videoBackendOf(this.app)?.videoFormats?.();
233
+ // null: still being asked — answer for now, and ask again next time
234
+ if (asked === null) return BGRA_ONLY;
235
+ this._preferred = Object.freeze(
236
+ Array.isArray(asked) && asked.length !== 0
237
+ ? asked.filter((f) => VIDEO_FORMATS.includes(f))
238
+ : ['BGRA'],
239
+ );
228
240
  return this._preferred;
229
241
  }
230
242
 
@@ -281,8 +293,9 @@ export const isVideoFrames = (v) => v instanceof VideoFrames;
281
293
 
282
294
  // --- converting a frame where nothing native will ----------------------------
283
295
  //
284
- // The drawn path on a backend with no native write (X11, win32, Wayland, a
285
- // Cocoa bridge older than its video surfaces): the frame converted here to
296
+ // The drawn path where nothing native writes the frame (win32, an X server
297
+ // with no Xv adaptor, a Cocoa bridge older than its video surfaces, and on
298
+ // any backend a frame the native writer declines): the frame converted here to
286
299
  // the straight RGBA `putImageData` takes. Not free — two million pixels a
287
300
  // frame at 1080p — which is why `preferredFormats` says BGRA first there.
288
301
  // The matrix and the range are the frame's; no transfer function is