ntk 2.1.1 → 3.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.
Files changed (48) hide show
  1. package/README.md +115 -110
  2. package/lib/app.js +48 -15
  3. package/lib/cleanup.js +14 -0
  4. package/lib/drawable.js +10 -16
  5. package/lib/events_map.js +81 -87
  6. package/lib/fontconfig.js +122 -14
  7. package/lib/glyphset.js +36 -18
  8. package/lib/image.js +138 -0
  9. package/lib/index.js +110 -52
  10. package/lib/path.js +734 -0
  11. package/lib/picture.js +76 -38
  12. package/lib/pixmap.js +34 -25
  13. package/lib/rasterize.js +178 -0
  14. package/lib/renderingcontext_2d.js +994 -517
  15. package/lib/renderingcontext_opengl.js +38 -31
  16. package/lib/renderingcontext_x11.js +56 -64
  17. package/lib/text/font.js +196 -0
  18. package/lib/text/fontmanager.js +233 -0
  19. package/lib/text/fontsource.js +179 -0
  20. package/lib/text/glyphs.js +456 -0
  21. package/lib/text/layout.js +356 -0
  22. package/lib/text/shape.js +141 -0
  23. package/lib/trapezoid.js +131 -0
  24. package/lib/widgets/css.js +676 -0
  25. package/lib/widgets/highlight.js +97 -0
  26. package/lib/widgets/htmlview.js +879 -0
  27. package/lib/widgets/markdown.js +148 -0
  28. package/lib/widgets/markdownview.js +487 -0
  29. package/lib/widgets/mermaid.js +720 -0
  30. package/lib/widgets/svgview.js +528 -0
  31. package/lib/widgets/tex.js +681 -0
  32. package/lib/window.js +568 -246
  33. package/package.json +35 -32
  34. package/.npmignore +0 -15
  35. package/examples/example-await.js +0 -23
  36. package/examples/example-co.js +0 -13
  37. package/examples/example.js +0 -14
  38. package/examples/fill-styles.js +0 -39
  39. package/examples/glclock.js +0 -100
  40. package/examples/glxpixmap.js +0 -12
  41. package/examples/gradient.js +0 -41
  42. package/examples/package.json +0 -17
  43. package/examples/simpletex.js +0 -72
  44. package/examples/teapot.js +0 -104
  45. package/examples/teapot.json +0 -6
  46. package/examples/text-rendering.js +0 -44
  47. package/lib/fontface.js +0 -109
  48. package/lib/widgets/webview.js +0 -33
package/README.md CHANGED
@@ -1,9 +1,20 @@
1
1
  ntk
2
2
  ===
3
3
 
4
- node.js desktop UI toolkit
4
+ node.js desktop UI toolkit for X11
5
5
 
