Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { CharCode } from '../../../../base/common/charCode.js';
7
import { KeyCode, KeyCodeUtils, IMMUTABLE_CODE_TO_KEY_CODE, ScanCode, ScanCodeUtils, NATIVE_WINDOWS_KEY_CODE_TO_KEY_CODE } from '../../../../base/common/keyCodes.js';
8
import { ResolvedKeybinding, KeyCodeChord, SingleModifierChord, ScanCodeChord, Keybinding, Chord } from '../../../../base/common/keybindings.js';
9
import { UILabelProvider } from '../../../../base/common/keybindingLabels.js';
10
import { OperatingSystem } from '../../../../base/common/platform.js';
11
import { IKeyboardEvent } from '../../../../platform/keybinding/common/keybinding.js';
12
import { IKeyboardMapper } from '../../../../platform/keyboardLayout/common/keyboardMapper.js';
13
import { BaseResolvedKeybinding } from '../../../../platform/keybinding/common/baseResolvedKeybinding.js';
14
import { toEmptyArrayIfContainsNull } from '../../../../platform/keybinding/common/resolvedKeybindingItem.js';
15
import { IWindowsKeyboardMapping } from '../../../../platform/keyboardLayout/common/keyboardLayout.js';
16
17
const LOG = false;
18
function log(str: string): void {
19
if (LOG) {
20
console.info(str);
21
}
22
}
23
24
25
export interface IScanCodeMapping {
26
scanCode: ScanCode;
27
keyCode: KeyCode;
28
value: string;
29
withShift: string;
30
withAltGr: string;
31
withShiftAltGr: string;
32
}
33
34
export class WindowsNativeResolvedKeybinding extends BaseResolvedKeybinding<KeyCodeChord> {
35
36
private readonly _mapper: WindowsKeyboardMapper;
37
38
constructor(mapper: WindowsKeyboardMapper, chords: KeyCodeChord[]) {
39
super(OperatingSystem.Windows, chords);
40
this._mapper = mapper;
41
}
42
43
protected _getLabel(chord: KeyCodeChord): string | null {
44
if (chord.isDuplicateModifierCase()) {
45
return '';
46
}
47
return this._mapper.getUILabelForKeyCode(chord.keyCode);
48
}
49
50
private _getUSLabelForKeybinding(chord: KeyCodeChord): string | null {
51
if (chord.isDuplicateModifierCase()) {
52
return '';
53
}
54
return KeyCodeUtils.toString(chord.keyCode);
55
}
56
57
public getUSLabel(): string | null {
58
return UILabelProvider.toLabel(this._os, this._chords, (keybinding) => this._getUSLabelForKeybinding(keybinding));
59
}
60
61
protected _getAriaLabel(chord: KeyCodeChord): string | null {
62
if (chord.isDuplicateModifierCase()) {
63
return '';
64
}
65
return this._mapper.getAriaLabelForKeyCode(chord.keyCode);
66
}
67
68
protected _getElectronAccelerator(chord: KeyCodeChord): string | null {
69
return this._mapper.getElectronAcceleratorForKeyBinding(chord);
70
}
71
72
protected _getUserSettingsLabel(chord: KeyCodeChord): string | null {
73
if (chord.isDuplicateModifierCase()) {
74
return '';
75
}
76
const result = this._mapper.getUserSettingsLabelForKeyCode(chord.keyCode);
77
return (result ? result.toLowerCase() : result);
78
}
79
80
protected _isWYSIWYG(chord: KeyCodeChord): boolean {
81
return this.__isWYSIWYG(chord.keyCode);
82
}
83
84
private __isWYSIWYG(keyCode: KeyCode): boolean {
85
if (
86
keyCode === KeyCode.LeftArrow
87
|| keyCode === KeyCode.UpArrow
88
|| keyCode === KeyCode.RightArrow
89
|| keyCode === KeyCode.DownArrow
90
) {
91
return true;
92
}
93
const ariaLabel = this._mapper.getAriaLabelForKeyCode(keyCode);
94
const userSettingsLabel = this._mapper.getUserSettingsLabelForKeyCode(keyCode);
95
return (ariaLabel === userSettingsLabel);
96
}
97
98
protected _getChordDispatch(chord: KeyCodeChord): string | null {
99
if (chord.isModifierKey()) {
100
return null;
101
}
102
let result = '';
103
104
if (chord.ctrlKey) {
105
result += 'ctrl+';
106
}
107
if (chord.shiftKey) {
108
result += 'shift+';
109
}
110
if (chord.altKey) {
111
result += 'alt+';
112
}
113
if (chord.metaKey) {
114
result += 'meta+';
115
}
116
result += KeyCodeUtils.toString(chord.keyCode);
117
118
return result;
119
}
120
121
protected _getSingleModifierChordDispatch(chord: KeyCodeChord): SingleModifierChord | null {
122
if (chord.keyCode === KeyCode.Ctrl && !chord.shiftKey && !chord.altKey && !chord.metaKey) {
123
return 'ctrl';
124
}
125
if (chord.keyCode === KeyCode.Shift && !chord.ctrlKey && !chord.altKey && !chord.metaKey) {
126
return 'shift';
127
}
128
if (chord.keyCode === KeyCode.Alt && !chord.ctrlKey && !chord.shiftKey && !chord.metaKey) {
129
return 'alt';
130
}
131
if (chord.keyCode === KeyCode.Meta && !chord.ctrlKey && !chord.shiftKey && !chord.altKey) {
132
return 'meta';
133
}
134
return null;
135
}
136
137
private static getProducedCharCode(chord: ScanCodeChord, mapping: IScanCodeMapping): string | null {
138
if (!mapping) {
139
return null;
140
}
141
if (chord.ctrlKey && chord.shiftKey && chord.altKey) {
142
return mapping.withShiftAltGr;
143
}
144
if (chord.ctrlKey && chord.altKey) {
145
return mapping.withAltGr;
146
}
147
if (chord.shiftKey) {
148
return mapping.withShift;
149
}
150
return mapping.value;
151
}
152
153
public static getProducedChar(chord: ScanCodeChord, mapping: IScanCodeMapping): string {
154
const char = this.getProducedCharCode(chord, mapping);
155
if (char === null || char.length === 0) {
156
return ' --- ';
157
}
158
return ' ' + char + ' ';
159
}
160
}
161
162
export class WindowsKeyboardMapper implements IKeyboardMapper {
163
164
private readonly _codeInfo: IScanCodeMapping[];
165
private readonly _scanCodeToKeyCode: KeyCode[];
166
private readonly _keyCodeToLabel: Array<string | null> = [];
167
private readonly _keyCodeExists: boolean[];
168
169
constructor(
170
private readonly _isUSStandard: boolean,
171
rawMappings: IWindowsKeyboardMapping,
172
private readonly _mapAltGrToCtrlAlt: boolean
173
) {
174
this._scanCodeToKeyCode = [];
175
this._keyCodeToLabel = [];
176
this._keyCodeExists = [];
177
this._keyCodeToLabel[KeyCode.Unknown] = KeyCodeUtils.toString(KeyCode.Unknown);
178
179
for (let scanCode = ScanCode.None; scanCode < ScanCode.MAX_VALUE; scanCode++) {
180
const immutableKeyCode = IMMUTABLE_CODE_TO_KEY_CODE[scanCode];
181
if (immutableKeyCode !== KeyCode.DependsOnKbLayout) {
182
this._scanCodeToKeyCode[scanCode] = immutableKeyCode;
183
this._keyCodeToLabel[immutableKeyCode] = KeyCodeUtils.toString(immutableKeyCode);
184
this._keyCodeExists[immutableKeyCode] = true;
185
}
186
}
187
188
const producesLetter: boolean[] = [];
189
let producesLetters = false;
190
191
this._codeInfo = [];
192
for (const strCode in rawMappings) {
193
if (rawMappings.hasOwnProperty(strCode)) {
194
const scanCode = ScanCodeUtils.toEnum(strCode);
195
if (scanCode === ScanCode.None) {
196
log(`Unknown scanCode ${strCode} in mapping.`);
197
continue;
198
}
199
const rawMapping = rawMappings[strCode];
200
201
const immutableKeyCode = IMMUTABLE_CODE_TO_KEY_CODE[scanCode];
202
if (immutableKeyCode !== KeyCode.DependsOnKbLayout) {
203
const keyCode = NATIVE_WINDOWS_KEY_CODE_TO_KEY_CODE[rawMapping.vkey] || KeyCode.Unknown;
204
if (keyCode === KeyCode.Unknown || immutableKeyCode === keyCode) {
205
continue;
206
}
207
if (scanCode !== ScanCode.NumpadComma) {
208
// Looks like ScanCode.NumpadComma doesn't always map to KeyCode.NUMPAD_SEPARATOR
209
// e.g. on POR - PTB
210
continue;
211
}
212
}
213
214
const value = rawMapping.value;
215
const withShift = rawMapping.withShift;
216
const withAltGr = rawMapping.withAltGr;
217
const withShiftAltGr = rawMapping.withShiftAltGr;
218
const keyCode = NATIVE_WINDOWS_KEY_CODE_TO_KEY_CODE[rawMapping.vkey] || KeyCode.Unknown;
219
220
const mapping: IScanCodeMapping = {
221
scanCode: scanCode,
222
keyCode: keyCode,
223
value: value,
224
withShift: withShift,
225
withAltGr: withAltGr,
226
withShiftAltGr: withShiftAltGr,
227
};
228
this._codeInfo[scanCode] = mapping;
229
this._scanCodeToKeyCode[scanCode] = keyCode;
230
231
if (keyCode === KeyCode.Unknown) {
232
continue;
233
}
234
this._keyCodeExists[keyCode] = true;
235
236
if (value.length === 0) {
237
// This key does not produce strings
238
this._keyCodeToLabel[keyCode] = null;
239
}
240
241
else if (value.length > 1) {
242
// This key produces a letter representable with multiple UTF-16 code units.
243
this._keyCodeToLabel[keyCode] = value;
244
}
245
246
else {
247
const charCode = value.charCodeAt(0);
248
249
if (charCode >= CharCode.a && charCode <= CharCode.z) {
250
const upperCaseValue = CharCode.A + (charCode - CharCode.a);
251
producesLetter[upperCaseValue] = true;
252
producesLetters = true;
253
this._keyCodeToLabel[keyCode] = String.fromCharCode(CharCode.A + (charCode - CharCode.a));
254
}
255
256
else if (charCode >= CharCode.A && charCode <= CharCode.Z) {
257
producesLetter[charCode] = true;
258
producesLetters = true;
259
this._keyCodeToLabel[keyCode] = value;
260
}
261
262
else {
263
this._keyCodeToLabel[keyCode] = value;
264
}
265
}
266
}
267
}
268
269
// Handle keyboard layouts where latin characters are not produced e.g. Cyrillic
270
const _registerLetterIfMissing = (charCode: CharCode, keyCode: KeyCode): void => {
271
if (!producesLetter[charCode]) {
272
this._keyCodeToLabel[keyCode] = String.fromCharCode(charCode);
273
}
274
};
275
_registerLetterIfMissing(CharCode.A, KeyCode.KeyA);
276
_registerLetterIfMissing(CharCode.B, KeyCode.KeyB);
277
_registerLetterIfMissing(CharCode.C, KeyCode.KeyC);
278
_registerLetterIfMissing(CharCode.D, KeyCode.KeyD);
279
_registerLetterIfMissing(CharCode.E, KeyCode.KeyE);
280
_registerLetterIfMissing(CharCode.F, KeyCode.KeyF);
281
_registerLetterIfMissing(CharCode.G, KeyCode.KeyG);
282
_registerLetterIfMissing(CharCode.H, KeyCode.KeyH);
283
_registerLetterIfMissing(CharCode.I, KeyCode.KeyI);
284
_registerLetterIfMissing(CharCode.J, KeyCode.KeyJ);
285
_registerLetterIfMissing(CharCode.K, KeyCode.KeyK);
286
_registerLetterIfMissing(CharCode.L, KeyCode.KeyL);
287
_registerLetterIfMissing(CharCode.M, KeyCode.KeyM);
288
_registerLetterIfMissing(CharCode.N, KeyCode.KeyN);
289
_registerLetterIfMissing(CharCode.O, KeyCode.KeyO);
290
_registerLetterIfMissing(CharCode.P, KeyCode.KeyP);
291
_registerLetterIfMissing(CharCode.Q, KeyCode.KeyQ);
292
_registerLetterIfMissing(CharCode.R, KeyCode.KeyR);
293
_registerLetterIfMissing(CharCode.S, KeyCode.KeyS);
294
_registerLetterIfMissing(CharCode.T, KeyCode.KeyT);
295
_registerLetterIfMissing(CharCode.U, KeyCode.KeyU);
296
_registerLetterIfMissing(CharCode.V, KeyCode.KeyV);
297
_registerLetterIfMissing(CharCode.W, KeyCode.KeyW);
298
_registerLetterIfMissing(CharCode.X, KeyCode.KeyX);
299
_registerLetterIfMissing(CharCode.Y, KeyCode.KeyY);
300
_registerLetterIfMissing(CharCode.Z, KeyCode.KeyZ);
301
302
if (!producesLetters) {
303
// Since this keyboard layout produces no latin letters at all, most of the UI will use the
304
// US kb layout equivalent for UI labels, so also try to render other keys with the US labels
305
// for consistency...
306
const _registerLabel = (keyCode: KeyCode, charCode: CharCode): void => {
307
// const existingLabel = this._keyCodeToLabel[keyCode];
308
// const existingCharCode = (existingLabel ? existingLabel.charCodeAt(0) : CharCode.Null);
309
// if (existingCharCode < 32 || existingCharCode > 126) {
310
this._keyCodeToLabel[keyCode] = String.fromCharCode(charCode);
311
// }
312
};
313
_registerLabel(KeyCode.Semicolon, CharCode.Semicolon);
314
_registerLabel(KeyCode.Equal, CharCode.Equals);
315
_registerLabel(KeyCode.Comma, CharCode.Comma);
316
_registerLabel(KeyCode.Minus, CharCode.Dash);
317
_registerLabel(KeyCode.Period, CharCode.Period);
318
_registerLabel(KeyCode.Slash, CharCode.Slash);
319
_registerLabel(KeyCode.Backquote, CharCode.BackTick);
320
_registerLabel(KeyCode.BracketLeft, CharCode.OpenSquareBracket);
321
_registerLabel(KeyCode.Backslash, CharCode.Backslash);
322
_registerLabel(KeyCode.BracketRight, CharCode.CloseSquareBracket);
323
_registerLabel(KeyCode.Quote, CharCode.SingleQuote);
324
}
325
}
326
327
public dumpDebugInfo(): string {
328
const result: string[] = [];
329
330
const immutableSamples = [
331
ScanCode.ArrowUp,
332
ScanCode.Numpad0
333
];
334
335
let cnt = 0;
336
result.push(`-----------------------------------------------------------------------------------------------------------------------------------------`);
337
for (let scanCode = ScanCode.None; scanCode < ScanCode.MAX_VALUE; scanCode++) {
338
if (IMMUTABLE_CODE_TO_KEY_CODE[scanCode] !== KeyCode.DependsOnKbLayout) {
339
if (immutableSamples.indexOf(scanCode) === -1) {
340
continue;
341
}
342
}
343
344
if (cnt % 6 === 0) {
345
result.push(`| HW Code combination | Key | KeyCode combination | UI label | User settings | WYSIWYG |`);
346
result.push(`-----------------------------------------------------------------------------------------------------------------------------------------`);
347
}
348
cnt++;
349
350
const mapping = this._codeInfo[scanCode];
351
const strCode = ScanCodeUtils.toString(scanCode);
352
353
const mods = [0b000, 0b010, 0b101, 0b111];
354
for (const mod of mods) {
355
const ctrlKey = (mod & 0b001) ? true : false;
356
const shiftKey = (mod & 0b010) ? true : false;
357
const altKey = (mod & 0b100) ? true : false;
358
const scanCodeChord = new ScanCodeChord(ctrlKey, shiftKey, altKey, false, scanCode);
359
const keyCodeChord = this._resolveChord(scanCodeChord);
360
const strKeyCode = (keyCodeChord ? KeyCodeUtils.toString(keyCodeChord.keyCode) : null);
361
const resolvedKb = (keyCodeChord ? new WindowsNativeResolvedKeybinding(this, [keyCodeChord]) : null);
362
363
const outScanCode = `${ctrlKey ? 'Ctrl+' : ''}${shiftKey ? 'Shift+' : ''}${altKey ? 'Alt+' : ''}${strCode}`;
364
const ariaLabel = (resolvedKb ? resolvedKb.getAriaLabel() : null);
365
const outUILabel = (ariaLabel ? ariaLabel.replace(/Control\+/, 'Ctrl+') : null);
366
const outUserSettings = (resolvedKb ? resolvedKb.getUserSettingsLabel() : null);
367
const outKey = WindowsNativeResolvedKeybinding.getProducedChar(scanCodeChord, mapping);
368
const outKb = (strKeyCode ? `${ctrlKey ? 'Ctrl+' : ''}${shiftKey ? 'Shift+' : ''}${altKey ? 'Alt+' : ''}${strKeyCode}` : null);
369
const isWYSIWYG = (resolvedKb ? resolvedKb.isWYSIWYG() : false);
370
const outWYSIWYG = (isWYSIWYG ? ' ' : ' NO ');
371
result.push(`| ${this._leftPad(outScanCode, 30)} | ${outKey} | ${this._leftPad(outKb, 25)} | ${this._leftPad(outUILabel, 25)} | ${this._leftPad(outUserSettings, 25)} | ${outWYSIWYG} |`);
372
}
373
result.push(`-----------------------------------------------------------------------------------------------------------------------------------------`);
374
}
375
376
377
return result.join('\n');
378
}
379
380
private _leftPad(str: string | null, cnt: number): string {
381
if (str === null) {
382
str = 'null';
383
}
384
while (str.length < cnt) {
385
str = ' ' + str;
386
}
387
return str;
388
}
389
390
public getUILabelForKeyCode(keyCode: KeyCode): string {
391
return this._getLabelForKeyCode(keyCode);
392
}
393
394
public getAriaLabelForKeyCode(keyCode: KeyCode): string {
395
return this._getLabelForKeyCode(keyCode);
396
}
397
398
public getUserSettingsLabelForKeyCode(keyCode: KeyCode): string {
399
if (this._isUSStandard) {
400
return KeyCodeUtils.toUserSettingsUS(keyCode);
401
}
402
return KeyCodeUtils.toUserSettingsGeneral(keyCode);
403
}
404
405
public getElectronAcceleratorForKeyBinding(chord: KeyCodeChord): string | null {
406
return KeyCodeUtils.toElectronAccelerator(chord.keyCode);
407
}
408
409
private _getLabelForKeyCode(keyCode: KeyCode): string {
410
return this._keyCodeToLabel[keyCode] || KeyCodeUtils.toString(KeyCode.Unknown);
411
}
412
413
public resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): WindowsNativeResolvedKeybinding {
414
const ctrlKey = keyboardEvent.ctrlKey || (this._mapAltGrToCtrlAlt && keyboardEvent.altGraphKey);
415
const altKey = keyboardEvent.altKey || (this._mapAltGrToCtrlAlt && keyboardEvent.altGraphKey);
416
const chord = new KeyCodeChord(ctrlKey, keyboardEvent.shiftKey, altKey, keyboardEvent.metaKey, keyboardEvent.keyCode);
417
return new WindowsNativeResolvedKeybinding(this, [chord]);
418
}
419
420
private _resolveChord(chord: Chord | null): KeyCodeChord | null {
421
if (!chord) {
422
return null;
423
}
424
if (chord instanceof KeyCodeChord) {
425
if (!this._keyCodeExists[chord.keyCode]) {
426
return null;
427
}
428
return chord;
429
}
430
const keyCode = this._scanCodeToKeyCode[chord.scanCode] || KeyCode.Unknown;
431
if (keyCode === KeyCode.Unknown || !this._keyCodeExists[keyCode]) {
432
return null;
433
}
434
return new KeyCodeChord(chord.ctrlKey, chord.shiftKey, chord.altKey, chord.metaKey, keyCode);
435
}
436
437
public resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[] {
438
const chords: KeyCodeChord[] = toEmptyArrayIfContainsNull(keybinding.chords.map(chord => this._resolveChord(chord)));
439
if (chords.length > 0) {
440
return [new WindowsNativeResolvedKeybinding(this, chords)];
441
}
442
return [];
443
}
444
}
445
446