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 +32 -7
- package/lib/widgets/htmlview.js +1 -1
- package/lib/yoga.js +119 -0
- package/package.json +2 -2
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
|
|
60
|
-
|
|
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
|
-
//
|
|
149
|
-
//
|
|
150
|
-
// second WASM
|
|
151
|
-
|
|
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 };
|
package/lib/widgets/htmlview.js
CHANGED
|
@@ -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
|
|
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": "
|
|
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.
|
|
51
|
+
"x11": "^3.6.0",
|
|
52
52
|
"yoga-layout": "^3.2.1"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|