diff --git a/build/filters.js b/build/filters.js index 872ed47f9ea..6f746545e9b 100644 --- a/build/filters.js +++ b/build/filters.js @@ -32,7 +32,7 @@ module.exports.unicodeFilter = [ '!LICENSES.chromium.html', '!**/LICENSE', - '!**/*.{dll,exe,png,bmp,jpg,scpt,cur,ttf,woff,eot,template,ico,icns}', + '!**/*.{dll,exe,png,bmp,jpg,scpt,cur,ttf,woff,eot,template,ico,icns,webm}', '!**/test/**', '!**/*.test.ts', '!**/*.{d.ts,json,md}', @@ -108,7 +108,7 @@ module.exports.indentationFilter = [ '!src/vs/*/**/*.d.ts', '!src/typings/**/*.d.ts', '!extensions/**/*.d.ts', - '!**/*.{svg,exe,png,bmp,jpg,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,template,yaml,yml,d.ts.recipe,ico,icns,plist}', + '!**/*.{svg,exe,png,bmp,jpg,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,template,yaml,yml,d.ts.recipe,ico,icns,plist,webm}', '!build/{lib,download,linux,darwin}/**/*.js', '!build/**/*.sh', '!build/azure-pipelines/**/*.js', @@ -133,6 +133,7 @@ module.exports.copyrightFilter = [ '!**/*.bat', '!**/*.cmd', '!**/*.ico', + '!**/*.webm', '!**/*.icns', '!**/*.xml', '!**/*.sh', diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index ecbac6597a7..8160ace0dce 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -371,6 +371,23 @@ export namespace Event { handler(undefined); return event(e => handler(e)); } + + export function runAndSubscribeWithStore(event: Event, handler: (e: T | undefined, disposableStore: DisposableStore) => any): IDisposable { + let store: DisposableStore | null = null; + + function run(e: T | undefined) { + store?.dispose(); + store = new DisposableStore(); + handler(e, store); + } + + run(undefined); + const disposable = event(e => run(e)); + return toDisposable(() => { + disposable.dispose(); + store?.dispose(); + }); + } } export type Listener = [(e: T) => void, any] | ((e: T) => void); diff --git a/src/vs/code/electron-browser/workbench/workbench.html b/src/vs/code/electron-browser/workbench/workbench.html index 0efd6539960..47066f520be 100644 --- a/src/vs/code/electron-browser/workbench/workbench.html +++ b/src/vs/code/electron-browser/workbench/workbench.html @@ -3,7 +3,7 @@ - + diff --git a/src/vs/code/electron-sandbox/workbench/workbench.html b/src/vs/code/electron-sandbox/workbench/workbench.html index 0efd6539960..47066f520be 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.html +++ b/src/vs/code/electron-sandbox/workbench/workbench.html @@ -3,7 +3,7 @@ - + diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts new file mode 100644 index 00000000000..5a8184b1663 --- /dev/null +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueContribution.ts @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { Event } from 'vs/base/common/event'; +import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { raceTimeout } from 'vs/base/common/async'; +import { FileAccess } from 'vs/base/common/network'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; + +export class AudioCueContribution extends DisposableStore implements IWorkbenchContribution { + constructor( + @IDebugService readonly debugService: IDebugService, + @IEditorService readonly editorService: IEditorService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, + ) { + super(); + + this.add(Event.runAndSubscribeWithStore(editorService.onDidActiveEditorChange, (_, store) => { + let lastLineNumber = -1; + + const activeTextEditorControl = editorService.activeTextEditorControl; + if (isCodeEditor(activeTextEditorControl)) { + store.add( + activeTextEditorControl.onDidChangeCursorPosition(() => { + const model = activeTextEditorControl.getModel(); + if (!model) { + return; + } + const position = activeTextEditorControl.getPosition(); + if (!position) { + return; + } + const lineNumber = position.lineNumber; + if (lineNumber === lastLineNumber) { + return; + } + lastLineNumber = lineNumber; + + const uri = model.uri; + + const breakpoints = debugService.getModel().getBreakpoints({ uri, lineNumber }); + const hasBreakpoints = breakpoints.length > 0; + + if (hasBreakpoints) { + this.handleBreakpointOnLine(); + } + }) + ); + } + })); + } + + private get audioCuesEnabled(): boolean { + const value = this._configurationService.getValue<'smart' | 'on' | 'off'>('audioCues.enabled'); + if (value === 'on') { + return true; + } else if (value === 'smart') { + return this.accessibilityService.isScreenReaderOptimized(); + } else { + return false; + } + } + + public handleBreakpointOnLine(): void { + this.playSound('breakpointHit'); + } + + private async playSound(fileName: string) { + if (!this.audioCuesEnabled) { + return; + } + + const url = FileAccess.asBrowserUri(`vs/workbench/contrib/audioCues/browser/media/${fileName}.webm`, require).toString(); + const audio = new Audio(url); + + try { + // Don't play when loading takes more than 1s, due to loading, decoding or playing issues. + // Delayed sounds are very confusing. + await raceTimeout(audio.play(), 1000); + } catch (e) { + audio.remove(); + } + } +} diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts new file mode 100644 index 00000000000..7356c411cdd --- /dev/null +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { AudioCueContribution } from 'vs/workbench/contrib/audioCues/browser/audioCueContribution'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AudioCueContribution, LifecyclePhase.Eventually); + +Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + 'properties': { + 'audioCues.enabled': { + 'type': 'string', + 'description': localize('audioCues.enabled', "Controls whether audio cues are enabled."), + 'enum': ['smart', 'on', 'off'], + 'default': 'smart', + 'enumDescriptions': [ + localize('audioCues.enabled.smart', "Enable audio cues when a screen reader is attached."), + localize('audioCues.enabled.on', "Enable audio cues."), + localize('audioCues.enabled.off', "Disable audio cues.") + ], + } + } +}); diff --git a/src/vs/workbench/contrib/audioCues/browser/media/breakpointHit.webm b/src/vs/workbench/contrib/audioCues/browser/media/breakpointHit.webm new file mode 100644 index 00000000000..030f938a009 Binary files /dev/null and b/src/vs/workbench/contrib/audioCues/browser/media/breakpointHit.webm differ diff --git a/src/vs/workbench/contrib/audioCues/browser/media/error.webm b/src/vs/workbench/contrib/audioCues/browser/media/error.webm new file mode 100644 index 00000000000..fbaccf5b439 Binary files /dev/null and b/src/vs/workbench/contrib/audioCues/browser/media/error.webm differ diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 322fb90ab0c..81da88ba314 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -328,4 +328,7 @@ import 'vs/workbench/contrib/workspaces/browser/workspaces.contribution'; // List import 'vs/workbench/contrib/list/browser/list.contribution'; +// Audio Cues +import 'vs/workbench/contrib/audioCues/browser/audioCues.contribution'; + //#endregion