react-x11 2.13.0 → 2.14.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 (63) hide show
  1. package/package.json +11 -8
  2. package/src/Reconciler.js +34 -21
  3. package/src/components/Select.js +8 -2
  4. package/src/events.js +8 -2
  5. package/src/index.d.ts +5 -0
  6. package/src/nodes/boxpaint.js +9 -0
  7. package/src/nodes/preedit.js +64 -14
  8. package/src/scale.js +52 -22
  9. package/src/screencolor.js +104 -17
  10. package/src/wayland/app.js +560 -0
  11. package/src/wayland/backendwindow.js +1123 -0
  12. package/src/wayland/clipboard.js +326 -0
  13. package/src/wayland/connection.js +482 -0
  14. package/src/wayland/context2d.js +2133 -0
  15. package/src/wayland/decorations.js +476 -0
  16. package/src/wayland/dmabuf.js +89 -0
  17. package/src/wayland/dnd.js +581 -0
  18. package/src/wayland/fdutil.js +108 -0
  19. package/src/wayland/framestyle.js +257 -0
  20. package/src/wayland/glarea.js +371 -0
  21. package/src/wayland/glcontext.js +415 -0
  22. package/src/wayland/glyphatlas.js +237 -0
  23. package/src/wayland/input.js +417 -0
  24. package/src/wayland/keysymnames.js +35 -0
  25. package/src/wayland/layershell.js +363 -0
  26. package/src/wayland/outputs.js +601 -0
  27. package/src/wayland/protocols/cursor-shape-v1.json +1 -0
  28. package/src/wayland/protocols/ext-idle-notify-v1.json +1 -0
  29. package/src/wayland/protocols/ext-image-capture-source-v1.json +1 -0
  30. package/src/wayland/protocols/ext-image-copy-capture-v1.json +1 -0
  31. package/src/wayland/protocols/fractional-scale-v1.json +1 -0
  32. package/src/wayland/protocols/index.json +127 -0
  33. package/src/wayland/protocols/keyboard-shortcuts-inhibit-unstable-v1.json +1 -0
  34. package/src/wayland/protocols/linux-dmabuf-v1.json +1 -0
  35. package/src/wayland/protocols/pointer-constraints-unstable-v1.json +1 -0
  36. package/src/wayland/protocols/presentation-time.json +1 -0
  37. package/src/wayland/protocols/primary-selection-unstable-v1.json +1 -0
  38. package/src/wayland/protocols/relative-pointer-unstable-v1.json +1 -0
  39. package/src/wayland/protocols/tablet-v2.json +1 -0
  40. package/src/wayland/protocols/text-input-unstable-v3.json +1 -0
  41. package/src/wayland/protocols/viewporter.json +1 -0
  42. package/src/wayland/protocols/wayland.json +1 -0
  43. package/src/wayland/protocols/wlr-layer-shell-unstable-v1.json +1 -0
  44. package/src/wayland/protocols/wlr-screencopy-unstable-v1.json +1 -0
  45. package/src/wayland/protocols/xdg-activation-v1.json +1 -0
  46. package/src/wayland/protocols/xdg-decoration-unstable-v1.json +1 -0
  47. package/src/wayland/protocols/xdg-output-unstable-v1.json +1 -0
  48. package/src/wayland/protocols/xdg-shell.json +1 -0
  49. package/src/wayland/protocols/xdg-toplevel-icon-v1.json +1 -0
  50. package/src/wayland/readback.js +99 -0
  51. package/src/wayland/screencopy.js +584 -0
  52. package/src/wayland/seat.js +584 -0
  53. package/src/wayland/shm.js +226 -0
  54. package/src/wayland/ssd.js +106 -0
  55. package/src/wayland/surface.js +123 -0
  56. package/src/wayland/swapchain.js +411 -0
  57. package/src/wayland/tablet.js +522 -0
  58. package/src/wayland/target.js +263 -0
  59. package/src/wayland/text.js +113 -0
  60. package/src/wayland/textinput.js +671 -0
  61. package/src/wayland/touch.js +284 -0
  62. package/src/wayland/window.js +827 -0
  63. package/src/wayland/xkb.js +425 -0
