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.
- package/package.json +11 -8
- package/src/Reconciler.js +34 -21
- package/src/components/Select.js +8 -2
- package/src/events.js +8 -2
- package/src/index.d.ts +5 -0
- package/src/nodes/boxpaint.js +9 -0
- package/src/nodes/preedit.js +64 -14
- package/src/scale.js +52 -22
- package/src/screencolor.js +104 -17
- package/src/wayland/app.js +560 -0
- package/src/wayland/backendwindow.js +1123 -0
- package/src/wayland/clipboard.js +326 -0
- package/src/wayland/connection.js +482 -0
- package/src/wayland/context2d.js +2133 -0
- package/src/wayland/decorations.js +476 -0
- package/src/wayland/dmabuf.js +89 -0
- package/src/wayland/dnd.js +581 -0
- package/src/wayland/fdutil.js +108 -0
- package/src/wayland/framestyle.js +257 -0
- package/src/wayland/glarea.js +371 -0
- package/src/wayland/glcontext.js +415 -0
- package/src/wayland/glyphatlas.js +237 -0
- package/src/wayland/input.js +417 -0
- package/src/wayland/keysymnames.js +35 -0
- package/src/wayland/layershell.js +363 -0
- package/src/wayland/outputs.js +601 -0
- package/src/wayland/protocols/cursor-shape-v1.json +1 -0
- package/src/wayland/protocols/ext-idle-notify-v1.json +1 -0
- package/src/wayland/protocols/ext-image-capture-source-v1.json +1 -0
- package/src/wayland/protocols/ext-image-copy-capture-v1.json +1 -0
- package/src/wayland/protocols/fractional-scale-v1.json +1 -0
- package/src/wayland/protocols/index.json +127 -0
- package/src/wayland/protocols/keyboard-shortcuts-inhibit-unstable-v1.json +1 -0
- package/src/wayland/protocols/linux-dmabuf-v1.json +1 -0
- package/src/wayland/protocols/pointer-constraints-unstable-v1.json +1 -0
- package/src/wayland/protocols/presentation-time.json +1 -0
- package/src/wayland/protocols/primary-selection-unstable-v1.json +1 -0
- package/src/wayland/protocols/relative-pointer-unstable-v1.json +1 -0
- package/src/wayland/protocols/tablet-v2.json +1 -0
- package/src/wayland/protocols/text-input-unstable-v3.json +1 -0
- package/src/wayland/protocols/viewporter.json +1 -0
- package/src/wayland/protocols/wayland.json +1 -0
- package/src/wayland/protocols/wlr-layer-shell-unstable-v1.json +1 -0
- package/src/wayland/protocols/wlr-screencopy-unstable-v1.json +1 -0
- package/src/wayland/protocols/xdg-activation-v1.json +1 -0
- package/src/wayland/protocols/xdg-decoration-unstable-v1.json +1 -0
- package/src/wayland/protocols/xdg-output-unstable-v1.json +1 -0
- package/src/wayland/protocols/xdg-shell.json +1 -0
- package/src/wayland/protocols/xdg-toplevel-icon-v1.json +1 -0
- package/src/wayland/readback.js +99 -0
- package/src/wayland/screencopy.js +584 -0
- package/src/wayland/seat.js +584 -0
- package/src/wayland/shm.js +226 -0
- package/src/wayland/ssd.js +106 -0
- package/src/wayland/surface.js +123 -0
- package/src/wayland/swapchain.js +411 -0
- package/src/wayland/tablet.js +522 -0
- package/src/wayland/target.js +263 -0
- package/src/wayland/text.js +113 -0
- package/src/wayland/textinput.js +671 -0
- package/src/wayland/touch.js +284 -0
- package/src/wayland/window.js +827 -0
- 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
|
+
}
|