diff --git a/app/src/terminal/api.ts b/app/src/terminal/api.ts index 4b6ef1f3..715d99d5 100644 --- a/app/src/terminal/api.ts +++ b/app/src/terminal/api.ts @@ -9,10 +9,11 @@ export interface SessionOptions { cwd?: string, env?: any, recoveryId?: string + recoveredTruePID?: number } export abstract class SessionPersistenceProvider { - abstract async recoverSession (recoveryId: any): Promise - abstract async createSession (options: SessionOptions): Promise + abstract async attachSession (recoveryId: any): Promise + abstract async startSession (options: SessionOptions): Promise abstract async terminateSession (recoveryId: string): Promise } diff --git a/app/src/terminal/buttonProvider.ts b/app/src/terminal/buttonProvider.ts index a3c3ed13..6b67f692 100644 --- a/app/src/terminal/buttonProvider.ts +++ b/app/src/terminal/buttonProvider.ts @@ -20,7 +20,11 @@ export class ButtonProvider extends ToolbarButtonProvider { } async getNewTab (): Promise { - return new TerminalTab(await this.sessions.createNewSession({ command: 'zsh' })) + let cwd = null + if (this.app.activeTab instanceof TerminalTab) { + cwd = await this.app.activeTab.session.getWorkingDirectory() + } + return new TerminalTab(await this.sessions.createNewSession({ command: 'zsh', cwd })) } provide (): IToolbarButton[] { diff --git a/app/src/terminal/components/terminalTab.ts b/app/src/terminal/components/terminalTab.ts index 0cff2658..3eea4ced 100644 --- a/app/src/terminal/components/terminalTab.ts +++ b/app/src/terminal/components/terminalTab.ts @@ -1,5 +1,5 @@ -import { Subscription } from 'rxjs' -import { Component, NgZone, Output, Inject, EventEmitter, ElementRef } from '@angular/core' +import { BehaviorSubject, Subscription } from 'rxjs' +import { Component, NgZone, Inject, ElementRef } from '@angular/core' import { ConfigService } from 'services/config' @@ -16,9 +16,8 @@ import { hterm, preferenceManager } from '../hterm' styles: [require('./terminalTab.scss')], }) export class TerminalTabComponent extends BaseTabComponent { - title: string - @Output() titleChange = new EventEmitter() terminal: any + title$ = new BehaviorSubject('') configSubscription: Subscription focusedSubscription: Subscription startupTime: number @@ -47,8 +46,7 @@ export class TerminalTabComponent extends BaseTabComponent { }) this.terminal.setWindowTitle = (title) => { this.zone.run(() => { - this.title = title - this.titleChange.emit(title) + this.model.title = title }) } this.terminal.onTerminalReady = () => { diff --git a/app/src/terminal/persistenceProviders.ts b/app/src/terminal/persistenceProviders.ts index c1b64149..d7daeaf8 100644 --- a/app/src/terminal/persistenceProviders.ts +++ b/app/src/terminal/persistenceProviders.ts @@ -1,25 +1,12 @@ import * as fs from 'fs-promise' const exec = require('child-process-promise').exec +const spawn = require('child-process-promise').spawn import { SessionOptions, SessionPersistenceProvider } from './api' -export class NullPersistenceProvider extends SessionPersistenceProvider { - async recoverSession (_recoveryId: any): Promise { - return null - } - - async createSession (_options: SessionOptions): Promise { - return null - } - - async terminateSession (_recoveryId: string): Promise { - return - } -} - - export class ScreenPersistenceProvider extends SessionPersistenceProvider { + /* list(): Promise { return exec('screen -list').then((result) => { return result.stdout.split('\n') @@ -29,17 +16,30 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider { return [] }) } + */ + + async attachSession (recoveryId: any): Promise { + let lines = (await exec('screen -list')).stdout.split('\n') + let screenPID = lines + .filter(line => line.indexOf('.' + recoveryId) !== -1) + .map(line => parseInt(line.trim().split('.')[0]))[0] + + if (!screenPID) { + return null + } + + lines = (await exec(`ps -o pid --ppid ${screenPID}`)).stdout.split('\n') + let recoveredTruePID = parseInt(lines[1].split(/\s/).filter(x => !!x)[0]) - async recoverSession (recoveryId: any): Promise { - // TODO check return { recoveryId, + recoveredTruePID, command: 'screen', args: ['-r', recoveryId], } } - async createSession (options: SessionOptions): Promise { + async startSession (options: SessionOptions): Promise { let configPath = '/tmp/.termScreenConfig' await fs.writeFile(configPath, ` escape ^^^ @@ -52,10 +52,12 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider { hardstatus off `, 'utf-8') let recoveryId = `term-tab-${Date.now()}` - options.args = ['-c', configPath, '-U', '-S', recoveryId, '--', options.command].concat(options.args || []) - options.command = 'screen' - options.recoveryId = recoveryId - return options + let args = ['-d', '-m', '-c', configPath, '-U', '-S', recoveryId, '--', options.command].concat(options.args || []) + await spawn('screen', args, { + cwd: options.cwd, + env: options.env || process.env, + }) + return recoveryId } async terminateSession (recoveryId: string): Promise { diff --git a/app/src/terminal/services/sessions.ts b/app/src/terminal/services/sessions.ts index a41ac731..5cefddc6 100644 --- a/app/src/terminal/services/sessions.ts +++ b/app/src/terminal/services/sessions.ts @@ -1,4 +1,5 @@ import * as nodePTY from 'node-pty' +import * as fs from 'fs-promise' import { Injectable, EventEmitter } from '@angular/core' import { Logger, LogService } from 'services/log' @@ -12,6 +13,7 @@ export class Session { closed = new EventEmitter() destroyed = new EventEmitter() recoveryId: string + truePID: number private pty: any private initialDataBuffer = '' private initialDataBufferReleased = false @@ -40,6 +42,8 @@ export class Session { env: env, }) + this.truePID = options.recoveredTruePID || (this.pty).pid + this.open = true this.pty.on('data', (data) => { @@ -105,6 +109,10 @@ export class Session { this.destroyed.emit() this.pty.destroy() } + + async getWorkingDirectory (): Promise { + return await fs.readlink(`/proc/${this.truePID}/cwd`) + } } @@ -122,7 +130,8 @@ export class SessionsService { } async createNewSession (options: SessionOptions) : Promise { - options = await this.persistence.createSession(options) + let recoveryId = await this.persistence.startSession(options) + options = await this.persistence.attachSession(recoveryId) let session = this.addSession(options) return session } @@ -141,7 +150,7 @@ export class SessionsService { } async recover (recoveryId: string) : Promise { - const options = await this.persistence.recoverSession(recoveryId) + const options = await this.persistence.attachSession(recoveryId) if (!options) { return null }