ntk 4.3.0 → 5.0.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/lib/index.js CHANGED
@@ -4,6 +4,7 @@ import App from './app.js';
4
4
  import Clipboard from './clipboard.js';
5
5
  import { BLANK_CURSOR, CursorCache, cursorNames, cursorShapes, resolveCursorShape } from './cursor.js';
6
6
  import Window from './window.js';
7
+ import { loadLayout } from './yoga.js';
7
8
  import { decodeKey, groupForState } from './keyboard.js';
8
9
  import Pixmap from './pixmap.js';
9
10
  import Picture from './picture.js';
@@ -31,6 +32,16 @@ import './renderingcontext_x11.js';
31
32
  import './renderingcontext_2d.js';
32
33
  import './renderingcontext_opengl.js';
33
34
 
35
+ // One socket write per frame instead of one per request (x11 >= 3.6). A frame
36
+ // is emitted in one synchronous run of _runFrame() and ends with the frame
37
+ // fence's GetInputFocus, which x11 flushes on because it expects a reply — so
38
+ // the batch goes out at the frame boundary without ntk flushing anything by
39
+ // hand. 64 KB holds a heavy frame (a 72 KB frame is 2 writes, a typical one
40
+ // is 1); the client's own gates — the size cap, a 5 ms age limit and a flush
41
+ // before the event loop polls — bound how long anything can wait, so this
42
+ // cannot delay a frame or a reply. `bufferRequests: false` opts out.
43
+ const DEFAULT_BUFFER_REQUESTS = { maxSize: 64 * 1024 };
44
+
34
45
  /**
35
46
  * Connect to the X server and resolve with an App.
36
47
  *
@@ -46,7 +57,9 @@ import './renderingcontext_opengl.js';
46
57
  * { glxVisual } — a visual id for getContext('opengl') to use instead of
47
58
  * querying the server for one — and
48
59
  * { onXError } — called with X protocol errors no request callback
49
- * claimed (default: console.warn)
60
+ * claimed (default: console.warn).
61
+ * x11's { bufferRequests } defaults to a 64 KB output buffer here, which
62
+ * makes a frame one socket write; pass false to write per request.
50
63
  * @param {function} [callback] optional node-style callback
51
64
  * @returns {Promise<App>}
52
65
  */
@@ -56,8 +69,15 @@ export function createClient(options, callback) {
56
69
  options = {};
57
70
  }
58
71
 
