mirror of
https://github.com/Eugeny/tabby.git
synced 2025-07-22 11:28:00 +00:00
.
This commit is contained in:
@@ -29,6 +29,6 @@ html
|
|||||||
div
|
div
|
||||||
h1.terminus-title Terminus
|
h1.terminus-title Terminus
|
||||||
.progress
|
.progress
|
||||||
.bar(style='width: 50%')
|
.bar(style='width: 0%')
|
||||||
|
|
||||||
|
|
||||||
|
@@ -21,7 +21,7 @@ if ((<any>global).require('electron-is-dev')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
findPlugins().then(async plugins => {
|
findPlugins().then(async plugins => {
|
||||||
let pluginsModules = loadPlugins(plugins, (current, total) => {
|
let pluginsModules = await loadPlugins(plugins, (current, total) => {
|
||||||
(<HTMLElement>document.querySelector('.progress .bar')).style.width = 100 * current / total + '%'
|
(<HTMLElement>document.querySelector('.progress .bar')).style.width = 100 * current / total + '%'
|
||||||
})
|
})
|
||||||
let module = await getRootModule(pluginsModules)
|
let module = await getRootModule(pluginsModules)
|
||||||
|
@@ -56,10 +56,11 @@ export async function findPlugins (): Promise<IPluginEntry[]> {
|
|||||||
return foundPlugins
|
return foundPlugins
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadPlugins (foundPlugins: IPluginEntry[], progress: ProgressCallback): any[] {
|
export async function loadPlugins (foundPlugins: IPluginEntry[], progress: ProgressCallback): Promise<any[]> {
|
||||||
let plugins: any[] = []
|
let plugins: any[] = []
|
||||||
progress(0, 1)
|
progress(0, 1)
|
||||||
foundPlugins.forEach((foundPlugin, index) => {
|
let index = 0
|
||||||
|
for (let foundPlugin of foundPlugins) {
|
||||||
console.info(`Loading ${foundPlugin.name}: ${(<any>global).require.resolve(foundPlugin.path)}`)
|
console.info(`Loading ${foundPlugin.name}: ${(<any>global).require.resolve(foundPlugin.path)}`)
|
||||||
progress(index, foundPlugins.length)
|
progress(index, foundPlugins.length)
|
||||||
try {
|
try {
|
||||||
@@ -68,7 +69,9 @@ export function loadPlugins (foundPlugins: IPluginEntry[], progress: ProgressCal
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Could not load ${foundPlugin.name}:`, error)
|
console.error(`Could not load ${foundPlugin.name}:`, error)
|
||||||
}
|
}
|
||||||
})
|
await delay(1)
|
||||||
|
index++
|
||||||
|
}
|
||||||
progress(1, 1)
|
progress(1, 1)
|
||||||
return plugins
|
return plugins
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,14 @@
|
|||||||
title-bar(*ngIf='config.store.appearance.frame == "full" && config.store.appearance.dock == "off"')
|
title-bar(
|
||||||
|
*ngIf='config.store.appearance.frame == "full" && config.store.appearance.dock == "off"',
|
||||||
|
[class.inset]='hostApp.platform == Platform.macOS'
|
||||||
|
)
|
||||||
|
|
||||||
.content(
|
.content(
|
||||||
[class.tabs-on-top]='config.store.appearance.tabsOnTop'
|
[class.tabs-on-top]='config.store.appearance.tabsLocation == "top"'
|
||||||
)
|
)
|
||||||
.tab-bar
|
.tab-bar(
|
||||||
|
[class.inset]='hostApp.platform == Platform.macOS && config.store.appearance.frame == "thin" && config.store.appearance.tabsLocation == "top"'
|
||||||
|
)
|
||||||
.tabs
|
.tabs
|
||||||
tab-header(
|
tab-header(
|
||||||
*ngFor='let tab of app.tabs; let idx = index',
|
*ngFor='let tab of app.tabs; let idx = index',
|
||||||
@@ -11,6 +16,7 @@ title-bar(*ngIf='config.store.appearance.frame == "full" && config.store.appeara
|
|||||||
[tab]='tab',
|
[tab]='tab',
|
||||||
[active]='tab == app.activeTab',
|
[active]='tab == app.activeTab',
|
||||||
[hasActivity]='tab.hasActivity',
|
[hasActivity]='tab.hasActivity',
|
||||||
|
[class.drag-region]='hostApp.platform == Platform.macOS',
|
||||||
@animateTab,
|
@animateTab,
|
||||||
(click)='app.selectTab(tab)',
|
(click)='app.selectTab(tab)',
|
||||||
(closeClicked)='app.closeTab(tab)',
|
(closeClicked)='app.closeTab(tab)',
|
||||||
@@ -35,7 +41,7 @@ title-bar(*ngIf='config.store.appearance.frame == "full" && config.store.appeara
|
|||||||
i.fa([class]='"fa fa-" + button.icon')
|
i.fa([class]='"fa fa-" + button.icon')
|
||||||
|
|
||||||
.btn-group.window-controls(
|
.btn-group.window-controls(
|
||||||
*ngIf='config.store.appearance.frame == "thin"',
|
*ngIf='config.store.appearance.frame == "thin" && (hostApp.platform == Platform.Windows || hostApp.platform == Platform.Linux)',
|
||||||
)
|
)
|
||||||
button.btn.btn-secondary.btn-minimize.btn-tab-bar(
|
button.btn.btn-secondary.btn-minimize.btn-tab-bar(
|
||||||
(click)='hostApp.minimize()',
|
(click)='hostApp.minimize()',
|
||||||
|
@@ -71,9 +71,13 @@ $tab-border-radius: 4px;
|
|||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.window-controls {
|
.window-controls {
|
||||||
flex: 0 0 none;
|
flex: 0 0 none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.inset {
|
||||||
|
padding-left: 85px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs-content {
|
.tabs-content {
|
||||||
|
@@ -3,7 +3,7 @@ import { trigger, style, animate, transition, state } from '@angular/animations'
|
|||||||
import { ToasterConfig } from 'angular2-toaster'
|
import { ToasterConfig } from 'angular2-toaster'
|
||||||
|
|
||||||
import { ElectronService } from '../services/electron.service'
|
import { ElectronService } from '../services/electron.service'
|
||||||
import { HostAppService } from '../services/hostApp.service'
|
import { HostAppService, Platform } from '../services/hostApp.service'
|
||||||
import { HotkeysService } from '../services/hotkeys.service'
|
import { HotkeysService } from '../services/hotkeys.service'
|
||||||
import { Logger, LogService } from '../services/log.service'
|
import { Logger, LogService } from '../services/log.service'
|
||||||
import { QuitterService } from '../services/quitter.service'
|
import { QuitterService } from '../services/quitter.service'
|
||||||
@@ -43,6 +43,7 @@ import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api'
|
|||||||
})
|
})
|
||||||
export class AppRootComponent {
|
export class AppRootComponent {
|
||||||
toasterConfig: ToasterConfig
|
toasterConfig: ToasterConfig
|
||||||
|
Platform = Platform
|
||||||
private logger: Logger
|
private logger: Logger
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@@ -61,4 +61,8 @@ $tabs-height: 40px;
|
|||||||
display: block;
|
display: block;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.drag-region {
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -31,7 +31,7 @@ $titlebar-height: 30px;
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.inset-titlebar {
|
&.inset {
|
||||||
flex-basis: 36px;
|
flex-basis: 36px;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import { Component, HostBinding } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { HostAppService, Platform } from '../services/hostApp.service'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'title-bar',
|
selector: 'title-bar',
|
||||||
@@ -7,9 +6,4 @@ import { HostAppService, Platform } from '../services/hostApp.service'
|
|||||||
styles: [require('./titleBar.component.scss')],
|
styles: [require('./titleBar.component.scss')],
|
||||||
})
|
})
|
||||||
export class TitleBarComponent {
|
export class TitleBarComponent {
|
||||||
@HostBinding('class.inset-titlebar') insetTitlebar = false
|
|
||||||
|
|
||||||
constructor (public hostApp: HostAppService) {
|
|
||||||
this.insetTitlebar = hostApp.platform == Platform.macOS
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
appearance:
|
appearance:
|
||||||
dock: 'off'
|
dock: off
|
||||||
dockScreen: 'current'
|
dockScreen: current
|
||||||
dockFill: 50
|
dockFill: 50
|
||||||
tabsOnTop: true
|
tabsLocation: top
|
||||||
theme: 'Standard'
|
theme: Standard
|
||||||
frame: 'thin'
|
frame: thin
|
||||||
|
@@ -108,7 +108,6 @@ app-root {
|
|||||||
background: $body-bg2;
|
background: $body-bg2;
|
||||||
border-left: 1px solid transparent;
|
border-left: 1px solid transparent;
|
||||||
border-right: 1px solid transparent;
|
border-right: 1px solid transparent;
|
||||||
margin-bottom: -1px;
|
|
||||||
|
|
||||||
.index {
|
.index {
|
||||||
color: #555;
|
color: #555;
|
||||||
@@ -133,45 +132,37 @@ app-root {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.tabs-on-top .tab-bar {
|
&.tabs-on-top .tab-bar {
|
||||||
|
border-bottom: 1px solid $border-color;
|
||||||
|
|
||||||
tab-header {
|
tab-header {
|
||||||
border-top: 1px solid transparent;
|
border-top: 1px solid transparent;
|
||||||
border-bottom: 1px solid $border-color;
|
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
border-top: 1px solid $teal;
|
border-top: 1px solid $teal;
|
||||||
border-bottom: 1px solid transparent;
|
margin-bottom: -1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.has-activity:not(.active) {
|
&.has-activity:not(.active) {
|
||||||
border-top: 1px solid $green;
|
border-top: 1px solid $green;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&>.btn-group, .drag-space {
|
|
||||||
border-bottom: 1px solid $border-color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.tabs-on-top) .tab-bar {
|
&:not(.tabs-on-top) .tab-bar {
|
||||||
margin-bottom: 3px;
|
border-top: 1px solid $border-color;
|
||||||
|
|
||||||
tab-header {
|
tab-header {
|
||||||
border-bottom: 1px solid transparent;
|
border-bottom: 1px solid transparent;
|
||||||
border-top: 1px solid $border-color;
|
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
border-bottom: 1px solid $teal;
|
border-bottom: 1px solid $teal;
|
||||||
border-top: 1px solid transparent;
|
margin-top: -1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.has-activity:not(.active) {
|
&.has-activity:not(.active) {
|
||||||
border-bottom: 1px solid $green;
|
border-bottom: 1px solid $green;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&>.btn-group, .drag-space {
|
|
||||||
border-top: 1px solid $border-color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,20 +19,20 @@ ngb-tabset.vertical(type='tabs')
|
|||||||
label Show tabs
|
label Show tabs
|
||||||
br
|
br
|
||||||
div(
|
div(
|
||||||
'[(ngModel)]'='config.store.appearance.tabsOnTop',
|
'[(ngModel)]'='config.store.appearance.tabsLocation',
|
||||||
(ngModelChange)='config.save()',
|
(ngModelChange)='config.save()',
|
||||||
ngbRadioGroup
|
ngbRadioGroup
|
||||||
)
|
)
|
||||||
label.btn.btn-secondary
|
label.btn.btn-secondary
|
||||||
input(
|
input(
|
||||||
type='radio',
|
type='radio',
|
||||||
[value]='true'
|
[value]='"top"'
|
||||||
)
|
)
|
||||||
| On the top
|
| On the top
|
||||||
label.btn.btn-secondary
|
label.btn.btn-secondary
|
||||||
input(
|
input(
|
||||||
type='radio',
|
type='radio',
|
||||||
[value]='false'
|
[value]='"bottom"'
|
||||||
)
|
)
|
||||||
| At the bottom
|
| At the bottom
|
||||||
.col.col-lg-6
|
.col.col-lg-6
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { Observable } from 'rxjs'
|
||||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||||
export { TerminalTabComponent }
|
export { TerminalTabComponent }
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ export interface SessionOptions {
|
|||||||
cwd?: string
|
cwd?: string
|
||||||
env?: any
|
env?: any
|
||||||
recoveryId?: string
|
recoveryId?: string
|
||||||
recoveredTruePID?: number
|
recoveredTruePID$?: Observable<number>
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class SessionPersistenceProvider {
|
export abstract class SessionPersistenceProvider {
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
import * as fs from 'fs-promise'
|
import * as fs from 'fs-promise'
|
||||||
import { exec, spawn } from 'mz/child_process'
|
import { exec, spawn } from 'mz/child_process'
|
||||||
|
import { exec as execCallback } from 'child_process'
|
||||||
|
|
||||||
|
import { AsyncSubject } from 'rxjs'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { Logger, LogService } from 'terminus-core'
|
import { Logger, LogService } from 'terminus-core'
|
||||||
import { SessionOptions, SessionPersistenceProvider } from './api'
|
import { SessionOptions, SessionPersistenceProvider } from './api'
|
||||||
@@ -36,12 +38,12 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async attachSession (recoveryId: any): Promise<SessionOptions> {
|
async attachSession (recoveryId: any): Promise<SessionOptions> {
|
||||||
let lines: string[]
|
let lines = await new Promise<string[]>(resolve => {
|
||||||
try {
|
execCallback('screen -list', (_err, stdout) => {
|
||||||
lines = (await exec('screen -list'))[0].toString().split('\n')
|
// returns an error code on macOS
|
||||||
} catch (result) {
|
resolve(stdout.split('\n'))
|
||||||
lines = result.stdout.split('\n')
|
})
|
||||||
}
|
})
|
||||||
let screenPID = lines
|
let screenPID = lines
|
||||||
.filter(line => line.indexOf('.' + recoveryId) !== -1)
|
.filter(line => line.indexOf('.' + recoveryId) !== -1)
|
||||||
.map(line => parseInt(line.trim().split('.')[0]))[0]
|
.map(line => parseInt(line.trim().split('.')[0]))[0]
|
||||||
@@ -50,25 +52,36 @@ export class ScreenPersistenceProvider extends SessionPersistenceProvider {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
let child = (await listProcesses()).find(x => x.ppid === screenPID)
|
let truePID_ = new AsyncSubject<number>()
|
||||||
|
|
||||||
if (!child) {
|
this.extractShellPID(screenPID).then(pid => {
|
||||||
this.logger.error(`Could not find any children of the screen process (PID ${screenPID})!`)
|
truePID_.next(pid)
|
||||||
}
|
truePID_.complete()
|
||||||
if (child.command == 'login') {
|
})
|
||||||
child = (await listProcesses()).find(x => x.ppid === child.pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
let recoveredTruePID = child.pid
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
recoveryId,
|
recoveryId,
|
||||||
recoveredTruePID,
|
recoveredTruePID$: truePID_.asObservable(),
|
||||||
command: 'screen',
|
command: 'screen',
|
||||||
args: ['-r', recoveryId],
|
args: ['-r', recoveryId],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async extractShellPID (screenPID: number): Promise<number> {
|
||||||
|
let child = (await listProcesses()).find(x => x.ppid === screenPID)
|
||||||
|
|
||||||
|
if (!child) {
|
||||||
|
throw new Error(`Could not find any children of the screen process (PID ${screenPID})!`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child.command == 'login') {
|
||||||
|
await delay(1000)
|
||||||
|
child = (await listProcesses()).find(x => x.ppid === child.pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return child.pid
|
||||||
|
}
|
||||||
|
|
||||||
async startSession (options: SessionOptions): Promise<any> {
|
async startSession (options: SessionOptions): Promise<any> {
|
||||||
let configPath = '/tmp/.termScreenConfig'
|
let configPath = '/tmp/.termScreenConfig'
|
||||||
await fs.writeFile(configPath, `
|
await fs.writeFile(configPath, `
|
||||||
|
@@ -43,7 +43,13 @@ export class Session {
|
|||||||
env: env,
|
env: env,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.truePID = options.recoveredTruePID || (<any>this.pty).pid
|
if (options.recoveredTruePID$) {
|
||||||
|
options.recoveredTruePID$.subscribe(pid => {
|
||||||
|
this.truePID = pid
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.truePID = (<any>this.pty).pid
|
||||||
|
}
|
||||||
|
|
||||||
this.open = true
|
this.open = true
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user