ntk 8.2.0 → 8.3.1
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 +346 -7
- package/lib/cleanup.js +23 -7
- package/lib/drawable.js +10 -0
- package/lib/events_map.js +100 -1
- package/lib/glyphdirectory.js +275 -0
- package/lib/glyphdwire.js +142 -0
- package/lib/glyphset.js +32 -5
- package/lib/index.js +25 -2
- package/lib/pictformat.js +196 -0
- package/lib/pixmap.js +166 -7
- package/lib/region.js +148 -0
- package/lib/renderingcontext_2d.js +717 -235
- package/lib/shapeglyphs.js +94 -26
- package/lib/sharedglyphs.js +433 -0
- package/lib/text/glyphs.js +143 -26
- package/lib/widgets/svgview.js +7 -5
- package/lib/window.js +308 -40
- package/lib/xembed.js +3 -1
- package/package.json +2 -2
package/lib/window.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import x11 from 'x11';
|
|
2
2
|
|
|
3
3
|
import { builtin } from './builtin.js';
|
|
4
|
-
import { safeRelease } from './cleanup.js';
|
|
4
|
+
import { connectionGone, safeRelease } from './cleanup.js';
|
|
5
5
|
import Drawable from './drawable.js';
|
|
6
6
|
import { packIcons, unpackIcons } from './imagedata.js';
|
|
7
7
|
import Pixmap from './pixmap.js';
|
|
@@ -332,6 +332,24 @@ function warnHint(key, message) {
|
|
|
332
332
|
console.warn(`ntk: ${message} Further occurrences are not reported.`);
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
+
/**
|
|
336
|
+
* A GetGeometry reply in ntk's spelling. node-x11 calls the position `xPos`/
|
|
337
|
+
* `yPos` and the reply's root window `windowid`, neither of which is what X
|
|
338
|
+
* calls them; the `root` here is the root of the screen the drawable is on,
|
|
339
|
+
* not this window's parent.
|
|
340
|
+
*/
|
|
341
|
+
function unpackGeometry(res) {
|
|
342
|
+
return {
|
|
343
|
+
x: res.xPos,
|
|
344
|
+
y: res.yPos,
|
|
345
|
+
width: res.width,
|
|
346
|
+
height: res.height,
|
|
347
|
+
depth: res.depth,
|
|
348
|
+
borderWidth: res.borderWidth,
|
|
349
|
+
root: res.windowid
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
335
353
|
/** A Window, a Pixmap, or a bare XID — all of these name a server resource. */
|
|
336
354
|
function resourceId(value) {
|
|
337
355
|
return typeof value === 'number' ? value : (value?.id ?? 0);
|
|
@@ -404,18 +422,37 @@ export default class Window extends Drawable {
|
|
|
404
422
|
// the whole property without dropping what an earlier one set
|
|
405
423
|
this._sizeHints = null;
|
|
406
424
|
this._wmHints = null;
|
|
425
|
+
// `wnd.ready` (see the getter): resolved as soon as this wrapper knows
|
|
426
|
+
// its geometry — at the end of the constructor for a window ntk created,
|
|
427
|
+
// when GetGeometry replies for one adopted by id
|
|
407
428
|
this._readyPromiseResolve = null;
|
|
408
429
|
this._readyPromise = new Promise((resolve) => {
|
|
409
430
|
this._readyPromiseResolve = resolve;
|
|
410
431
|
});
|
|
432
|
+
// constructor round trips still outstanding, for an adopted window
|
|
433
|
+
this._readyPending = 0;
|
|
411
434
|
|
|
412
435
|
const parentId = args.parent ? args.parent.id : app.display.screen[0].root;
|
|
413
436
|
|
|
414
437
|
// double buffering (see docs/window.md): windows we created get a
|
|
415
438
|
// backing pixmap when a 2d context is requested, unless opted out
|
|
416
439
|
this._foreign = !!args.id;
|
|
440
|
+
// Adopting a window is not the same as "another client paints these
|
|
441
|
+
// pixels", though it is the safe default reading of one. The Composite
|
|
442
|
+
// overlay window and a window handed over by an embedding host (XEmbed's
|
|
443
|
+
// plug side, an `--wid` handoff) are ours completely — they are foreign
|
|
444
|
+
// only in the sense that `createWindow` did not make them, and they are
|
|
445
|
+
// exactly the windows that most want an unflickering double buffer and a
|
|
446
|
+
// vblank-paced blit. Saying so is `{ id, backingStore: true }` or
|
|
447
|
+
// `{ id, present: true }`; from there such a window behaves like any
|
|
448
|
+
// other window ntk owns (issue #294).
|
|
449
|
+
this._ownsPixels = !this._foreign || args.backingStore === true || args.present === true;
|
|
417
450
|
this._backingOptOut = args.backingStore === false;
|
|
418
451
|
this._backing = null;
|
|
452
|
+
// an adopted window has no geometry, depth or visual to build a backing
|
|
453
|
+
// pixmap from until its constructor's questions reply — see
|
|
454
|
+
// _enableBackingStoreWhenReady
|
|
455
|
+
this._backingScheduled = false;
|
|
419
456
|
// What the backing store clears to, and the window attribute it mirrors.
|
|
420
457
|
// Undefined means "nothing was asked for" — see _clearColour.
|
|
421
458
|
this._clearPixel = args.backgroundPixel;
|
|
@@ -528,11 +565,19 @@ export default class Window extends Drawable {
|
|
|
528
565
|
for (const name of forwardedXAttributes) {
|
|
529
566
|
if (args[name] !== undefined) values[name] = args[name];
|
|
530
567
|
}
|
|
568
|
+
// what setCursor compares against, so setting the cursor a window was
|
|
569
|
+
// created with is free rather than a redundant round trip
|
|
570
|
+
this._currentCursor = values.cursor;
|
|
531
571
|
// a window on a non-default visual (GLX, ARGB) also needs its own
|
|
532
572
|
// colormap and an explicit border pixel: inheriting either from a
|
|
533
573
|
// parent of a different depth is a BadMatch (X11 CreateWindow)
|
|
534
574
|
this.visual = args.visual ?? 0;
|
|
535
575
|
this.depth = args.depth ?? 0;
|
|
576
|
+
// `visual` is the CreateWindow argument, where 0 means CopyFromParent;
|
|
577
|
+
// `visualId` is the visual the pixels are actually in, which is what a
|
|
578
|
+
// picture format is picked from (issue #295). CopyFromParent is only
|
|
579
|
+
// ever the parent's, and the default parent is the root.
|
|
580
|
+
this.visualId = this.visual || args.parent?.visualId || app.display.screen[0].root_visual;
|
|
536
581
|
if (this.visual) {
|
|
537
582
|
if (values.colormap === undefined) {
|
|
538
583
|
this._ownedColormap = app.createColormap(this.visual);
|
|
@@ -549,24 +594,46 @@ export default class Window extends Drawable {
|
|
|
549
594
|
this.id = args.id;
|
|
550
595
|
this.eventMask = 0;
|
|
551
596
|
this.visual = 0;
|
|
597
|
+
// nothing is known about another client's window until it answers
|
|
598
|
+
this.visualId = 0;
|
|
552
599
|
this.depth = 0;
|
|
553
600
|
// an adopted window's geometry is not known until GetGeometry replies
|
|
554
601
|
this._deliveredGeom = null;
|
|
555
|
-
//
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
602
|
+
// Two questions, because a drawable's pixels take both to describe:
|
|
603
|
+
// GetGeometry for width, height and depth, GetWindowAttributes for the
|
|
604
|
+
// visual — depth alone does not name a picture format, and the windows
|
|
605
|
+
// whose format was picked wrongest are exactly these, the ones another
|
|
606
|
+
// client chose the visual for (issue #295). They go out in the same
|
|
607
|
+
// batch, so the pair costs one round trip, not two, and `ready` is the
|
|
608
|
+
// wait for both.
|
|
609
|
+
//
|
|
610
|
+
// Nothing is asked of a connection that is going away: node-x11 throws
|
|
611
|
+
// synchronously at a request once close() has begun, and adoption
|
|
612
|
+
// happens from places with no caller to catch — a routed child-event,
|
|
613
|
+
// XEmbed, the shared glyph cache all build windows from inside the X
|
|
614
|
+
// event dispatch (issue #321). There is nothing to learn there either,
|
|
615
|
+
// so `ready` settles with the geometry unknown, the same outcome as a
|
|
616
|
+
// window destroyed before its reply landed (see the getter).
|
|
617
|
+
this._readyPending = connectionGone(X) ? 0 : 2;
|
|
618
|
+
if (this._readyPending) {
|
|
619
|
+
X.GetGeometry(this.id, (err, res) => {
|
|
620
|
+
// A window that was destroyed between the id reaching us and this
|
|
621
|
+
// request reaching the server answers BadWindow, and there is
|
|
622
|
+
// nothing to record. `ready` still resolves: adopting a window that
|
|
623
|
+
// has since gone is ordinary for a window manager, and a wait that
|
|
624
|
+
// never ends is worse than one that ends with width undefined.
|
|
625
|
+
this._readyPending--;
|
|
626
|
+
if (err) this._settleReady();
|
|
627
|
+
else this._applyGeometry(unpackGeometry(res));
|
|
628
|
+
});
|
|
629
|
+
X.GetWindowAttributes(this.id, (err, attrs) => {
|
|
630
|
+
this._readyPending--;
|
|
631
|
+
if (!err) this.visualId = attrs.visual;
|
|
632
|
+
this._settleReady();
|
|
633
|
+
});
|
|
634
|
+
} else {
|
|
635
|
+
this._settleReady();
|
|
636
|
+
}
|
|
570
637
|
}
|
|
571
638
|
|
|
572
639
|
Window._cacheFor(app).set(this.id, this);
|
|
@@ -630,11 +697,21 @@ export default class Window extends Drawable {
|
|
|
630
697
|
// region and an event selection it never uses. `present: true` is still
|
|
631
698
|
// honoured eagerly, for a caller who wants it up before the first paint.
|
|
632
699
|
this._presentOptOut = args.present === false;
|
|
633
|
-
if (args.present
|
|
700
|
+
if (args.present === true) {
|
|
634
701
|
// fire and forget: until the extensions answer, blits use CopyArea,
|
|
635
|
-
// which is what they would have done anyway
|
|
702
|
+
// which is what they would have done anyway. Present needs nothing of
|
|
703
|
+
// the window but its id, so an adopted one can have it up before its
|
|
704
|
+
// geometry has even replied.
|
|
636
705
|
this.enablePresent().catch((err) => this.app.options?.onXError?.(err));
|
|
637
706
|
}
|
|
707
|
+
// A window adopted with ownership declared starts its backing store now
|
|
708
|
+
// rather than waiting for the first getContext('2d'): the allocation
|
|
709
|
+
// needs the constructor's replies anyway, so starting it here means it
|
|
710
|
+
// is normally already there by the time the caller's `await wnd.ready`
|
|
711
|
+
// returns and it draws.
|
|
712
|
+
if (this._foreign && this._ownsPixels && !this._backingOptOut) {
|
|
713
|
+
this._enableBackingStoreWhenReady();
|
|
714
|
+
}
|
|
638
715
|
|
|
639
716
|
X.event_consumers[this.id] = this;
|
|
640
717
|
this.on('event', (ev) => {
|
|
@@ -714,15 +791,24 @@ export default class Window extends Drawable {
|
|
|
714
791
|
this.on('child-event', (ev) => {
|
|
715
792
|
const eventName = xevents.eventName[ev.type];
|
|
716
793
|
if (!eventName) return;
|
|
794
|
+
// Events buffered before `close()` keep being dispatched after it, and
|
|
795
|
+
// a window this client already saw destroyed has no substructure left
|
|
796
|
+
// to report. Neither is worth building a child for — and the questions
|
|
797
|
+
// the constructor would ask on the way out are what used to throw into
|
|
798
|
+
// the dispatcher (issue #321).
|
|
799
|
+
if (this._destroyed || connectionGone(X)) return;
|
|
717
800
|
const child = new Window(app, { id: ev.wid });
|
|
718
801
|
const ntkev = { ...ev, parent: this, window: child, target: child };
|
|
719
802
|
// wait until we know that we track correct x,y,w,h values
|
|
720
|
-
child.
|
|
803
|
+
child.ready.then(() => {
|
|
721
804
|
this.emit(eventName, ntkev);
|
|
722
805
|
});
|
|
723
806
|
});
|
|
724
807
|
|
|
725
808
|
this.on('newListener', (name) => {
|
|
809
|
+
// one of ntk's own handlers going on, not a caller asking for an
|
|
810
|
+
// event: nothing to arm and nothing to select — see _listenInternal
|
|
811
|
+
if (this._internalListen) return;
|
|
726
812
|
// Listening for 'close' is the opt-in. WM_DELETE_WINDOW only reaches a
|
|
727
813
|
// client that advertised it in WM_PROTOCOLS — a window manager kills
|
|
728
814
|
// anyone else outright — and having to know that, on top of decoding a
|
|
@@ -749,6 +835,13 @@ export default class Window extends Drawable {
|
|
|
749
835
|
const eventMask = xevents.mask[name];
|
|
750
836
|
if (!eventMask) return;
|
|
751
837
|
if ((eventMask & this.eventMask) === 0) {
|
|
838
|
+
// A connection on its way out can select nothing, and node-x11 throws
|
|
839
|
+
// synchronously at a request from then on. This hook runs from
|
|
840
|
+
// `.on()` — including the calls made while adopting a window from
|
|
841
|
+
// inside the X event dispatch, where there is no caller to catch it
|
|
842
|
+
// (issue #321). Before the mask is touched, so it goes on saying what
|
|
843
|
+
// this connection holds.
|
|
844
|
+
if (connectionGone(X)) return;
|
|
752
845
|
this.eventMask |= eventMask;
|
|
753
846
|
// the selection can legitimately fail — SubstructureRedirect is
|
|
754
847
|
// one-client-only, so `on('map_request')` is how you find out
|
|
@@ -756,14 +849,29 @@ export default class Window extends Drawable {
|
|
|
756
849
|
// error hook rather than dropping it; selectInput() is the
|
|
757
850
|
// explicit form that hands the error straight back.
|
|
758
851
|
X.ChangeWindowAttributes(this.id, { eventMask: this.eventMask }, (err) => {
|
|
759
|
-
if (err)
|
|
852
|
+
if (!err) return;
|
|
853
|
+
// the request failed as a whole, so the server kept the mask it
|
|
854
|
+
// had: drop the bits again rather than leave `eventMask` claiming
|
|
855
|
+
// a selection this connection does not hold. selectInput() trusts
|
|
856
|
+
// it to decide whether a write can be skipped.
|
|
857
|
+
this.eventMask &= ~eventMask;
|
|
858
|
+
this.app.options.onXError?.(err);
|
|
760
859
|
});
|
|
761
860
|
}
|
|
762
861
|
});
|
|
763
862
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
863
|
+
// ntk's own bookkeeping listens for the same events an app does, and the
|
|
864
|
+
// hook above reads a listener as a caller expressing interest and selects
|
|
865
|
+
// for it. For a window ntk created that is free — StructureNotify is in
|
|
866
|
+
// the CreateWindow value list already — but for an adopted one the
|
|
867
|
+
// tracked mask starts at zero and the write is absolute, so it would
|
|
868
|
+
// replace whatever this connection had selected on a window another
|
|
869
|
+
// client owns (issue #322). Subscribe past the hook: adopting a window
|
|
870
|
+
// costs no requests and changes no server-side state, and a caller that
|
|
871
|
+
// wants these events still asks for them, with on('map') or selectInput.
|
|
872
|
+
this._listenInternal('map', () => (this._mapped = true));
|
|
873
|
+
this._listenInternal('unmap', () => (this._mapped = false));
|
|
874
|
+
this._listenInternal('destroy', () => {
|
|
767
875
|
this._destroyed = true;
|
|
768
876
|
this._forget();
|
|
769
877
|
this._teardownFrame(false); // the server has already taken the window
|
|
@@ -773,10 +881,100 @@ export default class Window extends Drawable {
|
|
|
773
881
|
});
|
|
774
882
|
|
|
775
883
|
if (typeof this.width !== 'undefined') {
|
|
776
|
-
this.
|
|
884
|
+
this._settleReady();
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Attach one of ntk's own handlers without the `newListener` hook reading
|
|
890
|
+
* it as a caller asking for the event. The hook's job is to turn expressed
|
|
891
|
+
* interest into a server-side selection; ntk's internal bookkeeping
|
|
892
|
+
* expresses none — it only wants the events that arrive anyway — and on an
|
|
893
|
+
* adopted window a selection nobody asked for overwrites another
|
|
894
|
+
* subsystem's (issue #322). The flag is read synchronously by the hook,
|
|
895
|
+
* which EventEmitter emits before the listener is stored.
|
|
896
|
+
*/
|
|
897
|
+
_listenInternal(name, fn) {
|
|
898
|
+
this._internalListen = true;
|
|
899
|
+
try {
|
|
900
|
+
this.on(name, fn);
|
|
901
|
+
} finally {
|
|
902
|
+
this._internalListen = false;
|
|
777
903
|
}
|
|
778
904
|
}
|
|
779
905
|
|
|
906
|
+
/**
|
|
907
|
+
* Resolves with this window once its geometry is known — immediately for a
|
|
908
|
+
* window ntk created (it already knows what it asked for), when the
|
|
909
|
+
* GetGeometry sent by the constructor replies for one adopted by id.
|
|
910
|
+
*
|
|
911
|
+
* Adoption is the case that needs it: until the reply lands, `width`,
|
|
912
|
+
* `height`, `x` and `y` are `undefined` and `depth` is `0`, so anything
|
|
913
|
+
* that picks a pixel format from the depth — `getContext('2d')`,
|
|
914
|
+
* `createPattern` — would pick the wrong one. Awaiting is safe
|
|
915
|
+
* unconditionally, so code that takes windows from either source need not
|
|
916
|
+
* ask which it has:
|
|
917
|
+
*
|
|
918
|
+
* const wnd = new Window(app, { id: ev.wid });
|
|
919
|
+
* await wnd.ready;
|
|
920
|
+
* const ctx = wnd.getContext('2d');
|
|
921
|
+
*
|
|
922
|
+
* The promise never rejects. A window destroyed before the reply arrives
|
|
923
|
+
* resolves all the same, with `width` still `undefined` — the request that
|
|
924
|
+
* hits the gone window is the one that reports it.
|
|
925
|
+
*/
|
|
926
|
+
get ready() {
|
|
927
|
+
return this._readyPromise;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Ask the server where this window is now — `{ x, y, width, height, depth,
|
|
932
|
+
* borderWidth, root }`, with `x`/`y` relative to the parent, as X reports
|
|
933
|
+
* them.
|
|
934
|
+
*
|
|
935
|
+
* `wnd.x`/`y`/`width`/`height`/`depth` are kept current from the event
|
|
936
|
+
* stream and are the cheap answer; this is the round trip for the cases
|
|
937
|
+
* the events cannot cover — a window nobody selected StructureNotify on, a
|
|
938
|
+
* window ntk created with the default `depth: 0` (CopyFromParent, whose
|
|
939
|
+
* real depth only the server knows), or simply the state at a point in
|
|
940
|
+
* time. The reply is written back to those properties, and resolves
|
|
941
|
+
* `ready` if it was still pending.
|
|
942
|
+
*/
|
|
943
|
+
getGeometry() {
|
|
944
|
+
return new Promise((resolve, reject) => {
|
|
945
|
+
this.X.GetGeometry(this.id, (err, res) => {
|
|
946
|
+
if (err) return reject(err);
|
|
947
|
+
const geometry = unpackGeometry(res);
|
|
948
|
+
this._applyGeometry(geometry);
|
|
949
|
+
resolve(geometry);
|
|
950
|
+
});
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/** Record a GetGeometry reply, and let anything waiting on it go. */
|
|
955
|
+
_applyGeometry({ x, y, width, height, depth }) {
|
|
956
|
+
this.x = x;
|
|
957
|
+
this.y = y;
|
|
958
|
+
this.width = width;
|
|
959
|
+
this.height = height;
|
|
960
|
+
this.depth = depth;
|
|
961
|
+
// Only where nothing is known yet: on an adopted window a 'resize'
|
|
962
|
+
// reports both moved and resized until there is a baseline to compare
|
|
963
|
+
// against (see _tagResize), and the event stream may already have set
|
|
964
|
+
// one — that is the delivered geometry, and this reply is not.
|
|
965
|
+
this._deliveredGeom ??= { x, y, width, height };
|
|
966
|
+
this._settleReady();
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Let anything waiting on `ready` go, once the questions the constructor
|
|
971
|
+
* asked have all been answered. A window ntk created asked none.
|
|
972
|
+
*/
|
|
973
|
+
_settleReady() {
|
|
974
|
+
if (this._readyPending > 0) return;
|
|
975
|
+
this._readyPromiseResolve(this);
|
|
976
|
+
}
|
|
977
|
+
|
|
780
978
|
_forget() {
|
|
781
979
|
Window._cacheFor(this.app).delete(this.id);
|
|
782
980
|
delete this.X.event_consumers[this.id];
|
|
@@ -790,14 +988,43 @@ export default class Window extends Drawable {
|
|
|
790
988
|
* an 'expose' (and 'draw') event is emitted only when a real redraw is
|
|
791
989
|
* needed (first paint, resize). Opt out with
|
|
792
990
|
* `createWindow({ backingStore: false })`.
|
|
991
|
+
*
|
|
992
|
+
* A window adopted by id is left alone by default — another client's
|
|
993
|
+
* pixels are not ntk's to buffer — and opts in with
|
|
994
|
+
* `createWindow({ id, backingStore: true })` (see `_ownsPixels`).
|
|
793
995
|
*/
|
|
794
996
|
getContext(name, ...args) {
|
|
795
|
-
if (name === '2d' && !this._backing && !this._backingOptOut &&
|
|
796
|
-
this.
|
|
997
|
+
if (name === '2d' && !this._backing && !this._backingOptOut && this._ownsPixels) {
|
|
998
|
+
if (this._foreign) this._enableBackingStoreWhenReady();
|
|
999
|
+
else this._enableBackingStore();
|
|
797
1000
|
}
|
|
798
1001
|
return super.getContext(name, ...args);
|
|
799
1002
|
}
|
|
800
1003
|
|
|
1004
|
+
/**
|
|
1005
|
+
* The backing store for an adopted window, once there is something to build
|
|
1006
|
+
* it out of.
|
|
1007
|
+
*
|
|
1008
|
+
* A pixmap needs a width, a height, a depth and a visual, and an adopted
|
|
1009
|
+
* window knows none of them until `GetGeometry` and `GetWindowAttributes`
|
|
1010
|
+
* reply — so the allocation waits on `ready`. A 2d context taken in the
|
|
1011
|
+
* meantime draws straight to the window and re-binds itself to the pixmap
|
|
1012
|
+
* when it appears (the `_backing` event), which is the same path a resize
|
|
1013
|
+
* reallocation takes.
|
|
1014
|
+
*/
|
|
1015
|
+
_enableBackingStoreWhenReady() {
|
|
1016
|
+
if (this._backingScheduled) return;
|
|
1017
|
+
this._backingScheduled = true;
|
|
1018
|
+
this.ready.then(() => {
|
|
1019
|
+
// A window destroyed before the replies arrived still settles `ready`,
|
|
1020
|
+
// with its geometry never filled in — there is no pixmap to make for
|
|
1021
|
+
// one, and CreatePixmap with an undefined size is not the way to find
|
|
1022
|
+
// that out.
|
|
1023
|
+
if (this._destroyed || this._backing || !this.width || !this.height) return;
|
|
1024
|
+
this._enableBackingStore();
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
|
|
801
1028
|
_enableBackingStore() {
|
|
802
1029
|
this._dirty = false;
|
|
803
1030
|
this._backingValid = false;
|
|
@@ -805,7 +1032,7 @@ export default class Window extends Drawable {
|
|
|
805
1032
|
// Now there is a pixmap to present. Inert where the extensions are
|
|
806
1033
|
// missing, so this costs a window on such a server two extension queries
|
|
807
1034
|
// that node-x11 answers from its cache after the first.
|
|
808
|
-
if (!this._presentOptOut &&
|
|
1035
|
+
if (!this._presentOptOut && this._ownsPixels) {
|
|
809
1036
|
this.enablePresent().catch((err) => this.app.options?.onXError?.(err));
|
|
810
1037
|
}
|
|
811
1038
|
|
|
@@ -817,7 +1044,8 @@ export default class Window extends Drawable {
|
|
|
817
1044
|
// server sends them even if user code only listens to 'draw'
|
|
818
1045
|
if (!(this.eventMask & x11.eventMask.Exposure)) {
|
|
819
1046
|
this.eventMask |= x11.eventMask.Exposure;
|
|
820
|
-
|
|
1047
|
+
// no callback: it would only buy node-x11's void-sync round trip, see setCursor
|
|
1048
|
+
this.X.ChangeWindowAttributes(this.id, { eventMask: this.eventMask });
|
|
821
1049
|
}
|
|
822
1050
|
|
|
823
1051
|
// a pure move is a ConfigureNotify too, and reallocating a backing
|
|
@@ -863,8 +1091,11 @@ export default class Window extends Drawable {
|
|
|
863
1091
|
this._clearPixel = pixel;
|
|
864
1092
|
if (this._destroyed) return this;
|
|
865
1093
|
if (this._clearGc) this.X.ChangeGC(this._clearGc, { foreground: pixel });
|
|
866
|
-
|
|
867
|
-
|
|
1094
|
+
// The window attribute half is only ours to write on a window whose
|
|
1095
|
+
// pixels are ours — an adopted one has to have said so (see _ownsPixels).
|
|
1096
|
+
if (this._ownsPixels) {
|
|
1097
|
+
// no callback: it would only buy node-x11's void-sync round trip, see setCursor
|
|
1098
|
+
this.X.ChangeWindowAttributes(this.id, { backgroundPixel: pixel });
|
|
868
1099
|
}
|
|
869
1100
|
const backing = this._backing;
|
|
870
1101
|
if (backing && this._clearGc) {
|
|
@@ -896,7 +1127,10 @@ export default class Window extends Drawable {
|
|
|
896
1127
|
parent: this,
|
|
897
1128
|
width: newW,
|
|
898
1129
|
height: newH,
|
|
899
|
-
depth
|
|
1130
|
+
depth,
|
|
1131
|
+
// it holds this window's pixels, so it is read through this window's
|
|
1132
|
+
// visual — a pixmap has none of its own (issue #295)
|
|
1133
|
+
visual: this.visualId
|
|
900
1134
|
});
|
|
901
1135
|
if (!this._clearGc) {
|
|
902
1136
|
this._clearGc = this.X.AllocID();
|
|
@@ -925,7 +1159,11 @@ export default class Window extends Drawable {
|
|
|
925
1159
|
|
|
926
1160
|
_handleExpose(ev) {
|
|
927
1161
|
if (this._backingValid) {
|
|
928
|
-
|
|
1162
|
+
// an expose can arrive after the connection started closing, and this
|
|
1163
|
+
// runs inside the event dispatch where a throw has no caller (#321)
|
|
1164
|
+
safeRelease(this.X, () => {
|
|
1165
|
+
this.X.CopyArea(this._backing.id, this.id, this._presentGc, ev.x, ev.y, ev.x, ev.y, ev.width, ev.height);
|
|
1166
|
+
});
|
|
929
1167
|
} else {
|
|
930
1168
|
this._requestRedraw();
|
|
931
1169
|
}
|
|
@@ -2997,9 +3235,19 @@ export default class Window extends Drawable {
|
|
|
2997
3235
|
* pointer stays visible
|
|
2998
3236
|
*/
|
|
2999
3237
|
setCursor(nameOrShape) {
|
|
3238
|
+
// resolved first: an unknown name has to throw whether or not the memo
|
|
3239
|
+
// below short-circuits the request
|
|
3000
3240
|
const cursor = nameOrShape == null ? 0 : this.app.cursors.get(nameOrShape);
|
|
3241
|
+
if (cursor === this._currentCursor) return this;
|
|
3242
|
+
this._currentCursor = cursor;
|
|
3001
3243
|
safeRelease(this.X, () => {
|
|
3002
|
-
|
|
3244
|
+
// no callback, deliberately: node-x11 guarantees a callback on a *void*
|
|
3245
|
+
// request eventually fires, and buys that by injecting a GetInputFocus
|
|
3246
|
+
// round trip when nothing else in the tick expects a reply (node-x11
|
|
3247
|
+
// issue #85). A callback that ignores its argument costs that round trip
|
|
3248
|
+
// per cursor change and buys nothing — an X error reaches
|
|
3249
|
+
// `client.emit('error')`, and so app's onXError hook, either way.
|
|
3250
|
+
this.X.ChangeWindowAttributes(this.id, { cursor });
|
|
3003
3251
|
});
|
|
3004
3252
|
return this;
|
|
3005
3253
|
}
|
|
@@ -3012,7 +3260,8 @@ export default class Window extends Drawable {
|
|
|
3012
3260
|
} else {
|
|
3013
3261
|
return this;
|
|
3014
3262
|
}
|
|
3015
|
-
|
|
3263
|
+
// no callback: it would only buy node-x11's void-sync round trip, see setCursor
|
|
3264
|
+
this.X.ChangeWindowAttributes(this.id, { eventMask: this.eventMask });
|
|
3016
3265
|
return this;
|
|
3017
3266
|
}
|
|
3018
3267
|
|
|
@@ -3307,7 +3556,13 @@ export default class Window extends Drawable {
|
|
|
3307
3556
|
getAttributes() {
|
|
3308
3557
|
return new Promise((resolve, reject) => {
|
|
3309
3558
|
safeRelease(this.X, () =>
|
|
3310
|
-
this.X.GetWindowAttributes(this.id, (err, attrs) =>
|
|
3559
|
+
this.X.GetWindowAttributes(this.id, (err, attrs) => {
|
|
3560
|
+
if (err) return reject(err);
|
|
3561
|
+
// the visual names the picture format (see `visualId`), and this
|
|
3562
|
+
// reply is the only place the server ever states it
|
|
3563
|
+
this.visualId = attrs.visual;
|
|
3564
|
+
resolve(attrs);
|
|
3565
|
+
})
|
|
3311
3566
|
);
|
|
3312
3567
|
});
|
|
3313
3568
|
}
|
|
@@ -3327,13 +3582,26 @@ export default class Window extends Drawable {
|
|
|
3327
3582
|
* either makes you the window manager or rejects with BadAccess because
|
|
3328
3583
|
* something else already is. The mask is OR-ed into whatever handlers
|
|
3329
3584
|
* have already asked for.
|
|
3585
|
+
*
|
|
3586
|
+
* A mask this connection already holds is a request that would change
|
|
3587
|
+
* nothing, so it resolves without one — `eventMask` is what this
|
|
3588
|
+
* connection selected and nothing else adds to it behind the caller's
|
|
3589
|
+
* back. It resolves rather than returning undefined because the promise is
|
|
3590
|
+
* the whole interface: an awaited no-op still has to mean "you have it".
|
|
3330
3591
|
*/
|
|
3331
3592
|
selectInput(mask) {
|
|
3332
|
-
this.eventMask
|
|
3593
|
+
const added = mask & ~this.eventMask;
|
|
3594
|
+
if (added === 0) return Promise.resolve(this);
|
|
3595
|
+
this.eventMask |= added;
|
|
3333
3596
|
return new Promise((resolve, reject) => {
|
|
3334
|
-
this.X.ChangeWindowAttributes(this.id, { eventMask: this.eventMask }, (err) =>
|
|
3335
|
-
|
|
3336
|
-
|
|
3597
|
+
this.X.ChangeWindowAttributes(this.id, { eventMask: this.eventMask }, (err) => {
|
|
3598
|
+
if (!err) return resolve(this);
|
|
3599
|
+
// a refused write left the server's mask as it was — give back the
|
|
3600
|
+
// bits, or the next call would find them held and skip a request
|
|
3601
|
+
// that has not happened yet
|
|
3602
|
+
this.eventMask &= ~added;
|
|
3603
|
+
reject(err);
|
|
3604
|
+
});
|
|
3337
3605
|
});
|
|
3338
3606
|
}
|
|
3339
3607
|
|
|
@@ -3515,7 +3783,7 @@ export default class Window extends Drawable {
|
|
|
3515
3783
|
// there can be multiple screens (and roots)
|
|
3516
3784
|
const root = new Window(app, { id: tree.root });
|
|
3517
3785
|
const wrappers = [...children, root, ...(parent ? [parent] : [])];
|
|
3518
|
-
Promise.all(wrappers.map((w) => w.
|
|
3786
|
+
Promise.all(wrappers.map((w) => w.ready)).then(() => {
|
|
3519
3787
|
callback(null, { parent, root, children });
|
|
3520
3788
|
});
|
|
3521
3789
|
});
|
package/lib/xembed.js
CHANGED
|
@@ -559,7 +559,9 @@ export class XEmbedSocket extends EventEmitter {
|
|
|
559
559
|
// reparent, so its ReparentNotify does not come back to us
|
|
560
560
|
safeRelease(this.X, () => {
|
|
561
561
|
client.eventMask = 0;
|
|
562
|
-
|
|
562
|
+
// no callback: it would only buy node-x11's void-sync round trip, see
|
|
563
|
+
// Window#setCursor
|
|
564
|
+
this.X.ChangeWindowAttributes(client.id, { eventMask: 0 });
|
|
563
565
|
});
|
|
564
566
|
client.reparentTo(this.app.rootWindow(), x, y);
|
|
565
567
|
client.removeFromSaveSet();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ntk",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.3.1",
|
|
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",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"linebreak": "^1.1.0",
|
|
45
45
|
"parse-color": "^1.0.0",
|
|
46
46
|
"pngjs": "^7.0.0",
|
|
47
|
-
"x11": "^
|
|
47
|
+
"x11": "^4.0.1"
|
|
48
48
|
},
|
|
49
49
|
"optionalDependencies": {
|
|
50
50
|
"x11-dri": ">=0.2.0 <1"
|