import React from "react" ; import socketIO from "socket.io" ; import * as http from "http" ; import { tmpdir } from "os" ; import { spawn , IPty } from "node-pty" ; import { AppBase , AppsManager } from "@web-desktop-environment/app-sdk" ; import * as os from "os" ; interface TerminalInput { process ? : string ; args ? : string [ ] ; location ? : string ; } interface TerminalState { port ? : number ; } export const maxTerminalHistoryLength = 100000 ; class Terminal extends AppBase < TerminalInput , TerminalState > { constructor ( props ) { super ( props ) ; this . state = { useDefaultWindow : true , defaultWindowTitle : "terminal" , } ; } name = "terminal" ; server = http . createServer ( ) ; socketServer = new socketIO . Server ( this . server ) ; history = "" ; ptyProcess = new PTY ( ( data ) => { this . history += data ; this . history = this . history . slice ( this . history . length - maxTerminalHistoryLength , this . history . length ) ; this . socketServer . emit ( "output" , data ) ; } , this . props . input . process , this . props . input . location , this . props . input . args ) ; componentDidUnmount = false ; onComponentWillUnmount : Function [ ] = [ ] ; componentWillUnmount = ( ) => { this . componentDidUnmount = true ; this . onComponentWillUnmount . forEach ( ( f ) => f ( ) ) ; } ; componentDidMount = ( ) => { this . api . portManager . getPort ( ) . then ( ( { port } ) => { this . server . listen ( port ) ; this . setState ( { port } ) ; } ) ; this . socketServer . on ( "connection" , ( client ) => { this . socketServer . emit ( "output" , this . history ) ; client . on ( "input" , ( data ) => { this . ptyProcess . write ( data ) ; } ) ; client . on ( "setColumns" , ( columns ) => { this . ptyProcess . setCols ( columns ) ; } ) ; } ) ; let lastProcessName = "" ; const updateCurrentProcess = setInterval ( ( ) => { const newProcessName = this . ptyProcess . ptyProcess . process ; if ( lastProcessName !== newProcessName ) { lastProcessName = newProcessName ; this . setState ( { defaultWindowTitle : `terminal : ${ lastProcessName }` , } ) ; } } , 100 ) ; this . ptyProcess . ptyProcess . onExit ( ( ) => { if ( ! this . componentDidUnmount && this . props . propsForRunningAsSelfContainedApp ) { this . props . propsForRunningAsSelfContainedApp . close ( ) ; } } ) ; this . onComponentWillUnmount . push ( ( ) => { clearInterval ( updateCurrentProcess ) ; } ) ; } ; renderApp : AppBase < TerminalInput , TerminalState > [ "renderApp" ] = ( { Terminal , } ) => { const { port } = this . state ; return port && < Terminal port = { port } />; } ; } const getDefaultBash = ( ) => { if ( process . env . SHELL ) { return process . env . SHELL ; } if ( os . platform ( ) === "win32" ) { return "cmd" ; } return "bash" ; } ; export const registerApp = ( ) => AppsManager . registerApp ( { terminal : { displayName : "Terminal" , description : "a terminal window" , App : Terminal , defaultInput : { process : getDefaultBash ( ) , args : [ "-i" ] , location : tmpdir ( ) , } , icon : { type : "icon" , icon : "FcCommandLine" , } , window : { height : 400 , width : 1000 , position : { x : 50 , y : 50 } , maxHeight : 900 , maxWidth : 1200 , minWidth : 350 , minHeight : 200 , allowLocalScreenSnapping : true , } , } , } ) ; class PTY { shell : string ; public ptyProcess : IPty ; out : ( data : string ) => void ; constructor ( out , shell , cwd , args ) { this . shell = shell ; this . ptyProcess = null ; this . out = out ; this . startPtyProcess ( cwd , args ) ; } setCols = ( columns ) => { try { this . ptyProcess . resize ( columns , this . ptyProcess . rows ) ; } catch ( e ) { } } ; /**
     * Spawn an instance of pty with a selected shell.
     */ startPtyProcess ( cwd , args ) { this . ptyProcess = spawn ( this . shell , args , { name : "xterm-color" , cwd , } ) ; this . ptyProcess . onData ( ( data ) => { this . sendToClient ( data ) ; } ) ; } exitPtyProcess ( ) { this . ptyProcess . kill ( ) ; } param { * } data write ( data ) { this . ptyProcess . write ( data ) ; } sendToClient ( data ) { this . out ( data ) ; } }