2 * Copyright (c) 2019 The xterm.js authors. All rights reserved.
6 import { IOscHandler, IHandlerCollection, OscFallbackHandlerType, IOscParser } from 'common/parser/Types';
7 import { OscState, PAYLOAD_LIMIT } from 'common/parser/Constants';
8 import { utf32ToString } from 'common/input/TextDecoder';
9 import { IDisposable } from 'common/Types';
12 export class OscParser implements IOscParser {
13 private _state = OscState.START;
15 private _handlers: IHandlerCollection<IOscHandler> = Object.create(null);
16 private _handlerFb: OscFallbackHandlerType = () => { };
18 public addHandler(ident: number, handler: IOscHandler): IDisposable {
19 if (this._handlers[ident] === undefined) {
20 this._handlers[ident] = [];
22 const handlerList = this._handlers[ident];
23 handlerList.push(handler);
26 const handlerIndex = handlerList.indexOf(handler);
27 if (handlerIndex !== -1) {
28 handlerList.splice(handlerIndex, 1);
33 public setHandler(ident: number, handler: IOscHandler): void {
34 this._handlers[ident] = [handler];
36 public clearHandler(ident: number): void {
37 if (this._handlers[ident]) delete this._handlers[ident];
39 public setHandlerFallback(handler: OscFallbackHandlerType): void {
40 this._handlerFb = handler;
43 public dispose(): void {
44 this._handlers = Object.create(null);
45 this._handlerFb = () => {};
48 public reset(): void {
49 // cleanup handlers if payload was already sent
50 if (this._state === OscState.PAYLOAD) {
54 this._state = OscState.START;
57 private _start(): void {
58 const handlers = this._handlers[this._id];
60 this._handlerFb(this._id, 'START');
62 for (let j = handlers.length - 1; j >= 0; j--) {
68 private _put(data: Uint32Array, start: number, end: number): void {
69 const handlers = this._handlers[this._id];
71 this._handlerFb(this._id, 'PUT', utf32ToString(data, start, end));
73 for (let j = handlers.length - 1; j >= 0; j--) {
74 handlers[j].put(data, start, end);
79 private _end(success: boolean): void {
80 // other than the old code we always have to call .end
81 // to keep the bubbling we use `success` to indicate
82 // whether a handler should execute
83 const handlers = this._handlers[this._id];
85 this._handlerFb(this._id, 'END', success);
87 let j = handlers.length - 1;
89 if (handlers[j].end(success) !== false) {
94 // cleanup left over handlers
96 handlers[j].end(false);
101 public start(): void {
102 // always reset leftover handlers
105 this._state = OscState.ID;
109 * Put data to current OSC command.
110 * Expects the identifier of the OSC command in the form
111 * OSC id ; payload ST/BEL
112 * Payload chunks are not further processed and get
113 * directly passed to the handlers.
115 public put(data: Uint32Array, start: number, end: number): void {
116 if (this._state === OscState.ABORT) {
119 if (this._state === OscState.ID) {
120 while (start < end) {
121 const code = data[start++];
123 this._state = OscState.PAYLOAD;
127 if (code < 0x30 || 0x39 < code) {
128 this._state = OscState.ABORT;
131 if (this._id === -1) {
134 this._id = this._id * 10 + code - 48;
137 if (this._state === OscState.PAYLOAD && end - start > 0) {
138 this._put(data, start, end);
143 * Indicates end of an OSC command.
144 * Whether the OSC got aborted or finished normally
145 * is indicated by `success`.
147 public end(success: boolean): void {
148 if (this._state === OscState.START) {
151 // do nothing if command was faulty
152 if (this._state !== OscState.ABORT) {
153 // if we are still in ID state and get an early end
154 // means that the command has no payload thus we still have
155 // to announce START and send END right after
156 if (this._state === OscState.ID) {
162 this._state = OscState.START;
167 * Convenient class to allow attaching string based handler functions
170 export class OscHandler implements IOscHandler {
172 private _hitLimit: boolean = false;
174 constructor(private _handler: (data: string) => any) {}
176 public start(): void {
178 this._hitLimit = false;
181 public put(data: Uint32Array, start: number, end: number): void {
182 if (this._hitLimit) {
185 this._data += utf32ToString(data, start, end);
186 if (this._data.length > PAYLOAD_LIMIT) {
188 this._hitLimit = true;
192 public end(success: boolean): any {
194 if (this._hitLimit) {
196 } else if (success) {
197 ret = this._handler(this._data);
200 this._hitLimit = false;