react-x11 2.51.1 → 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 (121) hide show
  1. package/README.md +94 -61
  2. package/docs/README.md +256 -0
  3. package/docs/accessibility.md +575 -0
  4. package/docs/appearance.md +317 -0
  5. package/docs/click-to-component.md +92 -0
  6. package/docs/clipboard.md +262 -0
  7. package/docs/components.md +1231 -0
  8. package/docs/dbus.md +205 -0
  9. package/docs/debugging.md +371 -0
  10. package/docs/desktop-calendar.md +247 -0
  11. package/docs/desktop.md +523 -0
  12. package/docs/devtools.md +133 -0
  13. package/docs/drag-and-drop.md +585 -0
  14. package/docs/ecosystem/animation.md +360 -0
  15. package/docs/ecosystem/data-fetching.md +186 -0
  16. package/docs/ecosystem/dev-tooling.md +393 -0
  17. package/docs/ecosystem/forms.md +277 -0
  18. package/docs/ecosystem/headless.md +194 -0
  19. package/docs/ecosystem/i18n.md +86 -0
  20. package/docs/ecosystem/icons.md +377 -0
  21. package/docs/ecosystem/layout.md +206 -0
  22. package/docs/ecosystem/routing.md +135 -0
  23. package/docs/ecosystem/state.md +344 -0
  24. package/docs/ecosystem/testing.md +474 -0
  25. package/docs/ecosystem/theming.md +494 -0
  26. package/docs/ecosystem.md +439 -0
  27. package/docs/elements.md +2079 -0
  28. package/docs/embedding.md +239 -0
  29. package/docs/events.md +742 -0
  30. package/docs/extending.md +1700 -0
  31. package/docs/eyedropper.md +164 -0
  32. package/docs/filedialog.md +222 -0
  33. package/docs/frame.md +436 -0
  34. package/docs/gl.md +173 -0
  35. package/docs/globalmenu.md +195 -0
  36. package/docs/macos.md +592 -0
  37. package/docs/notifications.md +150 -0
  38. package/docs/packaging.md +468 -0
  39. package/docs/permissions.md +143 -0
  40. package/docs/react-features.md +405 -0
  41. package/docs/remote.md +179 -0
  42. package/docs/scale.md +225 -0
  43. package/docs/security.md +132 -0
  44. package/docs/styling.md +1710 -0
  45. package/docs/system.md +519 -0
  46. package/docs/testing.md +494 -0
  47. package/docs/typescript.md +75 -0
  48. package/docs/uri-schemes.md +340 -0
  49. package/docs/wayland-backend.md +332 -0
  50. package/docs/windows-integrations.md +177 -0
  51. package/docs/windows.md +380 -0
  52. package/package.json +11 -3
  53. package/src/Reconciler.js +2 -2
  54. package/src/a11y.js +1 -1
  55. package/src/backend/context2d.js +3 -3
  56. package/src/bootstrap.js +1 -1
  57. package/src/bus.js +39 -26
  58. package/src/cli/create.d.ts +43 -0
  59. package/src/cli/create.js +305 -0
  60. package/src/cli/env.js +484 -0
  61. package/src/cli/index.js +29 -2
  62. package/src/cli/templates/AGENTS.template.md +195 -0
  63. package/src/cli/templates/DESIGN.template.md +269 -0
  64. package/src/cocoa/app.js +6 -6
  65. package/src/cocoa/bezels.js +1 -1
  66. package/src/cocoa/fonts.js +1 -1
  67. package/src/cocoa/globalmenu.js +1 -1
  68. package/src/cocoa/keymap.js +1 -1
  69. package/src/cocoa/panewindow.js +1 -1
  70. package/src/cocoa/player.js +1 -1
  71. package/src/cocoa/presenter.js +2 -2
  72. package/src/cocoa/promotion.js +1 -1
  73. package/src/cocoa/relaunch.js +1 -1
  74. package/src/cocoa/threaded.js +1 -1
  75. package/src/cocoa/video.js +1 -1
  76. package/src/cocoa/window.js +4 -4
  77. package/src/components/Menu.js +7 -2
  78. package/src/components/Switch.js +1 -1
  79. package/src/components/native.js +1 -1
  80. package/src/desktopintegration.js +2 -1
  81. package/src/desktopsettings.js +193 -24
  82. package/src/dnd.js +2 -2
  83. package/src/filedialog.js +3 -3
  84. package/src/glnodes.js +1 -1
  85. package/src/grid.js +1 -1
  86. package/src/keyboardstate.js +28 -0
  87. package/src/layouts.js +1 -1
  88. package/src/node.d.ts +10 -10
  89. package/src/nodes/animation.js +1 -1
  90. package/src/nodes/invalidate.js +1 -1
  91. package/src/nodes/layouthost.js +1 -1
  92. package/src/nodes/video.js +19 -8
  93. package/src/nodes/window/droptarget.js +1 -1
  94. package/src/pacing.js +1 -1
  95. package/src/portal.js +31 -0
  96. package/src/refresh/index.js +2 -1
  97. package/src/screencolor.js +5 -10
  98. package/src/types/system.d.ts +8 -6
  99. package/src/types/video.d.ts +5 -2
  100. package/src/videobackend.js +28 -0
  101. package/src/videoframes.js +29 -16
  102. package/src/wayland/app.js +86 -0
  103. package/src/wayland/clipboard.js +14 -7
  104. package/src/wayland/connection.js +3 -0
  105. package/src/wayland/context2d.js +129 -10
  106. package/src/wayland/protocols/index.json +6 -0
  107. package/src/wayland/protocols/xdg-foreign-unstable-v2.json +1 -0
  108. package/src/wayland/seat.js +28 -1
  109. package/src/wayland/swapchain.js +23 -0
  110. package/src/wayland/video.js +376 -0
  111. package/src/wayland/xkb.js +137 -0
  112. package/src/win32/a11y.js +1 -1
  113. package/src/win32/app.js +2 -2
  114. package/src/win32/fonts.js +1 -1
  115. package/src/win32/glarea.js +4 -4
  116. package/src/win32/overlay.js +1 -1
  117. package/src/win32/panewindow.js +3 -3
  118. package/src/win32/shell.js +1 -1
  119. package/src/win32/window.js +2 -2
  120. package/src/windowid.js +1 -1
  121. package/src/xvideo.js +189 -0
