ntk 4.1.0 → 4.2.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 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: ${Object.keys(cursorShapes).join(', ')}`
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
@@ -1763,9 +1763,15 @@ export default class Window extends Drawable {
1763
1763
  * Set the mouse cursor shown over this window. Accepts a friendly name
1764
1764
  * ('text', 'pointer', 'wait', ... — see cursorShapes in lib/cursor.js and
1765
1765
  * docs/window.md) or a raw X11 cursor-font glyph index. Cursors are
1766
- * created once per connection and cached on the app. `setCursor(null)`
1767
- * restores the parent window's cursor (X cursor = None). Throws
1768
- * synchronously on unknown names.
1766
+ * created once per connection and cached on the app. Throws synchronously
1767
+ * on unknown names.
1768
+ *
1769
+ * Two things that sound alike and are not:
1770
+ *
1771
+ * setCursor('none') the pointer is invisible over this window
1772
+ * setCursor(null) X cursor None — *inherit* the parent's cursor,
1773
+ * which for a top-level window is the root's, so the
1774
+ * pointer stays visible
1769
1775
  */
1770
1776
  setCursor(nameOrShape) {
1771
1777
  const cursor = nameOrShape == null ? 0 : this.app.cursors.get(nameOrShape);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ntk",
3
- "version": "4.1.0",
3
+ "version": "4.2.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",