@@ -0,0 +1,425 @@
1
+ // An XKB keymap, read from the text the compositor sends.
2
+ //
3
+ // On X11 the server interprets the keyboard: a key event arrives with a
4
+ // keycode and the client asks `GetKeyboardMapping` what it means. On Wayland
5
+ // the compositor sends the whole keymap once — as a file descriptor holding
6
+ // `xkb_keymap { ... }` in libxkbcommon's text format — and every event after
7
+ // that is a raw keycode the client interprets itself. libxkbcommon is how a C
8
+ // client does that. This file is how this one does, and it is here rather
9
+ // than a `dlopen` of libxkbcommon for the reason the RFC gave: a keymap
10
+ // parser is the kind of code this project writes well, it runs on every
11
+ // runtime without a native library, and the subset a desktop actually uses
12
+ // is small.
13
+ //
14
+ // What is read:
15
+ //
16
+ // xkb_keycodes `<AD01> = 24;` name -> keycode
17
+ // xkb_types `type "FOUR_LEVEL" { modifiers= Shift+LevelThree;
18
+ // map[Shift]= 2; ... }` modifier set -> level
19
+ // xkb_symbols `key <AD01> { [ q, Q ] };` keycode -> keysyms per level,
20
+ // with `symbols[Group2]` and `type=` where given
21
+ // `modifier_map Mod5 { <LVL3> }` real modifier -> keycodes
22
+ // xkb_compat `interpret ISO_Level3_Shift { virtualModifier= LevelThree; }`
23
+ // keysym -> virtual modifier
24
+ //
25
+ // From the last two, a virtual modifier like `LevelThree` resolves to the
26
+ // real modifier bit the compositor will actually report in
27
+ // `wl_keyboard.modifiers` — which is what makes AltGr work rather than being
28
+ // assumed to be Mod5. The assumptions are kept as a fallback for a keymap
29
+ // that does not say.
30
+ //
31
+ // What is not read: key actions (the compositor applies those; we only see
32
+ // their effect in the modifier state), indicators, geometry, and the
33
+ // per-type `preserve` rules.
34
+ //
35
+ // The output is two things. `keycode2keysyms` is the X core shape —
36
+ // `[g1l1, g1l2, g2l1, g2l2, …]` — because that is what `keyboard.js`'s
37
+ // accelerator resolution and ntk's `decodeKey` already consume, unchanged.
38
+ // `decode()` is the full answer, using the key's real type so that level 3
39
+ // and 4 (AltGr) resolve where the two-level core shape cannot express them.
40
+
41
+ import { charOf } from '../keysyms.js';
42
+ import { keysymFromName } from './keysymnames.js';
43
+
44
+ /** Real modifier bits, as X and XKB both number them. */
45
+ export const REAL_MODS = {
46
+ Shift: 1,
47
+ Lock: 2,
48
+ Control: 4,
49
+ Mod1: 8,
50
+ Mod2: 16,
51
+ Mod3: 32,
52
+ Mod4: 64,
53
+ Mod5: 128,
54
+ };
55
+
56
+ /** What a virtual modifier means when the keymap does not say. */
57
+ const VMOD_FALLBACK = {
58
+ LevelThree: REAL_MODS.Mod5,
59
+ NumLock: REAL_MODS.Mod2,
60
+ Alt: REAL_MODS.Mod1,
61
+ Meta: REAL_MODS.Mod1,
62
+ Super: REAL_MODS.Mod4,
63
+ Hyper: REAL_MODS.Mod4,
64
+ LevelFive: REAL_MODS.Mod3,
65
+ ScrollLock: 0,
66
+ AltGr: REAL_MODS.Mod5,
67
+ };
68
+
69
+ const GROUP_SHIFT = 13;
70
+
71
+ export class XkbKeymap {
72
+ constructor() {
73
+ this.minKeycode = 8;
74
+ this.maxKeycode = 255;
75
+ /** name -> keycode, e.g. AD01 -> 24 */
76
+ this.names = new Map();
77
+ /** type name -> { mask, map: Map<modmask, level>, levels } */
78
+ this.types = new Map();
79
+ /** keycode -> { groups: Array<{ type, syms: number[] }> } */
80
+ this.keys = new Map();
81
+ /** virtual modifier name -> real modifier mask */
82
+ this.vmods = new Map();
83
+ /** keycode -> real modifier mask (from modifier_map) */
84
+ this.modmap = new Map();
85
+ /** keycode -> keysyms in the X core layout */
86
+ this.keycode2keysyms = [];
87
+ /** keysym -> keycode, for the accelerator path's reverse lookups */
88
+ this.keysym2keycode = new Map();
89
+ }
90
+
91
+ static parse(text) {
92
+ const km = new XkbKeymap();
93
+ km._parse(text);
94
+ return km;
95
+ }
96
+
97
+ // ---- parsing -------------------------------------------------------------
98
+
99
+ _parse(text) {
100
+ const src = stripComments(text);
101
+ const section = (name) => {
102
+ const m = src.match(new RegExp(`xkb_${name}\\s+(?:"[^"]*"\\s+)?\\{`));
103
+ if (!m) return '';
104
+ return balanced(src, m.index + m[0].length - 1);
105
+ };
106
+ this._parseKeycodes(section('keycodes'));
107
+ this._parseTypes(section('types'));
108
+ this._parseCompat(section('compatibility'));
109
+ this._parseSymbols(section('symbols'));
110
+ this._resolveVmods();
111
+ this._buildCoreTable();
112
+ }
113
+
114
+ _parseKeycodes(s) {
115
+ const range = s.match(/minimum\s*=\s*(\d+)\s*;\s*maximum\s*=\s*(\d+)/);
116
+ if (range) {
117
+ this.minKeycode = +range[1];
118
+ this.maxKeycode = +range[2];
119
+ }
120
+ for (const m of s.matchAll(/<([A-Za-z0-9_+-]+)>\s*=\s*(\d+)\s*;/g)) {
121
+ this.names.set(m[1], +m[2]);
122
+ }
123
+ // `alias <LatQ> = <AD01>;`
124
+ for (const m of s.matchAll(
125
+ /alias\s+<([A-Za-z0-9_+-]+)>\s*=\s*<([A-Za-z0-9_+-]+)>\s*;/g,
126
+ )) {
127
+ const code = this.names.get(m[2]);
128
+ if (code !== undefined) this.names.set(m[1], code);
129
+ }
130
+ }
131
+
132
+ _parseTypes(s) {
133
+ const re = /type\s+"([^"]+)"\s*\{/g;
134
+ let m;
135
+ while ((m = re.exec(s))) {
136
+ const body = balanced(s, m.index + m[0].length - 1);
137
+ re.lastIndex = m.index + m[0].length + body.length;
138
+ const type = { name: m[1], mods: [], map: [], levels: 1 };
139
+ const mods = body.match(/modifiers\s*=\s*([^;]+);/);
140
+ if (mods)
141
+ type.mods = mods[1]
142
+ .split('+')
143
+ .map((x) => x.trim())
144
+ .filter((x) => x && x !== 'none' && x !== 'None');
145
+ for (const e of body.matchAll(
146
+ /map\[([^\]]+)\]\s*=\s*(?:Level)?(\d+)\s*;/gi,
147
+ )) {
148
+ const set = e[1]
149
+ .split('+')
150
+ .map((x) => x.trim())
151
+ .filter(Boolean);
152
+ const level = +e[2];
153
+ type.map.push({ set, level });
154
+ if (level > type.levels) type.levels = level;
155
+ }
156
+ for (const e of body.matchAll(/level_name\[(?:Level)?(\d+)\]/gi)) {
157
+ if (+e[1] > type.levels) type.levels = +e[1];
158
+ }
159
+ this.types.set(type.name, type);
160
+ }
161
+ }
162
+
163
+ _parseCompat(s) {
164
+ // interpret <keysym>[+cond] { virtualModifier= X; ... }
165
+ const re = /interpret\s+([A-Za-z0-9_]+)(?:\+[^{]*)?\s*\{/g;
166
+ let m;
167
+ while ((m = re.exec(s))) {
168
+ const body = balanced(s, m.index + m[0].length - 1);
169
+ re.lastIndex = m.index + m[0].length + body.length;
170
+ const vm = body.match(/virtualModifier\s*=\s*([A-Za-z0-9_]+)/i);
171
+ if (!vm) continue;
172
+ const sym = keysymFromName(m[1]);
173
+ if (sym) (this._interp ??= new Map()).set(sym, vm[1]);
174
+ }
175
+ }
176
+
177
+ _parseSymbols(s) {
178
+ // key <NAME> { ... };
179
+ const re = /key\s+<([A-Za-z0-9_+-]+)>\s*\{/g;
180
+ let m;
181
+ while ((m = re.exec(s))) {
182
+ const body = balanced(s, m.index + m[0].length - 1);
183
+ re.lastIndex = m.index + m[0].length + body.length;
184
+ const code = this.names.get(m[1]);
185
+ if (code === undefined) continue;
186
+ const key = { groups: [] };
187
+ // explicit per-group or global type
188
+ const typeAll = body.match(/(?:^|[,{\s])type\s*=\s*"([^"]+)"/);
189
+ const typeByGroup = new Map();
190
+ for (const t of body.matchAll(/type\[Group(\d)\]\s*=\s*"([^"]+)"/g))
191
+ typeByGroup.set(+t[1], t[2]);
192
+ // symbols[GroupN]= [ a, b ] and the bare form { [ a, b ] }
193
+ const lists = [];
194
+ for (const g of body.matchAll(
195
+ /symbols\[Group(\d)\]\s*=\s*\[([^\]]*)\]/g,
196
+ )) {
197
+ lists[+g[1] - 1] = g[2];
198
+ }
199
+ if (lists.length === 0) {
200
+ const bare = body.match(/\[([^\]]*)\]/);
201
+ if (bare) lists[0] = bare[1];
202
+ }
203
+ for (let gi = 0; gi < lists.length; gi++) {
204
+ if (lists[gi] === undefined) continue;
205
+ const syms = lists[gi]
206
+ .split(',')
207
+ .map((x) => x.trim())
208
+ .filter(Boolean)
209
+ .map((name) => keysymFromName(name) ?? 0);
210
+ const typeName =
211
+ typeByGroup.get(gi + 1) ?? typeAll?.[1] ?? implicitType(syms);
212
+ key.groups[gi] = {
213
+ type:
214
+ this.types.get(typeName) ??
215
+ this.types.get(implicitType(syms)) ??
216
+ null,
217
+ syms,
218
+ };
219
+ }
220
+ if (key.groups.length) this.keys.set(code, key);
221
+ }
222
+ // modifier_map Mod5 { <LVL3>, <RALT> };
223
+ for (const mm of s.matchAll(
224
+ /modifier_map\s+([A-Za-z0-9]+)\s*\{([^}]*)\}/g,
225
+ )) {
226
+ const bit = REAL_MODS[mm[1]];
227
+ if (!bit) continue;
228
+ for (const k of mm[2].matchAll(/<([A-Za-z0-9_+-]+)>/g)) {
229
+ const code = this.names.get(k[1]);
230
+ if (code !== undefined)
231
+ this.modmap.set(code, (this.modmap.get(code) ?? 0) | bit);
232
+ }
233
+ // bare keysyms are allowed too: modifier_map Shift { Shift_L }
234
+ for (const k of mm[2].split(',')) {
235
+ const name = k.trim();
236
+ if (!name || name.startsWith('<')) continue;
237
+ const sym = keysymFromName(name);
238
+ if (sym) (this._modmapSyms ??= []).push({ sym, bit });
239
+ }
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Virtual modifier -> real modifier: the keys whose keysym `interpret`
245
+ * binds to the virtual modifier are looked up in `modifier_map`, and the
246
+ * real bits found there are the answer.
247
+ */
248
+ _resolveVmods() {
249
+ const bySym = new Map(); // keysym -> real mask, from modmap via symbols
250
+ for (const [code, key] of this.keys) {
251
+ const bits = this.modmap.get(code);
252
+ if (!bits) continue;
253
+ for (const g of key.groups)
254
+ for (const sym of g?.syms ?? [])
255
+ if (sym) bySym.set(sym, (bySym.get(sym) ?? 0) | bits);
256
+ }
257
+ for (const { sym, bit } of this._modmapSyms ?? [])
258
+ bySym.set(sym, (bySym.get(sym) ?? 0) | bit);
259
+ for (const [sym, vmod] of this._interp ?? []) {
260
+ const real = bySym.get(sym);
261
+ if (real) this.vmods.set(vmod, (this.vmods.get(vmod) ?? 0) | real);
262
+ }
263
+ for (const [name, bit] of Object.entries(VMOD_FALLBACK)) {
264
+ if (!this.vmods.has(name)) this.vmods.set(name, bit);
265
+ }
266
+ }
267
+
268
+ /** A modifier name (real or virtual) as a real mask. */
269
+ _modMask(name) {
270
+ if (name in REAL_MODS) return REAL_MODS[name];
271
+ return this.vmods.get(name) ?? 0;
272
+ }
273
+
274
+ /**
275
+ * The X core keyboard mapping — two keysyms per group, up to four groups —
276
+ * which is what `GetKeyboardMapping` would have answered.
277
+ */
278
+ _buildCoreTable() {
279
+ const table = [];
280
+ for (const [code, key] of this.keys) {
281
+ const row = [];
282
+ const ngroups = Math.max(1, key.groups.length);
283
+ for (let gi = 0; gi < ngroups; gi++) {
284
+ const g = key.groups[gi];
285
+ row.push(g?.syms?.[0] ?? 0, g?.syms?.[1] ?? 0);
286
+ }
287
+ table[code] = row;
288
+ const first = key.groups[0]?.syms?.[0];
289
+ if (first && !this.keysym2keycode.has(first))
290
+ this.keysym2keycode.set(first, code);
291
+ }
292
+ this.keycode2keysyms = table;
293
+ }
294
+
295
+ // ---- decoding ------------------------------------------------------------
296
+
297
+ /**
298
+ * Which level of a key's type the current modifiers select.
299
+ *
300
+ * XKB semantics: only the type's own modifiers are considered; the first
301
+ * `map[]` entry whose set equals the masked state wins; no entry means
302
+ * level 1. The set is matched as a mask rather than by name so that a
303
+ * virtual modifier bound to two real ones still matches.
304
+ */
305
+ _levelFor(type, mods) {
306
+ if (!type) return mods & REAL_MODS.Shift ? 1 : 0;
307
+ let relevant = 0;
308
+ for (const m of type.mods) relevant |= this._modMask(m);
309
+ const masked = mods & relevant;
310
+ for (const { set, level } of type.map) {
311
+ let want = 0;
312
+ for (const m of set) want |= this._modMask(m);
313
+ if (want === masked) return level - 1;
314
+ }
315
+ return 0;
316
+ }
317
+
318
+ /**
319
+ * Decode a key event.
320
+ *
321
+ * @param {number} keycode X-style (evdev + 8)
322
+ * @param {number} mods real modifier mask (depressed | latched | locked)
323
+ * @param {number} group effective layout group
324
+ * @returns {{keysym:number, baseKeysym:number, codepoint:number|undefined, group:number}|undefined}
325
+ */
326
+ decode(keycode, mods, group = 0) {
327
+ const key = this.keys.get(keycode);
328
+ if (!key) return undefined;
329
+ let g = key.groups[group];
330
+ // a group with nothing on this key falls back to the first, as X does
331
+ if (!g || !g.syms.some(Boolean))
332
+ g = key.groups.find((x) => x?.syms?.some(Boolean));
333
+ if (!g) return undefined;
334
+ let level = this._levelFor(g.type, mods);
335
+ if (level >= g.syms.length || !g.syms[level]) {
336
+ // Lock on a one-level key, or Shift on a key with no upper level:
337
+ // fall back the way the core protocol does — the first symbol,
338
+ // uppercased when it has a case.
339
+ level = 0;
340
+ }
341
+ let keysym = g.syms[level] ?? 0;
342
+ // Caps Lock on a plain two-level alphabetic key whose type does not
343
+ // fold Lock (a keymap with only "TWO_LEVEL" for letters): X core rule.
344
+ if (
345
+ level === 0 &&
346
+ mods & REAL_MODS.Lock &&
347
+ g.syms.length >= 2 &&
348
+ !(mods & REAL_MODS.Shift)
349
+ ) {
350
+ const lower = charOf(g.syms[0]);
351
+ const upper = charOf(g.syms[1]);
352
+ if (
353
+ lower &&
354
+ upper &&
355
+ lower !== upper &&
356
+ lower.toUpperCase() === upper &&
357
+ g.type?.mods?.includes('Lock') === false
358
+ ) {
359
+ keysym = g.syms[1];
360
+ }
361
+ }
362
+ if (!keysym) return undefined;
363
+ const ch = charOf(keysym);
364
+ const codepoint = ch ? ch.codePointAt(0) : undefined;
365
+ const base = key.groups[0]?.syms?.[0] || keysym;
366
+ return {
367
+ keysym,
368
+ baseKeysym: base,
369
+ codepoint:
370
+ codepoint !== undefined && codepoint >= 0x20 ? codepoint : undefined,
371
+ group,
372
+ };
373
+ }
374
+
375
+ /**
376
+ * The X-style `state` word: real modifiers in the low byte, the group in
377
+ * bits 13-14 — what events.js's `buttons` field carries and what ntk's
378
+ * `groupForState` reads.
379
+ */
380
+ static stateOf(mods, group) {
381
+ return (mods & 0xff) | ((group & 3) << GROUP_SHIFT);
382
+ }
383
+ }
384
+
385
+ /**
386
+ * The type XKB assigns a key that names none: one symbol is ONE_LEVEL, two
387
+ * are ALPHABETIC when they are a case pair and TWO_LEVEL otherwise, four are
388
+ * FOUR_LEVEL(_ALPHABETIC / _SEMIALPHABETIC).
389
+ */
390
+ function implicitType(syms) {
391
+ const n = syms.filter(Boolean).length;
392
+ if (n <= 1) return 'ONE_LEVEL';
393
+ const casePair = (a, b) => {
394
+ const la = charOf(a);
395
+ const lb = charOf(b);
396
+ return la && lb && la !== lb && la.toUpperCase() === lb;
397
+ };
398
+ if (n === 2) return casePair(syms[0], syms[1]) ? 'ALPHABETIC' : 'TWO_LEVEL';
399
+ if (casePair(syms[0], syms[1]))
400
+ return casePair(syms[2], syms[3])
401
+ ? 'FOUR_LEVEL_ALPHABETIC'
402
+ : 'FOUR_LEVEL_SEMIALPHABETIC';
403
+ return 'FOUR_LEVEL';
404
+ }
405
+
406
+ /** Everything from `//` or `#` to the end of the line, outside strings. */
407
+ function stripComments(text) {
408
+ return text.replace(/^\s*(\/\/|#).*$/gm, '');
409
+ }
410
+
411
+ /** The text between the brace at `open` and its match, exclusive. */
412
+ function balanced(s, open) {
413
+ let depth = 0;
414
+ for (let i = open; i < s.length; i++) {
415
+ const c = s[i];
416
+ if (c === '"') {
417
+ i = s.indexOf('"', i + 1);
418
+ if (i < 0) return s.slice(open + 1);
419
+ continue;
420
+ }
421
+ if (c === '{') depth++;
422
+ else if (c === '}' && --depth === 0) return s.slice(open + 1, i);
423
+ }
424
+ return s.slice(open + 1);
425
+ }