@@ -0,0 +1,177 @@
1
+ # Desktop integration on Windows: what works, and what does not
2
+
3
+ What an app on the [win32 backend](windows.md) can reach today, what it
4
+ cannot, and which of the gaps have a route. Every row was measured on this
5
+ backend rather than inferred from the API's existence: "works" means a test
6
+ asserts it or it was driven and seen.
7
+
8
+ ## Works
9
+
10
+ | what | the API | how |
11
+ | ------------------------ | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
12
+ | a window, composed | `<window>` | DirectComposition surface per window, Direct2D verbs |
13
+ | text and layout | `<text>`, `app.fonts` | DirectWrite: layout, line metrics, hit testing, carets |
14
+ | glyph runs | `ctx.drawGlyphs`, `face.glyphIdFor`/`advanceOf` | `DrawGlyphRun` — what `<Terminal backend="vt">` positions its grid with |
15
+ | variable font axes | `style.variations` | `IDWriteTextLayout4::SetFontAxisValues` |
16
+ | app-supplied fonts | `loadFont`, `openFont` | a DirectWrite font set of the app's own, with the file's axes kept |
17
+ | OpenGL | `<glarea>` | WGL, and `WGL_NV_DX_interop2` to reach the composed surface |
18
+ | keyboard | `onKeyDown`, focus | `ToUnicodeEx` without consuming dead keys; X keysym vocabulary |
19
+ | pointer | `onMouseDown`, wheel, capture | all five buttons, `SetCapture` for drags leaving the window |
20
+ | focus | `onFocus`/`onBlur` | `WM_ACTIVATE` |
21
+ | window states | `useWindowState`, `<window maximized fullscreen alwaysOnTop>` | `ShowWindow`, `SetWindowPos`, a remembered frame for fullscreen |
22
+ | screens and DPI | `useScreens`, `useScale` | per-monitor v2; no live per-window re-scale yet ([windows.md](windows.md#scale-and-dpi)) |
23
+ | light/dark and accent | `useSystemAppearance` | `UISettings` and the theme registry; the frame follows through `DWMWA_USE_IMMERSIVE_DARK_MODE` |
24
+ | native control bezels | `<button>`, `<checkbox>`… | the visual styles engine, on a light desktop; dark ones are drawn (Windows' are not public) |
25
+ | file dialogs | `useFileDialog` | the Common Item Dialog, parented, modal on the UI thread |
26
+ | clipboard | `useClipboard` | text only, and format enumeration |
27
+ | tray icon and menu | `useTray` | `Shell_NotifyIcon` |
28
+ | taskbar progress | `useProgress` | `ITaskbarList3::SetProgressValue` |
29
+ | a badge on the icon | `useBadge`, `setBadge` | the count drawn into an overlay icon — Windows has no badge API |
30
+ | demanding attention | `setUrgent` | `FlashWindowEx` |
31
+ | global hotkeys | `useAccelerator` | `RegisterHotKey` |
32
+ | notifications | `notify`, `useNotifier` | a tray balloon, which the shell shows as a toast |
33
+ | idle time | `useIdle` | `GetLastInputInfo`, polled on the same adaptive wait the X rung uses |
34
+ | keeping the screen awake | `keepAwake`, `useKeepAwake` | `SetThreadExecutionState`, counted |
35
+ | reading a window back | `window.snapshot()` | `PrintWindow` with `PW_CLIENTONLY` and `PW_RENDERFULLCONTENT` |
36
+ | locale | `systemLocale`, `useLocale` | ICU's own resolution, which reads the OS on Windows — no backend code |
37
+ | drag and drop | `useDropTarget`, `useDragSource` | `IDropTarget` and `DoDragDrop` over OLE, both directions |
38
+ | a thumbnail toolbar | `useThumbnailToolbar` | `ITaskbarList3::ThumbBarAddButtons` — **Windows only** |
39
+ | a jump list | `useJumpList` | `ICustomDestinationList` — **Windows only** |
40
+ | recent documents | `useRecentDocument` | `SHAddToRecentDocs` — **Windows only** |
41
+ | input methods | `onCompositionStart`/`Update`/`End`, `<textinput>` | IMM32: the preedit is drawn in the field, the candidate list follows the caret |
42
+ | screen readers | `role`, `aria-*`, `announce()` | a UI Automation provider over a pushed mirror of the tree |
43
+ | window identity | `<window appId>` | the AppUserModelID on the window's property store — taskbar grouping, pinning, the jump list |
44
+
45
+ ## Built, with limits worth knowing
46
+
47
+ ### Input methods — built on IMM32, with the gaps that layer has
48
+
49
+ CJK input works, through the same composition events a Wayland text-input
50
+ drives, so an application handles one composition and gets both. The preedit
51
+ is drawn **in the field** rather than in a floating box, and the candidate
52
+ list follows the caret.
53
+
54
+ What it does not carry is what IMM32 itself no longer carries. Modern input
55
+ methods — the new Microsoft Japanese and Chinese IMEs included — are TSF text
56
+ services reaching an IMM32 window through a compatibility layer, which does
57
+ not pass on **voice typing** (Win+H inserts nothing), the shell's
58
+ **handwriting** panel, or **shape-writing** on the touch keyboard. A TSF text
59
+ store would close those; it is not built. Reconversion (asking the IME to
60
+ re-open a committed word) is not wired up either, though IMM32 carries it.
61
+
62
+ ### Screen readers — built, without text ranges
63
+
64
+ Screen readers are served by a UI Automation provider of this project's own,
65
+ answering from a copy of the tree the app pushes, so a screen reader is never
66
+ paced by the app's busiest moment. Roles, names, descriptions, states,
67
+ bounding rectangles, focus and the Invoke, Toggle, Value and RangeValue
68
+ patterns all cross; `announce()` goes out as a UIA notification. Everything an
69
+ app writes — `role`, `aria-label`, `aria-checked`, `aria-valuenow` — is read
70
+ by the same code the AT-SPI bridge reads, so the two backends cannot disagree
71
+ about what a node is.
72
+
73
+ **What is missing is `ITextProvider`.** A `<textinput>` exposes its value, so
74
+ a screen reader reads the field and hears edits, but caret-by-caret and
75
+ word-by-word navigation _inside_ it is not there.
76
+
77
+ ## Not implemented
78
+
79
+ Ordered by what it costs an app today.
80
+
81
+ ### `useLauncherMenu` is inert
82
+
83
+ The jump list is built (`useJumpList` above) but it is **not** what
84
+ `useLauncherMenu` means, and is deliberately not wired to it. A launcher-menu
85
+ item carries a callback; a jump-list task starts a _new process_ with
86
+ arguments, because the shell launches the program rather than calling into
87
+ the running one. Mapping one onto the other would quietly change what a click
88
+ does.
89
+
90
+ ### Activation — file associations, URL schemes, second launches
91
+
92
+ `useAppOpen` and `useAppActivate` never fire on Windows: there are no file
93
+ associations, no URL schemes and no single-instance handoff yet, so a second
94
+ launch is a second process. [uri-schemes.md](uri-schemes.md) documents what
95
+ the other platforms do.
96
+
97
+ ### Eyedropper — the pixel is read, the ladder has no rung
98
+
99
+ The bridge reads a screen pixel (`app.screenColorAt`, `app.pointerPosition`),
100
+ but `useEyedropper()` reports `supported: false` here until a rung draws the
101
+ loupe over it.
102
+
103
+ ### Calendar — blocked, not missing
104
+
105
+ `useDesktopCalendarEvents` wants the Windows appointments API, which requires
106
+ package identity. Unpackaged, there is nothing to call, and the hook reports
107
+ `supported: false` rather than guessing.
108
+
109
+ ### Permissions — nothing to ask
110
+
111
+ `usePermission` has no Windows meaning for an unpackaged app: the capability
112
+ API is for packaged apps, and everything else sits under one per-device
113
+ switch. The answer is `'unknown'` until something is attempted, and the
114
+ attempt's own error is the answer.
115
+
116
+ ### Global menu — not a Windows concept
117
+
118
+ `useGlobalMenu` is a menu bar owned by the desktop. Windows has none; a
119
+ `<MenuBar>` drawn in the window is the whole story here, and that works.
120
+
121
+ ### Keyboard grabs
122
+
123
+ `grabKeyboard` accepts and does nothing: there is no Windows mechanism for it.
124
+ A popup that asked for keys gets them only while it is the foreground window.
125
+
126
+ `grabPointer` is a different story — see below.
127
+
128
+ ## Built, with limits worth knowing: `<popup grab>`
129
+
130
+ Windows has no pointer grab to give — `SetCapture` sends a window the mouse
131
+ only while a button is already down — so the dismissal is reproduced from two
132
+ signals, and `<Select>`, `<Menu>` and any `<popup grab>` close the way they do
133
+ everywhere else:
134
+
135
+ - a press delivered to **another window of this application** — a click in
136
+ the owner behind the menu, in a second window, or in the menu a submenu came
137
+ from;
138
+ - the application **losing activation** — a press in another application or
139
+ on the desktop. A popup never takes activation itself, so opening one raises
140
+ no blur.
141
+
142
+ **The gap**: a press on the _non-client_ area of one of our own windows — a
143
+ title bar, a resize border — is not reported, so a menu left open while the
144
+ user drags the window behind it stays open. X11's grab covers that and this
145
+ does not.
146
+
147
+ ## What Windows has that nothing else does
148
+
149
+ Some Windows surfaces have no cross-platform API to fit into, so they have
150
+ APIs of their own, and **a backend installs a method, and its presence is the
151
+ capability**. They are reported as features of the launcher, beside the badge
152
+ and the progress bar:
153
+
154
+ ```jsx
155
+ const launcher = useDesktopCapability('launcher');
156
+ launcher.backend; // 'taskbar' here, 'cocoa' on a Dock
157
+ launcher.features.thumbnailToolbar; // the buttons under the hover preview
158
+ launcher.features.tasks; // the jump list's Tasks category
159
+ launcher.features.recentDocuments; // the shell's Recent lists
160
+ ```
161
+
162
+ They live there rather than in `useSupports()` because `useSupports` answers
163
+ for the **display** — transparency, shaders, embedding — and a taskbar button
164
+ is not a display property; and because a boolean cannot say _which_ launcher
165
+ answered. Every other backend answers false for the three, and the hooks do
166
+ nothing where the method is absent. `examples/taskbar.jsx` is the shape;
167
+ there is no `process.platform` in it.
168
+
169
+ - **Thumbnail toolbars** — `useThumbnailToolbar`: up to seven buttons under
170
+ the taskbar thumbnail, play/pause/next for a media app.
171
+ - **Recent documents** — `useRecentDocument`.
172
+ - **A jump list** — `useJumpList` (see [`useLauncherMenu`](#uselaunchermenu-is-inert)).
173
+ - **Taskbar overlay as a status light**: `setBadge` takes a label, and an app
174
+ that wants a dot rather than a number can pass one character.
175
+
176
+ Ideas not built yet are in the
177
+ [design notes](../design/windows-integrations.md#what-windows-has-that-nothing-else-does).
@@ -0,0 +1,380 @@
1
+ # A native Windows backend
2
+
3
+ The `win32` backend draws a react-x11 app into real Win32 windows: one
4
+ DirectComposition surface per window, Direct2D for drawing, DirectWrite for
5
+ text. It is **the default on Windows**, and it needs the `@windowkit/win32`
6
+ native bridge; without the bridge, `'auto'` falls back to X11. The examples
7
+ run on it, except `wm` and `xeyes`, which are X11 window-manager demos.
8
+
9
+ [windows-integrations.md](windows-integrations.md) is the row-by-row status of
10
+ what an app can reach on Windows. Why the backend is built the way it is — the
11
+ threading model, the rendering internals, the plan and its open questions — is
12
+ the design record: [design/windows.md](../design/windows.md).
13
+
14
+ ## Choosing a backend
15
+
16
+ `createRoot({ backend })`, or `REACT_X11_BACKEND` without touching code:
17
+
18
+ | asked | on Windows |
19
+ | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
20
+ | `'auto'` (the default) | `'win32'` when `@windowkit/win32` loads. Otherwise X11 through `DISPLAY`, with one console warning outside production, so a WSLg or Cygwin setup keeps working |
21
+ | `'win32'` | the bridge is required: if it cannot load, `createRoot()` rejects with an error that lists what it tried and how to install it |
22
+ | `'x11'` | an X server through `DISPLAY`. Also what `'auto'` picks when you pass `display` or `stream`. Windows has no `fc-match`, so tell ntk where the fonts are with `fontSource` or `REACT_X11_FONT_SOURCE` |
23
+
24
+ ```jsx
25
+ const root = await createRoot(); // win32 on Windows, x11 if the bridge is missing
26
+ const root = await createRoot({ backend: 'win32' }); // the bridge or an error
27
+ ```
28
+
29
+ Asking for `'win32'` on any other platform throws: the backend is
30
+ Windows-only.
31
+
32
+ ## Installing the bridge
33
+
34
+ - `@windowkit/win32` is an `optionalDependency` of react-x11 with
35
+ `os: ["win32"]`, so `npm install react-x11` on Windows installs it and
36
+ every other platform skips it. If optional dependencies were omitted,
37
+ install it yourself: `npm install @windowkit/win32`.
38
+ - Prebuilds ship for **x64 and ARM64**. Building from source needs Visual
39
+ Studio with the "Desktop development with C++" workload.
40
+ - It is an N-API addon, so it loads under Bun (`bun.exe`) as well as Node.
41
+ - `REACT_X11_WIN32_PATH=<path>` loads the bridge from a path instead of the
42
+ installed package: a checkout, or the file shipped beside a compiled
43
+ executable (see [Packaging and identity](#packaging-and-identity)).
44
+ - An older bridge degrades feature by feature rather than failing: a missing
45
+ verb answers "unsupported" (for example, `useWindowState()` sees `false`,
46
+ and a `<glarea>`'s children are not drawn).
47
+ - Built and measured on Windows 11. Windows 10 is not a verified target.
48
+
49
+ ## Threads and the event loop
50
+
51
+ The bridge runs every window on a UI thread of its own, and your JS stays on
52
+ Node's main thread. Windows' modal loops — a live resize, a native menu, a
53
+ drag (`DoDragDrop` does not return until the drop), a file dialog — run on
54
+ that UI thread, so **JS is never frozen by one**: timers fire, sockets read,
55
+ React commits and frames present while the user drags or a dialog is open.
56
+ This is the main behavioural difference from the Cocoa backend's pump mode.
57
+
58
+ Events reach JS as soon as they happen, without waiting for a timer tick, and
59
+ a handler's `setState` commits on the event that caused it.
60
+
61
+ ## Rendering
62
+
63
+ - **Every window is composited by DWM**, so `transparent` always works and
64
+ `useSupports('transparency')` is always true — there is no "if a compositor
65
+ is running".
66
+ - **ClearType on opaque windows, grayscale on transparent ones.** Direct2D
67
+ draws subpixel text only onto a surface whose alpha is ignored, so text in
68
+ a `<window transparent>` or `<popup transparent>` is grayscale-antialiased.
69
+ - **Frames are paced by DWM's compositor clock**, at the refresh rate of the
70
+ monitor under the window; `frameRate` works as on the other backends. An
71
+ idle app polls nothing.
72
+ - Only damaged rectangles are repainted, and scrolling is a blit, as on X11
73
+ and Cocoa.
74
+ - **Reading pixels back**: `window.snapshot()` works once the window has
75
+ been composed. It rejects for a minimised or not-yet-composed window; draw
76
+ into an offscreen surface and read that instead.
77
+ - `<video frames>` draws; `<video src>` does not —
78
+ `useSupports('mediaPlayback')` is false here.
79
+ - Debugging: `REACT_X11_WIN32_DEBUG=1` reports every frame and what it drew
80
+ into, the size each paragraph is shaped at, and drag and drop;
81
+ `REACT_X11_WIN32_FULL_REPAINT=1` paints the whole window every frame, the
82
+ control for a damage bug.
83
+
84
+ ## Scale and DPI
85
+
86
+ - Windows are **per-monitor-v2 DPI aware** whatever `node.exe`'s manifest
87
+ says, so nothing is bitmap-stretched and layout runs in device pixels.
88
+ - **Fractional scale is the normal case** (125%, 150%, 175%). Layout rounds
89
+ to whole device pixels, so a one-logical-pixel hairline at 1.25× lands on
90
+ one device pixel or two depending on where it falls
91
+ ([scale.md](scale.md)).
92
+ - `useScale()` is the **primary monitor's** scale when the app starts, static
93
+ for the life of the root as on every backend, and every window is laid out
94
+ at it.
95
+ - `useScreens()` lists every monitor; its `available` is the monitor's exact
96
+ work area.
97
+ - **Not yet: live per-window scale.** A window on, or dragged to, a monitor
98
+ at another scale is not re-laid out at that scale: it stays sharp but is
99
+ the wrong physical size there.
100
+
101
+ ## Text: DirectWrite behind the engine contract
102
+
103
+ Text is DirectWrite end to end — font matching, fallback, shaping, bidi, line
104
+ breaking, hit testing — so it looks like the platform's text, the way the
105
+ Cocoa backend uses CoreText.
106
+
107
+ - **Generic families**: `sans-serif`, `system-ui` and `ui-sans-serif` →
108
+ Segoe UI; `serif` → Times New Roman; `monospace` and `ui-monospace` →
109
+ Consolas; `cursive` → Segoe Script.
110
+ - **Font stacks** resolve to the first family installed. A face filed under
111
+ another family ("Arial Black", "Segoe UI Light") is found as GDI finds it,
112
+ and Courier/Courier New, Times/Times New Roman and Arial/Helvetica stand in
113
+ for each other.
114
+ - In laid-out text, characters the font lacks fall back to a system font,
115
+ as DirectWrite layouts do. `app.fonts.fallbackFor()` answers `null` (not
116
+ bound yet), so code drawing its own glyph runs gets no fallback face.
117
+ - `loadFont` and `openFont` load the app's own fonts, variable axes kept;
118
+ `style.variations` sets axes.
119
+ - Measurements are rounded up to whole device pixels, and lines break at the
120
+ same opportunities as on Cocoa, but metrics are DirectWrite's: expect a
121
+ pixel or so of difference from the X11 backend's layouts.
122
+
123
+ ## Input
124
+
125
+ ### Pointer
126
+
127
+ - All five mouse buttons and the wheel. A drag that leaves the window keeps
128
+ receiving motion while a button is held.
129
+ - A precision touchpad sends fractions of a notch; those arrive as wheel
130
+ events with `smooth: true`.
131
+ - Motion and wheel are coalesced to one per frame, as on X11.
132
+ - Not yet: pen- and touch-specific events, touchpad inertia, pinch.
133
+
134
+ ### Keyboard
135
+
136
+ - Key events carry the same X keysym vocabulary, `codepoint` and `key` as on
137
+ every backend; the editing, navigation, function and keypad keys have their
138
+ X keysyms.
139
+ - Dead keys compose in the layout, as the layout defines them; the backend
140
+ never consumes the dead-key state behind your back.
141
+ - Tab and Return never insert a control character into a field.
142
+
143
+ ### IME
144
+
145
+ Input methods work, through IMM32 — Chinese, Japanese and Korean composition
146
+ reach the same `onCompositionStart`/`Update`/`End` events a Wayland
147
+ text-input drives, so one handler serves both.
148
+
149
+ - The **preedit is drawn in the field**, laid out and styled with the rest of
150
+ the text; Windows' floating composition window is suppressed. The
151
+ **candidate list** is the input method's own and follows the caret.
152
+ - A commit arrives before the next preedit, and the active clause is the
153
+ preedit cursor (`cursorBegin`/`cursorEnd`), as on Wayland.
154
+ - A `sensitive` field is not offered to the input method at all, so its text
155
+ never reaches a word history — as Windows does for its own password boxes.
156
+ - **What IMM32 does not carry**: modern input methods are TSF text services
157
+ reaching IMM32 windows through a compatibility layer, and that layer drops
158
+ **voice typing** (Win+H inserts nothing), the shell's **handwriting**
159
+ panel and **shape-writing** on the touch keyboard. A TSF text store, which
160
+ would close those, is not built. Reconversion is not wired up either.
161
+ - `REACT_X11_TRACE_IME=1` prints a line per input-method event.
162
+
163
+ ## Windowing semantics: what maps, what bends, what breaks
164
+
165
+ **Maps**:
166
+
167
+ - `title`, `width`, `height`, `x`, `y` and the natural size. `x`/`y` are
168
+ virtual-screen coordinates, negative left of and above the primary monitor.
169
+ - `minWidth`/`minHeight`/`maxWidth`/`maxHeight` and `resizable` become the
170
+ window's size limits.
171
+ - `fullscreen`, `alwaysOnTop`, maximise and minimise use Windows' own calls;
172
+ full screen is a borderless monitor-sized window that remembers the frame
173
+ it came from.
174
+ - `transparent` — always available ([Rendering](#rendering)).
175
+ - `appId` — the window's AppUserModelID
176
+ ([Packaging and identity](#packaging-and-identity)).
177
+ - `<popup>` — no taskbar button, never takes activation, placed against the
178
+ monitor's work area as everywhere.
179
+ - Drag and drop, both directions: `dropAccept`, `onDrag*` and `dragData`
180
+ hold, and JS keeps running through the drag. Windows itself refuses a drop
181
+ from Explorer into an elevated app.
182
+
183
+ **Bends**:
184
+
185
+ - `<popup grab>`: there is no pointer grab on Windows, so a press in another
186
+ of the app's windows, or the app losing activation, closes the popup
187
+ instead. The gap: a press on one of the app's own title bars or resize
188
+ borders does not, so a menu stays open while its window is dragged.
189
+ - `states`: `shaded` and `sticky` have no Windows call and resolve false,
190
+ which `useWindowState()` reports. `focused` can be refused by Windows'
191
+ foreground rules, and then simply does not change.
192
+ - `embeddable` and `<foreign>`: see
193
+ [Embedding](#embedding-frame-foreign-window-embeddable).
194
+ - The global menu: Windows has none; draw a `<MenuBar>`.
195
+
196
+ **Inert** — accepted and ignored, so shared code stays branch-free:
197
+
198
+ - `decorations={false}`, today. A titlebar drawn in the window and moved
199
+ from `onMouseMove` loses snapping and the Snap Layouts flyout.
200
+ - `transientFor`, `windowType`.
201
+ - `grabKeyboard`: a popup gets keys only while it is the foreground window.
202
+ - `onClientMessage` never fires; there is no X.
203
+
204
+ **Breaks**, by design: window managers (`examples/wm.jsx`) — DWM owns every
205
+ frame — and `ssh -X` remoting, for which the X11 backend stays the answer
206
+ ([remote.md](remote.md)).
207
+
208
+ ## The desktop around the app
209
+
210
+ No D-Bus or portals here: each service has a Windows rung or an honest
211
+ floor. [windows-integrations.md](windows-integrations.md) has the measured
212
+ status of each; in short:
213
+
214
+ - **Work**: `useFileDialog()` (Windows' own dialog, owned by the window that
215
+ asked), `useSystemAppearance()` (light/dark, accent, high contrast, reduced
216
+ motion; the window frame follows dark mode), `useTray()`, `useProgress()`,
217
+ `useBadge()`/`setBadge()` (drawn into the taskbar's overlay icon — pass one
218
+ character for a dot), `setUrgent()`, `useIdle()`, `keepAwake()`,
219
+ `useLocale()`.
220
+ - **Global shortcuts** (`useAccelerator()`): system-wide, no admin. A chord
221
+ another app holds fails; Windows-key chords are reserved.
222
+ - **Notifications** (`notify()`, `useNotifier()`) are tray balloons, which
223
+ the shell shows as toasts and keeps in the Action Center: plain text with a
224
+ severity — no actions, click events, updates or custom icon, as the
225
+ capabilities say.
226
+ - **Clipboard** (`useClipboard()`): text only — writing anything else
227
+ rejects. No `PRIMARY`. `watch()` notices a change within about 400 ms.
228
+ - **Windows only**: `useJumpList()`, `useThumbnailToolbar()`,
229
+ `useRecentDocument()`, reported as features of
230
+ `useDesktopCapability('launcher')`.
231
+ - **Answer "no"**: `useLauncherMenu()` is inert (a jump-list task starts a new
232
+ process — use `useJumpList()`); `useAppOpen()` and `useAppActivate()` never
233
+ fire (no file associations, URL schemes or single instance yet);
234
+ `useEyedropper()` reports `supported: false`; `usePermission()` answers
235
+ `'unknown'`; `useDesktopCalendarEvents()` is `supported: false` unless the
236
+ app is packaged.
237
+ - **Native control bezels** are the system's own while the desktop is light,
238
+ and drawn in dark mode, because Windows' dark common controls are not
239
+ public.
240
+
241
+ ## Accessibility: UI Automation
242
+
243
+ Screen readers read the app through UI Automation. What an app writes —
244
+ `role`, `aria-*`, `announce()` — is read by the same code the Linux AT-SPI
245
+ bridge reads, so the two cannot disagree about what a node is.
246
+
247
+ - **Crosses**: roles, names, descriptions, states, bounding rectangles,
248
+ focus and hit testing; the Invoke, Toggle, Value and RangeValue patterns;
249
+ `announce()` as a UIA notification.
250
+ - The bridge answers from a copy of the tree the app pushes after commits, so
251
+ a screen reader never waits on a busy JS thread. Pushes are paced as
252
+ [accessibility.md](accessibility.md#how-often-the-bridge-speaks) describes.
253
+ - **Missing: text ranges.** A `<textinput>` exposes its value, so a screen
254
+ reader reads the field and hears edits, but cannot move caret by caret or
255
+ word by word inside it.
256
+ - `REACT_X11_TRACE_A11Y=1` prints a line per push and action.
257
+
258
+ ## 3D: `<glarea>`
259
+
260
+ `<glarea>` draws through OpenGL on the **GPU vendor's driver**, and its frames
261
+ are composited with the rest of the window, the window's alpha included.
262
+
263
+ - The context is an OpenGL ES-profile one: GLSL ES shaders, and the
264
+ WebGL 2-shaped `gl` (`gl.bindBuffer`, …) of the X11 direct backend.
265
+ `@react-x11/components/three` runs on it. No `glPolicy` is needed: this
266
+ backend is always direct.
267
+ - It needs the driver's `WGL_NV_DX_interop2`, which NVIDIA and AMD drivers
268
+ have and some Intel configurations lack.
269
+ - **Without a vendor driver** — a virtual machine without GPU passthrough, a
270
+ CI runner, a fresh install before Windows Update fetched the driver, some
271
+ Remote Desktop sessions — Windows offers only the OpenGL 1.1 software
272
+ rasterizer, which has no shaders. `<glarea>` then reports one `onError`
273
+ naming the reason, and `useSupports('shaders')` is false. The ANGLE
274
+ fallback that would cover these machines is not built.
275
+ - `useSupports('shaders')` is false for one tick and then true: the GL probe
276
+ (about 120 ms) runs the first time something asks, not at startup.
277
+ - A `<glarea>`'s children are drawn on composited panes above the surface,
278
+ translucency included (`useSupports('glOverlay')` is true). The surface
279
+ takes no pointer input; the pointer reaches the tree.
280
+
281
+ Background: [design/windows-gl.md](../design/windows-gl.md).
282
+
283
+ ## Embedding: `<Frame>`, `<foreign>`, `<window embeddable>`
284
+
285
+ The API is the same as everywhere; what answers it differs.
286
+
287
+ - **`<Frame src>` works.** The pane runs in its own process and draws into a
288
+ composition surface the host shows; `props`, `fallback` and `restart()`
289
+ behave as on every backend ([frame.md](frame.md)). The host is the pane's
290
+ parent process; a pane started some other way names it with
291
+ `createRoot({ win32: { paneHostPid } })`.
292
+ - **`<foreign>` refuses.** Windows' cross-process child windows attach the two
293
+ programs' input queues, so a hung guest would hang the host.
294
+ `useSupports('embedding')` is false, and a `<foreign>` reports one
295
+ `onError`:
296
+
297
+ ```
298
+ react-x11: <foreign> needs a backend with cross-process window embedding,
299
+ and this one has none — so nothing can be put in it. Ask
300
+ useSupports('embedding') before rendering one.
301
+ ```
302
+
303
+ ```jsx
304
+ const embedding = useSupports('embedding');
305
+ return embedding ? <foreign onReady={spawnInto} /> : <text>X11 only</text>;
306
+ ```
307
+
308
+ - **`<window embeddable>`** is created and not shown, as everywhere.
309
+ `windowHandleOf(ref)` / `useWindowHandle(ref)` returns a **composition
310
+ surface handle** already valid in the host process — not an HWND, because a
311
+ composition target stops presenting once its window is made a child. Pass
312
+ it to the host out of band, as an XID is passed on X11. The host must bind
313
+ a composition surface to a visual of its own (another react-x11 app, WinUI
314
+ 3, a DirectComposition-based host); a plain Win32 or OLE document host
315
+ cannot. The host is the parent process unless
316
+ `createRoot({ win32: { paneHostPid } })` names another.
317
+ - OLE embedding (a pane inside a Word document) is not supported.
318
+
319
+ Background: [design/windows-embedding.md](../design/windows-embedding.md).
320
+
321
+ ## Packaging and identity
322
+
323
+ - **`node app.js` works**, with two cosmetic problems. `node.exe` is a
324
+ console program, so launched from a shortcut it opens a console window.
325
+ And without an `appId`, the taskbar groups the app's windows with every
326
+ other `node.exe` window under Node's icon, and pinning pins `node.exe`.
327
+ - **`<window appId>`** (or the class half of `wmClass`) sets the window's
328
+ AppUserModelID, per window, before it is shown. The taskbar groups by it,
329
+ pinning pins it, and the jump list belongs to it. A pinned tile relaunches
330
+ the command line that started the process, named after the window's title,
331
+ with the executable's icon.
332
+ - **A single executable** ([packaging.md](packaging.md) tier 3,
333
+ `node --build-sea`) is a copy of `node.exe` with the app inside. After the
334
+ build: a manifest of the app's own with `rcedit`, the GUI subsystem with
335
+ `editbin /SUBSYSTEM:WINDOWS` so no console opens, then the signature.
336
+ Windows 11 24H2 also has a manifest setting that detaches the console when
337
+ launched from Explorer and keeps it in a terminal. Ship the bridge beside
338
+ the executable and name it with `REACT_X11_WIN32_PATH`.
339
+ - **`bun build --compile`** has `--windows-hide-console` and icon and
340
+ metadata flags, but no way to supply a manifest; `bun.exe`'s own manifest
341
+ has no compatibility section, and what that changes for DPI and DWM is not
342
+ yet verified.
343
+ - **Distribution**: Authenticode signing, which SmartScreen's reputation
344
+ follows. MSIX package identity is what unlocks the calendar, the
345
+ capability-status API and declared protocol activation.
346
+ - [packaging.md](packaging.md) has no Windows tier yet.
347
+
348
+ ## Testing
349
+
350
+ - `react-x11/test` runs a tree against an in-process X server or the mock,
351
+ on Windows as anywhere ([testing.md](testing.md)) — it exercises the X11
352
+ backend, not this one.
353
+ - `window.snapshot()` reads a composed window back on a real Windows desktop
354
+ (above).
355
+ - Test at **fractional scales** — 1.25 and 1.5, not only 1 and 2. Logical and
356
+ device pixels part company there, and a test at scale 1 cannot tell which
357
+ of the two a coordinate is in.
358
+ - GitHub-hosted Windows runners have an interactive desktop (1024×768) and no
359
+ GPU driver, so `<glarea>` reports its error there; the rest of the backend
360
+ on Direct3D's software device (WARP) is not yet verified on them.
361
+
362
+ ## Not supported yet
363
+
364
+ - Live per-window scale: a window on a monitor at another scale is not
365
+ re-laid out ([Scale and DPI](#scale-and-dpi)).
366
+ - UI Automation text ranges: no caret or word navigation inside a field.
367
+ - TSF: voice typing, handwriting and shape-writing; IME reconversion.
368
+ - `decorations={false}`, `transientFor`, and a drag-region API for drawn
369
+ titlebars (snapping, Snap Layouts).
370
+ - Clipboard formats other than text: images, HTML, file lists.
371
+ - File associations, URL schemes, single instance (`useAppOpen`,
372
+ `useAppActivate`); `useLauncherMenu`.
373
+ - Toasts with actions or click events (notifications are balloons).
374
+ - `useEyedropper()`; the calendar without package identity.
375
+ - `<glarea>` without a vendor OpenGL driver (ANGLE).
376
+ - `<video src>`.
377
+ - Compositor-side animation: transitions run on the frame clock, so a long
378
+ JS block stalls them, unlike on Cocoa.
379
+ - Pen- and touch-specific input, touchpad inertia and pinch.
380
+ - `<foreign>`, OLE embedding, window managers and `ssh -X` — by design.
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "react-x11",
3
- "version": "2.51.1",
3
+ "version": "2.53.0",
4
4
  "description": "react renderer with X11 as a target",
5
5
  "main": "./src/index.js",
6
6
  "bin": {
7
7
  "react-x11": "./src/cli/index.js"
8
8
  },
9
9
  "files": [
10
- "src"
10
+ "src",
11
+ "!src/cli/create-react-x11",
12
+ "docs/*.md",
13
+ "docs/*/*.md"
11
14
  ],
12
15
  "scripts": {
13
16
  "test": "node --import tsx --test --test-timeout=60000",
@@ -69,6 +72,7 @@
69
72
  "labs:direct-gl": "tsx examples/labs/direct-gl.jsx",
70
73
  "labs:gl-overlay": "tsx examples/labs/gl-overlay.jsx",
71
74
  "examples:viewer3d": "tsx examples/viewer3d.jsx",
75
+ "examples:video": "tsx examples/video.jsx",
72
76
  "examples:transparent": "tsx examples/transparent.jsx",
73
77
  "examples:windows": "tsx examples/windows.jsx",
74
78
  "examples:wm": "tsx examples/wm.jsx",
@@ -107,7 +111,7 @@
107
111
  "ical.js": "^2.2.1",
108
112
  "image-in-browser": "^3.6.0",
109
113
  "linebreak": "^1.1.0",
110
- "ntk": "^8.23.2",
114
+ "ntk": "^8.24.0",
111
115
  "react-reconciler": "^0.34.0",
112
116
  "yoga-layout": "^3.2.1"
113
117
  },
@@ -228,6 +232,10 @@
228
232
  "./frame/child": {
229
233
  "types": "./src/frame/child.d.ts",
230
234
  "default": "./src/frame/child.js"
235
+ },
236
+ "./create": {
237
+ "types": "./src/cli/create.d.ts",
238
+ "default": "./src/cli/create.js"
231
239
  }
232
240
  },
233
241
  "types": "./src/index.d.ts"
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