fix OSC parsing
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IOscHandler, IHandlerCollection, OscFallbackHandler } from 'common/parser/Types';
|
||||
import { OscState } from 'common/parser/Constants';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { utf32ToString } from 'common/input/TextDecoder';
|
||||
import { IDisposable } from 'common/Types';
|
||||
|
||||
|
||||
export class OscParser extends Disposable {
|
||||
private _state = OscState.START;
|
||||
private _id = -1;
|
||||
private _handlers: IHandlerCollection<IOscHandler> = Object.create(null);
|
||||
private _handlerFb: OscFallbackHandler = () => { };
|
||||
|
||||
addOscHandler(ident: number, handler: IOscHandler): IDisposable {
|
||||
if (this._handlers[ident] === undefined) {
|
||||
this._handlers[ident] = [];
|
||||
}
|
||||
const handlerList = this._handlers[ident];
|
||||
handlerList.push(handler);
|
||||
return {
|
||||
dispose: () => {
|
||||
const handlerIndex = handlerList.indexOf(handler);
|
||||
if (handlerIndex !== -1) {
|
||||
handlerList.splice(handlerIndex, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
setOscHandler(ident: number, handler: IOscHandler): void {
|
||||
this._handlers[ident] = [handler];
|
||||
}
|
||||
clearOscHandler(ident: number): void {
|
||||
if (this._handlers[ident]) delete this._handlers[ident];
|
||||
}
|
||||
setOscHandlerFallback(handler: OscFallbackHandler): void {
|
||||
this._handlerFb = handler;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._handlers = {};
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
// cleanup handlers if payload was already sent
|
||||
if (this._state === OscState.PAYLOAD) {
|
||||
this.end(false);
|
||||
}
|
||||
this._id = -1;
|
||||
this._state = OscState.START;
|
||||
}
|
||||
|
||||
private _start(): void {
|
||||
const handlers = this._handlers[this._id];
|
||||
if (!handlers) {
|
||||
this._handlerFb(this._id, 'START');
|
||||
} else {
|
||||
for (let j = handlers.length - 1; j >= 0; j--) {
|
||||
handlers[j].start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _put(data: Uint32Array, start: number, end: number): void {
|
||||
const handlers = this._handlers[this._id];
|
||||
if (!handlers) {
|
||||
this._handlerFb(this._id, 'PUT', utf32ToString(data, start, end));
|
||||
} else {
|
||||
for (let j = handlers.length - 1; j >= 0; j--) {
|
||||
handlers[j].put(data, start, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _end(success: boolean): void {
|
||||
// other than the old code we always have to call .end
|
||||
// to keep the bubbling we use `success` to indicate
|
||||
// whether a handler should execute
|
||||
const handlers = this._handlers[this._id];
|
||||
if (!handlers) {
|
||||
this._handlerFb(this._id, 'END', success);
|
||||
} else {
|
||||
let j = handlers.length - 1;
|
||||
for (; j >= 0; j--) {
|
||||
if (handlers[j].end(success) !== false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
j--;
|
||||
// cleanup left over handlers
|
||||
for (; j >= 0; j--) {
|
||||
handlers[j].end(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
this._id = -1;
|
||||
this._state = OscState.ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put data to current OSC command.
|
||||
* Expects the identifier of the OSC command in the form
|
||||
* OSC id ; payload ST/BEL
|
||||
* Payload chunks are not further processed and get
|
||||
* directly passed to the handlers.
|
||||
*/
|
||||
public put(data: Uint32Array, start: number, end: number): void {
|
||||
if (this._state === OscState.ABORT) {
|
||||
return;
|
||||
}
|
||||
if (this._state === OscState.ID) {
|
||||
while (start < end) {
|
||||
const code = data[start++];
|
||||
if (code === 0x3b) {
|
||||
this._state = OscState.PAYLOAD;
|
||||
this._start();
|
||||
break;
|
||||
}
|
||||
if (code < 0x30 || 0x39 < code) {
|
||||
this._state = OscState.ABORT;
|
||||
return;
|
||||
}
|
||||
if (this._id === -1) {
|
||||
this._id = 0;
|
||||
}
|
||||
this._id = this._id * 10 + code - 48;
|
||||
}
|
||||
}
|
||||
if (this._state === OscState.PAYLOAD && end - start > 0) {
|
||||
this._put(data, start, end);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates end of an OSC command.
|
||||
* Whether the OSC got aborted or finished normally
|
||||
* is indicated by `success`.
|
||||
*/
|
||||
public end(success: boolean): void {
|
||||
if (this._state === OscState.START) {
|
||||
return;
|
||||
}
|
||||
// do nothing if command was faulty
|
||||
if (this._state !== OscState.ABORT) {
|
||||
// if we are still in ID state and get an early end
|
||||
// means that the command has no payload thus we still have
|
||||
// to announce START and send END right after
|
||||
if (this._state === OscState.ID) {
|
||||
this._start();
|
||||
}
|
||||
this._end(success);
|
||||
}
|
||||
this._id = -1;
|
||||
this._state = OscState.START;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convenient class to allow attaching string based handler functions
|
||||
* as OSC handlers.
|
||||
*/
|
||||
export class OscHandlerFactory implements IOscHandler {
|
||||
private _data = '';
|
||||
constructor(private _handler: (data: string) => any) {}
|
||||
public start(): void {
|
||||
this._data = '';
|
||||
}
|
||||
public put(data: Uint32Array, start: number, end: number): void {
|
||||
this._data += utf32ToString(data, start, end);
|
||||
}
|
||||
public end(success: boolean): any {
|
||||
let ret;
|
||||
if (success) {
|
||||
ret = this._handler(this._data);
|
||||
}
|
||||
this._data = '';
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user