ntk 5.2.0 → 5.4.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/app.js +4 -2
- package/lib/clipboard.js +652 -83
- package/lib/fontconfig.js +99 -12
- package/lib/image.js +13 -19
- package/lib/imagedata.js +388 -0
- package/lib/index.js +32 -2
- package/lib/renderingcontext_2d.js +416 -42
- package/lib/surface.js +118 -0
- package/lib/text/fontmanager.js +27 -4
- package/lib/text/fontsource.js +255 -3
- package/lib/widgets/svgview.js +118 -2
- package/lib/window.js +137 -1
- package/package.json +1 -1
package/lib/window.js
CHANGED
|
@@ -2,6 +2,7 @@ import x11 from 'x11';
|
|
|
2
2
|
|
|
3
3
|
import { safeRelease } from './cleanup.js';
|
|
4
4
|
import Drawable from './drawable.js';
|
|
5
|
+
import { packIcons, unpackIcons } from './imagedata.js';
|
|
5
6
|
import Pixmap from './pixmap.js';
|
|
6
7
|
import * as xevents from './events_map.js';
|
|
7
8
|
import { decodeKey } from './keyboard.js';
|
|
@@ -432,6 +433,11 @@ export default class Window extends Drawable {
|
|
|
432
433
|
if (args.windowType) {
|
|
433
434
|
this.setWindowType(args.windowType);
|
|
434
435
|
}
|
|
436
|
+
if (args.icon) {
|
|
437
|
+
// needs the atom interned first, so unlike the writers above it is not
|
|
438
|
+
// on the wire by the time createWindow returns
|
|
439
|
+
this.setIcon(args.icon).catch((err) => app.options.onXError?.(err));
|
|
440
|
+
}
|
|
435
441
|
// ICCCM/EWMH hints at creation: `hints: { ... }`, the `sizeHints`/
|
|
436
442
|
// `resizable` arguments that predate it, or the hint names on their own
|
|
437
443
|
// at the top level. All of them land in one setHints call so that a
|
|
@@ -505,6 +511,9 @@ export default class Window extends Drawable {
|
|
|
505
511
|
// them in the order they happened (a drag sees the move, then the up)
|
|
506
512
|
this._flushCoalesced();
|
|
507
513
|
this.emit(eventName, ntkev);
|
|
514
|
+
// a WM_DELETE_WINDOW ClientMessage is the window manager *asking*, and
|
|
515
|
+
// 'close' is that question in a form an application can answer
|
|
516
|
+
if (eventName === 'message') this._emitCloseRequest(ntkev);
|
|
508
517
|
// anything drawn during the handlers becomes visible in one blit
|
|
509
518
|
if (this._dirty) this._present();
|
|
510
519
|
});
|
|
@@ -527,6 +536,18 @@ export default class Window extends Drawable {
|
|
|
527
536
|
});
|
|
528
537
|
|
|
529
538
|
this.on('newListener', (name) => {
|
|
539
|
+
// Listening for 'close' is the opt-in. WM_DELETE_WINDOW only reaches a
|
|
540
|
+
// client that advertised it in WM_PROTOCOLS — a window manager kills
|
|
541
|
+
// anyone else outright — and having to know that, on top of decoding a
|
|
542
|
+
// ClientMessage by hand, is the protocol showing through the toolkit.
|
|
543
|
+
if (name === 'close' && !this._closeArmed) {
|
|
544
|
+
this._closeArmed = true;
|
|
545
|
+
// interning WM_PROTOCOLS here as well as in addProtocol: the
|
|
546
|
+
// dispatch path matches against both atoms and node-x11 caches them
|
|
547
|
+
// per connection, so this costs one round trip and no more
|
|
548
|
+
this.atom('WM_PROTOCOLS').catch(() => {});
|
|
549
|
+
this.addProtocol('WM_DELETE_WINDOW').catch((err) => this.app.options.onXError?.(err));
|
|
550
|
+
}
|
|
530
551
|
// 'statechange' is derived from a PropertyNotify, so it needs the atom
|
|
531
552
|
// to compare against as well as the mask the table below selects
|
|
532
553
|
if (name === 'statechange' && !this._netWmStateAtom) {
|
|
@@ -634,7 +655,7 @@ export default class Window extends Drawable {
|
|
|
634
655
|
});
|
|
635
656
|
if (!this._clearGc) {
|
|
636
657
|
// reusable across reallocs: a GC is valid for any drawable of the
|
|
637
|
-
// same screen and depth
|
|
658
|
+
// same screen and depth, so one outlives every backing pixmap
|
|
638
659
|
this._clearGc = this.X.AllocID();
|
|
639
660
|
this.X.CreateGC(this._clearGc, pixmap.id, {
|
|
640
661
|
foreground: this.display.screen[0].white_pixel,
|
|
@@ -1415,6 +1436,9 @@ export default class Window extends Drawable {
|
|
|
1415
1436
|
* atom name: `'WM_DELETE_WINDOW'`, `'WM_TAKE_FOCUS'`, `'_NET_WM_PING'`,
|
|
1416
1437
|
* `'_NET_WM_SYNC_REQUEST'`. Each arrives as a `'message'` event.
|
|
1417
1438
|
*
|
|
1439
|
+
* `WM_DELETE_WINDOW` needs none of this by hand: listening for the
|
|
1440
|
+
* `'close'` event adds the protocol and decodes the message for you.
|
|
1441
|
+
*
|
|
1418
1442
|
* Replaces the whole list. `addProtocol`/`removeProtocol` are the
|
|
1419
1443
|
* accumulating forms, and the ones to reach for: the property is a *set*,
|
|
1420
1444
|
* and a plain write of one atom silently drops the rest.
|
|
@@ -1528,6 +1552,82 @@ export default class Window extends Drawable {
|
|
|
1528
1552
|
});
|
|
1529
1553
|
}
|
|
1530
1554
|
|
|
1555
|
+
/**
|
|
1556
|
+
* EWMH `_NET_WM_ICON` — the icon a taskbar, alt-tab switcher or titlebar
|
|
1557
|
+
* draws for this window.
|
|
1558
|
+
*
|
|
1559
|
+
* wnd.setIcon(await loadImage('icon-48.png'));
|
|
1560
|
+
* wnd.setIcon([icon16, icon32, icon48]);
|
|
1561
|
+
* wnd.setIcon(await ctx.getImageData(0, 0, 64, 64));
|
|
1562
|
+
* wnd.setIcon(null); // remove it
|
|
1563
|
+
*
|
|
1564
|
+
* An image is an ntk [`Image`](images.md), an `ImageData`, or anything
|
|
1565
|
+
* with `{ width, height, data }` where `data` is straight
|
|
1566
|
+
* (non-premultiplied) RGBA — the same contract as the rest of ntk, so
|
|
1567
|
+
* whatever `loadImage()` or `getImageData()` gives you goes straight in.
|
|
1568
|
+
*
|
|
1569
|
+
* Passing several is the useful case: supply the sizes you have and the
|
|
1570
|
+
* window manager picks whichever suits the slot it is filling, instead of
|
|
1571
|
+
* scaling a single one badly. Nothing requires a particular order or a
|
|
1572
|
+
* particular set of sizes; 16, 32 and 48 cover most desktops.
|
|
1573
|
+
*
|
|
1574
|
+
* This is the modern mechanism. `setWmHints({ icon })` writes the ICCCM
|
|
1575
|
+
* pixmap instead, which is 1-bit-or-matched-depth and has no alpha; a
|
|
1576
|
+
* window manager that understands both prefers this one.
|
|
1577
|
+
*
|
|
1578
|
+
* @param {object|object[]|null} images
|
|
1579
|
+
* @returns {Promise<Window>}
|
|
1580
|
+
*/
|
|
1581
|
+
async setIcon(images) {
|
|
1582
|
+
const list = images == null ? [] : Array.isArray(images) ? images : [images];
|
|
1583
|
+
if (!list.length) return this.deleteProperty('_NET_WM_ICON');
|
|
1584
|
+
|
|
1585
|
+
const data = packIcons(list, this.app.display);
|
|
1586
|
+
const property = await this.atom('_NET_WM_ICON');
|
|
1587
|
+
const cardinal = this.X.atoms.CARDINAL;
|
|
1588
|
+
if (this._destroyed) return this;
|
|
1589
|
+
|
|
1590
|
+
// A ChangeProperty carries 24 bytes of header, so a full icon set can
|
|
1591
|
+
// outrun the request limit on a connection created with
|
|
1592
|
+
// `disableBigRequests` — 256x256 alone is 262152 bytes against a 262140
|
|
1593
|
+
// cap. Replace with the first chunk, append the rest.
|
|
1594
|
+
const maxBytes = ((this.app.display.max_request_length ?? 65535) - 6) * 4;
|
|
1595
|
+
safeRelease(this.X, () => {
|
|
1596
|
+
for (let o = 0; o < data.length; o += maxBytes) {
|
|
1597
|
+
this.X.ChangeProperty(
|
|
1598
|
+
o === 0 ? 0 : 2, // Replace, then Append
|
|
1599
|
+
this.id,
|
|
1600
|
+
property,
|
|
1601
|
+
cardinal,
|
|
1602
|
+
32,
|
|
1603
|
+
data.subarray(o, Math.min(o + maxBytes, data.length))
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
});
|
|
1607
|
+
return this;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
/**
|
|
1611
|
+
* Read `_NET_WM_ICON` back — every size the window advertises, largest
|
|
1612
|
+
* last or in whatever order its client wrote them, as `ImageData`.
|
|
1613
|
+
*
|
|
1614
|
+
* Useful on your own window, and the point of it on someone else's: a
|
|
1615
|
+
* window manager drawing titlebars reads this off each client it frames.
|
|
1616
|
+
* Property bytes from another client are not to be trusted, so a
|
|
1617
|
+
* truncated or nonsensical run yields the images that parsed cleanly
|
|
1618
|
+
* rather than throwing.
|
|
1619
|
+
*
|
|
1620
|
+
* Resolves to `null` when the window has no icon.
|
|
1621
|
+
*
|
|
1622
|
+
* @returns {Promise<ImageData[]|null>}
|
|
1623
|
+
*/
|
|
1624
|
+
async getIcon() {
|
|
1625
|
+
const prop = await this.getProperty('_NET_WM_ICON');
|
|
1626
|
+
if (!prop || !prop.data.length) return null;
|
|
1627
|
+
const icons = unpackIcons(prop.data, this.app.display);
|
|
1628
|
+
return icons.length ? icons : null;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1531
1631
|
/** os.hostname(), looked up once, and absent in a browser bundle. */
|
|
1532
1632
|
static _hostname() {
|
|
1533
1633
|
if (Window._cachedHostname === undefined) {
|
|
@@ -2363,6 +2463,42 @@ export default class Window extends Drawable {
|
|
|
2363
2463
|
return this;
|
|
2364
2464
|
}
|
|
2365
2465
|
|
|
2466
|
+
/**
|
|
2467
|
+
* Turn a WM_DELETE_WINDOW ClientMessage into the `close` event.
|
|
2468
|
+
*
|
|
2469
|
+
* Only fires when something is listening, so a window that handles the
|
|
2470
|
+
* raw `message` event itself — the only way to do this before `close`
|
|
2471
|
+
* existed — keeps behaving exactly as it did, rather than suddenly
|
|
2472
|
+
* acquiring a second handler that destroys it.
|
|
2473
|
+
*/
|
|
2474
|
+
_emitCloseRequest(ev) {
|
|
2475
|
+
if (ev.format !== 32 || !this.listenerCount('close')) return;
|
|
2476
|
+
// node-x11 caches interned atoms per connection, so these are the ids
|
|
2477
|
+
// the 'close' listener's addProtocol already interned
|
|
2478
|
+
const X = this.X;
|
|
2479
|
+
if (!X.atoms.WM_PROTOCOLS || ev.message_type !== X.atoms.WM_PROTOCOLS) return;
|
|
2480
|
+
if (!X.atoms.WM_DELETE_WINDOW || ev.data?.[0] !== X.atoms.WM_DELETE_WINDOW) return;
|
|
2481
|
+
|
|
2482
|
+
let prevented = false;
|
|
2483
|
+
this.emit('close', {
|
|
2484
|
+
name: 'close',
|
|
2485
|
+
window: this,
|
|
2486
|
+
target: this,
|
|
2487
|
+
// the timestamp the window manager stamped the request with, for
|
|
2488
|
+
// passing back to setInputFocus and friends
|
|
2489
|
+
time: ev.data[1],
|
|
2490
|
+
preventDefault() {
|
|
2491
|
+
prevented = true;
|
|
2492
|
+
},
|
|
2493
|
+
get defaultPrevented() {
|
|
2494
|
+
return prevented;
|
|
2495
|
+
}
|
|
2496
|
+
});
|
|
2497
|
+
// the default action, and the reason preventDefault has to be called
|
|
2498
|
+
// synchronously: there is no point at which it could be awaited
|
|
2499
|
+
if (!prevented && !this._destroyed) this.destroy();
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2366
2502
|
destroy() {
|
|
2367
2503
|
this._destroyed = true;
|
|
2368
2504
|
this._forget();
|