Skip to content

Commit

Permalink
Merge branch 'master' of github.com:Eugeny/tabby
Browse files Browse the repository at this point in the history
  • Loading branch information
Eugeny committed Apr 24, 2023
2 parents 20479e9 + ff66f05 commit b9b98bd
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 52 deletions.
1 change: 1 addition & 0 deletions tabby-core/src/api/profileProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface Profile {
icon?: string
color?: string
disableDynamicTitle: boolean
behaviorOnSessionEnd: 'auto'|'keep'|'reconnect'|'close'

weight: number
isBuiltin: boolean
Expand Down
1 change: 1 addition & 0 deletions tabby-core/src/services/profiles.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class ProfilesService {
isBuiltin: false,
isTemplate: false,
terminalColorScheme: null,
behaviorOnSessionEnd: 'auto',
}

constructor (
Expand Down
16 changes: 13 additions & 3 deletions tabby-local/src/components/terminalTab.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export class TerminalTabComponent extends BaseTerminalTabComponent<LocalProfile>
this.sessionOptions = this.profile.options

this.logger = this.log.create('terminalTab')
this.session = new Session(this.injector)

const isConPTY = isWindowsBuild(WIN_BUILD_CONPTY_SUPPORTED) && this.config.store.terminal.useConPTY

Expand Down Expand Up @@ -56,20 +55,23 @@ export class TerminalTabComponent extends BaseTerminalTabComponent<LocalProfile>
}

initializeSession (columns: number, rows: number): void {

const session = new Session(this.injector)

if (this.profile.options.runAsAdministrator && this.uac?.isAvailable) {
this.profile = {
...this.profile,
options: this.uac.patchSessionOptionsForUAC(this.profile.options),
}
}

this.session!.start({
session.start({
...this.profile.options,
width: columns,
height: rows,
})

this.attachSessionHandlers(true)
this.setSession(session)
this.recoveryStateChangedHint.next()
}

Expand Down Expand Up @@ -125,4 +127,12 @@ export class TerminalTabComponent extends BaseTerminalTabComponent<LocalProfile>
super.ngOnDestroy()
this.session?.destroy()
}

/**
* Return true if the user explicitly exit the session.
* Always return true for terminalTab as the session can only be ended by the user
*/
protected isSessionExplicitlyTerminated (): boolean {
return true
}
}
32 changes: 24 additions & 8 deletions tabby-serial/src/components/serialTab.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,18 @@ export class SerialTabComponent extends BaseTerminalTabComponent<SerialProfile>
}
})

this.frontendReady$.pipe(first()).subscribe(() => {
this.initializeSession()
})

super.ngOnInit()

setImmediate(() => {
this.setTitle(this.profile.name)
})
}

protected onFrontendReady (): void {
this.initializeSession()
super.onFrontendReady()
}

async initializeSession () {
const session = new SerialSession(this.injector, this.profile)
this.setSession(session)
Expand All @@ -82,12 +83,21 @@ export class SerialTabComponent extends BaseTerminalTabComponent<SerialProfile>
this.session?.resize(this.size.columns, this.size.rows)
})
this.attachSessionHandler(this.session!.destroyed$, () => {
this.write(this.translate.instant(_('Press any key to reconnect')) + '\r\n')
this.input$.pipe(first()).subscribe(() => {
if (!this.session?.open) {
if (this.frontend) {
// Session was closed abruptly
this.write('\r\n' + colors.black.bgWhite(' SERIAL ') + ` session closed\r\n`)

if (this.profile.behaviorOnSessionEnd === 'reconnect') {
this.reconnect()
} else if (this.profile.behaviorOnSessionEnd === 'keep' || this.profile.behaviorOnSessionEnd === 'auto' && !this.isSessionExplicitlyTerminated()) {
this.write(this.translate.instant(_('Press any key to reconnect')) + '\r\n')
this.input$.pipe(first()).subscribe(() => {
if (!this.session?.open) {
this.reconnect()
}
})
}
})
}
})
super.attachSessionHandlers()
}
Expand Down Expand Up @@ -116,4 +126,10 @@ export class SerialTabComponent extends BaseTerminalTabComponent<SerialProfile>
this.session?.serial?.update({ baudRate: rate })
this.profile.options.baudrate = rate
}

