import { spawn , SpawnOptions } from "child_process" ; import { callback } from "awaiting" ; const { abspath } = require ( "@cocalc/backend/misc_node" ) ; const { path_split } = require ( "@cocalc/util/misc" ) ; import { clone } from "underscore" ; const x11_channels = { } ; const pid2path = { } ; export function get_path_for_pid ( pid ) { return pid2path [ pid ] ; } class X11Channel { logger : any ; display : number ; path : string ; name : string ; channel : any ; clip : any ; constructor ( { primus , path , name , logger , display , } ) { this . logger = logger ; this . log ( "creating new x11 channel" ) ; this . display = display ; this . path = path ; this . name = name ; this . channel = primus . channel ( this . name ) ; this . init_handlers ( ) ; } log ( ... args ) { this . logger . debug ( `x11 channel ${ this . path } -- ` , ... args ) ; } new_connection ( spark ) { if ( this . channel === undefined ) { return ; } this . log ( `new connection from ${ spark . address . ip } -- ${ spark . id }` ) ; spark . on ( "data" , async ( data ) => { try { await this . handle_data ( spark , data ) ; } catch ( err ) { spark . write ( { error : `error handling command -- ${ err }` } ) ; } } ) ; } init_handlers ( ) { this . channel . on ( "connection" , this . new_connection . bind ( this ) ) ; } async handle_data ( _ , data ) { this . log ( "handle_data " , data ) ; if ( typeof data !== "object" ) { return ; } switch ( data . cmd ) { case "paste" : await this . paste ( data . value , data . wid ? data . wid : 0 ) ; break ; case "launch" : await this . launch ( data . command , data . args ) ; break ; default : throw Error ( "WARNING: unknown command -- " + data . cmd ) ; } } async paste ( value , wid ) { this . log ( "paste" , value , wid ) ; await this . set_clipboard ( value ) ; await this . cause_paste ( wid ) ; } async set_clipboard ( value ) { this . log ( "set_clipboard to string of length" , value . length ) ; const p = spawn ( "xclip" , [ "-selection" , "clipboard" , "-d" , `:${ this . display }` , ] ) ; p . stdin . write ( value ) ; p . stdin . end ( ) ; await callback ( ( cb ) => p . on ( "exit" , cb ) ) ; } cause_paste ( wid ) { this . log ( "paste to window " , wid ) ; const env = { DISPLAY : `:${ this . display }` } ; const args = [ "key" , "Control_L+v" ] ; if ( wid ) { args . push ( "--window" ) ; args . push ( `${ wid }` ) ; } this . log ( "xdotool" , args ) ; spawn ( "xdotool" , args , { env } ) . on ( "close" , ( code ) => { console . log ( `xdotool exited with code ${ code }` ) ; } ) ; } launch ( command , args ? ) { const env = clone ( process . env ) ; env . DISPLAY = `:${ this . display }` ; const cwd = this . get_cwd ( ) ; const options = { cwd , env , detached : true , stdio : "ignore" } ; args = args != null ? args : [ ] ; try { const sub = spawn ( command , args , options ) ; sub . unref ( ) ; pid2path [ sub . pid ] = this . path ; sub . on ( "exit" , ( ) => { delete pid2path [ sub . pid ] ; } ) ; } catch ( err ) { this . channel . write ( { error : `error launching ${ command } -- ${ err }` , } ) ; return ; } } private get_cwd ( ) { return path_split ( abspath ( this . path ) ) . head ; } } export async function x11_channel ( _ , primus , logger , path , display ) { const name = `x11:${ path }` ; if ( x11_channels [ name ] === undefined ) { x11_channels [ name ] = new X11Channel ( { primus , path , name , logger , display , } ) ; } return name ; }