diff --git a/src/vs/platform/native/common/native.ts b/src/vs/platform/native/common/native.ts index 7d5bec764da..2a87cf7d285 100644 --- a/src/vs/platform/native/common/native.ts +++ b/src/vs/platform/native/common/native.ts @@ -121,6 +121,8 @@ export interface ICommonNativeHostService { getOSColorScheme(): Promise; + hasWSLFeatureInstalled(): Promise; + // Process killProcess(pid: number, code: string): Promise; diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index 92f431facc6..84d42eb4877 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -40,6 +40,7 @@ import { IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-m import { isWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { IWorkspacesManagementMainService } from 'vs/platform/workspaces/electron-main/workspacesManagementMainService'; import { VSBuffer } from 'vs/base/common/buffer'; +import { hasWSLFeatureInstalled } from 'vs/platform/remote/node/wsl'; export interface INativeHostMainService extends AddFirstParameterToFunctions /* only methods, not events */, number | undefined /* window ID */> { } @@ -568,6 +569,12 @@ export class NativeHostMainService extends Disposable implements INativeHostMain } + // WSL + public async hasWSLFeatureInstalled(): Promise { + return isWindows && hasWSLFeatureInstalled(); + } + + //#endregion diff --git a/src/vs/platform/remote/node/wsl.ts b/src/vs/platform/remote/node/wsl.ts new file mode 100644 index 00000000000..ef49a830f16 --- /dev/null +++ b/src/vs/platform/remote/node/wsl.ts @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as os from 'os'; +import * as cp from 'child_process'; +import { promises as fs } from 'fs'; +import path = require('path'); + +let hasWSLFeaturePromise: Promise | undefined; + +export async function hasWSLFeatureInstalled(refresh = false): Promise { + if (hasWSLFeaturePromise === undefined || refresh) { + hasWSLFeaturePromise = testWSLFeatureInstalled(); + } + return hasWSLFeaturePromise; +} + +async function testWSLFeatureInstalled(): Promise { + const windowsBuildNumber = getWindowsBuildNumber(); + if (windowsBuildNumber === undefined) { + return false; + } + if (windowsBuildNumber >= 22000) { + const wslExePath = getWSLExecutablePath(); + if (wslExePath) { + return new Promise(s => { + cp.execFile(wslExePath, ['--status'], err => s(!err)); + }); + } + } else { + const dllPath = getLxssManagerDllPath(); + if (dllPath) { + try { + if ((await fs.stat(dllPath)).isFile()) { + return true; + } + } catch (e) { + } + } + } + return false; +} + +function getWindowsBuildNumber(): number | undefined { + const osVersion = (/(\d+)\.(\d+)\.(\d+)/g).exec(os.release()); + if (osVersion) { + return parseInt(osVersion[3]); + } + return undefined; +} + +function getSystem32Path(subPath: string): string | undefined { + const systemRoot = process.env['SystemRoot']; + if (systemRoot) { + const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); + return path.join(systemRoot, is32ProcessOn64Windows ? 'Sysnative' : 'System32', subPath); + } + return undefined; +} + +function getWSLExecutablePath(): string | undefined { + return getSystem32Path('wsl.exe'); +} + +/** + * In builds < 22000 this dll inidcates that WSL is installed + */ +function getLxssManagerDllPath(): string | undefined { + return getSystem32Path('lxss\\LxssManager.dll'); +} diff --git a/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts index f3f098ff4c6..74be20ebf6c 100644 --- a/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts @@ -7,11 +7,11 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { Disposable } from 'vs/base/common/lifecycle'; -import { isMacintosh } from 'vs/base/common/platform'; +import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchContributionsExtensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ILabelService } from 'vs/platform/label/common/label'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { Schemas } from 'vs/base/common/network'; @@ -27,6 +27,9 @@ import { OpenLocalFileFolderCommand, OpenLocalFileCommand, OpenLocalFolderComman import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { TELEMETRY_SETTING_ID } from 'vs/platform/telemetry/common/telemetry'; import { getTelemetryLevel } from 'vs/platform/telemetry/common/telemetryUtils'; +import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; class RemoteAgentDiagnosticListener implements IWorkbenchContribution { constructor( @@ -131,11 +134,49 @@ class RemoteEmptyWorkbenchPresentation extends Disposable implements IWorkbenchC } } +/** + * Sets the 'wslFeatureInstalled' context key if the WSL feature is or was installed on this machine. + */ +class WSLContextKeyInitializer extends Disposable implements IWorkbenchContribution { + + constructor( + @IContextKeyService contextKeyService: IContextKeyService, + @INativeHostService nativeHostService: INativeHostService, + @IStorageService storageService: IStorageService, + @ILifecycleService lifecycleService: ILifecycleService + ) { + super(); + + const contextKeyId = 'wslFeatureInstalled'; + const storageKey = 'remote.wslFeatureInstalled'; + + const defaultValue = storageService.getBoolean(storageKey, StorageScope.APPLICATION, undefined); + + const hasWSLFeatureContext = new RawContextKey(contextKeyId, !!defaultValue, nls.localize('wslFeatureInstalled', "Whether the platform has the WSL feature installed")); + const contextKey = hasWSLFeatureContext.bindTo(contextKeyService); + + if (defaultValue === undefined) { + lifecycleService.when(LifecyclePhase.Eventually).then(async () => { + nativeHostService.hasWSLFeatureInstalled().then(res => { + if (res) { + contextKey.set(true); + // once detected, set to true + storageService.store(storageKey, true, StorageScope.APPLICATION, StorageTarget.MACHINE); + } + }); + }); + } + } +} + const workbenchContributionsRegistry = Registry.as(WorkbenchContributionsExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentDiagnosticListener, 'RemoteAgentDiagnosticListener', LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteExtensionHostEnvironmentUpdater, 'RemoteExtensionHostEnvironmentUpdater', LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteTelemetryEnablementUpdater, 'RemoteTelemetryEnablementUpdater', LifecyclePhase.Ready); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteEmptyWorkbenchPresentation, 'RemoteEmptyWorkbenchPresentation', LifecyclePhase.Ready); +if (isWindows) { + workbenchContributionsRegistry.registerWorkbenchContribution(WSLContextKeyInitializer, 'WSLContextKeyInitializer', LifecyclePhase.Ready); +} Registry.as(ConfigurationExtensions.Configuration) .registerConfiguration({ diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index e0017227ce1..0c6d93f52bf 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -240,6 +240,7 @@ export class TestNativeHostService implements INativeHostService { async getOSStatistics(): Promise { return Object.create(null); } async getOSVirtualMachineHint(): Promise { return 0; } async getOSColorScheme(): Promise { return { dark: true, highContrast: false }; } + async hasWSLFeatureInstalled(): Promise { return false; } async killProcess(): Promise { } async setDocumentEdited(edited: boolean): Promise { } async openExternal(url: string): Promise { return false; }