6
- A set of wrappers around low level [node-x11](https://github.com/sidorares/node-x11) module to simplify X Window UI progamming - windows creation, event handling, 2d/3d graphics etc.
6
+ A set of wrappers around the low level [node-x11](https://github.com/sidorares/node-x11)
7
+ module to simplify X Window UI programming — window creation, DOM-style event
8
+ handling, 2d/3d graphics — using API concepts you already know from the web.
9
+
10
+ Everything, including font rasterization, is pure JavaScript: `npm install`
11
+ never compiles anything.
12
+
13
+ **Docs & live playground:** <https://sidorares.github.io/ntk/> — the
14
+ playground runs ordinary ntk code in your browser against node-x11's
15
+ in-browser pure-JS X server (XRender included) with bundled fonts. The same server also works headless in node — see
16
+ [docs/xserver.md](docs/xserver.md) — so ntk apps and tests can run with no
17
+ real X server at all.
7
18
 
8
19
  # Installation
9
20
 
@@ -11,141 +22,135 @@ A set of wrappers around low level [node-x11](https://github.com/sidorares/node-
11
22
  npm install ntk
12
23
  ```
13
24
 
25
+ Requires Node.js >= 20.19 and an X server. Full documentation lives in
26
+ [docs/](docs/README.md).
27
+
14
28
  ## Basic usage
15
29
 
16
30
  ```js
17
- require('ntk').createClient( (err, app) => {
18
- var mainWnd = app.createWindow({ width: 500, height: 300, title: 'Hello' });
19
- mainWnd.on('mousedown', (ev) => { mainWnd.setTitle('click: ' + [ev.x, ev.y].join(',')); });
20
- mainWnd.map();
21
- });
31
+ import { createClient } from 'ntk';
32
+
33
+ const app = await createClient();
34
+ const wnd = app.createWindow({ width: 500, height: 300, title: 'Hello' });
35
+ wnd.on('mousedown', (ev) => wnd.setTitle(`click: ${ev.x},${ev.y}`));
36
+ wnd.map();
22
37
  ```
23
38
 
24
39
  ## 2d graphics
25
40
 
26
- Each window ( or pixmap ) can be used to create 2d and 3d canvas. 2d canvas implements HTML [context2d api](https://html.spec.whatwg.org/multipage/scripting.html#2dcontext) ( some features not yet supported ) via [XRender extension](http://www.x.org/releases/X11R7.7/doc/renderproto/renderproto.txt), and most operations are performed on the X Server side ( image composition, scaling, blur, text composition, gradients etc ). Initial font rendering is performed by ntk using [freetype2](https://github.com/ericfreese/node-freetype2) module and fonts names mapped to file names using [font-manager](https://github.com/devongovett/font-manager) module.
41
+ Each window (or pixmap) can create a 2d canvas implementing the HTML
42
+ [context2d api](https://html.spec.whatwg.org/multipage/canvas.html#2dcontext)
43
+ via the
44
+ [XRender extension](https://www.x.org/releases/X11R7.7/doc/renderproto/renderproto.txt) —
45
+ paths (arcs, beziers, `Path2D` with SVG path data, non-zero/even-odd fill
46
+ rules), transforms with `save()`/`restore()`, clipping, `globalAlpha` and
47
+ Porter-Duff composite ops ([docs/context-2d.md](docs/context-2d.md)).
48
+ Most operations are performed on the X server side (image composition,
49
+ scaling, blur, text composition, gradients etc). Text is fully shaped in
50
+ pure JS — OpenType kerning/ligatures and complex scripts (fontkit), bidi
51
+ (bidi-js), automatic font fallback — rasterized by a built-in scanline
52
+ rasterizer and cached server-side as XRender glyphs, so drawing a line of
53
+ text costs about a byte per glyph on the wire. Font names resolve through
54
+ fontconfig (`fc-match`). Very large and continuously animated sizes render
55
+ as server-side trapezoids instead of cached bitmaps. A `TextLayout` engine
56
+ wraps styled text to a target width, a `MarkdownView` widget renders
57
+ markdown (with syntax-highlighted code fences, mermaid flowchart/sequence
58
+ diagrams, and KaTeX math via `TexView`) on top of it — see
59
+ [docs/text.md](docs/text.md), [docs/mermaid.md](docs/mermaid.md) and
60
+ [docs/tex.md](docs/tex.md).
61
+
62
+ PNG/JPEG images decode client-side (`loadImage`) and composite server-side
63
+ via `ctx.drawImage` ([docs/images.md](docs/images.md)). An `HtmlView`
64
+ widget renders a static HTML + CSS subset — block flow and flexbox laid
65
+ out by yoga-layout, no scripts, no network — with app-controlled link
66
+ navigation ([docs/html.md](docs/html.md)). An `SvgView` widget renders
67
+ static SVG (shapes, gradients, transforms, `use`) through the same 2d
68
+ pipeline ([docs/svg.md](docs/svg.md)).
27
69
 
28
70
  ```js
29
- var ntk = require('ntk');
30
-
31
- ntk.createClient( (err, app) => {
32
- var wnd = app.createWindow({ width: 800, height: 600});
33
- var ctx = wnd.getContext('2d');
34
-
35
- wnd.on('mousemove', (ev) => {
36
- var gradient = ctx.createRadialGradient(0, 0, 10, ev.x, ev.y, 500);
37
- gradient.addColorStop(0, "red");
38
- gradient.addColorStop(0.5, "green");
39
- gradient.addColorStop(1, "rgba(255, 255, 255, 0)");
40
- ctx.fillStyle = gradient;
41
- ctx.fillRect(0, 0, ctx.width, ctx.height);
42
- });
43
-
44
- wnd.map();
71
+ import { createClient } from 'ntk';
72
+
73
+ const app = await createClient();
74
+ const wnd = app.createWindow({ width: 800, height: 600 });
75
+ const ctx = wnd.getContext('2d');
76
+
77
+ wnd.on('mousemove', (ev) => {
78
+ const gradient = ctx.createRadialGradient(0, 0, 10, ev.x, ev.y, 500);
79
+ gradient.addColorStop(0, 'red');
80
+ gradient.addColorStop(0.5, 'green');
81
+ gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
82
+ ctx.fillStyle = gradient;
83
+ ctx.fillRect(0, 0, ctx.width, ctx.height);
45
84
  });
85
+
86
+ wnd.map();
46
87
  ```
47
88
 
48
- `ctx.drawImage()` also accepts [node-canvas](https://github.com/Automattic/node-canvas) as a source, for images with lot's of drawing calls it might be more efficient to perform all drawing locally and transfer pixels to server when ready:
89
+ `ctx.drawImage()` also accepts a [node-canvas](https://github.com/Automattic/node-canvas)
90
+ canvas as a source — for images with lots of drawing calls it might be more
91
+ efficient to draw locally and transfer pixels to the server when ready.
49
92
 
50
- ```js
51
- var ntk = require('ntk');
52
- var Canvas = require('canvas');
93
+ ## Frame pacing & networked displays
53
94
 
54
- var canvas = new Canvas(800, 800);
55
- var canvasCtx = canvas.getContext('2d');
95
+ Noisy events (`resize`, `mousemove`, `expose`) are coalesced into paced
96
+ frames — the latest state wins, nothing queues up — and each frame is
97
+ fenced with a server round-trip, so rendering automatically slows to the
98
+ connection's real throughput instead of drawing a trail of stale updates
99
+ over ssh-forwarded displays. Animation uses the DOM-style
100
+ `requestAnimationFrame`:
56
101
 
57
- function drawSomething(ctx) {
58
- // ...
102
+ ```js
103
+ function frame(now) {
104
+ // ... draw ...
105
+ wnd.requestAnimationFrame(frame); // ~60fps locally, RTT-paced remotely
59
106
  }
107
+ wnd.requestAnimationFrame(frame);
108
+ ```
60
109
 
61
- ntk.createClient( (err, app) => {
62
- var wnd = app.createWindow({ width: 800, height: 800});
110
+ See [docs/window.md](docs/window.md) for the knobs (`frameInterval`,
111
+ `frameSync`, `coalesceEvents`) and the raw uncoalesced event stream.
63
112
 
64
- var ctx = wnd.getContext('2d');
113
+ ## Resource management
65
114
 
66
- setInterval(function() {
67
- drawSomething(canvasCtx);
68
- ctx.drawImage(canvas);
69
- }, 20);
115
+ Server-side resources support `using` / `await using` (Node 24+):
70
116
 
71
- wnd.map();
72
- });
117
+ ```js
118
+ {
119
+ await using app = await createClient();
120
+ using pixmap = app.createPixmap({ width: 256, height: 256, depth: 24 });
121
+ // ... draw ...
122
+ } // pixmap freed, connection closed
73
123
  ```
74
124
 
75
125
  ## 3d graphics
76
126
 
77
- At the moment only indirect GLX is supported with most of OpenGL 1.4 api implemented. Note that on some systems indirect GLX might be disabled by default, [you'll need to enable it for gl to work](https://github.com/sidorares/node-x11/issues/117#issuecomment-214762185).
127
+ Only indirect GLX is supported, with most of the OpenGL 1.4 api implemented.
128
+ Note that on many systems indirect GLX is disabled by default —
129
+ [you'll need to enable it for gl to work](https://github.com/sidorares/node-x11/issues/117#issuecomment-214762185).
78
130
 
79
131
  ```js
80
- var ntk = require('ntk');
81
-
82
- var width = 300;
83
- var height = 300;
84
- var angle = 0;
85
-
86
- function resize(gl) {
87
- gl.Viewport(0, 0, width, height);
88
- gl.MatrixMode(gl.PROJECTION);
89
- gl.LoadIdentity();
90
- if (width < height)
91
- gl.Frustum(-1.0, 1.0, -height/width, height/width, -35.0, 20);
92
- else
93
- gl.Frustum(-width/height, width/height, -1.0, 1.0, -35.0, 20);
94
- }
95
-
96
- function draw(gl) {
97
- gl.MatrixMode(gl.MODELVIEW);
98
-
99
- gl.ClearColor(0.3,0.3,0.3,0.0);
100
- gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
101
-
102
- gl.LoadIdentity();
103
- gl.Rotatef(-90 + 360*angle/width, 0, 0, 1);
104
- gl.Begin(gl.TRIANGLE_STRIP);
105
- gl.TexCoord2f(0.0,0.0);
106
- gl.Vertex3f(-1, -1, 0);
107
-
108
- gl.TexCoord2f(1.0, 0.0);
109
- gl.Vertex3f(1, -1, 0);
110
-
111
- gl.TexCoord2f(0.0, 1.0);
112
- gl.Vertex3f(-1, 1, 0);
113
-
114
- gl.TexCoord2f(1.0,1.0);
115
- gl.Vertex3f(1, 1, 0);
116
- gl.End();
117
- gl.SwapBuffers();
118
- }
119
-
120
- var texture = require('baboon-image');
121
-
122
- ntk.createClient(function(err, app) {
123
-
124
- var wnd = app.createWindow({width: width, height: height});
125
- wnd.map();
126
- var gl = wnd.getContext('opengl');
127
- gl.Enable(gl.TEXTURE_2D);
128
- gl.GenTextures(1, function(err, textures) {
129
- gl.BindTexture(gl.TEXTURE_2D, textures[0]);
130
- gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
131
- gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
132
- gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGB, 512, 512, 0, gl.RGB, gl.UNSIGNED_BYTE, texture.data);
133
- draw(gl);
134
-
135
- wnd.on('resize', function(ev) {
136
- width = ev.width;
137
- height = ev.height;
138
- resize(gl);
139
- draw(gl);
140
- }).on('mousemove', function(ev) {
141
- angle = ev.x;
142
- draw(gl);
143
- });
144
- });
145
- });
146
-
132
+ import { createClient } from 'ntk';
133
+
134
+ const app = await createClient();
135
+ const wnd = app.createWindow({ width: 300, height: 300 });
136
+ wnd.map();
137
+
138
+ const gl = wnd.getContext('opengl');
139
+ gl.ClearColor(0.3, 0.3, 0.3, 0.0);
140
+ gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
141
+ gl.Begin(gl.TRIANGLES);
142
+ gl.Color3f(1, 0, 0);
143
+ gl.Vertex3f(-1, -1, 0);
144
+ gl.Vertex3f(1, -1, 0);
145
+ gl.Vertex3f(0, 1, 0);
146
+ gl.End();
147
+ gl.SwapBuffers();
147
148
  ```
148
149
 
150
+ See [examples/](examples/) for more (teapot, GL clock, textures, text
151
+ rendering).
152
+
149
153
  ## High level widgets / layout management etc
150
154
 
151
- Likely to be implemented outside as part of [react-x11](https://github.com/sidorares/react-x11)
155
+ Likely to be implemented outside as part of a react renderer
156
+ ([react-x11](https://github.com/sidorares/react-x11)).
package/lib/app.js CHANGED
@@ -1,21 +1,54 @@
1
- var Window = require('./window');
2
- var Pixmap = require('./pixmap');
1
+ import Pixmap from './pixmap.js';
2
+ import FontManager from './text/fontmanager.js';
3
+ import Window from './window.js';
3
4
 
4
- module.exports = App;
5
+ /**
6
+ * A connection to an X server. Owns the underlying node-x11 client
7
+ * (`app.X`) and acts as a factory for windows and pixmaps.
8
+ */
9
+ export default class App {
10
+ /**
11
+ * @param {object} display node-x11 display
12
+ * @param {object} [options] environment hooks: { fontSource (see
13
+ * text/fontsource.js), glxVisual (visual id for getContext('opengl')
14
+ * when glxinfo can't be shelled out to) }
15
+ */
16
+ constructor(display, options = {}) {
17
+ this.display = display;
18
+ this.X = display.client;
19
+ this.options = options;
20
+ this._fonts = null;
21
+ }
5
22
 
6
- function App(display) {
7
- this.display = display;
8
- this.X = display.client;
9
- }
23
+ /** the text API entry point: font matching/loading, shaping, layout */
24
+ get fonts() {
25
+ if (!this._fonts) this._fonts = new FontManager({ source: this.options.fontSource });
26
+ return this._fonts;
27
+ }
10
28
 
11
- App.prototype.createWindow = function(args) {
12
- return new Window(this, args);
13
- }
29
+ createWindow(args) {
30
+ return new Window(this, args);
31
+ }
14
32
 
15
- App.prototype.rootWindow = function() {
16
- return new Window(this, { id: this.display.screen[0].root} );
17
- }
33
+ rootWindow() {
34
+ return new Window(this, { id: this.display.screen[0].root });
35
+ }
36
+
37
+ createPixmap(args) {
38
+ return new Pixmap(this, args);
39
+ }
40
+
41
+ // flush pending requests and close the connection
42
+ close() {
43
+ return new Promise((resolve) => this.X.close(resolve));
44
+ }
45
+
46
+ async [Symbol.asyncDispose]() {
47
+ await this.close();
48
+ }
18
49
 
19
- App.prototype.createPixmap = function(args) {
20
- return new Pixmap(this, args);
50
+ // sync variant: terminates without waiting for a flush round-trip
51
+ [Symbol.dispose]() {
52
+ this.X.terminate();
53
+ }
21
54
  }
package/lib/cleanup.js ADDED
@@ -0,0 +1,14 @@
1
+ // Release a server-side resource, tolerating a connection that is closing or
2
+ // already gone — the X server frees all of a client's resources on disconnect,
3
+ // so there is nothing left to do and nothing worth throwing about. This
4
+ // matters for FinalizationRegistry callbacks, which run after app.close() if
5
+ // wrappers get garbage collected late and have no user code around them to
6
+ // catch.
7
+ export function safeRelease(X, fn) {
8
+ if (X._closing || !X.stream || X.stream.destroyed || X.stream.writableEnded) return;
9
+ try {
10
+ fn();
11
+ } catch {
12
+ // connection raced shut between the check and the request
13
+ }
14
+ }
package/lib/drawable.js CHANGED
@@ -1,18 +1,12 @@
1
- var EventEmitter = require('events').EventEmitter;
2
- var util = require('util');
3
-
4
- module.exports = Drawable;
5
-
6
- function Drawable() {
7
- EventEmitter.call(this);
1
+ import { EventEmitter } from 'node:events';
2
+
3
+ export default class Drawable extends EventEmitter {
4
+ getContext(name, ...args) {
5
+ const factory = Drawable.renderingContextFactory[name];
6
+ if (!factory) throw new Error(`Unknown rendering context: ${name}`);
7
+ return factory(this, ...args);
8
+ }
8
9
  }
9
- util.inherits(Drawable, EventEmitter);
10
10
 
11
- Drawable.prototype.renderingContextFactory = {};
12
-
13
- Drawable.prototype.getContext = function(name, arg1, arg2, arg3) {
14
- var renderingContextFactory = this.renderingContextFactory[name];
15
- if (!renderingContextFactory)
16
- throw new Error('Unknown rendering context:', name);
17
- return renderingContextFactory(this, arg1, arg2, arg3);
18
- }
11
+ // populated by the renderingcontext_* modules on import
12
+ Drawable.renderingContextFactory = {};
package/lib/events_map.js CHANGED
@@ -1,98 +1,92 @@
1
- var x11 = require('x11');
1
+ import x11 from 'x11';
2
2
 
3
3
  // todo: move event names to node-x11, it should be part of event object
4
- var eventIdToName = module.exports.eventName = [];
5
- eventIdToName[2] = 'keydown';
6
- eventIdToName[3] = 'keyup';
7
- eventIdToName[4] = 'mousedown';
8
- eventIdToName[5] = 'mouseup';
9
- eventIdToName[6] = 'mousemove';
10
- eventIdToName[7] = 'mouseover';
11
- eventIdToName[8] = 'mouseout';
12
- eventIdToName[12] = 'expose';
13
- eventIdToName[17] = 'destroy';
14
- eventIdToName[18] = 'unmap';
15
- eventIdToName[19] = 'map';
16
- eventIdToName[20] = 'map_request';
17
- eventIdToName[21] = 'reparent';
18
- eventIdToName[22] = 'resize';
19
- eventIdToName[23] = 'configure_request';
4
+ export const eventName = [];
5
+ eventName[2] = 'keydown';
6
+ eventName[3] = 'keyup';
7
+ eventName[4] = 'mousedown';
8
+ eventName[5] = 'mouseup';
9
+ eventName[6] = 'mousemove';
10
+ eventName[7] = 'mouseover';
11
+ eventName[8] = 'mouseout';
12
+ eventName[12] = 'expose';
13
+ eventName[17] = 'destroy';
14
+ eventName[18] = 'unmap';
15
+ eventName[19] = 'map';
16
+ eventName[20] = 'map_request';
17
+ eventName[21] = 'reparent';
18
+ eventName[22] = 'resize';
19
+ eventName[23] = 'configure_request';
20
20
  // 25 - gravity
21
- eventIdToName[25] = 'resize_request';
22
- eventIdToName[26] = 'circulate';
23
- eventIdToName[27] = 'circulate_request';
24
- eventIdToName[28] = 'property';
25
- eventIdToName[29] = 'selection_clear';
26
- eventIdToName[30] = 'selection_request';
27
- eventIdToName[31] = 'selection';
28
- eventIdToName[33] = 'message';
21
+ eventName[25] = 'resize_request';
22
+ eventName[26] = 'circulate';
23
+ eventName[27] = 'circulate_request';
24
+ eventName[28] = 'property';
25
+ eventName[29] = 'selection_clear';
26
+ eventName[30] = 'selection_request';
27
+ eventName[31] = 'selection';
28
+ eventName[33] = 'message';
29
29
 
30
30
  // event mask you need to select to receive event
31
31
  // 0 = no need to express interest, event is sent regardless of mask
32
- module.exports.mask = {
33
- 'mousemove': x11.eventMask.PointerMotion,
34
- 'mouseover': x11.eventMask.EnterWindow,
35
- 'mouseout': x11.eventMask.LeaveWindow,
36
- 'mousedown': x11.eventMask.ButtonPress,
37
- 'mouseup': x11.eventMask.ButtonRelease,
38
- 'map' : x11.eventMask.StructureNotify,
39
- 'unmap': x11.eventMask.StructureNotify,
40
- 'resize': x11.eventMask.StructureNotify,
41
- 'reparent': x11.eventMask.StructureNotify,
42
- 'keydown': x11.eventMask.KeyPress,
43
- 'expose': x11.eventMask.Exposure,
44
- 'map_request': x11.eventMask.SubstructureRedirect,
45
- 'configure_request': x11.eventMask.SubstructureRedirect,
46
- 'destroy': x11.eventMask.StructureNotify,
47
- 'keyup': x11.eventMask.KeyRelease,
48
- 'property': x11.eventMask.PropertyChange,
49
- 'selection': 0,
50
- 'selection_request': 0,
51
- 'message': 0
32
+ export const mask = {
33
+ mousemove: x11.eventMask.PointerMotion,
34
+ mouseover: x11.eventMask.EnterWindow,
35
+ mouseout: x11.eventMask.LeaveWindow,
36
+ mousedown: x11.eventMask.ButtonPress,
37
+ mouseup: x11.eventMask.ButtonRelease,
38
+ map: x11.eventMask.StructureNotify,
39
+ unmap: x11.eventMask.StructureNotify,
40
+ resize: x11.eventMask.StructureNotify,
41
+ reparent: x11.eventMask.StructureNotify,
42
+ keydown: x11.eventMask.KeyPress,
43
+ expose: x11.eventMask.Exposure,
44
+ map_request: x11.eventMask.SubstructureRedirect,
45
+ configure_request: x11.eventMask.SubstructureRedirect,
46
+ destroy: x11.eventMask.StructureNotify,
47
+ keyup: x11.eventMask.KeyRelease,
48
+ property: x11.eventMask.PropertyChange,
49
+ selection: 0,
50
+ selection_request: 0,
51
+ message: 0
52
52
  };
53
53
 
54
- module.exports.maskCamelCase = {
55
- 'onMouseMove': x11.eventMask.PointerMotion,
56
- 'onMouseOver': x11.eventMask.EnterWindow,
57
- 'onMouseOut': x11.eventMask.LeaveWindow,
58
- 'onMouseDown': x11.eventMask.ButtonPress,
59
- 'onMouseUp': x11.eventMask.ButtonRelease,
60
- 'onMap' : x11.eventMask.StructureNotify,
61
- 'onUnmap': x11.eventMask.StructureNotify,
62
- 'onResize': x11.eventMask.StructureNotify,
63
- 'onReparent': x11.eventMask.StructureNotify,
64
- 'onKeyDown': x11.eventMask.KeyPress,
65
- 'onExpose': x11.eventMask.Exposure,
66
- 'onMapRequest': x11.eventMask.SubstructureRedirect,
67
- 'onConfigureRequest': x11.eventMask.SubstructureRedirect,
68
- 'onDestroy': x11.eventMask.StructureNotify,
69
- 'onKeyUp': x11.eventMask.KeyRelease,
70
- 'onPropertyChange': x11.eventMask.PropertyChange,
71
- // TODO
72
- 'onSelection': 0,
73
- 'onSelectionRequest': 0,
74
- 'onMessage': 0
54
+ // events that are noisy by nature: intermediate occurrences carry no
55
+ // information the final one doesn't, so Window merges bursts into a single
56
+ // event per paced frame (see docs/window.md). 'last' — the newest event
57
+ // wins (older ones ride along in ev.coalesced); 'union' — damage
58
+ // rectangles accumulate (bounding box in ev.x/y/width/height, every rect
59
+ // in ev.rects).
60
+ export const coalesce = {
61
+ mousemove: 'last',
62
+ resize: 'last',
63
+ expose: 'union'
75
64
  };
76
65
 
77
- module.exports.toSnake = {
78
- 'onMouseMove': 'mousemove',
79
- 'onMouseOver': 'mouseover',
80
- 'onMouseOut': 'mouseout',
81
- 'onMouseDown': 'mousedown',
82
- 'onMouseUp': 'mouseup',
83
- 'onMap' : 'map',
84
- 'onUnmap': 'unmap',
85
- 'onResize': 'resize',
86
- 'onReparent': 'reparent',
87
- 'onKeyDown': 'keydown',
88
- 'onExpose': 'expose',
89
- 'onMapRequest': 'map_request',
90
- 'onConfigureRequest': 'configure_request',
91
- 'onDestroy': 'destroy',
92
- 'onKeyUp': 'keyup',
93
- 'onPropertyChange': 'property',
94
- // TODO
95
- 'onSelection': 'selection',
96
- 'onSelectionRequest': 'selection_request',
97
- 'onMessage': 'message'
66
+ export const toSnake = {
67
+ onMouseMove: 'mousemove',
68
+ onMouseOver: 'mouseover',
69
+ onMouseOut: 'mouseout',
70
+ onMouseDown: 'mousedown',
71
+ onMouseUp: 'mouseup',
72
+ onMap: 'map',
73
+ onUnmap: 'unmap',
74
+ onResize: 'resize',
75
+ onReparent: 'reparent',
76
+ onKeyDown: 'keydown',
77
+ onExpose: 'expose',
78
+ onMapRequest: 'map_request',
79
+ onConfigureRequest: 'configure_request',
80
+ onDestroy: 'destroy',
81
+ onKeyUp: 'keyup',
82
+ onPropertyChange: 'property',
83
+ onSelection: 'selection',
84
+ onSelectionRequest: 'selection_request',
85
+ onMessage: 'message'
98
86
  };
87
+
88
+ export const maskCamelCase = Object.fromEntries(
89
+ Object.entries(toSnake).map(([camel, snake]) => [camel, mask[snake]])
90
+ );
91
+
92
+ export default { eventName, mask, maskCamelCase, toSnake, coalesce };