From d5cf4ac0eb9b4e1976e531f9a9c41ca2c702d127 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 31 Mar 2021 15:57:49 -0700 Subject: [PATCH] Fix terminal mouse reporting via binary events (#120145) * fix #96058 --- src/vs/platform/terminal/common/terminal.ts | 2 ++ .../platform/terminal/node/ptyHostService.ts | 3 +++ src/vs/platform/terminal/node/ptyService.ts | 7 ++++++ .../platform/terminal/node/terminalProcess.ts | 24 +++++++++++++------ .../api/common/extHostTerminalService.ts | 4 ++++ .../contrib/terminal/browser/remotePty.ts | 5 ++++ .../terminal/browser/remoteTerminalService.ts | 1 + .../contrib/terminal/browser/terminal.ts | 5 ++++ .../terminal/browser/terminalInstance.ts | 3 +++ .../browser/terminalProcessExtHostProxy.ts | 4 ++++ .../browser/terminalProcessManager.ts | 4 ++++ .../terminal/common/remoteTerminalChannel.ts | 3 +++ .../contrib/terminal/common/terminal.ts | 1 + .../terminal/electron-sandbox/localPty.ts | 6 +++++ .../electron-sandbox/localTerminalService.ts | 4 ++++ .../test/browser/workbenchTestServices.ts | 6 +++++ 16 files changed, 75 insertions(+), 7 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index fd0d139c7789f..bb11028d37638 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -155,6 +155,7 @@ export interface IPtyService { getCwd(id: number): Promise; getLatency(id: number): Promise; acknowledgeDataEvent(id: number, charCount: number): Promise; + processBinary(id: number, data: string): void; /** Confirm the process is _not_ an orphan. */ orphanQuestionReply(id: number): Promise; @@ -346,6 +347,7 @@ export interface ITerminalChildProcess { */ shutdown(immediate: boolean): void; input(data: string): void; + processBinary(data: string): void; resize(cols: number, rows: number): void; /** diff --git a/src/vs/platform/terminal/node/ptyHostService.ts b/src/vs/platform/terminal/node/ptyHostService.ts index 6d56aad168dde..6eae8bef817f1 100644 --- a/src/vs/platform/terminal/node/ptyHostService.ts +++ b/src/vs/platform/terminal/node/ptyHostService.ts @@ -188,6 +188,9 @@ export class PtyHostService extends Disposable implements IPtyService { setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise { return this._proxy.setTerminalLayoutInfo(args); } + processBinary(id: number, data: string): void { + this._proxy.processBinary(id, data); + } async getTerminalLayoutInfo(args: IGetTerminalLayoutInfoArgs): Promise { return await this._proxy.getTerminalLayoutInfo(args); } diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index 121973445982c..3fa370f918492 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -157,6 +157,10 @@ export class PtyService extends Disposable implements IPtyService { return this._throwIfNoPty(id).orphanQuestionReply(); } + processBinary(id: number, data: string): void { + return this._throwIfNoPty(id).writeBinary(data); + } + async setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise { this._workspaceLayoutInfos.set(args.workspaceId, args); } @@ -337,6 +341,9 @@ export class PersistentTerminalProcess extends Disposable { } return this._terminalProcess.input(data); } + writeBinary(data: string): void { + return this._terminalProcess.processBinary(data); + } resize(cols: number, rows: number): void { if (this._inReplay) { return; diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index 0ddec65dfe57c..3916597f65a33 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -311,23 +311,27 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess } } - public input(data: string): void { + public input(data: string, isBinary?: boolean): void { if (this._isDisposed || !this._ptyProcess) { return; } for (let i = 0; i <= Math.floor(data.length / WRITE_MAX_CHUNK_SIZE); i++) { this._writeQueue.push(data.substr(i * WRITE_MAX_CHUNK_SIZE, WRITE_MAX_CHUNK_SIZE)); } - this._startWrite(); + this._startWrite(isBinary); } - private _startWrite(): void { + public processBinary(data: string): void { + this.input(data, true); + } + + private _startWrite(isBinary?: boolean): void { // Don't write if it's already queued of is there is nothing to write if (this._writeTimeout !== undefined || this._writeQueue.length === 0) { return; } - this._doWrite(); + this._doWrite(isBinary); // Don't queue more writes if the queue is empty if (this._writeQueue.length === 0) { @@ -342,10 +346,16 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess }, WRITE_INTERVAL_MS); } - private _doWrite(): void { + private _doWrite(isBinary?: boolean): void { + console.info('writing binary', isBinary); const data = this._writeQueue.shift()!; - this._logService.trace('IPty#write', `${data.length} characters`); - this._ptyProcess!.write(data); + if (isBinary) { + this._logService.info('IPty#write (binary)', `${data.length} characters`); + this._ptyProcess!.write(Buffer.from(data, 'binary') as any); + } else { + this._logService.info('IPty#write', `${data.length} characters`); + this._ptyProcess!.write(data); + } } public resize(cols: number, rows: number): void { diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 7f8e37a632b06..bc6dc84f2a249 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -225,6 +225,10 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess { } } + processBinary(data: string) { + throw new Error('not implemented'); + } + acknowledgeDataEvent(charCount: number): void { // No-op, flow control is not supported in extension owned terminals. If this is ever // implemented it will need new pause and resume VS Code APIs. diff --git a/src/vs/workbench/contrib/terminal/browser/remotePty.ts b/src/vs/workbench/contrib/terminal/browser/remotePty.ts index a3a04aa97aa69..66c9199989f90 100644 --- a/src/vs/workbench/contrib/terminal/browser/remotePty.ts +++ b/src/vs/workbench/contrib/terminal/browser/remotePty.ts @@ -17,6 +17,8 @@ export class RemotePty extends Disposable implements ITerminalChildProcess { public readonly _onProcessData = this._register(new Emitter()); public readonly onProcessData: Event = this._onProcessData.event; + public readonly _onProcessBinary = this._register(new Emitter()); + public readonly onProcessBinary: Event = this._onProcessBinary.event; private readonly _onProcessExit = this._register(new Emitter()); public readonly onProcessExit: Event = this._onProcessExit.event; public readonly _onProcessReady = this._register(new Emitter<{ pid: number, cwd: string }>()); @@ -118,6 +120,9 @@ export class RemotePty extends Disposable implements ITerminalChildProcess { handleData(e: string | IProcessDataEvent) { this._onProcessData.fire(e); } + processBinary(e: string) { + this._onProcessBinary.fire(e); + } handleExit(e: number | undefined) { this._onProcessExit.fire(e); } diff --git a/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts b/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts index eca405fb55aa5..a8b60d6a36f20 100644 --- a/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts @@ -49,6 +49,7 @@ export class RemoteTerminalService extends Disposable implements IRemoteTerminal this._remoteTerminalChannel = channel; channel.onProcessData(e => this._ptys.get(e.id)?.handleData(e.event)); + channel.onProcessBinary(e => this._ptys.get(e.id)?.processBinary(e.event)); channel.onProcessExit(e => { const pty = this._ptys.get(e.id); if (pty) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index d7388f681a993..ee16f2378ff70 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -292,6 +292,11 @@ export interface ITerminalInstance { */ onData: Event; + /** + * Attach a listener to the binary data stream coming from xterm and going to pty + */ + onBinary: Event; + /** * Attach a listener to listen for new lines added to this terminal instance. * diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index bf3f0908d2188..5ace875fb3f41 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -184,6 +184,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { public get onTitleChanged(): Event { return this._onTitleChanged.event; } private readonly _onData = new Emitter(); public get onData(): Event { return this._onData.event; } + private readonly _onBinary = new Emitter(); + public get onBinary(): Event { return this._onBinary.event; } private readonly _onLineData = new Emitter(); public get onLineData(): Event { return this._onLineData.event; } private readonly _onRequestExtHostProcess = new Emitter(); @@ -468,6 +470,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._processManager.onProcessData(e => this._onProcessData(e)); this._xterm.onData(data => this._processManager.write(data)); + this._xterm.onBinary(data => this._processManager.processBinary(data)); this.processReady.then(async () => { if (this._linkManager) { this._linkManager.processCwd = await this._processManager.getInitialCwd(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts index 9744d4ff0f5cb..6e4f9a1ec22f6 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy.ts @@ -102,6 +102,10 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal } } + processBinary(data: string): void { + throw new Error('not implemented'); + } + public async start(): Promise { if (!this._shellLaunchConfig.isExtensionCustomPtyTerminal) { throw new Error('Attempt to start an ext host process that is not an extension terminal'); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 88890f6a3db3e..098a6c78f6ab2 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -499,6 +499,10 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce } } + public processBinary(data: string): void { + this._process?.processBinary(data); + } + public getInitialCwd(): Promise { return Promise.resolve(this._initialCwd ? this._initialCwd : ''); } diff --git a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts index e055f798fb395..8a587682a1978 100644 --- a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts +++ b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts @@ -95,6 +95,9 @@ export class RemoteTerminalChannelClient { public get onProcessData(): Event<{ id: number, event: IProcessDataEvent | string }> { return this._channel.listen<{ id: number, event: IProcessDataEvent | string }>('$onProcessDataEvent'); } + public get onProcessBinary(): Event<{ id: number, event: string }> { + return this._channel.listen<{ id: number, event: string }>('$onProcessBinaryEvent'); + } public get onProcessExit(): Event<{ id: number, event: number | undefined }> { return this._channel.listen<{ id: number, event: number | undefined }>('$onProcessExitEvent'); } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index f672eac3045e6..2cabd07901776 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -306,6 +306,7 @@ export interface ITerminalProcessManager extends IDisposable { setDimensions(cols: number, rows: number, sync: false): Promise; setDimensions(cols: number, rows: number, sync: true): void; acknowledgeDataEvent(charCount: number): void; + processBinary(data: string): void; getInitialCwd(): Promise; getCwd(): Promise; diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts index 334b75367db29..527496aa225c5 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localPty.ts @@ -50,6 +50,12 @@ export class LocalPty extends Disposable implements ITerminalChildProcess { shutdown(immediate: boolean): void { this._localPtyService.shutdown(this.id, immediate); } + processBinary(data: string): void { + if (this._inReplay) { + return; + } + this._localPtyService.processBinary(this.id, data); + } input(data: string): void { if (this._inReplay) { return; diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts index c892298e0c56a..bdd6688c6d223 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts @@ -136,6 +136,10 @@ export class LocalTerminalService extends Disposable implements ILocalTerminalSe return result; } + public processBinary(id: number, data: string): void { + this._localPtyService.processBinary(id, data); + } + private _getWorkspaceId(): string { return this._workspaceContextService.getWorkspace().id; } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 7dea1df11ce64..b8a48bea90d1f 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1521,6 +1521,9 @@ export class TestLocalTerminalService implements ILocalTerminalService { async listProcesses(reduceGraceTime: boolean): Promise { throw new Error('Method not implemented.'); } async setTerminalLayoutInfo(argsOrLayout?: ISetTerminalLayoutInfoArgs | ITerminalsLayoutInfoById) { throw new Error('Method not implemented.'); } async getTerminalLayoutInfo(): Promise { throw new Error('Method not implemented.'); } + processBinary(id: number, data: string): void { + throw new Error('Method not implemented.'); + } } class TestTerminalChildProcess implements ITerminalChildProcess { @@ -1544,6 +1547,9 @@ class TestTerminalChildProcess implements ITerminalChildProcess { async getInitialCwd(): Promise { return ''; } async getCwd(): Promise { return ''; } async getLatency(): Promise { return 0; } + processBinary(data: string): void { + throw new Error('not implemented'); + } } export class TestQuickInputService implements IQuickInputService {