protected isSessionExplicitlyTerminated (): boolean {
return super.isSessionExplicitlyTerminated() ||
this.recentInputs.endsWith('close\r') ||
this.recentInputs.endsWith('quit\r')
}
}
12 changes: 12 additions & 0 deletions tabby-settings/src/components/editProfileModal.component.pug
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@
.description(translate) Connection name will be used instead
toggle([(ngModel)]='profile.disableDynamicTitle')

.form-line
.header
.title(translate) When a session ends
.description(*ngIf='profile.behaviorOnSessionEnd == "auto"', translate) Only close the tab when session is explicitly terminated
select.form-control(
[(ngModel)]='profile.behaviorOnSessionEnd',
)
option(ngValue='auto', translate) Auto
option(ngValue='keep', translate) Keep
option(*ngIf='profile.type == "serial" || profile.type == "telnet" || profile.type == "ssh"', ngValue='reconnect', translate) Reconnect
option(ngValue='close', translate) Close

.mb-4

.col-12.col-lg-8(*ngIf='this.profileProvider.settingsComponent')
Expand Down
50 changes: 25 additions & 25 deletions tabby-ssh/src/components/sshTab.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export class SSHTabComponent extends BaseTerminalTabComponent<SSHProfile> implem
sftpPath = '/'
enableToolbar = true
activeKIPrompt: KeyboardInteractivePrompt|null = null
private recentInputs = ''
private reconnectOffered = false

constructor (
Expand Down Expand Up @@ -71,17 +70,14 @@ export class SSHTabComponent extends BaseTerminalTabComponent<SSHProfile> implem
}
})

this.frontendReady$.pipe(first()).subscribe(() => {
this.initializeSession()
this.input$.subscribe(data => {
this.recentInputs += data
this.recentInputs = this.recentInputs.substring(this.recentInputs.length - 32)
})
})

super.ngOnInit()
}

protected onFrontendReady (): void {
this.initializeSession()
super.onFrontendReady()
}

async setupOneSession (injector: Injector, profile: SSHProfile, multiplex = true): Promise<SSHSession> {
let session = await this.sshMultiplexer.getSession(profile)
if (!multiplex || !session || !profile.options.reuseSession) {
Expand Down Expand Up @@ -157,24 +153,22 @@ export class SSHTabComponent extends BaseTerminalTabComponent<SSHProfile> implem
protected attachSessionHandlers (): void {
const session = this.session!
this.attachSessionHandler(session.destroyed$, () => {
if (
// Ctrl-D
this.recentInputs.charCodeAt(this.recentInputs.length - 1) === 4 ||
this.recentInputs.endsWith('exit\r')
) {
// User closed the session
this.destroy()
} else if (this.frontend) {
if (this.frontend) {
// Session was closed abruptly
this.write('\r\n' + colors.black.bgWhite(' SSH ') + ` ${this.sshSession?.profile.options.host}: session closed\r\n`)
if (!this.reconnectOffered) {
this.reconnectOffered = true
this.write(this.translate.instant(_('Press any key to reconnect')) + '\r\n')
this.input$.pipe(first()).subscribe(() => {
if (!this.session?.open && this.reconnectOffered) {
this.reconnect()
}
})

if (this.profile.behaviorOnSessionEnd === 'reconnect') {
this.reconnect()
} else if (this.profile.behaviorOnSessionEnd === 'keep' || this.profile.behaviorOnSessionEnd === 'auto' && !this.isSessionExplicitlyTerminated()) {
if (!this.reconnectOffered) {
this.reconnectOffered = true
this.write(this.translate.instant(_('Press any key to reconnect')) + '\r\n')
this.input$.pipe(first()).subscribe(() => {
if (!this.session?.open && this.reconnectOffered) {
this.reconnect()
}
})
}
}
}
})
Expand Down Expand Up @@ -266,4 +260,10 @@ export class SSHTabComponent extends BaseTerminalTabComponent<SSHProfile> implem
onClick (): void {
this.sftpPanelVisible = false
}

protected isSessionExplicitlyTerminated (): boolean {
return super.isSessionExplicitlyTerminated() ||
this.recentInputs.charCodeAt(this.recentInputs.length - 1) === 4 ||
this.recentInputs.endsWith('exit\r')
}
}
38 changes: 26 additions & 12 deletions tabby-telnet/src/components/telnetTab.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,26 +36,33 @@ export class TelnetTabComponent extends BaseTerminalTabComponent<TelnetProfile>
}
})

this.frontendReady$.pipe(first()).subscribe(() => {
this.initializeSession()
})

super.ngOnInit()
}