59
- const promise = new Promise((resolve, reject) => {
60
- x11.createClient(options || {}, (error, display) => {
72
+ const x11Options = { ...(options || {}) };
73
+ if (x11Options.bufferRequests === undefined) x11Options.bufferRequests = DEFAULT_BUFFER_REQUESTS;
74
+
75
+ // the layout engine's WASM loads alongside the connection, so widgets are
76
+ // usable synchronously by the time the App exists (see lib/yoga.js)
77
+ const layout = loadLayout();
78
+
79
+ const connecting = new Promise((resolve, reject) => {
80
+ x11.createClient(x11Options, (error, display) => {
61
81
  if (error) return reject(error);
62
82
 
63
83
  display.client.require('glx', (glxError, GLX) => {
@@ -98,6 +118,8 @@ export function createClient(options, callback) {
98
118
  });
99
119
  });
100
120
 
121
+ const promise = Promise.all([connecting, layout]).then(([app]) => app);
122
+
101
123
  if (callback) {
102
124
  promise.then(
103
125
  (app) => callback(null, app),
@@ -145,8 +167,11 @@ export {
145
167
  decodeKey,
146
168
  groupForState
147
169
  };
148
- // the yoga-layout instance ntk lays HtmlView out with — downstream layout
149
- // consumers (e.g. the react-x11 renderer) must share it to avoid loading a
150
- // second WASM copy with potentially mismatched enums
151
- export { default as Yoga } from 'yoga-layout';
170
+ // The layout engine ntk lays HtmlView out with — downstream layout consumers
171
+ // (e.g. the react-x11 renderer) must import it from here rather than from
172
+ // `yoga-layout`, or they get a second WASM instance whose Nodes cannot be
173
+ // mixed with ntk's. Its enum constants are readable as soon as ntk is
174
+ // imported; `Node`/`Config` need the WASM, which `createClient()` loads —
175
+ // `loadLayout()` is there for widgets used without an App.
176
+ export { default as Yoga, loadLayout, layoutLoaded } from './yoga.js';
152
177
  export default { createClient };
@@ -1,7 +1,7 @@
1
1
  import { selectAll } from 'css-select';
2
2
  import { textContent } from 'domutils';
3
3
  import { parseDocument } from 'htmlparser2';
4
- import Yoga from 'yoga-layout';
4
+ import Yoga from '../yoga.js';
5
5
 
6
6
  import { decodeImage, Image } from '../image.js';
7
7
  import { TextLayout } from '../text/layout.js';
package/lib/yoga.js ADDED
@@ -0,0 +1,119 @@
1
+ // The layout engine, without a top-level await.
2
+ //
3
+ // `yoga-layout`'s default entry is `const Yoga = wrapAssembly(await
4
+ // loadYoga())`. That one `await` is contagious: every bundle containing ntk
5
+ // inherits it, esbuild then refuses to emit CommonJS ("Top-level await is
6
+ // currently not supported with the cjs output format"), and Node's single
7
+ // executable format runs its embedded main as CommonJS — so an ntk app could
8
+ // not be shipped as one binary at all.
9
+ //
10
+ // So ntk imports the half of the package that has no WASM in it —
11
+ // `yoga-layout/load` exports the enums as plain JavaScript and the assembly
12
+ // behind an async function — and loads the assembly during `createClient()`,
13
+ // which is already asynchronous.
14
+ //
15
+ // The object exported here keeps yoga's own shape, and that is the point:
16
+ // the flat SCREAMING_CASE constants are present from the first tick, so a
17
+ // consumer that builds a lookup table at module scope (ntk's own HtmlView,
18
+ // react-x11's styles.js) still reads them at import time. `Node`, `Config`
19
+ // and the rest of the assembly appear when `loadLayout()` resolves.
20
+
21
+ import {
22
+ loadYoga,
23
+ Align,
24
+ BoxSizing,
25
+ Dimension,
26
+ Direction,
27
+ Display,
28
+ Edge,
29
+ Errata,
30
+ ExperimentalFeature,
31
+ FlexDirection,
32
+ Gutter,
33
+ Justify,
34
+ LogLevel,
35
+ MeasureMode,
36
+ NodeType,
37
+ Overflow,
38
+ PositionType,
39
+ Unit,
40
+ Wrap
41
+ } from 'yoga-layout/load';
42
+
43
+ const ENUMS = {
44
+ Align,
45
+ BoxSizing,
46
+ Dimension,
47
+ Direction,
48
+ Display,
49
+ Edge,
50
+ Errata,
51
+ ExperimentalFeature,
52
+ FlexDirection,
53
+ Gutter,
54
+ Justify,
55
+ LogLevel,
56
+ MeasureMode,
57
+ NodeType,
58
+ Overflow,
59
+ PositionType,
60
+ Unit,
61
+ Wrap
62
+ };
63
+
64
+ // yoga's generator names each constant <ENUM>_<MEMBER>, both snake-cased from
65
+ // PascalCase: FlexDirection.ColumnReverse -> FLEX_DIRECTION_COLUMN_REVERSE.
66
+ // yoga-export.test.js pins every name against the real assembly, so a rename
67
+ // upstream fails loudly instead of yielding an undefined constant.
68
+ const screamingSnake = (name) => name.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toUpperCase();
69
+
70
+ /** The layout engine: enums now, assembly after `loadLayout()`. */
71
+ const Yoga = {};
72
+ for (const [enumName, members] of Object.entries(ENUMS)) {
73
+ // a bundler may stub yoga out entirely — the browser playground does,
74
+ // since no demo uses HtmlView — and importing ntk must still work
75
+ if (!members || typeof members !== 'object') continue;
76
+ const prefix = screamingSnake(enumName);
77
+ for (const [member, value] of Object.entries(members)) {
78
+ if (typeof value === 'number') Yoga[`${prefix}_${screamingSnake(member)}`] = value;
79
+ }
80
+ }
81
+
82
+ const notLoaded = (what) => () => {
83
+ throw new Error(
84
+ `ntk: the layout engine is not loaded, so Yoga.${what} is not available yet. ` +
85
+ 'createClient() loads it; widgets used without an App need `await loadLayout()` first.'
86
+ );
87
+ };
88
+
89
+ // A useful message instead of "Cannot read properties of undefined"
90
+ for (const name of ['Node', 'Config']) {
91
+ Object.defineProperty(Yoga, name, { configurable: true, get: notLoaded(name) });
92
+ }
93
+
94
+ let loading = null;
95
+
96
+ /**
97
+ * Load the layout engine's WebAssembly. Idempotent, and resolves with the
98
+ * same `Yoga` object this module exports — `createClient()` awaits it, so
99
+ * applications rarely call it themselves. Widgets used without an App
100
+ * (`new HtmlView(null, …)`) need it.
101
+ */
102
+ export function loadLayout() {
103
+ if (!loading) {
104
+ loading = loadYoga().then((assembly) => {
105
+ for (const name of ['Node', 'Config']) delete Yoga[name]; // drop the throwing getters
106
+ Object.assign(Yoga, assembly);
107
+ return Yoga;
108
+ });
109
+ }
110
+ return loading;
111
+ }
112
+
113
+ /** Whether the assembly is in place — layout will not throw. */
114
+ export function layoutLoaded() {
115
+ return Object.getOwnPropertyDescriptor(Yoga, 'Node')?.value !== undefined;
116
+ }
117
+
118
+ export { Yoga };
119
+ export default Yoga;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ntk",
3
- "version": "4.3.0",
3
+ "version": "5.0.0",
4
4
  "description": "Desktop UI toolkit for X11 with canvas-like 2d and OpenGL rendering",
5
5
  "author": "Andrey Sidorov <sidorares@yandex.ru>",
6
6
  "license": "MIT",
@@ -48,7 +48,7 @@
48
48
  "parse-color": "^1.0.0",
49
49
  "pngjs": "^7.0.0",
50
50
  "postcss": "^8.5.23",
51
- "x11": "^3.4.0",
51
+ "x11": "^3.6.0",
52
52
  "yoga-layout": "^3.2.1"
53
53
  },
54
54
  "scripts": {