Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/host/electron-browser/nativeHostService.ts
5240 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 { Emitter, Event } from '../../../../base/common/event.js';
7
import { IHostService, IToastOptions, IToastResult } from '../browser/host.js';
8
import { FocusMode, INativeHostService } from '../../../../platform/native/common/native.js';
9
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
10
import { ILabelService, Verbosity } from '../../../../platform/label/common/label.js';
11
import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';
12
import { IWindowOpenable, IOpenWindowOptions, isFolderToOpen, isWorkspaceToOpen, IOpenEmptyWindowOptions, IPoint, IRectangle, IOpenedAuxiliaryWindow, IOpenedMainWindow } from '../../../../platform/window/common/window.js';
13
import { Disposable, DisposableSet, IDisposable } from '../../../../base/common/lifecycle.js';
14
import { NativeHostService } from '../../../../platform/native/common/nativeHostService.js';
15
import { INativeWorkbenchEnvironmentService } from '../../environment/electron-browser/environmentService.js';
16
import { IMainProcessService } from '../../../../platform/ipc/common/mainProcessService.js';
17
import { disposableWindowInterval, getActiveDocument, getWindowId, getWindowsCount, hasWindow, onDidRegisterWindow } from '../../../../base/browser/dom.js';
18
import { memoize } from '../../../../base/common/decorators.js';
19
import { isAuxiliaryWindow } from '../../../../base/browser/window.js';
20
import { VSBuffer } from '../../../../base/common/buffer.js';
21
import { CancellationToken } from '../../../../base/common/cancellation.js';
22
import { showBrowserToast } from '../browser/toasts.js';
23
import { generateUuid } from '../../../../base/common/uuid.js';
24
25
class WorkbenchNativeHostService extends NativeHostService {
26
27
constructor(
28
@INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService,
29
@IMainProcessService mainProcessService: IMainProcessService
30
) {
31
super(environmentService.window.id, mainProcessService);
32
}
33
}
34
35
class WorkbenchHostService extends Disposable implements IHostService {
36
37
declare readonly _serviceBrand: undefined;
38
39
constructor(
40
@INativeHostService private readonly nativeHostService: INativeHostService,
41
@ILabelService private readonly labelService: ILabelService,
42
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
43
) {
44
super();
45
46
this.onDidChangeFocus = Event.latch(
47
Event.any(
48
Event.map(Event.filter(this.nativeHostService.onDidFocusMainOrAuxiliaryWindow, id => hasWindow(id), this._store), () => this.hasFocus, this._store),
49
Event.map(Event.filter(this.nativeHostService.onDidBlurMainOrAuxiliaryWindow, id => hasWindow(id), this._store), () => this.hasFocus, this._store),
50
Event.map(this.onDidChangeActiveWindow, () => this.hasFocus, this._store)
51
), undefined, this._store
52
);
53
54
this.onDidChangeFullScreen = Event.filter(this.nativeHostService.onDidChangeWindowFullScreen, e => hasWindow(e.windowId), this._store);
55
56
this.registerListeners();
57
}
58
59
private registerListeners(): void {
60
61
// Make sure to hide all OS toasts when the window gains focus
62
this._register(this.onDidChangeFocus(focus => {
63
if (focus) {
64
this.clearToasts();
65
}
66
}));
67
}
68
69
//#region Focus
70
71
readonly onDidChangeFocus: Event<boolean>;
72
73
get hasFocus(): boolean {
74
return getActiveDocument().hasFocus();
75
}
76
77
async hadLastFocus(): Promise<boolean> {
78
const activeWindowId = await this.nativeHostService.getActiveWindowId();
79
80
if (typeof activeWindowId === 'undefined') {
81
return false;
82
}
83
84
return activeWindowId === this.nativeHostService.windowId;
85
}
86
87
//#endregion
88
89
//#region Window
90
91
@memoize
92
get onDidChangeActiveWindow(): Event<number> {
93
const emitter = this._register(new Emitter<number>());
94
95
// Emit via native focus tracking
96
this._register(Event.filter(this.nativeHostService.onDidFocusMainOrAuxiliaryWindow, id => hasWindow(id), this._store)(id => emitter.fire(id)));
97
98
this._register(onDidRegisterWindow(({ window, disposables }) => {
99
100
// Emit via interval: immediately when opening an auxiliary window,
101
// it is possible that document focus has not yet changed, so we
102
// poll for a while to ensure we catch the event.
103
disposables.add(disposableWindowInterval(window, () => {
104
const hasFocus = window.document.hasFocus();
105
if (hasFocus) {
106
emitter.fire(window.vscodeWindowId);
107
}
108
109
return hasFocus;
110
}, 100, 20));
111
}));
112
113
return Event.latch(emitter.event, undefined, this._store);
114
}
115
116
readonly onDidChangeFullScreen: Event<{ readonly windowId: number; readonly fullscreen: boolean }>;
117
118
openWindow(options?: IOpenEmptyWindowOptions): Promise<void>;
119
openWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise<void>;
120
openWindow(arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: IOpenWindowOptions): Promise<void> {
121
if (Array.isArray(arg1)) {
122
return this.doOpenWindow(arg1, arg2);
123
}
124
125
return this.doOpenEmptyWindow(arg1);
126
}
127
128
private doOpenWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise<void> {
129
const remoteAuthority = this.environmentService.remoteAuthority;
130
if (remoteAuthority) {
131
toOpen.forEach(openable => openable.label = openable.label || this.getRecentLabel(openable));
132
133
if (options?.remoteAuthority === undefined) {
134
// set the remoteAuthority of the window the request came from.
135
// It will be used when the input is neither file nor vscode-remote.
136
options = options ? { ...options, remoteAuthority } : { remoteAuthority };
137
}
138
}
139
140
return this.nativeHostService.openWindow(toOpen, options);
141
}
142
143
private getRecentLabel(openable: IWindowOpenable): string {
144
if (isFolderToOpen(openable)) {
145
return this.labelService.getWorkspaceLabel(openable.folderUri, { verbose: Verbosity.LONG });
146
}
147
148
if (isWorkspaceToOpen(openable)) {
149
return this.labelService.getWorkspaceLabel({ id: '', configPath: openable.workspaceUri }, { verbose: Verbosity.LONG });
150
}
151
152
return this.labelService.getUriLabel(openable.fileUri, { appendWorkspaceSuffix: true });
153
}
154
155
private doOpenEmptyWindow(options?: IOpenEmptyWindowOptions): Promise<void> {
156
const remoteAuthority = this.environmentService.remoteAuthority;
157
if (!!remoteAuthority && options?.remoteAuthority === undefined) {
158
// set the remoteAuthority of the window the request came from
159
options = options ? { ...options, remoteAuthority } : { remoteAuthority };
160
}
161
return this.nativeHostService.openWindow(options);
162
}
163
164
toggleFullScreen(targetWindow: Window): Promise<void> {
165
return this.nativeHostService.toggleFullScreen({ targetWindowId: isAuxiliaryWindow(targetWindow) ? targetWindow.vscodeWindowId : undefined });
166
}
167
168
async moveTop(targetWindow: Window): Promise<void> {
169
if (getWindowsCount() <= 1) {
170
return; // does not apply when only one window is opened
171
}
172
173
return this.nativeHostService.moveWindowTop(isAuxiliaryWindow(targetWindow) ? { targetWindowId: targetWindow.vscodeWindowId } : undefined);
174
}
175
176
getCursorScreenPoint(): Promise<{ readonly point: IPoint; readonly display: IRectangle }> {
177
return this.nativeHostService.getCursorScreenPoint();
178
}
179
180
getWindows(options: { includeAuxiliaryWindows: true }): Promise<Array<IOpenedMainWindow | IOpenedAuxiliaryWindow>>;
181
getWindows(options: { includeAuxiliaryWindows: false }): Promise<Array<IOpenedMainWindow>>;
182
getWindows(options: { includeAuxiliaryWindows: boolean }): Promise<Array<IOpenedMainWindow | IOpenedAuxiliaryWindow>> {
183
if (options.includeAuxiliaryWindows === false) {
184
return this.nativeHostService.getWindows({ includeAuxiliaryWindows: false });
185
}
186
187
return this.nativeHostService.getWindows({ includeAuxiliaryWindows: true });
188
}
189
190
//#endregion
191
192
//#region Lifecycle
193
194
focus(targetWindow: Window, options?: { mode?: FocusMode }): Promise<void> {
195
return this.nativeHostService.focusWindow({
196
mode: options?.mode,
197
targetWindowId: getWindowId(targetWindow)
198
});
199
}
200
201
restart(): Promise<void> {
202
return this.nativeHostService.relaunch();
203
}
204
205
reload(options?: { disableExtensions?: boolean }): Promise<void> {
206
return this.nativeHostService.reload(options);
207
}
208
209
close(): Promise<void> {
210
return this.nativeHostService.closeWindow();
211
}
212
213
async withExpectedShutdown<T>(expectedShutdownTask: () => Promise<T>): Promise<T> {
214
return await expectedShutdownTask();
215
}
216
217
//#endregion
218
219
//#region Screenshots
220
221
getScreenshot(rect?: IRectangle): Promise<VSBuffer | undefined> {
222
return this.nativeHostService.getScreenshot(rect);
223
}
224
225
//#endregion
226
227
//#region Native Handle
228
229
private _nativeWindowHandleCache = new Map<number, Promise<VSBuffer | undefined>>();
230
async getNativeWindowHandle(windowId: number): Promise<VSBuffer | undefined> {
231
if (!this._nativeWindowHandleCache.has(windowId)) {
232
this._nativeWindowHandleCache.set(windowId, this.nativeHostService.getNativeWindowHandle(windowId));
233
}
234
return this._nativeWindowHandleCache.get(windowId)!;
235
}
236
237
//#endregion
238
239
//#region Toast Notifications
240
241
private readonly activeBrowserToasts = this._register(new DisposableSet());
242
243
async showToast(options: IToastOptions, token: CancellationToken): Promise<IToastResult> {
244
const id = generateUuid();
245
const listener = token.onCancellationRequested(() => this.nativeHostService.clearToast(id));
246
247
try {
248
// Try native OS notifications first
249
const nativeToast = await this.nativeHostService.showToast({ ...options, id });
250
if (nativeToast.supported) {
251
return nativeToast;
252
}
253
254
// Then fallback to browser notifications
255
return await showBrowserToast({
256
onDidCreateToast: (toast: IDisposable) => this.activeBrowserToasts.add(toast),
257
onDidDisposeToast: (toast: IDisposable) => this.activeBrowserToasts.deleteAndDispose(toast)
258
}, options, token);
259
} finally {
260
listener.dispose();
261
}
262
}
263
264
private async clearToasts(): Promise<void> {
265
await this.nativeHostService.clearToasts();
266
267
this.activeBrowserToasts.clearAndDisposeAll();
268
}
269
270
//#endregion
271
}
272
273
registerSingleton(IHostService, WorkbenchHostService, InstantiationType.Delayed);
274
registerSingleton(INativeHostService, WorkbenchNativeHostService, InstantiationType.Delayed);
275
276