Eugene Pankov 8 years ago
parent
commit
9312db1fc6

+ 4 - 0
app/src/entry.preload.ts

@@ -31,3 +31,7 @@ process.on('uncaughtException', (err) => {
     Raven.captureException(err)
     console.error(err)
 })
+
+const childProcess = require('child_process')
+childProcess.spawn = require('electron').remote.require('child_process').spawn
+childProcess.exec = require('electron').remote.require('child_process').exec

+ 1 - 0
app/webpack.config.js

@@ -55,6 +55,7 @@ module.exports = {
     '@angular/forms': 'commonjs @angular/forms',
     '@angular/common': 'commonjs @angular/common',
     '@ng-bootstrap/ng-bootstrap': 'commonjs @ng-bootstrap/ng-bootstrap',
+    'child_process': 'commonjs child_process',
     'electron': 'commonjs electron',
     'electron-is-dev': 'commonjs electron-is-dev',
     'module': 'commonjs module',

+ 1 - 1
terminus-core/src/components/appRoot.component.pug

@@ -19,7 +19,7 @@ title-bar(
                 [class.drag-region]='hostApp.platform == Platform.macOS',
                 @animateTab,
                 (click)='app.selectTab(tab)',
-                (closeClicked)='app.closeTab(tab)',
+                (closeClicked)='app.closeTab(tab, true)',
             )
         
         .btn-group

+ 11 - 11
terminus-core/src/components/appRoot.component.ts

@@ -79,7 +79,7 @@ export class AppRootComponent {
             }
             if (this.app.activeTab) {
                 if (hotkey === 'close-tab') {
-                    this.app.closeTab(this.app.activeTab)
+                    this.app.closeTab(this.app.activeTab, true)
                 }
                 if (hotkey === 'toggle-last-tab') {
                     this.app.toggleLastTab()
@@ -138,16 +138,6 @@ export class AppRootComponent {
         }
     }
 
-    private getToolbarButtons (aboveZero: boolean): IToolbarButton[] {
-        let buttons: IToolbarButton[] = []
-        this.toolbarButtonProviders.forEach((provider) => {
-            buttons = buttons.concat(provider.provide())
-        })
-        return buttons
-            .filter((button) => (button.weight > 0) === aboveZero)
-            .sort((a: IToolbarButton, b: IToolbarButton) => (a.weight || 0) - (b.weight || 0))
-    }
-
     @HostListener('dragover')
     onDragOver () {
         return false
@@ -157,4 +147,14 @@ export class AppRootComponent {
     onDrop () {
         return false
     }
+
+    private getToolbarButtons (aboveZero: boolean): IToolbarButton[] {
+        let buttons: IToolbarButton[] = []
+        this.toolbarButtonProviders.forEach((provider) => {
+            buttons = buttons.concat(provider.provide())
+        })
+        return buttons
+            .filter((button) => (button.weight > 0) === aboveZero)
+            .sort((a: IToolbarButton, b: IToolbarButton) => (a.weight || 0) - (b.weight || 0))
+    }
 }

+ 4 - 0
terminus-core/src/components/baseTab.component.ts

@@ -31,6 +31,10 @@ export abstract class BaseTabComponent {
         return null
     }
 
+    async canClose (): Promise<boolean> {
+        return true
+    }
+
     destroy (): void {
         this.focused$.complete()
         this.blurred$.complete()

+ 8 - 2
terminus-core/src/services/app.service.ts

@@ -82,10 +82,16 @@ export class AppService {
         }
     }
 
-    closeTab (tab: BaseTabComponent) {
+    async closeTab (tab: BaseTabComponent, checkCanClose?: boolean): Promise<void> {
+        if (!this.tabs.includes(tab)) {
+            return
+        }
+        if (checkCanClose && !await tab.canClose()) {
+            return
+        }
+        this.tabs = this.tabs.filter((x) => x !== tab)
         tab.destroy()
         let newIndex = Math.max(0, this.tabs.indexOf(tab) - 1)
-        this.tabs = this.tabs.filter((x) => x !== tab)
         if (tab === this.activeTab) {
             this.selectTab(this.tabs[newIndex])
         }

+ 4 - 0
terminus-core/src/services/electron.service.ts

@@ -27,4 +27,8 @@ export class ElectronService {
     remoteRequire (name: string): any {
         return this.remote.require(name)
     }
+
+    remoteRequirePluginModule (plugin: string, module: string, globals: any): any {
+        return this.remoteRequire(globals.require.resolve(`${plugin}/node_modules/${module}`))
+    }
 }

+ 1 - 0
terminus-terminal/package.json

@@ -41,6 +41,7 @@
     "hterm-umdjs": "1.1.3",
     "mz": "^2.6.0",
     "node-pty": "0.6.8",
+    "ps-node": "^0.1.6",
     "runes": "^0.4.2",
     "winreg": "^1.2.3"
   },

+ 1 - 0
terminus-terminal/src/api.ts

@@ -1,6 +1,7 @@
 import { Observable } from 'rxjs'
 import { TerminalTabComponent } from './components/terminalTab.component'
 export { TerminalTabComponent }
+export { IChildProcess } from './services/sessions.service'
 
 export abstract class TerminalDecorator {
     // tslint:disable-next-line no-empty

+ 9 - 7
terminus-terminal/src/components/terminalTab.component.ts

@@ -1,4 +1,3 @@
-const dataurl = require('dataurl')
 import { BehaviorSubject, Subject, Subscription } from 'rxjs'
 import 'rxjs/add/operator/bufferTime'
 import { Component, NgZone, Inject, Optional, ViewChild, HostBinding, Input } from '@angular/core'
@@ -297,12 +296,7 @@ export class TerminalTabComponent extends BaseTabComponent {
             `
         }
         css += config.appearance.css
-        preferenceManager.set('user-css', dataurl.convert({
-            data: css,
-            mimetype: 'text/css',
-            charset: 'utf8',
-        }))
-
+        this.hterm.setCSS(css)
         this.hterm.setBracketedPaste(config.terminal.bracketedPaste)
     }
 
@@ -345,6 +339,14 @@ export class TerminalTabComponent extends BaseTabComponent {
         }
     }
 
+    async canClose (): Promise<boolean> {
+        let children = await this.session.getChildProcesses()
+        if (children.length === 0) {
+            return true
+        }
+        return confirm(`"${children[0].command}" is still running. Close?`)
+    }
+
     private setFontSize () {
         preferenceManager.set('font-size', this.config.store.terminal.fontSize * Math.pow(1.1, this.zoom))
     }

+ 10 - 0
terminus-terminal/src/hterm.ts

@@ -22,6 +22,16 @@ preferenceManager.set('color-palette-overrides', {
 
 hterm.hterm.Terminal.prototype.showOverlay = () => null
 
+hterm.hterm.Terminal.prototype.setCSS = function (css) {
+    const doc = this.scrollPort_.document_
+    if (!doc.querySelector('#user-css')) {
+        const node = doc.createElement('style')
+        node.id = 'user-css'
+        doc.head.appendChild(node)
+    }
+    doc.querySelector('#user-css').innerText = css
+}
+
 const oldCharWidthDisregardAmbiguous = hterm.lib.wc.charWidthDisregardAmbiguous
 hterm.lib.wc.charWidthDisregardAmbiguous = codepoint => {
     if ((codepoint >= 0x1f300 && codepoint <= 0x1f64f) ||

+ 0 - 1
terminus-terminal/src/pathDrop.ts

@@ -2,7 +2,6 @@ import { Injectable } from '@angular/core'
 import { TerminalDecorator } from './api'
 import { TerminalTabComponent } from './components/terminalTab.component'
 
-
 @Injectable()
 export class PathDropDecorator extends TerminalDecorator {
     attach (terminal: TerminalTabComponent): void {

+ 4 - 3
terminus-terminal/src/persistenceProviders.ts

@@ -64,12 +64,13 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider {
             recoveryId,
             recoveredTruePID$: truePID$.asObservable(),
             command: 'screen',
-            args: ['-r', recoveryId],
+            args: ['-d', '-r', recoveryId],
         }
     }
 
     async extractShellPID (screenPID: number): Promise<number> {
-        let child = (await listProcesses()).find(x => x.ppid === screenPID)
+        let processes = await listProcesses()
+        let child = processes.find(x => x.ppid === screenPID)
 
         if (!child) {
             throw new Error(`Could not find any children of the screen process (PID ${screenPID})!`)
@@ -77,7 +78,7 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider {
 
         if (child.command === 'login') {
             await delay(1000)
-            child = (await listProcesses()).find(x => x.ppid === child.pid)
+            child = processes.find(x => x.ppid === child.pid)
         }
 
         return child.pid

+ 26 - 2
terminus-terminal/src/services/sessions.service.ts

@@ -1,12 +1,20 @@
-import * as nodePTY from 'node-pty'
+const psNode = require('ps-node')
+// import * as nodePTY from 'node-pty'
+let nodePTY
 import * as fs from 'mz/fs'
 import { Subject } from 'rxjs'
 import { Injectable } from '@angular/core'
-import { Logger, LogService } from 'terminus-core'
+import { Logger, LogService, ElectronService } from 'terminus-core'
 import { exec } from 'mz/child_process'
 
 import { SessionOptions, SessionPersistenceProvider } from '../api'
 
+export interface IChildProcess {
+    pid: number
+    ppid: number
+    command: string
+}
+
 export class Session {
     open: boolean
     name: string
@@ -101,6 +109,20 @@ export class Session {
         this.pty.kill(signal)
     }
 
+    async getChildProcesses (): Promise<IChildProcess[]> {
+        if (!this.truePID) {
+            return []
+        }
+        return new Promise<IChildProcess[]>((resolve, reject) => {
+            psNode.lookup({ ppid: this.truePID }, (err, processes) => {
+                if (err) {
+                    return reject(err)
+                }
+                resolve(processes as IChildProcess[])
+            })
+        })
+    }
+
     async gracefullyKillProcess (): Promise<void> {
         if (process.platform === 'win32') {
             this.kill()
@@ -157,8 +179,10 @@ export class SessionsService {
 
     constructor (
         private persistence: SessionPersistenceProvider,
+        electron: ElectronService,
         log: LogService,
     ) {
+        nodePTY = electron.remoteRequirePluginModule('terminus-terminal', 'node-pty', global as any)
         this.logger = log.create('sessions')
     }
 

+ 5 - 1
terminus-terminal/tsconfig.json

@@ -3,6 +3,10 @@
   "exclude": ["node_modules", "dist"],
   "compilerOptions": {
     "baseUrl": "src",
-    "declarationDir": "dist"
+    "declarationDir": "dist",
+    "paths": {
+      "terminus-*": ["terminus-*"],
+      "*": ["app/node_modules/*"]
+    }
   }
 }

+ 1 - 0
terminus-terminal/webpack.config.js

@@ -44,6 +44,7 @@ module.exports = {
     ]
   },
   externals: [
+    'electron',
     'fs',
     'font-manager',
     'path',

+ 16 - 0
terminus-terminal/yarn.lock

@@ -32,6 +32,10 @@ big.js@^3.1.3:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.1.3.tgz#4cada2193652eb3ca9ec8e55c9015669c9806978"
 
+connected-domain@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/connected-domain/-/connected-domain-1.0.0.tgz#bfe77238c74be453a79f0cb6058deeb4f2358e93"
+
 [email protected]:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/dataurl/-/dataurl-0.1.0.tgz#1f4734feddec05ffe445747978d86759c4b33199"
@@ -98,10 +102,22 @@ object-assign@^4.0.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
 
+ps-node@^0.1.6:
+  version "0.1.6"
+  resolved "https://registry.yarnpkg.com/ps-node/-/ps-node-0.1.6.tgz#9af67a99d7b1d0132e51a503099d38a8d2ace2c3"
+  dependencies:
+    table-parser "^0.1.3"
+
 runes@^0.4.2:
   version "0.4.2"
   resolved "https://registry.yarnpkg.com/runes/-/runes-0.4.2.tgz#1ddc1ea41de769cb32fc068a64fbbc45cd21052e"
 
+table-parser@^0.1.3:
+  version "0.1.3"
+  resolved "https://registry.yarnpkg.com/table-parser/-/table-parser-0.1.3.tgz#0441cfce16a59481684c27d1b5a67ff15a43c7b0"
+  dependencies:
+    connected-domain "^1.0.0"
+
 thenify-all@^1.0.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"