Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.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 { addDisposableListener } from '../../../base/browser/dom.js';
7
import { CachedFunction } from '../../../base/common/cache.js';
8
import { getStructuralKey } from '../../../base/common/equals.js';
9
import { Event, IValueWithChangeEvent } from '../../../base/common/event.js';
10
import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';
11
import { FileAccess } from '../../../base/common/network.js';
12
import { derived, observableFromEvent, ValueWithChangeEventFromObservable } from '../../../base/common/observable.js';
13
import { localize } from '../../../nls.js';
14
import { IAccessibilityService } from '../../accessibility/common/accessibility.js';
15
import { IConfigurationService } from '../../configuration/common/configuration.js';
16
import { createDecorator } from '../../instantiation/common/instantiation.js';
17
import { observableConfigValue } from '../../observable/common/platformObservableUtils.js';
18
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
19
20
export const IAccessibilitySignalService = createDecorator<IAccessibilitySignalService>('accessibilitySignalService');
21
22
export interface IAccessibilitySignalService {
23
readonly _serviceBrand: undefined;
24
playSignal(signal: AccessibilitySignal, options?: IAccessbilitySignalOptions): Promise<void>;
25
playSignals(signals: (AccessibilitySignal | { signal: AccessibilitySignal; source: string })[]): Promise<void>;
26
playSignalLoop(signal: AccessibilitySignal, milliseconds: number): IDisposable;
27
28
getEnabledState(signal: AccessibilitySignal, userGesture: boolean, modality?: AccessibilityModality | undefined): IValueWithChangeEvent<boolean>;
29
getDelayMs(signal: AccessibilitySignal, modality: AccessibilityModality, mode: 'line' | 'positional'): number;
30
/**
31
* Avoid this method and prefer `.playSignal`!
32
* Only use it when you want to play the sound regardless of enablement, e.g. in the settings quick pick.
33
*/
34
playSound(signal: Sound, allowManyInParallel: boolean, token: typeof AcknowledgeDocCommentsToken): Promise<void>;
35
36
/** @deprecated Use getEnabledState(...).onChange */
37
isSoundEnabled(signal: AccessibilitySignal): boolean;
38
/** @deprecated Use getEnabledState(...).value */
39
isAnnouncementEnabled(signal: AccessibilitySignal): boolean;
40
/** @deprecated Use getEnabledState(...).onChange */
41
onSoundEnabledChanged(signal: AccessibilitySignal): Event<void>;
42
}
43
44
/** Make sure you understand the doc comments of the method you want to call when using this token! */
45
export const AcknowledgeDocCommentsToken = Symbol('AcknowledgeDocCommentsToken');
46
47
export type AccessibilityModality = 'sound' | 'announcement';
48
49
export interface IAccessbilitySignalOptions {
50
allowManyInParallel?: boolean;
51
52
modality?: AccessibilityModality;
53
54
/**
55
* The source that triggered the signal (e.g. "diffEditor.cursorPositionChanged").
56
*/
57
source?: string;
58
59
/**
60
* For actions like save or format, depending on the
61
* configured value, we will only
62
* play the sound if the user triggered the action.
63
*/
64
userGesture?: boolean;
65
66
/**
67
* The custom message to alert with.
68
* This will override the default announcement message.
69
*/
70
customAlertMessage?: string;
71
}
72
73
export class AccessibilitySignalService extends Disposable implements IAccessibilitySignalService {
74
readonly _serviceBrand: undefined;
75
private readonly sounds: Map<string, HTMLAudioElement>;
76
private readonly screenReaderAttached;
77
private readonly sentTelemetry;
78
79
constructor(
80
@IConfigurationService private readonly configurationService: IConfigurationService,
81
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
82
@ITelemetryService private readonly telemetryService: ITelemetryService,
83
) {
84
super();
85
this.sounds = new Map();
86
this.screenReaderAttached = observableFromEvent(this,
87
this.accessibilityService.onDidChangeScreenReaderOptimized,
88
() => /** @description accessibilityService.onDidChangeScreenReaderOptimized */ this.accessibilityService.isScreenReaderOptimized()
89
);
90
this.sentTelemetry = new Set<string>();
91
this.playingSounds = new Set<Sound>();
92
this._signalConfigValue = new CachedFunction((signal: AccessibilitySignal) => observableConfigValue<{
93
sound: EnabledState;
94
announcement: EnabledState;
95
}>(signal.settingsKey, { sound: 'off', announcement: 'off' }, this.configurationService));
96
this._signalEnabledState = new CachedFunction(
97
{ getCacheKey: getStructuralKey },
98
(arg: { signal: AccessibilitySignal; userGesture: boolean; modality?: AccessibilityModality | undefined }) => {
99
return derived(reader => {
100
/** @description sound enabled */
101
const setting = this._signalConfigValue.get(arg.signal).read(reader);
102
103
if (arg.modality === 'sound' || arg.modality === undefined) {
104
if (arg.signal.managesOwnEnablement || checkEnabledState(setting.sound, () => this.screenReaderAttached.read(reader), arg.userGesture)) {
105
return true;
106
}
107
}
108
if (arg.modality === 'announcement' || arg.modality === undefined) {
109
if (checkEnabledState(setting.announcement, () => this.screenReaderAttached.read(reader), arg.userGesture)) {
110
return true;
111
}
112
}
113
return false;
114
}).recomputeInitiallyAndOnChange(this._store);
115
}
116
);
117
}
118
119
public getEnabledState(signal: AccessibilitySignal, userGesture: boolean, modality?: AccessibilityModality | undefined): IValueWithChangeEvent<boolean> {
120
return new ValueWithChangeEventFromObservable(this._signalEnabledState.get({ signal, userGesture, modality }));
121
}
122
123
public async playSignal(signal: AccessibilitySignal, options: IAccessbilitySignalOptions = {}): Promise<void> {
124
const shouldPlayAnnouncement = options.modality === 'announcement' || options.modality === undefined;
125
const announcementMessage = options.customAlertMessage ?? signal.announcementMessage;
126
if (shouldPlayAnnouncement && this.isAnnouncementEnabled(signal, options.userGesture) && announcementMessage) {
127
this.accessibilityService.status(announcementMessage);
128
}
129
130
const shouldPlaySound = options.modality === 'sound' || options.modality === undefined;
131
if (shouldPlaySound && this.isSoundEnabled(signal, options.userGesture)) {
132
this.sendSignalTelemetry(signal, options.source);
133
await this.playSound(signal.sound.getSound(), options.allowManyInParallel);
134
}
135
}
136
137
public async playSignals(signals: (AccessibilitySignal | { signal: AccessibilitySignal; source: string })[]): Promise<void> {
138
for (const signal of signals) {
139
this.sendSignalTelemetry('signal' in signal ? signal.signal : signal, 'source' in signal ? signal.source : undefined);
140
}
141
const signalArray = signals.map(s => 'signal' in s ? s.signal : s);
142
const announcements = signalArray.filter(signal => this.isAnnouncementEnabled(signal)).map(s => s.announcementMessage);
143
if (announcements.length) {
144
this.accessibilityService.status(announcements.join(', '));
145
}
146
147
// Some sounds are reused. Don't play the same sound twice.
148
const sounds = new Set(signalArray.filter(signal => this.isSoundEnabled(signal)).map(signal => signal.sound.getSound()));
149
await Promise.all(Array.from(sounds).map(sound => this.playSound(sound, true)));
150
151
}
152
153
154
private sendSignalTelemetry(signal: AccessibilitySignal, source: string | undefined): void {
155
const isScreenReaderOptimized = this.accessibilityService.isScreenReaderOptimized();
156
const key = signal.name + (source ? `::${source}` : '') + (isScreenReaderOptimized ? '{screenReaderOptimized}' : '');
157
// Only send once per user session
158
if (this.sentTelemetry.has(key) || this.getVolumeInPercent() === 0) {
159
return;
160
}
161
this.sentTelemetry.add(key);
162
163
this.telemetryService.publicLog2<{
164
signal: string;
165
source: string;
166
isScreenReaderOptimized: boolean;
167
}, {
168
owner: 'hediet';
169
170
signal: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The signal that was played.' };
171
source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The source that triggered the signal (e.g. "diffEditorNavigation").' };
172
isScreenReaderOptimized: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user is using a screen reader' };
173
174
comment: 'This data is collected to understand how signals are used and if more signals should be added.';
175
}>('signal.played', {
176
signal: signal.name,
177
source: source ?? '',
178
isScreenReaderOptimized,
179
});
180
}
181
182
private getVolumeInPercent(): number {
183
const volume = this.configurationService.getValue<number>('accessibility.signalOptions.volume');
184
if (typeof volume !== 'number') {
185
return 50;
186
}
187
188
return Math.max(Math.min(volume, 100), 0);
189
}
190
191
private readonly playingSounds;
192
193
public async playSound(sound: Sound, allowManyInParallel = false): Promise<void> {
194
if (!allowManyInParallel && this.playingSounds.has(sound)) {
195
return;
196
}
197
this.playingSounds.add(sound);
198
const url = FileAccess.asBrowserUri(`vs/platform/accessibilitySignal/browser/media/${sound.fileName}`).toString(true);
199
200
try {
201
const sound = this.sounds.get(url);
202
if (sound) {
203
sound.volume = this.getVolumeInPercent() / 100;
204
sound.currentTime = 0;
205
await sound.play();
206
} else {
207
const playedSound = await playAudio(url, this.getVolumeInPercent() / 100);
208
this.sounds.set(url, playedSound);
209
}
210
} catch (e) {
211
if (!e.message.includes('play() can only be initiated by a user gesture')) {
212
// tracking this issue in #178642, no need to spam the console
213
console.error('Error while playing sound', e);
214
}
215
} finally {
216
this.playingSounds.delete(sound);
217
}
218
}
219
220
public playSignalLoop(signal: AccessibilitySignal, milliseconds: number): IDisposable {
221
let playing = true;
222
const playSound = () => {
223
if (playing) {
224
this.playSignal(signal, { allowManyInParallel: true }).finally(() => {
225
setTimeout(() => {
226
if (playing) {
227
playSound();
228
}
229
}, milliseconds);
230
});
231
}
232
};
233
playSound();
234
return toDisposable(() => playing = false);
235
}
236
237
private readonly _signalConfigValue;
238
239
private readonly _signalEnabledState;
240
241
public isAnnouncementEnabled(signal: AccessibilitySignal, userGesture?: boolean): boolean {
242
if (!signal.announcementMessage) {
243
return false;
244
}
245
return this._signalEnabledState.get({ signal, userGesture: !!userGesture, modality: 'announcement' }).get();
246
}
247
248
public isSoundEnabled(signal: AccessibilitySignal, userGesture?: boolean): boolean {
249
return this._signalEnabledState.get({ signal, userGesture: !!userGesture, modality: 'sound' }).get();
250
}
251
252
public onSoundEnabledChanged(signal: AccessibilitySignal): Event<void> {
253
return this.getEnabledState(signal, false).onDidChange;
254
}
255
256
public getDelayMs(signal: AccessibilitySignal, modality: AccessibilityModality, mode: 'line' | 'positional'): number {
257
if (!this.configurationService.getValue('accessibility.signalOptions.debouncePositionChanges')) {
258
return 0;
259
}
260
let value: { sound: number; announcement: number };
261
if (signal.name === AccessibilitySignal.errorAtPosition.name && mode === 'positional') {
262
value = this.configurationService.getValue('accessibility.signalOptions.experimental.delays.errorAtPosition');
263
} else if (signal.name === AccessibilitySignal.warningAtPosition.name && mode === 'positional') {
264
value = this.configurationService.getValue('accessibility.signalOptions.experimental.delays.warningAtPosition');
265
} else {
266
value = this.configurationService.getValue('accessibility.signalOptions.experimental.delays.general');
267
}
268
return modality === 'sound' ? value.sound : value.announcement;
269
}
270
}
271
272
type EnabledState = 'on' | 'off' | 'auto' | 'userGesture' | 'always' | 'never';
273
function checkEnabledState(state: EnabledState, getScreenReaderAttached: () => boolean, isTriggeredByUserGesture: boolean): boolean {
274
return state === 'on' || state === 'always' || (state === 'auto' && getScreenReaderAttached()) || state === 'userGesture' && isTriggeredByUserGesture;
275
}
276
277
/**
278
* Play the given audio url.
279
* @volume value between 0 and 1
280
*/
281
async function playAudio(url: string, volume: number): Promise<HTMLAudioElement> {
282
const disposables = new DisposableStore();
283
try {
284
return await doPlayAudio(url, volume, disposables);
285
} finally {
286
disposables.dispose();
287
}
288
}
289
290
function doPlayAudio(url: string, volume: number, disposables: DisposableStore): Promise<HTMLAudioElement> {
291
return new Promise<HTMLAudioElement>((resolve, reject) => {
292
const audio = new Audio(url);
293
audio.volume = volume;
294
disposables.add(addDisposableListener(audio, 'ended', () => {
295
resolve(audio);
296
}));
297
disposables.add(addDisposableListener(audio, 'error', (e) => {
298
// When the error event fires, ended might not be called
299
reject(e.error);
300
}));
301
audio.play().catch(e => {
302
// When play fails, the error event is not fired.
303
reject(e);
304
});
305
});
306
}
307
308
/**
309
* Corresponds to the audio files in ./media.
310
*/
311
export class Sound {
312
private static register(options: { fileName: string }): Sound {
313
const sound = new Sound(options.fileName);
314
return sound;
315
}
316
317
public static readonly error = Sound.register({ fileName: 'error.mp3' });
318
public static readonly warning = Sound.register({ fileName: 'warning.mp3' });
319
public static readonly success = Sound.register({ fileName: 'success.mp3' });
320
public static readonly foldedArea = Sound.register({ fileName: 'foldedAreas.mp3' });
321
public static readonly break = Sound.register({ fileName: 'break.mp3' });
322
public static readonly quickFixes = Sound.register({ fileName: 'quickFixes.mp3' });
323
public static readonly taskCompleted = Sound.register({ fileName: 'taskCompleted.mp3' });
324
public static readonly taskFailed = Sound.register({ fileName: 'taskFailed.mp3' });
325
public static readonly terminalBell = Sound.register({ fileName: 'terminalBell.mp3' });
326
public static readonly diffLineInserted = Sound.register({ fileName: 'diffLineInserted.mp3' });
327
public static readonly diffLineDeleted = Sound.register({ fileName: 'diffLineDeleted.mp3' });
328
public static readonly diffLineModified = Sound.register({ fileName: 'diffLineModified.mp3' });
329
public static readonly requestSent = Sound.register({ fileName: 'requestSent.mp3' });
330
public static readonly responseReceived1 = Sound.register({ fileName: 'responseReceived1.mp3' });
331
public static readonly responseReceived2 = Sound.register({ fileName: 'responseReceived2.mp3' });
332
public static readonly responseReceived3 = Sound.register({ fileName: 'responseReceived3.mp3' });
333
public static readonly responseReceived4 = Sound.register({ fileName: 'responseReceived4.mp3' });
334
public static readonly clear = Sound.register({ fileName: 'clear.mp3' });
335
public static readonly save = Sound.register({ fileName: 'save.mp3' });
336
public static readonly format = Sound.register({ fileName: 'format.mp3' });
337
public static readonly voiceRecordingStarted = Sound.register({ fileName: 'voiceRecordingStarted.mp3' });
338
public static readonly voiceRecordingStopped = Sound.register({ fileName: 'voiceRecordingStopped.mp3' });
339
public static readonly progress = Sound.register({ fileName: 'progress.mp3' });
340
public static readonly chatEditModifiedFile = Sound.register({ fileName: 'chatEditModifiedFile.mp3' });
341
public static readonly editsKept = Sound.register({ fileName: 'editsKept.mp3' });
342
public static readonly editsUndone = Sound.register({ fileName: 'editsUndone.mp3' });
343
public static readonly nextEditSuggestion = Sound.register({ fileName: 'nextEditSuggestion.mp3' });
344
public static readonly terminalCommandSucceeded = Sound.register({ fileName: 'terminalCommandSucceeded.mp3' });
345
public static readonly chatUserActionRequired = Sound.register({ fileName: 'chatUserActionRequired.mp3' });
346
public static readonly codeActionTriggered = Sound.register({ fileName: 'codeActionTriggered.mp3' });
347
public static readonly codeActionApplied = Sound.register({ fileName: 'codeActionApplied.mp3' });
348
349
private constructor(public readonly fileName: string) { }
350
}
351
352
export class SoundSource {
353
constructor(
354
public readonly randomOneOf: Sound[]
355
) { }
356
357
public getSound(deterministic = false): Sound {
358
if (deterministic || this.randomOneOf.length === 1) {
359
return this.randomOneOf[0];
360
} else {
361
const index = Math.floor(Math.random() * this.randomOneOf.length);
362
return this.randomOneOf[index];
363
}
364
}
365
}
366
367
export class AccessibilitySignal {
368
private constructor(
369
public readonly sound: SoundSource,
370
public readonly name: string,
371
public readonly legacySoundSettingsKey: string | undefined,
372
public readonly settingsKey: string,
373
public readonly legacyAnnouncementSettingsKey: string | undefined,
374
public readonly announcementMessage: string | undefined,
375
public readonly managesOwnEnablement: boolean = false
376
) { }
377
378
private static _signals = new Set<AccessibilitySignal>();
379
private static register(options: {
380
name: string;
381
sound: Sound | {
382
/**
383
* Gaming and other apps often play a sound variant when the same event happens again
384
* for an improved experience. This option enables playing a random sound.
385
*/
386
randomOneOf: Sound[];
387
};
388
legacySoundSettingsKey?: string;
389
settingsKey: string;
390
legacyAnnouncementSettingsKey?: string;
391
announcementMessage?: string;
392
delaySettingsKey?: string;
393
managesOwnEnablement?: boolean;
394
}): AccessibilitySignal {
395
const soundSource = new SoundSource('randomOneOf' in options.sound ? options.sound.randomOneOf : [options.sound]);
396
const signal = new AccessibilitySignal(
397
soundSource,
398
options.name,
399
options.legacySoundSettingsKey,
400
options.settingsKey,
401
options.legacyAnnouncementSettingsKey,
402
options.announcementMessage,
403
options.managesOwnEnablement
404
);
405
AccessibilitySignal._signals.add(signal);
406
return signal;
407
}
408
409
public static get allAccessibilitySignals() {
410
return [...this._signals];
411
}
412
413
public static readonly errorAtPosition = AccessibilitySignal.register({
414
name: localize('accessibilitySignals.positionHasError.name', 'Error at Position'),
415
sound: Sound.error,
416
announcementMessage: localize('accessibility.signals.positionHasError', 'Error'),
417
settingsKey: 'accessibility.signals.positionHasError',
418
delaySettingsKey: 'accessibility.signalOptions.delays.errorAtPosition'
419
});
420
public static readonly warningAtPosition = AccessibilitySignal.register({
421
name: localize('accessibilitySignals.positionHasWarning.name', 'Warning at Position'),
422
sound: Sound.warning,
423
announcementMessage: localize('accessibility.signals.positionHasWarning', 'Warning'),
424
settingsKey: 'accessibility.signals.positionHasWarning',
425
delaySettingsKey: 'accessibility.signalOptions.delays.warningAtPosition'
426
});
427
428
public static readonly errorOnLine = AccessibilitySignal.register({
429
name: localize('accessibilitySignals.lineHasError.name', 'Error on Line'),
430
sound: Sound.error,
431
legacySoundSettingsKey: 'audioCues.lineHasError',
432
legacyAnnouncementSettingsKey: 'accessibility.alert.error',
433
announcementMessage: localize('accessibility.signals.lineHasError', 'Error on Line'),
434
settingsKey: 'accessibility.signals.lineHasError',
435
});
436
437
public static readonly warningOnLine = AccessibilitySignal.register({
438
name: localize('accessibilitySignals.lineHasWarning.name', 'Warning on Line'),
439
sound: Sound.warning,
440
legacySoundSettingsKey: 'audioCues.lineHasWarning',
441
legacyAnnouncementSettingsKey: 'accessibility.alert.warning',
442
announcementMessage: localize('accessibility.signals.lineHasWarning', 'Warning on Line'),
443
settingsKey: 'accessibility.signals.lineHasWarning',
444
});
445
public static readonly foldedArea = AccessibilitySignal.register({
446
name: localize('accessibilitySignals.lineHasFoldedArea.name', 'Folded Area on Line'),
447
sound: Sound.foldedArea,
448
legacySoundSettingsKey: 'audioCues.lineHasFoldedArea',
449
legacyAnnouncementSettingsKey: 'accessibility.alert.foldedArea',
450
announcementMessage: localize('accessibility.signals.lineHasFoldedArea', 'Folded'),
451
settingsKey: 'accessibility.signals.lineHasFoldedArea',
452
});
453
public static readonly break = AccessibilitySignal.register({
454
name: localize('accessibilitySignals.lineHasBreakpoint.name', 'Breakpoint on Line'),
455
sound: Sound.break,
456
legacySoundSettingsKey: 'audioCues.lineHasBreakpoint',
457
legacyAnnouncementSettingsKey: 'accessibility.alert.breakpoint',
458
announcementMessage: localize('accessibility.signals.lineHasBreakpoint', 'Breakpoint'),
459
settingsKey: 'accessibility.signals.lineHasBreakpoint',
460
});
461
public static readonly inlineSuggestion = AccessibilitySignal.register({
462
name: localize('accessibilitySignals.lineHasInlineSuggestion.name', 'Inline Suggestion on Line'),
463
sound: Sound.quickFixes,
464
legacySoundSettingsKey: 'audioCues.lineHasInlineSuggestion',
465
settingsKey: 'accessibility.signals.lineHasInlineSuggestion',
466
});
467
public static readonly nextEditSuggestion = AccessibilitySignal.register({
468
name: localize('accessibilitySignals.nextEditSuggestion.name', 'Next Edit Suggestion on Line'),
469
sound: Sound.nextEditSuggestion,
470
legacySoundSettingsKey: 'audioCues.nextEditSuggestion',
471
settingsKey: 'accessibility.signals.nextEditSuggestion',
472
announcementMessage: localize('accessibility.signals.nextEditSuggestion', 'Next Edit Suggestion'),
473
});
474
public static readonly terminalQuickFix = AccessibilitySignal.register({
475
name: localize('accessibilitySignals.terminalQuickFix.name', 'Terminal Quick Fix'),
476
sound: Sound.quickFixes,
477
legacySoundSettingsKey: 'audioCues.terminalQuickFix',
478
legacyAnnouncementSettingsKey: 'accessibility.alert.terminalQuickFix',
479
announcementMessage: localize('accessibility.signals.terminalQuickFix', 'Quick Fix'),
480
settingsKey: 'accessibility.signals.terminalQuickFix',
481
});
482
483
public static readonly onDebugBreak = AccessibilitySignal.register({
484
name: localize('accessibilitySignals.onDebugBreak.name', 'Debugger Stopped on Breakpoint'),
485
sound: Sound.break,
486
legacySoundSettingsKey: 'audioCues.onDebugBreak',
487
legacyAnnouncementSettingsKey: 'accessibility.alert.onDebugBreak',
488
announcementMessage: localize('accessibility.signals.onDebugBreak', 'Breakpoint'),
489
settingsKey: 'accessibility.signals.onDebugBreak',
490
});
491
492
public static readonly noInlayHints = AccessibilitySignal.register({
493
name: localize('accessibilitySignals.noInlayHints', 'No Inlay Hints on Line'),
494
sound: Sound.error,
495
legacySoundSettingsKey: 'audioCues.noInlayHints',
496
legacyAnnouncementSettingsKey: 'accessibility.alert.noInlayHints',
497
announcementMessage: localize('accessibility.signals.noInlayHints', 'No Inlay Hints'),
498
settingsKey: 'accessibility.signals.noInlayHints',
499
});
500
501
public static readonly taskCompleted = AccessibilitySignal.register({
502
name: localize('accessibilitySignals.taskCompleted', 'Task Completed'),
503
sound: Sound.taskCompleted,
504
legacySoundSettingsKey: 'audioCues.taskCompleted',
505
legacyAnnouncementSettingsKey: 'accessibility.alert.taskCompleted',
506
announcementMessage: localize('accessibility.signals.taskCompleted', 'Task Completed'),
507
settingsKey: 'accessibility.signals.taskCompleted',
508
});
509
510
public static readonly taskFailed = AccessibilitySignal.register({
511
name: localize('accessibilitySignals.taskFailed', 'Task Failed'),
512
sound: Sound.taskFailed,
513
legacySoundSettingsKey: 'audioCues.taskFailed',
514
legacyAnnouncementSettingsKey: 'accessibility.alert.taskFailed',
515
announcementMessage: localize('accessibility.signals.taskFailed', 'Task Failed'),
516
settingsKey: 'accessibility.signals.taskFailed',
517
});
518
519
public static readonly terminalCommandFailed = AccessibilitySignal.register({
520
name: localize('accessibilitySignals.terminalCommandFailed', 'Terminal Command Failed'),
521
sound: Sound.error,
522
legacySoundSettingsKey: 'audioCues.terminalCommandFailed',
523
legacyAnnouncementSettingsKey: 'accessibility.alert.terminalCommandFailed',
524
announcementMessage: localize('accessibility.signals.terminalCommandFailed', 'Command Failed'),
525
settingsKey: 'accessibility.signals.terminalCommandFailed',
526
});
527
528
public static readonly terminalCommandSucceeded = AccessibilitySignal.register({
529
name: localize('accessibilitySignals.terminalCommandSucceeded', 'Terminal Command Succeeded'),
530
sound: Sound.terminalCommandSucceeded,
531
announcementMessage: localize('accessibility.signals.terminalCommandSucceeded', 'Command Succeeded'),
532
settingsKey: 'accessibility.signals.terminalCommandSucceeded',
533
});
534
535
public static readonly terminalBell = AccessibilitySignal.register({
536
name: localize('accessibilitySignals.terminalBell', 'Terminal Bell'),
537
sound: Sound.terminalBell,
538
legacySoundSettingsKey: 'audioCues.terminalBell',
539
legacyAnnouncementSettingsKey: 'accessibility.alert.terminalBell',
540
announcementMessage: localize('accessibility.signals.terminalBell', 'Terminal Bell'),
541
settingsKey: 'accessibility.signals.terminalBell',
542
});
543
544
public static readonly notebookCellCompleted = AccessibilitySignal.register({
545
name: localize('accessibilitySignals.notebookCellCompleted', 'Notebook Cell Completed'),
546
sound: Sound.taskCompleted,
547
legacySoundSettingsKey: 'audioCues.notebookCellCompleted',
548
legacyAnnouncementSettingsKey: 'accessibility.alert.notebookCellCompleted',
549
announcementMessage: localize('accessibility.signals.notebookCellCompleted', 'Notebook Cell Completed'),
550
settingsKey: 'accessibility.signals.notebookCellCompleted',
551
});
552
553
public static readonly notebookCellFailed = AccessibilitySignal.register({
554
name: localize('accessibilitySignals.notebookCellFailed', 'Notebook Cell Failed'),
555
sound: Sound.taskFailed,
556
legacySoundSettingsKey: 'audioCues.notebookCellFailed',
557
legacyAnnouncementSettingsKey: 'accessibility.alert.notebookCellFailed',
558
announcementMessage: localize('accessibility.signals.notebookCellFailed', 'Notebook Cell Failed'),
559
settingsKey: 'accessibility.signals.notebookCellFailed',
560
});
561
562
public static readonly diffLineInserted = AccessibilitySignal.register({
563
name: localize('accessibilitySignals.diffLineInserted', 'Diff Line Inserted'),
564
sound: Sound.diffLineInserted,
565
legacySoundSettingsKey: 'audioCues.diffLineInserted',
566
settingsKey: 'accessibility.signals.diffLineInserted',
567
});
568
569
public static readonly diffLineDeleted = AccessibilitySignal.register({
570
name: localize('accessibilitySignals.diffLineDeleted', 'Diff Line Deleted'),
571
sound: Sound.diffLineDeleted,
572
legacySoundSettingsKey: 'audioCues.diffLineDeleted',
573
settingsKey: 'accessibility.signals.diffLineDeleted',
574
});
575
576
public static readonly diffLineModified = AccessibilitySignal.register({
577
name: localize('accessibilitySignals.diffLineModified', 'Diff Line Modified'),
578
sound: Sound.diffLineModified,
579
legacySoundSettingsKey: 'audioCues.diffLineModified',
580
settingsKey: 'accessibility.signals.diffLineModified',
581
});
582
583
public static readonly chatEditModifiedFile = AccessibilitySignal.register({
584
name: localize('accessibilitySignals.chatEditModifiedFile', 'Chat Edit Modified File'),
585
sound: Sound.chatEditModifiedFile,
586
announcementMessage: localize('accessibility.signals.chatEditModifiedFile', 'File Modified from Chat Edits'),
587
settingsKey: 'accessibility.signals.chatEditModifiedFile',
588
});
589
590
public static readonly chatRequestSent = AccessibilitySignal.register({
591
name: localize('accessibilitySignals.chatRequestSent', 'Chat Request Sent'),
592
sound: Sound.requestSent,
593
legacySoundSettingsKey: 'audioCues.chatRequestSent',
594
legacyAnnouncementSettingsKey: 'accessibility.alert.chatRequestSent',
595
announcementMessage: localize('accessibility.signals.chatRequestSent', 'Chat Request Sent'),
596
settingsKey: 'accessibility.signals.chatRequestSent',
597
});
598
599
public static readonly chatResponseReceived = AccessibilitySignal.register({
600
name: localize('accessibilitySignals.chatResponseReceived', 'Chat Response Received'),
601
legacySoundSettingsKey: 'audioCues.chatResponseReceived',
602
sound: {
603
randomOneOf: [
604
Sound.responseReceived1,
605
Sound.responseReceived2,
606
Sound.responseReceived3,
607
Sound.responseReceived4
608
]
609
},
610
settingsKey: 'accessibility.signals.chatResponseReceived'
611
});
612
613
public static readonly codeActionTriggered = AccessibilitySignal.register({
614
name: localize('accessibilitySignals.codeActionRequestTriggered', 'Code Action Request Triggered'),
615
sound: Sound.codeActionTriggered,
616
legacySoundSettingsKey: 'audioCues.codeActionRequestTriggered',
617
legacyAnnouncementSettingsKey: 'accessibility.alert.codeActionRequestTriggered',
618
announcementMessage: localize('accessibility.signals.codeActionRequestTriggered', 'Code Action Request Triggered'),
619
settingsKey: 'accessibility.signals.codeActionTriggered',
620
});
621
622
public static readonly codeActionApplied = AccessibilitySignal.register({
623
name: localize('accessibilitySignals.codeActionApplied', 'Code Action Applied'),
624
legacySoundSettingsKey: 'audioCues.codeActionApplied',
625
sound: Sound.codeActionApplied,
626
settingsKey: 'accessibility.signals.codeActionApplied'
627
});
628
629
630
public static readonly progress = AccessibilitySignal.register({
631
name: localize('accessibilitySignals.progress', 'Progress'),
632
sound: Sound.progress,
633
legacySoundSettingsKey: 'audioCues.chatResponsePending',
634
legacyAnnouncementSettingsKey: 'accessibility.alert.progress',
635
announcementMessage: localize('accessibility.signals.progress', 'Progress'),
636
settingsKey: 'accessibility.signals.progress'
637
});
638
639
public static readonly clear = AccessibilitySignal.register({
640
name: localize('accessibilitySignals.clear', 'Clear'),
641
sound: Sound.clear,
642
legacySoundSettingsKey: 'audioCues.clear',
643
legacyAnnouncementSettingsKey: 'accessibility.alert.clear',
644
announcementMessage: localize('accessibility.signals.clear', 'Clear'),
645
settingsKey: 'accessibility.signals.clear'
646
});
647
648
public static readonly save = AccessibilitySignal.register({
649
name: localize('accessibilitySignals.save', 'Save'),
650
sound: Sound.save,
651
legacySoundSettingsKey: 'audioCues.save',
652
legacyAnnouncementSettingsKey: 'accessibility.alert.save',
653
announcementMessage: localize('accessibility.signals.save', 'Save'),
654
settingsKey: 'accessibility.signals.save'
655
});
656
657
public static readonly format = AccessibilitySignal.register({
658
name: localize('accessibilitySignals.format', 'Format'),
659
sound: Sound.format,
660
legacySoundSettingsKey: 'audioCues.format',
661
legacyAnnouncementSettingsKey: 'accessibility.alert.format',
662
announcementMessage: localize('accessibility.signals.format', 'Format'),
663
settingsKey: 'accessibility.signals.format'
664
});
665
666
public static readonly voiceRecordingStarted = AccessibilitySignal.register({
667
name: localize('accessibilitySignals.voiceRecordingStarted', 'Voice Recording Started'),
668
sound: Sound.voiceRecordingStarted,
669
legacySoundSettingsKey: 'audioCues.voiceRecordingStarted',
670
settingsKey: 'accessibility.signals.voiceRecordingStarted'
671
});
672
673
public static readonly voiceRecordingStopped = AccessibilitySignal.register({
674
name: localize('accessibilitySignals.voiceRecordingStopped', 'Voice Recording Stopped'),
675
sound: Sound.voiceRecordingStopped,
676
legacySoundSettingsKey: 'audioCues.voiceRecordingStopped',
677
settingsKey: 'accessibility.signals.voiceRecordingStopped'
678
});
679
680
public static readonly editsKept = AccessibilitySignal.register({
681
name: localize('accessibilitySignals.editsKept', 'Edits Kept'),
682
sound: Sound.editsKept,
683
announcementMessage: localize('accessibility.signals.editsKept', 'Edits Kept'),
684
settingsKey: 'accessibility.signals.editsKept',
685
});
686
687
public static readonly editsUndone = AccessibilitySignal.register({
688
name: localize('accessibilitySignals.editsUndone', 'Undo Edits'),
689
sound: Sound.editsUndone,
690
announcementMessage: localize('accessibility.signals.editsUndone', 'Edits Undone'),
691
settingsKey: 'accessibility.signals.editsUndone',
692
});
693
694
public static readonly chatUserActionRequired = AccessibilitySignal.register({
695
name: localize('accessibilitySignals.chatUserActionRequired', 'Chat User Action Required'),
696
sound: Sound.chatUserActionRequired,
697
announcementMessage: localize('accessibility.signals.chatUserActionRequired', 'Chat User Action Required'),
698
settingsKey: 'accessibility.signals.chatUserActionRequired',
699
managesOwnEnablement: true
700
});
701
}
702
703