From adb0afc5e6997d84951a979964fefaae7f790cb4 Mon Sep 17 00:00:00 2001 From: Seivan Date: Tue, 19 Aug 2025 01:26:13 +0200 Subject: [PATCH] Add swipe gesture recognizer and `onDidReceiveSwipeGesture` extension API. Defaults to disabled. --- .../windows/electron-main/windowImpl.ts | 35 +++++++++++++++++++ .../workbench/api/browser/mainThreadWindow.ts | 7 ++++ .../workbench/api/common/extHost.api.impl.ts | 3 ++ .../workbench/api/common/extHost.protocol.ts | 1 + src/vs/workbench/api/common/extHostWindow.ts | 7 ++++ .../browser/workbench.contribution.ts | 5 +++ src/vscode-dts/vscode.d.ts | 10 ++++++ 7 files changed, 68 insertions(+) diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index bcfce8495e9..f086c8b416b 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -737,6 +737,41 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { cb({ cancel: false, requestHeaders: Object.assign(details.requestHeaders, headers) }); }); + + this.registerSwipeGesture(); + } + private registerSwipeGesture(): void { + + const configurationKey = 'workbench.editor.swipeGestureRecognizer'; + const swipeListener = this._register(new DisposableStore()); + + const swipeGestureRecognizer = (electronWindow: electron.BrowserWindow) => { + + swipeListener.clear(); + if (this.configurationService.getValue(configurationKey) !== true) { + return; + } + + const disposable = this._register( + Event.fromNodeEventEmitter(electronWindow, 'swipe', + (event: Electron.Event, direction: 'left' | 'right' | 'up' | 'down') => ({ event, direction }))((e) => { + this.sendWhenReady('vscode:runAction', CancellationToken.None, { id: '_workbench.triggerSwipeGesture', args: [e.direction] }); + + })); + swipeListener.add(disposable); + + }; + + if (this.win !== null && isMacintosh) { + const win = this.win; + this._register(this.configurationService.onDidChangeConfiguration(event => { + if (event.affectsConfiguration(configurationKey)) { + swipeGestureRecognizer(win); + } + })); + swipeGestureRecognizer(win); + } + } private marketplaceHeadersPromise: Promise | undefined; diff --git a/src/vs/workbench/api/browser/mainThreadWindow.ts b/src/vs/workbench/api/browser/mainThreadWindow.ts index 393b182479b..4d4f0c35ccc 100644 --- a/src/vs/workbench/api/browser/mainThreadWindow.ts +++ b/src/vs/workbench/api/browser/mainThreadWindow.ts @@ -7,6 +7,7 @@ import { Event } from '../../../base/common/event.js'; import { DisposableStore } from '../../../base/common/lifecycle.js'; import { URI, UriComponents } from '../../../base/common/uri.js'; import { IOpenerService } from '../../../platform/opener/common/opener.js'; +import { CommandsRegistry } from '../../../platform/commands/common/commands.js'; import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; import { ExtHostContext, ExtHostWindowShape, IOpenUriOptions, MainContext, MainThreadWindowShape } from '../common/extHost.protocol.js'; import { IHostService } from '../../services/host/browser/host.js'; @@ -18,6 +19,7 @@ export class MainThreadWindow implements MainThreadWindowShape { private readonly proxy: ExtHostWindowShape; private readonly disposables = new DisposableStore(); + private readonly commandRegistration; constructor( extHostContext: IExtHostContext, @@ -31,10 +33,15 @@ export class MainThreadWindow implements MainThreadWindowShape { (this.proxy.$onDidChangeWindowFocus, this.proxy, this.disposables); userActivityService.onDidChangeIsActive(this.proxy.$onDidChangeWindowActive, this.proxy, this.disposables); this.registerNativeHandle(); + + this.commandRegistration = CommandsRegistry.registerCommand('_workbench.triggerSwipeGesture', (accessor, direction: 'left' | 'right' | 'up' | 'down') => { + this.proxy.$onDidReceiveSwipeGesture(direction); + }); } dispose(): void { this.disposables.dispose(); + this.commandRegistration.dispose(); } registerNativeHandle(): void { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index f13affa3aad..5bfac74d219 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -680,6 +680,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // namespace: window const window: typeof vscode.window = { + onDidReceiveSwipeGesture(listener, thisArg?, disposables?) { + return _asExtensionEvent(extHostWindow.onDidReceiveSwipeGesture)(listener, thisArg, disposables); + }, get activeTextEditor() { return extHostEditors.getActiveTextEditor(); }, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 51f8c0cfc3b..fc16a7eca02 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2684,6 +2684,7 @@ export interface ExtHostWindowShape { $onDidChangeWindowFocus(value: boolean): void; $onDidChangeWindowActive(value: boolean): void; $onDidChangeActiveNativeWindowHandle(handle: string | undefined): void; + $onDidReceiveSwipeGesture(direction: 'left' | 'right' | 'up' | 'down'): void; } export interface ExtHostLogLevelServiceShape { diff --git a/src/vs/workbench/api/common/extHostWindow.ts b/src/vs/workbench/api/common/extHostWindow.ts index 43032001831..57c352d455a 100644 --- a/src/vs/workbench/api/common/extHostWindow.ts +++ b/src/vs/workbench/api/common/extHostWindow.ts @@ -26,6 +26,10 @@ export class ExtHostWindow implements ExtHostWindowShape { private readonly _onDidChangeWindowState = new Emitter(); readonly onDidChangeWindowState: Event = this._onDidChangeWindowState.event; + private _onDidReceiveSwipeGesture = new Emitter<'left' | 'right' | 'up' | 'down'>(); + + readonly onDidReceiveSwipeGesture: Event<'left' | 'right' | 'up' | 'down'> = this._onDidReceiveSwipeGesture.event; + private _nativeHandle: Uint8Array | undefined; private _state = ExtHostWindow.InitialState; @@ -56,6 +60,9 @@ export class ExtHostWindow implements ExtHostWindowShape { this.onDidChangeWindowProperty('active', isActive); }); } + $onDidReceiveSwipeGesture(direction: 'left' | 'right' | 'up' | 'down'): void { + this._onDidReceiveSwipeGesture.fire(direction); + } get nativeHandle(): Uint8Array | undefined { return this._nativeHandle; diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index e82a1134f22..51d7d9570b7 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -337,6 +337,11 @@ const registry = Registry.as(ConfigurationExtensions.Con 'description': localize('mouseBackForwardToNavigate', "Enables the use of mouse buttons four and five for commands 'Go Back' and 'Go Forward'."), 'default': true }, + 'workbench.editor.swipeGestureRecognizer': { + 'type': 'boolean', + 'description': localize('swipeGestureRecognizer', "Register swipe gestures to be used in extensions."), + 'default': false + }, 'workbench.editor.navigationScope': { 'type': 'string', 'enum': ['default', 'editorGroup', 'editor'], diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index b206caf2d71..e1804987ce6 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -11007,6 +11007,16 @@ declare module 'vscode' { */ export const onDidChangeActiveTextEditor: Event; + /** + * Swipe gesture direction. + */ + export type SwipeGestureDirection = 'left' | 'right' | 'up' | 'down'; + + /** + * An {@link Event} which fires when a swipe gesture occurs. + */ + export const onDidReceiveSwipeGesture: Event; + /** * An {@link Event} which fires when the array of {@link window.visibleTextEditors visible editors} * has changed.