protected onFrontendReady (): void {
this.initializeSession()
super.onFrontendReady()
}

protected attachSessionHandlers (): void {
const session = this.session!
this.attachSessionHandler(session.destroyed$, () => {
if (this.frontend) {
// Session was closed abruptly
if (!this.reconnectOffered) {
this.reconnectOffered = true
this.write(this.translate.instant(_('Press any key to reconnect')) + '\r\n')
this.input$.pipe(first()).subscribe(() => {
if (!this.session?.open && this.reconnectOffered) {
this.reconnect()
}
})
this.write('\r\n' + colors.black.bgWhite(' TELNET ') + ` ${this.session?.profile.options.host}: session closed\r\n`)

if (this.profile.behaviorOnSessionEnd === 'reconnect') {
this.reconnect()
} else if (this.profile.behaviorOnSessionEnd === 'keep' || this.profile.behaviorOnSessionEnd === 'auto' && !this.isSessionExplicitlyTerminated()) {
if (!this.reconnectOffered) {
this.reconnectOffered = true
this.write(this.translate.instant(_('Press any key to reconnect')) + '\r\n')
this.input$.pipe(first()).subscribe(() => {
if (!this.session?.open && this.reconnectOffered) {
this.reconnect()
}
})
}
}
}
})
Expand Down Expand Up @@ -120,4 +127,11 @@ export class TelnetTabComponent extends BaseTerminalTabComponent<TelnetProfile>
},
)).response === 0
}

protected isSessionExplicitlyTerminated (): boolean {
return super.isSessionExplicitlyTerminated() ||
this.recentInputs.endsWith('close\r') ||
this.recentInputs.endsWith('quit\r')
}

}
22 changes: 18 additions & 4 deletions tabby-terminal/src/api/baseTerminalTab.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
protected output = new Subject<string>()
protected binaryOutput = new Subject<Buffer>()
protected sessionChanged = new Subject<BaseSession|null>()
protected recentInputs = ''
private bellPlayer: HTMLAudioElement
private termContainerSubscriptions = new SubscriptionContainer()
private sessionHandlers = new SubscriptionContainer()
Expand Down Expand Up @@ -415,6 +416,11 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
this.frontend!.write('\r\n\r\n')
}
}

this.input$.subscribe(data => {
this.recentInputs += data
this.recentInputs = this.recentInputs.substring(this.recentInputs.length - 32)
})
}

async buildContextMenu (): Promise<MenuItemOptions[]> {
Expand Down Expand Up @@ -765,11 +771,12 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
}
})

if (destroyOnSessionClose) {
this.attachSessionHandler(this.session.closed$, () => {
this.attachSessionHandler(this.session.closed$, () => {
const behavior = this.profile.behaviorOnSessionEnd
if (destroyOnSessionClose || behavior === 'close' || behavior === 'auto' && this.isSessionExplicitlyTerminated()) {
this.destroy()
})
}
}
})

this.attachSessionHandler(this.session.destroyed$, () => {
this.setSession(null)
Expand Down Expand Up @@ -835,4 +842,11 @@ export class BaseTerminalTabComponent<P extends BaseTerminalProfile> extends Bas
cb(this)
}
}

/**
* Return true if the user explicitly exit the session
*/
protected isSessionExplicitlyTerminated (): boolean {
return false
}
}

0 comments on commit b9b98bd

Please sign in to comment.