379 lines
17 KiB
TypeScript
379 lines
17 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import product from 'vs/platform/product/common/product';
|
|
import { zoomLevelToZoomFactor } from 'vs/platform/windows/common/windows';
|
|
import { Workbench } from 'vs/workbench/browser/workbench';
|
|
import { NativeWindow } from 'vs/workbench/electron-sandbox/window';
|
|
import { setZoomLevel, setZoomFactor, setFullscreen } from 'vs/base/browser/browser';
|
|
import { domContentLoaded } from 'vs/base/browser/dom';
|
|
import { onUnexpectedError } from 'vs/base/common/errors';
|
|
import { URI } from 'vs/base/common/uri';
|
|
import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService';
|
|
import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService, NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
|
|
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
|
import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceInitializationPayload, reviveIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
|
import { ILoggerService, ILogService } from 'vs/platform/log/common/log';
|
|
import { NativeStorageService } from 'vs/platform/storage/electron-sandbox/storageService';
|
|
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
|
import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
|
import { IStorageService } from 'vs/platform/storage/common/storage';
|
|
import { Disposable } from 'vs/base/common/lifecycle';
|
|
import { IMainProcessService, ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services';
|
|
import { SharedProcessService } from 'vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessService';
|
|
import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-sandbox/remoteAuthorityResolverService';
|
|
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
|
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-sandbox/remoteAgentServiceImpl';
|
|
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
|
import { FileService } from 'vs/platform/files/common/fileService';
|
|
import { IFileService } from 'vs/platform/files/common/files';
|
|
import { RemoteFileSystemProvider } from 'vs/workbench/services/remote/common/remoteAgentFileSystemChannel';
|
|
import { ConfigurationCache } from 'vs/workbench/services/configuration/common/configurationCache';
|
|
import { ISignService } from 'vs/platform/sign/common/sign';
|
|
import { basename } from 'vs/base/common/path';
|
|
import { IProductService } from 'vs/platform/product/common/productService';
|
|
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
|
import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHostService';
|
|
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
|
|
import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
|
|
import { KeyboardLayoutService } from 'vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayout';
|
|
import { IKeyboardLayoutService } from 'vs/platform/keyboardLayout/common/keyboardLayout';
|
|
import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
|
|
import { LoggerChannelClient, LogLevelChannelClient } from 'vs/platform/log/common/logIpc';
|
|
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
|
|
import { NativeLogService } from 'vs/workbench/services/log/electron-sandbox/logService';
|
|
import { WorkspaceTrustEnablementService, WorkspaceTrustManagementService } from 'vs/workbench/services/workspaces/common/workspaceTrust';
|
|
import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
|
|
import { registerWindowDriver } from 'vs/platform/driver/electron-sandbox/driver';
|
|
import { safeStringify } from 'vs/base/common/objects';
|
|
import { ISharedProcessWorkerWorkbenchService, SharedProcessWorkerWorkbenchService } from 'vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessWorkerWorkbenchService';
|
|
import { isMacintosh } from 'vs/base/common/platform';
|
|
import { Schemas } from 'vs/base/common/network';
|
|
|
|
export abstract class SharedDesktopMain extends Disposable {
|
|
|
|
constructor(
|
|
protected readonly configuration: INativeWorkbenchConfiguration
|
|
) {
|
|
super();
|
|
|
|
this.init();
|
|
}
|
|
|
|
private init(): void {
|
|
|
|
// Massage configuration file URIs
|
|
this.reviveUris();
|
|
|
|
// Browser config
|
|
const zoomLevel = this.configuration.zoomLevel || 0;
|
|
setZoomFactor(zoomLevelToZoomFactor(zoomLevel));
|
|
setZoomLevel(zoomLevel, true /* isTrusted */);
|
|
setFullscreen(!!this.configuration.fullscreen);
|
|
}
|
|
|
|
private reviveUris() {
|
|
|
|
// Workspace
|
|
const workspace = reviveIdentifier(this.configuration.workspace);
|
|
if (isWorkspaceIdentifier(workspace) || isSingleFolderWorkspaceIdentifier(workspace)) {
|
|
this.configuration.workspace = workspace;
|
|
}
|
|
|
|
// Files
|
|
const filesToWait = this.configuration.filesToWait;
|
|
const filesToWaitPaths = filesToWait?.paths;
|
|
[filesToWaitPaths, this.configuration.filesToOpenOrCreate, this.configuration.filesToDiff].forEach(paths => {
|
|
if (Array.isArray(paths)) {
|
|
paths.forEach(path => {
|
|
if (path.fileUri) {
|
|
path.fileUri = URI.revive(path.fileUri);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
if (filesToWait) {
|
|
filesToWait.waitMarkerFileUri = URI.revive(filesToWait.waitMarkerFileUri);
|
|
}
|
|
}
|
|
|
|
async open(): Promise<void> {
|
|
|
|
// Init services and wait for DOM to be ready in parallel
|
|
const [services] = await Promise.all([this.initServices(), domContentLoaded()]);
|
|
|
|
// Create Workbench
|
|
const workbench = new Workbench(document.body, { extraClasses: this.getExtraClasses() }, services.serviceCollection, services.logService);
|
|
|
|
// Listeners
|
|
this.registerListeners(workbench, services.storageService);
|
|
|
|
// Startup
|
|
const instantiationService = workbench.startup();
|
|
|
|
// Window
|
|
this._register(instantiationService.createInstance(NativeWindow));
|
|
|
|
// Logging
|
|
services.logService.trace('workbench configuration', safeStringify(this.configuration));
|
|
|
|
// Driver
|
|
if (this.configuration.driver) {
|
|
instantiationService.invokeFunction(async accessor => this._register(await registerWindowDriver(accessor, this.configuration.windowId)));
|
|
}
|
|
}
|
|
|
|
private getExtraClasses(): string[] {
|
|
if (isMacintosh) {
|
|
if (this.configuration.os.release > '20.0.0') {
|
|
return ['macos-bigsur-or-newer'];
|
|
}
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
private registerListeners(workbench: Workbench, storageService: NativeStorageService): void {
|
|
|
|
// Workbench Lifecycle
|
|
this._register(workbench.onWillShutdown(event => event.join(storageService.close(), 'join.closeStorage')));
|
|
this._register(workbench.onDidShutdown(() => this.dispose()));
|
|
}
|
|
|
|
protected abstract registerFileSystemProviders(
|
|
mainProcessService: IMainProcessService,
|
|
sharedProcessWorkerWorkbenchService: ISharedProcessWorkerWorkbenchService,
|
|
fileService: IFileService,
|
|
logService: ILogService,
|
|
nativeHostService: INativeHostService
|
|
): void;
|
|
|
|
private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService, storageService: NativeStorageService }> {
|
|
const serviceCollection = new ServiceCollection();
|
|
|
|
|
|
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
//
|
|
// NOTE: Please do NOT register services here. Use `registerSingleton()`
|
|
// from `workbench.common.main.ts` if the service is shared between
|
|
// desktop and web or `workbench.sandbox.main.ts` if the service
|
|
// is desktop only.
|
|
//
|
|
// DO NOT add services to `workbench.desktop.main.ts`, always add
|
|
// to `workbench.sandbox.main.ts` to support our Electron sandbox
|
|
//
|
|
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
|
|
|
|
// Main Process
|
|
const mainProcessService = this._register(new ElectronIPCMainProcessService(this.configuration.windowId));
|
|
serviceCollection.set(IMainProcessService, mainProcessService);
|
|
|
|
// Product
|
|
const productService: IProductService = { _serviceBrand: undefined, ...product };
|
|
serviceCollection.set(IProductService, productService);
|
|
|
|
// Environment
|
|
const environmentService = new NativeWorkbenchEnvironmentService(this.configuration, productService);
|
|
serviceCollection.set(INativeWorkbenchEnvironmentService, environmentService);
|
|
|
|
// Logger
|
|
const logLevelChannelClient = new LogLevelChannelClient(mainProcessService.getChannel('logLevel'));
|
|
const loggerService = new LoggerChannelClient(environmentService.configuration.logLevel, logLevelChannelClient.onDidChangeLogLevel, mainProcessService.getChannel('logger'));
|
|
serviceCollection.set(ILoggerService, loggerService);
|
|
|
|
// Log
|
|
const logService = this._register(new NativeLogService(`renderer${this.configuration.windowId}`, environmentService.configuration.logLevel, loggerService, logLevelChannelClient, environmentService));
|
|
serviceCollection.set(ILogService, logService);
|
|
|
|
// Shared Process
|
|
const sharedProcessService = new SharedProcessService(this.configuration.windowId, logService);
|
|
serviceCollection.set(ISharedProcessService, sharedProcessService);
|
|
|
|
// Shared Process Worker
|
|
const sharedProcessWorkerWorkbenchService = new SharedProcessWorkerWorkbenchService(this.configuration.windowId, logService, sharedProcessService);
|
|
serviceCollection.set(ISharedProcessWorkerWorkbenchService, sharedProcessWorkerWorkbenchService);
|
|
|
|
// Remote
|
|
const remoteAuthorityResolverService = new RemoteAuthorityResolverService();
|
|
serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService);
|
|
|
|
|
|
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
//
|
|
// NOTE: Please do NOT register services here. Use `registerSingleton()`
|
|
// from `workbench.common.main.ts` if the service is shared between
|
|
// desktop and web or `workbench.sandbox.main.ts` if the service
|
|
// is desktop only.
|
|
//
|
|
// DO NOT add services to `workbench.desktop.main.ts`, always add
|
|
// to `workbench.sandbox.main.ts` to support our Electron sandbox
|
|
//
|
|
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
|
|
|
|
// Sign
|
|
const signService = ProxyChannel.toService<ISignService>(mainProcessService.getChannel('sign'));
|
|
serviceCollection.set(ISignService, signService);
|
|
|
|
// Remote Agent
|
|
const remoteAgentService = this._register(new RemoteAgentService(environmentService, productService, remoteAuthorityResolverService, signService, logService));
|
|
serviceCollection.set(IRemoteAgentService, remoteAgentService);
|
|
|
|
// Native Host
|
|
const nativeHostService = new NativeHostService(this.configuration.windowId, mainProcessService) as INativeHostService;
|
|
serviceCollection.set(INativeHostService, nativeHostService);
|
|
|
|
// Files
|
|
const fileService = this._register(new FileService(logService));
|
|
serviceCollection.set(IFileService, fileService);
|
|
|
|
this.registerFileSystemProviders(mainProcessService, sharedProcessWorkerWorkbenchService, fileService, logService, nativeHostService);
|
|
|
|
// URI Identity
|
|
const uriIdentityService = new UriIdentityService(fileService);
|
|
serviceCollection.set(IUriIdentityService, uriIdentityService);
|
|
|
|
|
|
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
//
|
|
// NOTE: Please do NOT register services here. Use `registerSingleton()`
|
|
// from `workbench.common.main.ts` if the service is shared between
|
|
// desktop and web or `workbench.sandbox.main.ts` if the service
|
|
// is desktop only.
|
|
//
|
|
// DO NOT add services to `workbench.desktop.main.ts`, always add
|
|
// to `workbench.sandbox.main.ts` to support our Electron sandbox
|
|
//
|
|
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
|
|
// Remote file system
|
|
this._register(RemoteFileSystemProvider.register(remoteAgentService, fileService, logService));
|
|
|
|
const payload = this.resolveWorkspaceInitializationPayload(environmentService);
|
|
|
|
const [configurationService, storageService] = await Promise.all([
|
|
this.createWorkspaceService(payload, environmentService, fileService, remoteAgentService, uriIdentityService, logService).then(service => {
|
|
|
|
// Workspace
|
|
serviceCollection.set(IWorkspaceContextService, service);
|
|
|
|
// Configuration
|
|
serviceCollection.set(IWorkbenchConfigurationService, service);
|
|
|
|
return service;
|
|
}),
|
|
|
|
this.createStorageService(payload, environmentService, mainProcessService).then(service => {
|
|
|
|
// Storage
|
|
serviceCollection.set(IStorageService, service);
|
|
|
|
return service;
|
|
}),
|
|
|
|
this.createKeyboardLayoutService(mainProcessService).then(service => {
|
|
|
|
// KeyboardLayout
|
|
serviceCollection.set(IKeyboardLayoutService, service);
|
|
|
|
return service;
|
|
})
|
|
]);
|
|
|
|
// Workspace Trust Service
|
|
const workspaceTrustEnablementService = new WorkspaceTrustEnablementService(configurationService, environmentService);
|
|
serviceCollection.set(IWorkspaceTrustEnablementService, workspaceTrustEnablementService);
|
|
|
|
const workspaceTrustManagementService = new WorkspaceTrustManagementService(configurationService, remoteAuthorityResolverService, storageService, uriIdentityService, environmentService, configurationService, workspaceTrustEnablementService);
|
|
serviceCollection.set(IWorkspaceTrustManagementService, workspaceTrustManagementService);
|
|
|
|
// Update workspace trust so that configuration is updated accordingly
|
|
configurationService.updateWorkspaceTrust(workspaceTrustManagementService.isWorkspaceTrusted());
|
|
this._register(workspaceTrustManagementService.onDidChangeTrust(() => configurationService.updateWorkspaceTrust(workspaceTrustManagementService.isWorkspaceTrusted())));
|
|
|
|
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
//
|
|
// NOTE: Please do NOT register services here. Use `registerSingleton()`
|
|
// from `workbench.common.main.ts` if the service is shared between
|
|
// desktop and web or `workbench.sandbox.main.ts` if the service
|
|
// is desktop only.
|
|
//
|
|
// DO NOT add services to `workbench.desktop.main.ts`, always add
|
|
// to `workbench.sandbox.main.ts` to support our Electron sandbox
|
|
//
|
|
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
|
|
|
|
return { serviceCollection, logService, storageService };
|
|
}
|
|
|
|
private resolveWorkspaceInitializationPayload(environmentService: INativeWorkbenchEnvironmentService): IWorkspaceInitializationPayload {
|
|
let workspaceInitializationPayload: IWorkspaceInitializationPayload | undefined = this.configuration.workspace;
|
|
|
|
// Fallback to empty workspace if we have no payload yet.
|
|
if (!workspaceInitializationPayload) {
|
|
let id: string;
|
|
if (this.configuration.backupPath) {
|
|
// we know the backupPath must be a unique path so we leverage its name as workspace ID
|
|
id = basename(this.configuration.backupPath);
|
|
} else if (environmentService.isExtensionDevelopment) {
|
|
// fallback to a reserved identifier when in extension development where backups are not stored
|
|
id = 'ext-dev';
|
|
} else {
|
|
throw new Error('Unexpected window configuration without backupPath');
|
|
}
|
|
|
|
workspaceInitializationPayload = { id };
|
|
}
|
|
|
|
return workspaceInitializationPayload;
|
|
}
|
|
|
|
private async createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: INativeWorkbenchEnvironmentService, fileService: FileService, remoteAgentService: IRemoteAgentService, uriIdentityService: IUriIdentityService, logService: ILogService): Promise<WorkspaceService> {
|
|
const configurationCache = new ConfigurationCache([Schemas.file, Schemas.userData] /* Cache all non native resources */, environmentService, fileService);
|
|
const workspaceService = new WorkspaceService({ remoteAuthority: environmentService.remoteAuthority, configurationCache }, environmentService, fileService, remoteAgentService, uriIdentityService, logService);
|
|
|
|
try {
|
|
await workspaceService.initialize(payload);
|
|
|
|
return workspaceService;
|
|
} catch (error) {
|
|
onUnexpectedError(error);
|
|
|
|
return workspaceService;
|
|
}
|
|
}
|
|
|
|
private async createStorageService(payload: IWorkspaceInitializationPayload, environmentService: INativeWorkbenchEnvironmentService, mainProcessService: IMainProcessService): Promise<NativeStorageService> {
|
|
const storageService = new NativeStorageService(payload, mainProcessService, environmentService);
|
|
|
|
try {
|
|
await storageService.initialize();
|
|
|
|
return storageService;
|
|
} catch (error) {
|
|
onUnexpectedError(error);
|
|
|
|
return storageService;
|
|
}
|
|
}
|
|
|
|
private async createKeyboardLayoutService(mainProcessService: IMainProcessService): Promise<KeyboardLayoutService> {
|
|
const keyboardLayoutService = new KeyboardLayoutService(mainProcessService);
|
|
|
|
try {
|
|
await keyboardLayoutService.initialize();
|
|
|
|
return keyboardLayoutService;
|
|
} catch (error) {
|
|
onUnexpectedError(error);
|
|
|
|
return keyboardLayoutService;
|
|
}
|
|
}
|
|
}
|