UNPKG

28.6 kBJavaScriptView Raw
1/*
2 Terminal Kit
3
4 Copyright (c) 2009 - 2020 Cédric Ronvel
5
6 The MIT License (MIT)
7
8 Permission is hereby granted, free of charge, to any person obtaining a copy
9 of this software and associated documentation files (the "Software"), to deal
10 in the Software without restriction, including without limitation the rights
11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the Software is
13 furnished to do so, subject to the following conditions:
14
15 The above copyright notice and this permission notice shall be included in all
16 copies or substantial portions of the Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 SOFTWARE.
25*/
26
27"use strict" ;
28
29
30
31const termkit = require( '../termkit.js' ) ;
32const ScreenBuffer = require( '../ScreenBuffer.js' ) ;
33const Rect = require( '../Rect.js' ) ;
34const string = require( 'string-kit' ) ;
35const toInputSequence = require( './toInputSequence.js' ) ;
36const SequencesReader = require( './SequencesReader.js' ) ;
37
38const NextGenEvents = require( 'nextgen-events' ) ;
39const Promise = require( 'seventh' ) ;
40
41const spawn = require( 'child_process' ).spawn ;
42
43const logRed = ( ... args ) => console.error( '\x1b[31m' , ... args , '\x1b[m' ) ;
44const log = ( ... args ) => console.error( ... args ) ;
45
46
47
48/*
49 options:
50 * width: buffer width (default to dst.width)
51 * height: buffer height (default to dst.height)
52 * dst: writting destination
53 * palette: Palette instance
54 * eventInput: optional, an event emitter used as input source, can be a Terminal instance
55 * + any ScreenBuffer options
56
57 Properties:
58 * childStdin: the stdin stream for the child, that will be used for its keyboard/mouse event
59 * childStdout: the stdout stream for the child, that will be displayed on the terminal
60*/
61function Vte( options = {} ) {
62 this.width = Math.floor( options.width ) || ( options.dst ? options.dst.width : 80 ) ;
63 this.height = Math.floor( options.height ) || ( options.dst ? options.dst.height : 25 ) ;
64 this.palette = options.palette || ( this.dst && this.dst.palette ) ;
65
66 this.screenBuffer = new ScreenBuffer( Object.assign( {} , options , this , { wrap: true } ) ) ;
67 this.screenBuffer.setClearAttr( { defaultColor: true , bgDefaultColor: true } ) ;
68
69 // To avoid mistake, cx/cy are starting at 0, like the internal screenBuffer does
70 this.cx = 0 ;
71 this.cy = 0 ;
72 this.savedCx = 0 ;
73 this.savedCy = 0 ;
74
75 this.attr = 0 ;
76 this.resetAttr() ;
77
78 this.scrollingRegion = null ;
79 this.tabWidth = 8 ;
80 this.mouseEvent = null ;
81 this.focusEvent = false ;
82 this.mouseIsDragging = false ;
83
84 this.eventInput = options.eventInput ;
85 this.childSequencesReader = new SequencesReader() ;
86 this.childProcess = null ;
87
88 this.onEventInputKey = this.onEventInputKey.bind( this ) ;
89 this.onEventInputMouse = this.onEventInputMouse.bind( this ) ;
90 this.onEventInputTerminal = this.onEventInputTerminal.bind( this ) ;
91
92 this.onChildOutputReset = this.onChildOutputReset.bind( this ) ;
93 this.onChildOutputChar = this.onChildOutputChar.bind( this ) ;
94 this.onChildOutputCursor = this.onChildOutputCursor.bind( this ) ;
95 this.onChildOutputEdit = this.onChildOutputEdit.bind( this ) ;
96 this.onChildOutputAttr = this.onChildOutputAttr.bind( this ) ;
97 this.onChildOutputPalette = this.onChildOutputPalette.bind( this ) ;
98 this.onChildOutputCursorAttr = this.onChildOutputCursorAttr.bind( this ) ;
99 this.onChildOutputBell = this.onChildOutputBell.bind( this ) ;
100 this.onChildOutputDevice = this.onChildOutputDevice.bind( this ) ;
101 this.onChildOutputSystem = this.onChildOutputSystem.bind( this ) ;
102
103 this.onChildOutputControl = this.onChildOutputControl.bind( this ) ;
104 this.onChildOutputESC = this.onChildOutputESC.bind( this ) ;
105 this.onChildOutputCSI = this.onChildOutputCSI.bind( this ) ;
106 this.onChildOutputOSC = this.onChildOutputOSC.bind( this ) ;
107
108 // The real draw is sync, so there is no need for Promise.debounceUpdate(), Promise.debounce() avoid an extra draw with no delta
109 //this.drawDebounce = Promise.debounceUpdate( this.drawDelay.bind( this ) ) ;
110 this.drawDebounce = Promise.debounce( this.drawDelay.bind( this ) ) ;
111}
112
113module.exports = Vte ;
114
115Vte.prototype = Object.create( NextGenEvents.prototype ) ;
116Vte.prototype.constructor = Vte ;
117
118
119
120// Run a child process
121Vte.prototype.run = function( command , args ) {
122 var childPty , child ,
123 promise = new Promise() ;
124
125 if ( this.childProcess ) { return ; }
126
127 this.start() ;
128
129 try {
130 // If child_pty is present, then use it instead of spawn
131 // So programs launched would think they truly run inside a TTY
132 childPty = require( 'child_pty' ) ;
133 child = childPty.spawn( command , args , {
134 columns: this.width ,
135 rows: this.height
136 //stdio: [ 'pty' , 'pty' , 'pty' ]
137 } ) ;
138 }
139 catch ( error ) {
140 logRed( "'child_pty' optional dependency not found, using regular child_process.spawn()" ) ;
141 child = spawn( command , args ) ;
142 }
143
144 this.childProcess = child ;
145
146 //this.on( 'input' , data => { log( 'stdin:' , data ) ; child.stdin.write( data ) ; } ) ;
147 this.on( 'input' , data => child.stdin.write( data ) ) ;
148 this.childSequencesReader.streamToEvent( child.stdout ) ;
149
150 //child.stdout.on( 'data' , this.onChildOutput ) ;
151 //child.stderr.on( 'data' , this.onChildOutput ) ;
152
153 // Tmp, to close the app during alpha phase
154 child.on( 'close' , code => {
155 this.childProcess = null ;
156 promise.resolve() ;
157 } ) ;
158
159 return promise ;
160} ;
161
162
163
164Vte.prototype.start = function() {
165 if ( this.eventInput ) {
166 this.eventInput.on( 'key' , this.onEventInputKey ) ;
167 this.eventInput.on( 'mouse' , this.onEventInputMouse ) ;
168 this.eventInput.on( 'terminal' , this.onEventInputTerminal ) ;
169 }
170
171 this.childSequencesReader.on( 'reset' , this.onChildOutputReset ) ;
172 this.childSequencesReader.on( 'char' , this.onChildOutputChar ) ;
173 this.childSequencesReader.on( 'cursor' , this.onChildOutputCursor ) ;
174 this.childSequencesReader.on( 'edit' , this.onChildOutputEdit ) ;
175 this.childSequencesReader.on( 'attr' , this.onChildOutputAttr ) ;
176 this.childSequencesReader.on( 'palette' , this.onChildOutputPalette ) ;
177 this.childSequencesReader.on( 'cursorAttr' , this.onChildOutputCursorAttr ) ;
178 this.childSequencesReader.on( 'bell' , this.onChildOutputBell ) ;
179 this.childSequencesReader.on( 'device' , this.onChildOutputDevice ) ;
180 this.childSequencesReader.on( 'system' , this.onChildOutputSystem ) ;
181
182 this.childSequencesReader.on( 'control' , this.onChildOutputControl ) ;
183 this.childSequencesReader.on( 'ESC' , this.onChildOutputESC ) ;
184 this.childSequencesReader.on( 'CSI' , this.onChildOutputCSI ) ;
185 this.childSequencesReader.on( 'OSC' , this.onChildOutputOSC ) ;
186} ;
187
188
189
190Vte.prototype.draw = function() {
191 var stats = this.screenBuffer.draw( { delta: true } ) ;
192 this.screenBuffer.drawCursor() ;
193 log( 'draw stats:' , stats ) ;
194} ;
195
196
197
198// Full redraw, no delta
199Vte.prototype.redraw = function() {
200 var stats = this.screenBuffer.draw( { delta: false } ) ;
201 this.screenBuffer.drawCursor() ;
202 log( 'redraw stats:' , stats ) ;
203} ;
204
205
206
207Vte.prototype.drawDelay = async function() {
208 //await Promise.resolveTimeout( 10 ) ;
209 await Promise.resolveNextTick() ;
210 this.draw() ;
211} ;
212
213
214
215Vte.prototype.putChar = function( char ) {
216 var charCode = char.charCodeAt( 0 ) ; log( 'putChar:' , charCode <= 0x1f || charCode === 0x7f ? '(ctrl)' : char , charCode >= 0x10 ? '\\x' + charCode.toString( 16 ) : '\\x0' + charCode.toString( 16 ) , 'at:' , this.cx , this.cy ) ;
217 this.screenBuffer.put( { x: this.cx , y: this.cy , attr: this.attr } , char ) ;
218 this.cx ++ ;
219
220 if ( this.cx >= this.width ) {
221 this.newLine() ;
222 }
223 else {
224 this.drawDebounce() ;
225 }
226} ;
227
228
229
230// internal = don't adjust
231Vte.prototype.moveCursorTo = function( x , y , internal = false ) {
232 if ( internal ) {
233 if ( x !== undefined ) { this.cx = x ; }
234 if ( y !== undefined ) { this.cy = y ; }
235 }
236 else {
237 if ( x !== undefined ) { this.cx = x - 1 ; }
238 if ( y !== undefined ) { this.cy = y - 1 ; }
239 }
240
241 if ( this.cx < 0 ) { this.cx = 0 ; }
242 else if ( this.cx >= this.width - 1 ) { this.cx = this.width - 1 ; }
243
244 if ( this.cy < 0 ) { this.cy = 0 ; }
245 else if ( this.cy >= this.height - 1 ) { this.cy = this.height - 1 ; }
246
247 this.screenBuffer.cx = this.cx ;
248 this.screenBuffer.cy = this.cy ;
249 this.screenBuffer.drawCursor() ;
250} ;
251
252
253
254// Relative move
255Vte.prototype.moveCursor = function( x , y ) {
256 this.moveCursorTo( this.cx + x , this.cy + y , true ) ;
257} ;
258
259
260
261// Next horizontal tab
262Vte.prototype.nextTab = function() {
263 this.moveCursorTo( Math.ceil( ( this.cx + 1 ) / this.tabWidth ) * this.tabWidth , undefined , true ) ;
264} ;
265
266
267
268Vte.prototype.vScroll = function( lineOffset , noDraw ) {
269 var ymin = 0 ,
270 ymax = this.height - 1 ;
271
272 if ( this.scrollingRegion && this.cy >= this.scrollingRegion.ymin && this.cy <= this.scrollingRegion.ymax ) {
273 ( { ymin , ymax } = this.scrollingRegion ) ;
274 }
275
276 log( '################### vScroll:' , lineOffset , ymin , ymax ) ;
277 this.screenBuffer.vScroll( lineOffset , this.attr , ymin , ymax , true ) ;
278
279 if ( ! noDraw ) { this.drawDebounce() ; }
280} ;
281
282
283
284Vte.prototype.lineFeed = function( carriageReturn , noDraw ) {
285 var ymin = 0 ,
286 ymax = this.height - 1 ;
287
288 if ( this.scrollingRegion && this.cy >= this.scrollingRegion.ymin && this.cy <= this.scrollingRegion.ymax ) {
289 ( { ymin , ymax } = this.scrollingRegion ) ;
290 }
291
292 this.screenBuffer.cy = ++ this.cy ;
293
294 if ( carriageReturn ) {
295 this.screenBuffer.cx = this.cx = 0 ;
296 }
297
298 if ( this.cy > ymax ) {
299 // Scroll up!
300 // This will immediately scroll the underlying terminal using the scrolling region capability
301 this.screenBuffer.cy = this.cy = ymax ;
302 this.vScroll( -1 , noDraw ) ;
303 if ( ! noDraw ) { this.screenBuffer.drawCursor() ; }
304 }
305 else if ( ! noDraw ) {
306 this.drawDebounce() ;
307 }
308} ;
309
310Vte.prototype.newLine = function( noDraw ) { return this.lineFeed( true , noDraw ) ; } ;
311
312
313
314Vte.prototype.reverseLineFeed = function( carriageReturn , noDraw ) {
315 var ymin = 0 ,
316 ymax = this.height - 1 ;
317
318 if ( this.scrollingRegion && this.cy >= this.scrollingRegion.ymin && this.cy <= this.scrollingRegion.ymax ) {
319 ( { ymin , ymax } = this.scrollingRegion ) ;
320 }
321
322 this.screenBuffer.cy = -- this.cy ;
323
324 if ( carriageReturn ) {
325 this.screenBuffer.cx = this.cx = 0 ;
326 }
327
328 if ( this.cy < ymin ) {
329 // Scroll down!
330 // This will immediately scroll the underlying terminal using the scrolling region capability
331 this.screenBuffer.cy = this.cy = ymin ;
332 this.vScroll( 1 , noDraw ) ;
333 if ( ! noDraw ) { this.screenBuffer.drawCursor() ; }
334 }
335 else if ( ! noDraw ) {
336 this.drawDebounce() ;
337 }
338} ;
339
340
341
342Vte.prototype.eraseLine = function( mode , noDraw ) {
343 if ( mode === 'after' ) {
344 this.screenBuffer.fill( {
345 region: {
346 xmin: this.cx , xmax: this.width - 1 , ymin: this.cy , ymax: this.cy
347 } ,
348 attr: this.attr
349 } ) ;
350 }
351 else if ( mode === 'before' ) {
352 this.screenBuffer.fill( {
353 region: {
354 xmin: 0 , xmax: this.cx , ymin: this.cy , ymax: this.cy
355 } ,
356 attr: this.attr
357 } ) ;
358 }
359 else {
360 this.screenBuffer.fill( {
361 region: {
362 xmin: 0 , xmax: this.width - 1 , ymin: this.cy , ymax: this.cy
363 } ,
364 attr: this.attr
365 } ) ;
366 }
367
368 if ( ! noDraw ) { this.drawDebounce() ; }
369} ;
370
371
372
373Vte.prototype.eraseDisplay = function( mode , noDraw ) {
374 if ( mode === 'after' ) {
375 // First, erase the current line from the cursor
376 this.screenBuffer.fill( {
377 region: {
378 xmin: this.cx , xmax: this.width - 1 , ymin: this.cy , ymax: this.cy
379 } ,
380 attr: this.attr
381 } ) ;
382 // Then, erase all lines below the current line
383 this.screenBuffer.fill( {
384 region: {
385 xmin: 0 , xmax: this.width - 1 , ymin: this.cy + 1 , ymax: this.height - 1
386 } ,
387 attr: this.attr
388 } ) ;
389 }
390 else if ( mode === 'before' ) {
391 // First, erase all lines above the current line
392 this.screenBuffer.fill( {
393 region: {
394 xmin: 0 , xmax: this.width - 1 , ymin: 0 , ymax: this.cy - 1
395 } ,
396 attr: this.attr
397 } ) ;
398 // Then, erase the current line up to the cursor
399 this.screenBuffer.fill( {
400 region: {
401 xmin: 0 , xmax: this.cx , ymin: this.cy , ymax: this.cy
402 } ,
403 attr: this.attr
404 } ) ;
405 }
406 else {
407 this.screenBuffer.fill( { attr: this.attr } ) ;
408 }
409
410 if ( ! noDraw ) { this.drawDebounce() ; }
411} ;
412
413
414
415Vte.prototype.backDelete = function( count = 1 ) {
416 if ( count > this.cx ) { count = this.cx ; }
417 if ( count <= 0 ) { return ; }
418
419 // Shift the end of the line
420 this.screenBuffer.copyRegion( {
421 xmin: this.cx , ymin: this.cy , xmax: this.width - 1 , ymax: this.cy
422 } , { x: this.cx - count , y: this.cy } ) ;
423 this.cx -= count ;
424
425 // Fill the end of the line with blank
426 this.screenBuffer.fill( { region: {
427 xmin: this.width - count , ymin: this.cy , xmax: this.width - 1 , ymax: this.cy
428 } ,
429 attr: this.attr } , ' ' ) ;
430
431 this.screenBuffer.cx = this.cx ;
432
433 this.drawDebounce() ;
434} ;
435
436
437
438Vte.prototype.delete = function( count = 1 ) {
439 if ( count > this.width - this.cx ) { count = this.width - this.cx ; }
440 if ( count <= 0 ) { return ; }
441
442 // Shift the end of the line
443 if ( this.cx + count < this.width ) {
444 this.screenBuffer.copyRegion( {
445 xmin: this.cx + count , ymin: this.cy , xmax: this.width - 1 , ymax: this.cy
446 } , { x: this.cx , y: this.cy } ) ;
447 log( "delete:" , count , "copy region:" , {
448 xmin: this.cx + count , ymin: this.cy , xmax: this.width - 1 , ymax: this.cy
449 } , { x: this.cx , y: this.cy } ) ;
450 }
451
452 // Fill the end of the line with blank
453 this.screenBuffer.fill( { region: {
454 xmin: this.width - count , ymin: this.cy , xmax: this.width - 1 , ymax: this.cy
455 } ,
456 attr: this.attr } , ' ' ) ;
457 log( "delete:" , count , "fill region:" , {
458 xmin: this.width - count , ymin: this.cy , xmax: this.width - 1 , ymax: this.cy
459 } ) ;
460
461 this.drawDebounce() ;
462} ;
463
464
465
466Vte.prototype.erase = function( count = 1 ) {
467 if ( count > this.width - this.cx ) { count = this.width - this.cx ; }
468 if ( count <= 0 ) { return ; }
469
470 // Fill with blank
471 this.screenBuffer.fill( { region: {
472 xmin: this.cx , ymin: this.cy , xmax: this.cx + count - 1 , ymax: this.cy
473 } ,
474 attr: this.attr } , ' ' ) ;
475 log( "erase:" , count , "fill region:" , {
476 xmin: this.cx , ymin: this.cy , xmax: this.cx + count - 1 , ymax: this.cy
477 } ) ;
478
479 this.drawDebounce() ;
480} ;
481
482
483
484Vte.prototype.deleteLine = function( count = 1 ) {
485 if ( count > this.height - this.cy ) { count = this.height - this.cy ; }
486 if ( count <= 0 ) { return ; }
487
488 // Shift from the end of the screen
489 if ( this.cy + count < this.height ) {
490 this.screenBuffer.copyRegion( {
491 xmin: 0 , ymin: this.cy + count , xmax: this.width - 1 , ymax: this.height - 1
492 } , { x: 0 , y: this.cy } ) ;
493 log( "deleteLine:" , count , "copy region:" , {
494 xmin: 0 , ymin: this.cy + count , xmax: this.width - 1 , ymax: this.height - 1
495 } , { x: 0 , y: this.cy } ) ;
496 }
497
498 // Fill the end of the screen with blank
499 this.screenBuffer.fill( { region: {
500 xmin: 0 , ymin: this.height - count , xmax: this.width - 1 , ymax: this.height - 1
501 } ,
502 attr: this.attr } , ' ' ) ;
503 log( "deleteLine:" , count , "fill region:" , {
504 xmin: 0 , ymin: this.height - count , xmax: this.width - 1 , ymax: this.height - 1
505 } ) ;
506
507 // This move x to the left
508 this.cx = this.screenBuffer.cx = 0 ;
509
510 this.drawDebounce() ;
511} ;
512
513
514
515Vte.prototype.insertLine = function( count = 1 ) {
516 if ( count > this.height - this.cy ) { count = this.height - this.cy ; }
517 if ( count <= 0 ) { return ; }
518
519 // Shift to the end of the screen
520 if ( this.cy + count < this.height ) {
521 //this.screenBuffer.copyRegion( { xmin: 0 , ymin: this.cy + count , xmax: this.width - 1 , ymax: this.height - 1 } , { x: 0 , y: this.cy } ) ;
522 this.screenBuffer.copyRegion( {
523 xmin: 0 , ymin: this.cy , xmax: this.width - 1 , ymax: this.height - count
524 } , { x: 0 , y: this.cy + count } ) ;
525 log( "insertLine:" , count , "copy region:" , {
526 xmin: 0 , ymin: this.cy + count , xmax: this.width - 1 , ymax: this.height - 1
527 } , { x: 0 , y: this.cy } ) ;
528 }
529
530 // Fill the inserted lines with blank
531 //this.screenBuffer.fill( { region: { xmin: 0 , ymin: this.height - count , xmax: this.width - 1 , ymax: this.height - 1 } , attr: this.attr } , ' ' ) ;
532 this.screenBuffer.fill( { region: {
533 xmin: 0 , ymin: this.cy , xmax: this.width - 1 , ymax: this.cy + count
534 } ,
535 attr: this.attr } , ' ' ) ;
536 log( "insertLine:" , count , "fill region:" , {
537 xmin: 0 , ymin: this.height - count , xmax: this.width - 1 , ymax: this.height - 1
538 } ) ;
539
540 // This move x to the left
541 this.cx = this.screenBuffer.cx = 0 ;
542
543 this.drawDebounce() ;
544} ;
545
546
547
548Vte.prototype.resetAttr = function() { this.attr = this.screenBuffer.DEFAULT_ATTR ; } ;
549Vte.prototype.setAttr = function( attrObject ) { this.attr = this.screenBuffer.object2attr( attrObject ) ; } ;
550Vte.prototype.addAttr = function( attrObject ) { this.attr = this.screenBuffer.attrAndObject( this.attr , attrObject ) ; } ;
551
552
553
554Vte.prototype.setVScrollingRegion = function( ymin = null , ymax = null , internal = false ) {
555 log( "########################### setVScrollingRegion:" , ymin , ymax , internal ) ;
556 if ( ymin === null || ymax === null ) {
557 this.scrollingRegion = null ;
558 }
559 else if ( internal ) {
560 this.scrollingRegion = new Rect( 0 , Math.max( 0 , ymin ) , this.width - 1 , Math.min( this.height - 1 , ymax ) ) ;
561 }
562 else {
563 this.scrollingRegion = new Rect( 0 , Math.max( 0 , ymin - 1 ) , this.width - 1 , Math.min( this.height - 1 , ymax - 1 ) ) ;
564 }
565 log( "########################### setVScrollingRegion region:" , this.scrollingRegion ) ;
566} ;
567
568
569
570// Emit cursor location escape sequence
571Vte.prototype.emitCursorLocation = function( decVariant ) {
572 //this.emit( 'input' , '\x1b[' + ( decVariant ? '?' : '' ) + this.cy + ';' + this.cx + 'R' ) ;
573 this.emit( 'input' , string.format( toInputSequence.reports[ decVariant ? 'cursorLocationDecVariant' : 'cursorLocation' ] , this.cx , this.cy ) ) ;
574} ;
575
576
577
578// Emit the screen size
579Vte.prototype.emitScreenSize = function( decVariant ) {
580 this.emit( 'input' , string.format( toInputSequence.reports.screenSize , this.width , this.height ) ) ;
581} ;
582
583
584
585// Emit the focus
586Vte.prototype.emitFocus = function( isIn ) {
587 this.emit( 'input' , toInputSequence.reports[ isIn ? 'focusIn' : 'focusOut' ] ) ;
588} ;
589
590
591
592// Emit the focus
593Vte.prototype.emitRegisterColor = function( register ) {
594 logRed( "emitRegisterColor" , register ) ;
595 var rgb = this.screenBuffer.palette.getRgb( register ) ;
596 logRed( "emitRegisterColor >>> " , rgb ) ;
597 if ( ! rgb ) { return ; }
598 this.emit( 'input' , string.format( toInputSequence.reports.registerColor , register , rgb.r , rgb.g , rgb.b ) ) ;
599} ;
600
601
602
603// Emit mouse event escape sequence using the SGR protocol
604Vte.prototype.emitMouseSGR = function( type , data ) {
605 var code = 0 , released = false ;
606
607 if ( data.shift ) { code |= 4 ; }
608 if ( data.alt ) { code |= 8 ; }
609 if ( data.ctrl ) { code |= 16 ; }
610
611 switch ( type ) {
612 case 'MOUSE_LEFT_BUTTON_PRESSED' :
613 break ;
614 case 'MOUSE_MIDDLE_BUTTON_PRESSED' :
615 code |= 1 ;
616 break ;
617 case 'MOUSE_RIGHT_BUTTON_PRESSED' :
618 code |= 2 ;
619 break ;
620 case 'MOUSE_OTHER_BUTTON_PRESSED' :
621 code |= 3 ;
622 break ;
623 case 'MOUSE_LEFT_BUTTON_RELEASED' :
624 released = true ;
625 break ;
626 case 'MOUSE_MIDDLE_BUTTON_RELEASED' :
627 code |= 1 ;
628 released = true ;
629 break ;
630 case 'MOUSE_RIGHT_BUTTON_RELEASED' :
631 code |= 2 ;
632 released = true ;
633 break ;
634 case 'MOUSE_OTHER_BUTTON_RELEASED' :
635 code |= 3 ;
636 released = true ;
637 break ;
638 case 'MOUSE_WHEEL_UP' :
639 code |= 64 ;
640 break ;
641 case 'MOUSE_WHEEL_DOWN' :
642 code |= 65 ;
643 break ;
644 case 'MOUSE_MOTION' :
645 code |= 32 ;
646 break ;
647 }
648
649 this.emit( 'input' , '\x1b[<' + code + ';' + data.x + ';' + data.y + ( released ? 'm' : 'M' ) ) ;
650} ;
651
652
653
654// Event handlers
655
656
657
658Vte.prototype.onEventInputKey = function( key , altKeys , data ) {
659 log( 'onEventInputKey:' , key ) ;
660 if ( data.isCharacter ) {
661 this.emit( 'input' , key ) ;
662 }
663 else if ( toInputSequence.specialKeys[ key ] ) {
664 this.emit( 'input' , toInputSequence.specialKeys[ key ] ) ;
665 }
666} ;
667
668
669
670Vte.prototype.onEventInputMouse = function( type , data ) {
671 if ( ! this.mouseEvent ) { return ; }
672 //log( 'onEventInputMouse:' , type , data ) ;
673
674 // /!\ Not sure if it's the most reliable way to do that
675 if ( this.eventInput === this.screenBuffer.dst ) {
676 // We MUST add an offset to the coordinate
677 data.x -= this.screenBuffer.x - 1 ;
678 data.y -= this.screenBuffer.y - 1 ;
679 }
680
681 switch ( type ) {
682 case 'MOUSE_LEFT_BUTTON_PRESSED' :
683 case 'MOUSE_MIDDLE_BUTTON_PRESSED' :
684 case 'MOUSE_RIGHT_BUTTON_PRESSED' :
685 case 'MOUSE_OTHER_BUTTON_PRESSED' :
686 this.mouseIsDragging = true ;
687 this.emitMouseSGR( type , data ) ;
688 break ;
689 case 'MOUSE_LEFT_BUTTON_RELEASED' :
690 case 'MOUSE_MIDDLE_BUTTON_RELEASED' :
691 case 'MOUSE_RIGHT_BUTTON_RELEASED' :
692 case 'MOUSE_OTHER_BUTTON_RELEASED' :
693 this.mouseIsDragging = false ;
694 this.emitMouseSGR( type , data ) ;
695 break ;
696 case 'MOUSE_WHEEL_UP' :
697 case 'MOUSE_WHEEL_DOWN' :
698 this.emitMouseSGR( type , data ) ;
699 break ;
700 case 'MOUSE_MOTION' :
701 if ( this.mouseEvent === 'motion' || ( this.mouseEvent === 'drag' && this.mouseIsDragging ) ) {
702 this.emitMouseSGR( type , data ) ;
703 }
704 break ;
705 }
706} ;
707
708
709
710Vte.prototype.onEventInputTerminal = function( type , data ) {
711 switch ( type ) {
712 case 'FOCUS_IN' :
713 if ( this.focusEvent ) { this.emitFocus( true ) ; }
714 break ;
715 case 'FOCUS_OUT' :
716 if ( this.focusEvent ) { this.emitFocus( false ) ; }
717 break ;
718 }
719} ;
720
721
722
723Vte.prototype.onChildOutputReset = function() {
724 logRed( 'full reset' ) ;
725} ;
726
727
728
729Vte.prototype.onChildOutputChar = function( char , charCode ) {
730 //log( '>>> put char charCode:' , charCode ) ;
731 //log( 'put char:' , charCode <= 0x1f || charCode === 0x7f ? '(ctrl)' : char , charCode >= 0x10 ? '\\x' + charCode.toString( 16 ) : '\\x0' + charCode.toString( 16 ) ) ;
732 this.putChar( char ) ;
733} ;
734
735
736
737Vte.prototype.onChildOutputCursor = function( subType , arg , extraArgs ) {
738 log( 'cursor:' , subType , arg , extraArgs ) ;
739
740 var arg1 = extraArgs && extraArgs[ 0 ] ? + extraArgs[ 0 ] : undefined ;
741 var arg2 = extraArgs && extraArgs[ 1 ] ? + extraArgs[ 1 ] : undefined ;
742
743 switch ( subType ) {
744 //case 'newLine' : return this.newLine() ;
745 case 'lineFeed' :
746 return this.lineFeed() ;
747 case 'carriageReturn' :
748 return this.moveCursorTo( 0 , undefined , true ) ;
749 case 'tab' :
750 return this.nextTab() ;
751 case 'move' :
752 // unused
753 this.moveCursor( arg1 , arg2 ) ;
754 break ;
755 case 'up' :
756 this.moveCursor( 0 , -arg1 ) ;
757 break ;
758 case 'down' :
759 this.moveCursor( 0 , arg1 ) ;
760 break ;
761 case 'right' :
762 this.moveCursor( arg1 , 0 ) ;
763 break ;
764 case 'left' :
765 this.moveCursor( -arg1 , 0 ) ;
766 break ;
767 case 'moveToYX' :
768 // Swap the args
769 this.moveCursorTo( arg2 , arg1 ) ;
770 break ;
771 case 'column' :
772 this.moveCursorTo( arg1 ) ;
773 break ;
774 case 'row' :
775 this.moveCursorTo( undefined , arg1 ) ;
776 break ;
777 case 'previousLine' :
778 this.moveCursor( -this.cx , -arg1 ) ;
779 break ;
780 case 'nextLine' :
781 this.moveCursor( -this.cx , arg1 ) ;
782 break ;
783 case 'save' :
784 this.savedCx = this.cx ;
785 this.savedCy = this.cy ;
786 break ;
787 case 'restore' :
788 this.moveCursorTo( this.savedCx , this.savedCy , true ) ;
789 break ;
790 default :
791 logRed( 'Unknown/unsupported cursor action' , subType , arg , extraArgs ) ;
792 }
793} ;
794
795
796
797Vte.prototype.onChildOutputEdit = function( subType , arg , extraArgs ) {
798 var arg1 = extraArgs && extraArgs[ 0 ] ? + extraArgs[ 0 ] : undefined ;
799 var arg2 = extraArgs && extraArgs[ 1 ] ? + extraArgs[ 1 ] : undefined ;
800
801 switch ( subType ) {
802 case 'backDelete' :
803 log( 'backDelete' , arg1 ) ;
804 return this.backDelete( arg1 ) ;
805 case 'delete' :
806 log( 'delete' , arg1 ) ;
807 return this.delete( arg1 ) ;
808 case 'erase' :
809 log( 'erase' , arg ) ;
810 this.erase( arg ) ;
811 break ;
812 case 'deleteLine' :
813 log( 'deleteLine' , arg1 ) ;
814 this.deleteLine( arg1 ) ;
815 break ;
816 case 'insertLine' :
817 log( 'insertLine' , arg1 ) ;
818 this.insertLine( arg1 ) ;
819 break ;
820 case 'eraseLine' :
821 log( 'eraseLine' , arg ) ;
822 this.eraseLine( arg ) ;
823 break ;
824 case 'eraseDisplay' :
825 log( 'eraseDisplay' , arg ) ;
826 this.eraseDisplay( arg ) ;
827 break ;
828 case 'reverseLineFeed' :
829 log( 'reverseLineFeed' ) ;
830 this.reverseLineFeed( arg ) ;
831 break ;
832 case 'vScrollingRegion' :
833 log( 'vScrollingRegion' , arg1 , arg2 ) ;
834 this.setVScrollingRegion( arg1 , arg2 ) ;
835 //this.setVScrollingRegion( arg1 , arg2 , true ) ;
836 break ;
837 case 'vScrollUp' :
838 log( 'vScrollUp' , arg1 ) ;
839 this.vScroll( -arg1 ) ;
840 break ;
841 case 'vScrollDown' :
842 log( 'vScrollDown' , arg1 ) ;
843 this.vScroll( arg1 ) ;
844 break ;
845 default :
846 logRed( 'Unknown/unsupported edit action' , subType , arg , extraArgs ) ;
847 }
848} ;
849
850
851
852Vte.prototype.onChildOutputAttr = function( subType , arg , extraArgs ) {
853 switch ( subType ) {
854 case 'reset' :
855 log( 'ATTR reset' ) ;
856 this.resetAttr() ;
857 break ;
858 case 'bold' :
859 log( 'ATTR bold:' , arg ) ;
860 this.addAttr( { bold: arg } ) ;
861 break ;
862 case 'dim' :
863 log( 'ATTR dim:' , arg ) ;
864 this.addAttr( { dim: arg } ) ;
865 break ;
866 case 'italic' :
867 log( 'ATTR italic:' , arg ) ;
868 this.addAttr( { italic: arg } ) ;
869 break ;
870 case 'underline' :
871 log( 'ATTR underline:' , arg ) ;
872 this.addAttr( { underline: arg } ) ;
873 break ;
874 case 'blink' :
875 log( 'ATTR blink:' , arg ) ;
876 this.addAttr( { blink: arg } ) ;
877 break ;
878 case 'inverse' :
879 log( 'ATTR inverse:' , arg ) ;
880 this.addAttr( { inverse: arg } ) ;
881 break ;
882 case 'hidden' :
883 log( 'ATTR hidden:' , arg ) ;
884 this.addAttr( { hidden: arg } ) ;
885 break ;
886 case 'strike' :
887 log( 'ATTR strike:' , arg ) ;
888 this.addAttr( { strike: arg } ) ;
889 break ;
890 case 'noDimNoBold' :
891 log( 'ATTR noDimNoBold' ) ;
892 this.addAttr( { bold: false , dim: false } ) ;
893 break ;
894 case 'color' :
895 log( 'ATTR color:' , arg ) ;
896 this.addAttr( { color: arg } ) ;
897 break ;
898 case 'color256' :
899 log( 'ATTR color256:' , extraArgs ) ;
900 this.addAttr( { color: + extraArgs[ 0 ] } ) ;
901 break ;
902 case 'colorRgb' :
903 log( 'ATTR colorRgb:' , extraArgs , 'not supported ATM' ) ;
904 break ;
905 case 'bgColor' :
906 log( 'ATTR bgColor:' , arg ) ;
907 this.addAttr( { bgColor: arg } ) ;
908 break ;
909 case 'bgColor256' :
910 log( 'ATTR bgColor256:' , extraArgs ) ;
911 this.addAttr( { bgColor: + extraArgs[ 0 ] } ) ;
912 break ;
913 case 'bgColorRgb' :
914 log( 'ATTR bgColorRgb:' , extraArgs , 'not supported ATM' ) ;
915 break ;
916 default :
917 logRed( 'Unknown/unsupported ATTR' , subType , arg , extraArgs ) ;
918 }
919} ;
920
921
922
923Vte.prototype.onChildOutputPalette = function( subType , extraArgs ) {
924 logRed( 'Palette command:' , subType , extraArgs ) ;
925
926 var arg1 = extraArgs && extraArgs[ 0 ] ? + extraArgs[ 0 ] : undefined ;
927
928 switch ( subType ) {
929 case 'getColor' :
930 if ( ! isNaN( arg1 ) ) { this.emitRegisterColor( arg1 ) ; }
931 break ;
932 }
933} ;
934
935
936
937Vte.prototype.onChildOutputCursorAttr = function( subType , args ) {
938 logRed( 'Cursor ATTR command:' , subType , args ) ;
939} ;
940
941
942
943Vte.prototype.onChildOutputBell = function() {
944 logRed( 'bell' ) ;
945} ;
946
947
948
949Vte.prototype.onChildOutputDevice = function( subType , arg , extraArgs ) {
950 logRed( 'Device command:' , subType , arg , extraArgs ) ;
951 switch ( subType ) {
952 case 'mouseButton' :
953 this.mouseEvent = arg ? 'button' : null ;
954 break ;
955 case 'mouseDrag' :
956 this.mouseEvent = arg ? 'drag' : null ;
957 break ;
958 case 'mouseMotion' :
959 this.mouseEvent = arg ? 'motion' : null ;
960 break ;
961 case 'focusEvent' :
962 this.focusEvent = !! arg ;
963 break ;
964 case 'cursorLocation' :
965 // Arg is true for DEC mode (add a '?' to the sequence)
966 this.emitCursorLocation( arg ) ;
967 break ;
968 case 'screenSize' :
969 this.emitScreenSize( arg ) ;
970 break ;
971 default :
972 logRed( 'Unknown/unsupported device command' , subType , arg , extraArgs ) ;
973 }
974} ;
975
976
977
978Vte.prototype.onChildOutputSystem = function( subType , args ) {
979 logRed( 'System command:' , subType , args ) ;
980} ;
981
982
983
984// Triggered when unknown/unsupported sequences are produced
985
986Vte.prototype.onChildOutputControl = function( charCodeStr ) {
987 logRed( 'control' , charCodeStr ) ;
988} ;
989
990
991
992Vte.prototype.onChildOutputESC = function( type , args ) {
993 logRed( 'ESC -- type:' , type , args ) ;
994} ;
995
996
997
998Vte.prototype.onChildOutputCSI = function( type , args ) {
999 logRed( 'CSI -- type:' , type , ', args:' , args ) ;
1000} ;
1001
1002
1003
1004Vte.prototype.onChildOutputOSC = function( type , args ) {
1005 logRed( 'OSC -- type:' , type , ', args:' , args ) ;
1006} ;
1007