ntk 4.1.0 → 4.3.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/cursor.js +61 -2
- package/lib/index.js +3 -1
- package/lib/window.js +69 -3
- package/package.json +1 -1
package/lib/cursor.js
CHANGED
|
@@ -23,10 +23,24 @@ export const cursorShapes = {
|
|
|
23
23
|
'not-allowed': 0 // XC_X_cursor
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* The invisible cursor, spelled as CSS spells it. It is a name but not a
|
|
28
|
+
* shape: the 'cursor' font has no glyph meaning "no cursor", so this one is
|
|
29
|
+
* built from an empty bitmap instead of looked up. Kept out of
|
|
30
|
+
* `cursorShapes` for exactly that reason — every value in there is a glyph
|
|
31
|
+
* index, and this has none.
|
|
32
|
+
*/
|
|
33
|
+
export const BLANK_CURSOR = 'none';
|
|
34
|
+
|
|
35
|
+
/** Every name `setCursor` accepts, for the error message and for docs. */
|
|
36
|
+
export const cursorNames = [...Object.keys(cursorShapes), BLANK_CURSOR];
|
|
37
|
+
|
|
26
38
|
/**
|
|
27
39
|
* Resolve a friendly cursor name (see `cursorShapes`) or a raw cursor-font
|
|
28
40
|
* glyph index to the glyph index. Throws on unknown names so typos surface
|
|
29
|
-
* immediately instead of silently keeping the old cursor.
|
|
41
|
+
* immediately instead of silently keeping the old cursor. `'none'` is a
|
|
42
|
+
* valid cursor name but not a glyph, so it never reaches here — see
|
|
43
|
+
* `CursorCache.get`.
|
|
30
44
|
*/
|
|
31
45
|
export function resolveCursorShape(nameOrShape) {
|
|
32
46
|
if (typeof nameOrShape === 'number') {
|
|
@@ -38,7 +52,7 @@ export function resolveCursorShape(nameOrShape) {
|
|
|
38
52
|
const shape = cursorShapes[nameOrShape];
|
|
39
53
|
if (shape === undefined) {
|
|
40
54
|
throw new Error(
|
|
41
|
-
`unknown cursor name '${nameOrShape}' — valid names: ${
|
|
55
|
+
`unknown cursor name '${nameOrShape}' — valid names: ${cursorNames.join(', ')}`
|
|
42
56
|
);
|
|
43
57
|
}
|
|
44
58
|
return shape;
|
|
@@ -56,10 +70,12 @@ export class CursorCache {
|
|
|
56
70
|
this.X = app.X;
|
|
57
71
|
this._font = 0; // the 'cursor' font, opened on first use
|
|
58
72
|
this._byShape = new Map(); // glyph index -> cursor xid
|
|
73
|
+
this._blank = 0; // the invisible cursor, built on first use
|
|
59
74
|
}
|
|
60
75
|
|
|
61
76
|
/** cursor xid for a friendly name or raw glyph index, creating it on first use */
|
|
62
77
|
get(nameOrShape) {
|
|
78
|
+
if (nameOrShape === BLANK_CURSOR) return this.blank();
|
|
63
79
|
const shape = resolveCursorShape(nameOrShape);
|
|
64
80
|
const cached = this._byShape.get(shape);
|
|
65
81
|
if (cached) return cached;
|
|
@@ -79,6 +95,41 @@ export class CursorCache {
|
|
|
79
95
|
return cid;
|
|
80
96
|
}
|
|
81
97
|
|
|
98
|
+
/**
|
|
99
|
+
* The invisible cursor, created once per connection. Hiding the pointer is
|
|
100
|
+
* not the same thing as X's cursor None, which means *inherit the parent's*
|
|
101
|
+
* — for a top-level window that is the root's cursor, so the pointer stays
|
|
102
|
+
* on screen. A cursor whose mask is empty is the way to hide it: mask bits
|
|
103
|
+
* that are 0 are not drawn, so a 1x1 all-zero mask draws nothing at all.
|
|
104
|
+
*/
|
|
105
|
+
blank() {
|
|
106
|
+
if (this._blank) return this._blank;
|
|
107
|
+
const X = this.X;
|
|
108
|
+
const pid = X.AllocID();
|
|
109
|
+
// depth 1 — CreateCursor takes bitmaps, not pixmaps of the window depth
|
|
110
|
+
X.CreatePixmap(pid, X.display.screen[0].root, 1, 1, 1);
|
|
111
|
+
// a freshly created pixmap's contents are *undefined* per the protocol,
|
|
112
|
+
// so it has to be cleared. Skipping this is how the recipe ends up
|
|
113
|
+
// hiding the cursor on one server and drawing a stray pixel on another
|
|
114
|
+
const gc = X.AllocID();
|
|
115
|
+
X.CreateGC(gc, pid, { foreground: 0 });
|
|
116
|
+
X.PolyFillRectangle(pid, gc, [0, 0, 1, 1]);
|
|
117
|
+
X.FreeGC(gc);
|
|
118
|
+
X.ReleaseID(gc);
|
|
119
|
+
|
|
120
|
+
const cid = X.AllocID();
|
|
121
|
+
const black = { R: 0, G: 0, B: 0 };
|
|
122
|
+
// source and mask are the same empty bitmap; the colours are immaterial
|
|
123
|
+
// when nothing is drawn, but the request needs them
|
|
124
|
+
X.CreateCursor(cid, pid, pid, black, black, 0, 0);
|
|
125
|
+
// the cursor keeps its own copy of the bitmaps, so the pixmap goes now
|
|
126
|
+
// rather than living as long as the connection
|
|
127
|
+
X.FreePixmap(pid);
|
|
128
|
+
X.ReleaseID(pid);
|
|
129
|
+
this._blank = cid;
|
|
130
|
+
return cid;
|
|
131
|
+
}
|
|
132
|
+
|
|
82
133
|
/** free the server-side cursors (and the font); safe to call repeatedly */
|
|
83
134
|
dispose() {
|
|
84
135
|
const X = this.X;
|
|
@@ -89,6 +140,14 @@ export class CursorCache {
|
|
|
89
140
|
});
|
|
90
141
|
}
|
|
91
142
|
this._byShape.clear();
|
|
143
|
+
if (this._blank) {
|
|
144
|
+
const cid = this._blank;
|
|
145
|
+
this._blank = 0;
|
|
146
|
+
safeRelease(X, () => {
|
|
147
|
+
X.FreeCursor(cid);
|
|
148
|
+
X.ReleaseID(cid);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
92
151
|
if (this._font) {
|
|
93
152
|
const fid = this._font;
|
|
94
153
|
this._font = 0;
|
package/lib/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import x11 from 'x11';
|
|
|
2
2
|
|
|
3
3
|
import App from './app.js';
|
|
4
4
|
import Clipboard from './clipboard.js';
|
|
5
|
-
import { CursorCache, cursorShapes, resolveCursorShape } from './cursor.js';
|
|
5
|
+
import { BLANK_CURSOR, CursorCache, cursorNames, cursorShapes, resolveCursorShape } from './cursor.js';
|
|
6
6
|
import Window from './window.js';
|
|
7
7
|
import { decodeKey, groupForState } from './keyboard.js';
|
|
8
8
|
import Pixmap from './pixmap.js';
|
|
@@ -111,7 +111,9 @@ export {
|
|
|
111
111
|
App,
|
|
112
112
|
Clipboard,
|
|
113
113
|
Window,
|
|
114
|
+
BLANK_CURSOR,
|
|
114
115
|
CursorCache,
|
|
116
|
+
cursorNames,
|
|
115
117
|
cursorShapes,
|
|
116
118
|
resolveCursorShape,
|
|
117
119
|
Pixmap,
|
package/lib/window.js
CHANGED
|
@@ -693,6 +693,66 @@ export default class Window extends Drawable {
|
|
|
693
693
|
});
|
|
694
694
|
}
|
|
695
695
|
|
|
696
|
+
/**
|
|
697
|
+
* Scroll the pixels of `rect` (window coordinates, `{x, y, width, height}`)
|
|
698
|
+
* by (dx, dy) within the retained backing store, server-side: one CopyArea
|
|
699
|
+
* of the band that survives the shift, in place of the caller re-drawing
|
|
700
|
+
* everything that merely moved. Returns true when the blit was issued;
|
|
701
|
+
* false means "not possible here", and the caller repaints `rect` exactly
|
|
702
|
+
* as it would have without this method — every refusal is the status quo.
|
|
703
|
+
*
|
|
704
|
+
* Refused when there is no (valid) backing store, when the delta is
|
|
705
|
+
* fractional (a sub-pixel shift changes every pixel, so there is nothing
|
|
706
|
+
* to copy), when it is zero, or when nothing of `rect` survives the shift
|
|
707
|
+
* after clamping to window ∩ backing.
|
|
708
|
+
*
|
|
709
|
+
* Backing-store only, deliberately: pixmap contents cannot be occluded, so
|
|
710
|
+
* an overlapping self-copy is fully defined (the server fetches the source
|
|
711
|
+
* region before storing) and the GraphicsExpose handling a window-drawable
|
|
712
|
+
* scroll would need never exists. The copy goes out with the present GC —
|
|
713
|
+
* graphicsExposures: 0, where the 2d context's GC would emit a NoExposure
|
|
714
|
+
* packet per copy — and in-order with the caller's follow-up drawing of
|
|
715
|
+
* the exposed strip on the same connection.
|
|
716
|
+
*
|
|
717
|
+
* The whole of `rect` is marked dirty, so the next present shows the
|
|
718
|
+
* scrolled band through the normal fence-aware path; the caller only has
|
|
719
|
+
* to draw the strip the shift exposed, plus any chrome that moved with
|
|
720
|
+
* the content (a scrollbar thumb, say).
|
|
721
|
+
*/
|
|
722
|
+
scrollRegion(rect, dx, dy) {
|
|
723
|
+
if (!this._backing || !this._backingValid) return false;
|
|
724
|
+
if (!Number.isInteger(dx) || !Number.isInteger(dy) || (dx === 0 && dy === 0)) return false;
|
|
725
|
+
// clamp like a present: the backing is grow-only, so it can be larger
|
|
726
|
+
// than the window after a shrink
|
|
727
|
+
const w = Math.min(this.width, this._backing.width);
|
|
728
|
+
const h = Math.min(this.height, this._backing.height);
|
|
729
|
+
const x0 = Math.max(0, Math.floor(rect.x));
|
|
730
|
+
const y0 = Math.max(0, Math.floor(rect.y));
|
|
731
|
+
const x1 = Math.min(w, Math.ceil(rect.x + rect.width));
|
|
732
|
+
const y1 = Math.min(h, Math.ceil(rect.y + rect.height));
|
|
733
|
+
// the band that survives: dest = clamped rect ∩ (clamped rect + delta)
|
|
734
|
+
const dstX0 = Math.max(x0, x0 + dx);
|
|
735
|
+
const dstY0 = Math.max(y0, y0 + dy);
|
|
736
|
+
const dstX1 = Math.min(x1, x1 + dx);
|
|
737
|
+
const dstY1 = Math.min(y1, y1 + dy);
|
|
738
|
+
if (dstX1 <= dstX0 || dstY1 <= dstY0) return false;
|
|
739
|
+
safeRelease(this.X, () => {
|
|
740
|
+
this.X.CopyArea(
|
|
741
|
+
this._backing.id,
|
|
742
|
+
this._backing.id,
|
|
743
|
+
this._presentGc,
|
|
744
|
+
dstX0 - dx,
|
|
745
|
+
dstY0 - dy,
|
|
746
|
+
dstX0,
|
|
747
|
+
dstY0,
|
|
748
|
+
dstX1 - dstX0,
|
|
749
|
+
dstY1 - dstY0
|
|
750
|
+
);
|
|
751
|
+
});
|
|
752
|
+
this._markDirty({ x: x0, y: y0, w: x1 - x0, h: y1 - y0 });
|
|
753
|
+
return true;
|
|
754
|
+
}
|
|
755
|
+
|
|
696
756
|
/*
|
|
697
757
|
* Frame clock. Noisy events (see events_map coalesce), synthetic redraws
|
|
698
758
|
* and requestAnimationFrame callbacks are delivered in "frames", paced by
|
|
@@ -1763,9 +1823,15 @@ export default class Window extends Drawable {
|
|
|
1763
1823
|
* Set the mouse cursor shown over this window. Accepts a friendly name
|
|
1764
1824
|
* ('text', 'pointer', 'wait', ... — see cursorShapes in lib/cursor.js and
|
|
1765
1825
|
* docs/window.md) or a raw X11 cursor-font glyph index. Cursors are
|
|
1766
|
-
* created once per connection and cached on the app.
|
|
1767
|
-
*
|
|
1768
|
-
*
|
|
1826
|
+
* created once per connection and cached on the app. Throws synchronously
|
|
1827
|
+
* on unknown names.
|
|
1828
|
+
*
|
|
1829
|
+
* Two things that sound alike and are not:
|
|
1830
|
+
*
|
|
1831
|
+
* setCursor('none') the pointer is invisible over this window
|
|
1832
|
+
* setCursor(null) X cursor None — *inherit* the parent's cursor,
|
|
1833
|
+
* which for a top-level window is the root's, so the
|
|
1834
|
+
* pointer stays visible
|
|
1769
1835
|
*/
|
|
1770
1836
|
setCursor(nameOrShape) {
|
|
1771
1837
|
const cursor = nameOrShape == null ? 0 : this.app.cursors.get(nameOrShape);
|