Merge pull request #2566 from jerch/binary_event

onBinary event
This commit is contained in:
jerch
2019-11-14 22:39:32 +01:00
committed by GitHub
13 changed files with 97 additions and 33 deletions
@@ -33,6 +33,7 @@ export class AttachAddon implements ITerminalAddon {
if (this._bidirectional) {
this._disposables.push(terminal.onData(data => this._sendData(data)));
this._disposables.push(terminal.onBinary(data => this._sendBinary(data)));
}
this._disposables.push(addSocketListener(this._socket, 'close', () => this.dispose()));
@@ -51,6 +52,17 @@ export class AttachAddon implements ITerminalAddon {
}
this._socket.send(data);
}
private _sendBinary(data: string): void {
if (this._socket.readyState !== 1) {
return;
}
const buffer = new Uint8Array(data.length);
for (let i = 0; i < data.length; ++i) {
buffer[i] = data.charCodeAt(i) & 255;
}
this._socket.send(buffer);
}
}
function addSocketListener<K extends keyof WebSocketEventMap>(socket: WebSocket, type: K, handler: (this: WebSocket, ev: WebSocketEventMap[K]) => any): IDisposable {
+3
View File
@@ -169,6 +169,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
public get onCursorMove(): IEvent<void> { return this._onCursorMove.event; }
private _onData = new EventEmitter<string>();
public get onData(): IEvent<string> { return this._onData.event; }
private _onBinary = new EventEmitter<string>();
public get onBinary(): IEvent<string> { return this._onBinary.event; }
private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>();
public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; }
private _onLineFeed = new EventEmitter<void>();
@@ -221,6 +223,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this._coreService = this._instantiationService.createInstance(CoreService, () => this.scrollToBottom());
this._instantiationService.setService(ICoreService, this._coreService);
this._coreService.onData(e => this._onData.fire(e));
this._coreService.onBinary(e => this._onBinary.fire(e));
this._coreMouseService = this._instantiationService.createInstance(CoreMouseService);
this._instantiationService.setService(ICoreMouseService, this._coreMouseService);
this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService);
+1
View File
@@ -32,6 +32,7 @@ export class MockTerminal implements ITerminal {
onLineFeed: IEvent<void>;
onSelectionChange: IEvent<void>;
onData: IEvent<string>;
onBinary: IEvent<string>;
onTitleChange: IEvent<string>;
onScroll: IEvent<number>;
onKey: IEvent<{ key: string; domEvent: KeyboardEvent; }>;
+1
View File
@@ -180,6 +180,7 @@ export interface IPublicTerminal extends IDisposable {
markers: IMarker[];
onCursorMove: IEvent<void>;
onData: IEvent<string>;
onBinary: IEvent<string>;
onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>;
onLineFeed: IEvent<void>;
onScroll: IEvent<number>;
+2
View File
@@ -50,8 +50,10 @@ export class MockCoreService implements ICoreService {
decPrivateModes: IDecPrivateModes = {} as any;
onData: IEvent<string> = new EventEmitter<string>().event;
onUserInput: IEvent<void> = new EventEmitter<void>().event;
onBinary: IEvent<string> = new EventEmitter<string>().event;
reset(): void {}
triggerDataEvent(data: string, wasUserInput?: boolean): void {}
triggerBinaryEvent(data: string): void {}
}
export class MockDirtyRowService implements IDirtyRowService {
+3
View File
@@ -249,5 +249,8 @@ export interface ICoreMouseProtocol {
* The tracking encoding can be registered and activated at the CoreMouseService.
* If a ICoreMouseEvent passes all procotol restrictions it will be encoded
* with the active encoding and sent out.
* Note: Returning an empty string will supress sending a mouse report,
* which can be used to skip creating falsey reports in limited encodings
* (DEFAULT only supports up to 223 1-based as coord value).
*/
export type CoreMouseEncoding = (event: ICoreMouseEvent) => string;
+5 -4
View File
@@ -79,6 +79,7 @@ describe('CoreMouseService', () => {
cms = new CoreMouseService(bufferService, coreService);
reports = [];
coreService.triggerDataEvent = (data: string, userInput?: boolean) => reports.push(data);
coreService.triggerBinaryEvent = (data: string) => reports.push(data);
});
it('NONE', () => {
assert.equal(cms.triggerMouseEvent({ col: 0, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), false);
@@ -143,11 +144,11 @@ describe('CoreMouseService', () => {
cms.activeProtocol = 'ANY';
for (let i = 0; i < bufferService.cols; ++i) {
assert.equal(cms.triggerMouseEvent({ col: i, row: 0, button: CoreMouseButton.LEFT, action: CoreMouseAction.DOWN }), true);
// capped at 95
if (i < 95) {
assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]);
if (i > 222) {
// supress mouse reports if we are out of addressible range (max. 222)
assert.deepEqual(toBytes(reports.pop()), []);
} else {
assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, 0x7f, 0x21]);
assert.deepEqual(toBytes(reports.pop()), [0x1b, 0x5b, 0x4d, 0x20, i + 33, 0x21]);
}
}
});
+17 -8
View File
@@ -121,15 +121,17 @@ const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = {
/**
* DEFAULT - CSI M Pb Px Py
* Single byte encoding for coords and event code.
* Can encode values up to 223. The Encoding of higher
* values is not UTF-8 compatible (and currently limited
* to 95 in xterm.js).
* Can encode values up to 223 (1-based).
*/
DEFAULT: (e: ICoreMouseEvent) => {
let params = [eventCode(e, false) + 32, e.col + 32, e.row + 32];
// FIXME: we are currently limited to ASCII range
params = params.map(v => (v > 127) ? 127 : v);
// FIXED: params = params.map(v => (v > 255) ? 0 : value);
const params = [eventCode(e, false) + 32, e.col + 32, e.row + 32];
// supress mouse report if we exceed addressible range
// Note this is handled differently by emulators
// - xterm: sends 0;0 coords instead
// - vte, konsole: no report
if (params[0] > 255 || params[1] > 255 || params[2] > 255) {
return '';
}
return `\x1b[M${S(params[0])}${S(params[1])}${S(params[2])}`;
},
/**
@@ -266,7 +268,14 @@ export class CoreMouseService implements ICoreMouseService {
// encode report and send
const report = this._encodings[this._activeEncoding](e);
this._coreService.triggerDataEvent(report, true);
if (report) {
// always send DEFAULT as binary data
if (this._activeEncoding === 'DEFAULT') {
this._coreService.triggerBinaryEvent(report);
} else {
this._coreService.triggerDataEvent(report, true);
}
}
this._lastEvent = e;
+10
View File
@@ -23,6 +23,8 @@ export class CoreService implements ICoreService {
public get onData(): IEvent<string> { return this._onData.event; }
private _onUserInput = new EventEmitter<void>();
public get onUserInput(): IEvent<void> { return this._onUserInput.event; }
private _onBinary = new EventEmitter<string>();
public get onBinary(): IEvent<string> { return this._onBinary.event; }
constructor(
// TODO: Move this into a service
@@ -59,4 +61,12 @@ export class CoreService implements ICoreService {
this._logService.debug(`sending data "${data}"`, () => data.split('').map(e => e.charCodeAt(0)));
this._onData.fire(data);
}
public triggerBinaryEvent(data: string): void {
if (this._optionsService.options.disableStdin) {
return;
}
this._logService.debug(`sending binary "${data}"`, () => data.split('').map(e => e.charCodeAt(0)));
this._onBinary.fire(data);
}
}
+8 -1
View File
@@ -68,6 +68,7 @@ export interface ICoreService {
readonly onData: IEvent<string>;
readonly onUserInput: IEvent<void>;
readonly onBinary: IEvent<string>;
reset(): void;
@@ -78,8 +79,14 @@ export interface ICoreService {
* resulting from parsing incoming data). When true this will also:
* - Scroll to the bottom of the buffer.s
* - Fire the `onUserInput` event (so selection can be cleared).
*/
*/
triggerDataEvent(data: string, wasUserInput?: boolean): void;
/**
* Triggers the onBinary event in the public API.
* @param data The data that is being emitted.
*/
triggerBinaryEvent(data: string): void;
}
export const IDirtyRowService = createDecorator<IDirtyRowService>('DirtyRowService');
+1
View File
@@ -27,6 +27,7 @@ export class Terminal implements ITerminalApi {
public get onLineFeed(): IEvent<void> { return this._core.onLineFeed; }
public get onSelectionChange(): IEvent<void> { return this._core.onSelectionChange; }
public get onData(): IEvent<string> { return this._core.onData; }
public get onBinary(): IEvent<string> { return this._core.onBinary; }
public get onTitleChange(): IEvent<string> { return this._core.onTitleChange; }
public get onScroll(): IEvent<number> { return this._core.onScroll; }
public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._core.onKey; }
+23 -20
View File
@@ -220,6 +220,7 @@ describe('Mouse Tracking Tests', () => {
await page.evaluate(`
window.calls = [];
window.term.onData(e => calls.push( Array.from(e).map(el => el.charCodeAt(0)) ));
window.term.onBinary(e => calls.push( Array.from(e).map(el => el.charCodeAt(0)) ));
window.term.setOption('fontSize', ${fontSize});
window.term.resize(${cols}, ${rows});
`);
@@ -255,12 +256,17 @@ describe('Mouse Tracking Tests', () => {
await pollFor(page, () => getReports(encoding), [{col: 51, row: 11, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
// test at max rows/cols
// bug: we are capped at col 95 currently
// fix: allow values up to 223, any bigger should drop to 0
await mouseMove(cols - 1, rows - 1);
// capped at 223 (1-based)
await mouseMove(223 - 1, rows - 1);
await mouseDown('left');
await mouseUp('left');
await pollFor(page, () => getReports(encoding), [{col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
await pollFor(page, () => getReports(encoding), [{col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}}]);
// higher than 223 should not report at all
await mouseMove(257, rows - 1);
await mouseDown('left');
await mouseUp('left');
await pollFor(page, () => getReports(encoding), []);
// button press/move/release tests
// left button
@@ -511,14 +517,13 @@ describe('Mouse Tracking Tests', () => {
]);
// test at max rows/cols
// bug: we are capped at col 95 currently
// fix: allow values up to 223, any bigger should drop to 0
await mouseMove(cols - 1, rows - 1);
// capped at 223 (1-based)
await mouseMove(223 - 1, rows - 1);
await mouseDown('left');
await mouseUp('left');
await pollFor(page, () => getReports(encoding), [
{col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 95, row: rows, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
{col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 223, row: rows, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
]);
// button press/move/release tests
@@ -821,14 +826,13 @@ describe('Mouse Tracking Tests', () => {
]);
// test at max rows/cols
// bug: we are capped at col 95 currently
// fix: allow values up to 223, any bigger should drop to 0
await mouseMove(cols - 1, rows - 1);
// capped at 223 (1-based)
await mouseMove(223 - 1, rows - 1);
await mouseDown('left');
await mouseUp('left');
await pollFor(page, () => getReports(encoding), [
{col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 95, row: rows, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
{col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 223, row: rows, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
]);
// button press/move/release tests
@@ -1142,15 +1146,14 @@ describe('Mouse Tracking Tests', () => {
]);
// test at max rows/cols
// bug: we are capped at col 95 currently
// fix: allow values up to 223, any bigger should drop to 0
await mouseMove(cols - 1, rows - 1);
// capped at 223 (1-based)
await mouseMove(223 - 1, rows - 1);
await mouseDown('left');
await mouseUp('left');
await pollFor(page, () => getReports(encoding), [
{col: 95, row: rows, state: {action: 'move', button: '<none>', modifier: {control: false, shift: false, meta: false}}},
{col: 95, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 95, row: rows, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
{col: 223, row: rows, state: {action: 'move', button: '<none>', modifier: {control: false, shift: false, meta: false}}},
{col: 223, row: rows, state: {action: 'press', button: 'left', modifier: {control: false, shift: false, meta: false}}},
{col: 223, row: rows, state: {action: 'release', button: '<none>', modifier: {control: false, shift: false, meta: false}}}
]);
// button press/move/release tests
+11
View File
@@ -421,6 +421,17 @@ declare module 'xterm' {
*/
constructor(options?: ITerminalOptions);
/**
* Adds an event listener for when a binary event fires. This is used to
* enable non UTF-8 conformant binary messages to be sent to the backend.
* Currently this is only used for a certain type of mouse reports that
* happen to be not UTF-8 compatible.
* The event value is a JS string, pass it to the underlying pty as
* binary data, e.g. `pty.write(Buffer.from(data, 'binary'))`.
* @returns an `IDisposable` to stop listening.
*/
onBinary: IEvent<string>;
/**
* Adds an event listener for the cursor moves.
* @returns an `IDisposable` to stop listening.