mirror of
https://github.com/Eugeny/tabby.git
synced 2025-01-18 14:04:17 +08:00
parent
5bd1bfd565
commit
73574374f0
@ -95,3 +95,7 @@ input[type=range] {
|
||||
&::-moz-range-track { @include track(); }
|
||||
&::-ms-track { @include track(); }
|
||||
}
|
||||
|
||||
a[ngbdropdownitem] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -2,7 +2,10 @@ import stripAnsi from 'strip-ansi'
|
||||
import { BaseSession } from 'terminus-terminal'
|
||||
import { SerialPort } from 'serialport'
|
||||
import { Logger } from 'terminus-core'
|
||||
import { Subject, Observable } from 'rxjs'
|
||||
import { Subject, Observable, interval } from 'rxjs'
|
||||
import { debounce } from 'rxjs/operators'
|
||||
import { ReadLine, createInterface as createReadline, clearLine } from 'readline'
|
||||
import { PassThrough, Readable, Writable } from 'stream'
|
||||
|
||||
export interface LoginScript {
|
||||
expect: string
|
||||
@ -24,6 +27,7 @@ export interface SerialConnection {
|
||||
xany: boolean
|
||||
scripts?: LoginScript[]
|
||||
color?: string
|
||||
inputMode?: InputMode
|
||||
}
|
||||
|
||||
export const BAUD_RATES = [
|
||||
@ -35,6 +39,8 @@ export interface SerialPortInfo {
|
||||
description?: string
|
||||
}
|
||||
|
||||
export type InputMode = null | 'readline'
|
||||
|
||||
export class SerialSession extends BaseSession {
|
||||
scripts?: LoginScript[]
|
||||
serial: SerialPort
|
||||
@ -42,17 +48,38 @@ export class SerialSession extends BaseSession {
|
||||
|
||||
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
|
||||
private serviceMessage = new Subject<string>()
|
||||
private inputReadline: ReadLine
|
||||
private inputPromptVisible = true
|
||||
private inputReadlineInStream: Readable & Writable
|
||||
private inputReadlineOutStream: Readable & Writable
|
||||
|
||||
constructor (public connection: SerialConnection) {
|
||||
super()
|
||||
this.scripts = connection.scripts ?? []
|
||||
|
||||
this.inputReadlineInStream = new PassThrough()
|
||||
this.inputReadlineOutStream = new PassThrough()
|
||||
this.inputReadline = createReadline({
|
||||
input: this.inputReadlineInStream,
|
||||
output: this.inputReadlineOutStream,
|
||||
terminal: true,
|
||||
} as any)
|
||||
this.inputReadlineOutStream.on('data', data => {
|
||||
if (this.connection.inputMode == 'readline') {
|
||||
this.emitOutput(data)
|
||||
}
|
||||
})
|
||||
this.inputReadline.on('line', line => {
|
||||
this.onInput(new Buffer(line + '\n'))
|
||||
})
|
||||
this.output$.pipe(debounce(() => interval(500))).subscribe(() => this.onOutputSettled())
|
||||
}
|
||||
|
||||
async start (): Promise<void> {
|
||||
this.open = true
|
||||
|
||||
this.serial.on('readable', () => {
|
||||
this.onData(this.serial.read())
|
||||
this.onOutput(this.serial.read())
|
||||
})
|
||||
|
||||
this.serial.on('end', () => {
|
||||
@ -66,18 +93,23 @@ export class SerialSession extends BaseSession {
|
||||
}
|
||||
|
||||
write (data: Buffer): void {
|
||||
if (this.serial) {
|
||||
this.serial.write(data.toString())
|
||||
if (this.connection.inputMode == 'readline') {
|
||||
this.inputReadlineInStream.write(data)
|
||||
} else {
|
||||
this.onInput(data)
|
||||
}
|
||||
}
|
||||
|
||||
async destroy (): Promise<void> {
|
||||
this.serviceMessage.complete()
|
||||
this.inputReadline.close()
|
||||
await super.destroy()
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
|
||||
resize (_, __) { }
|
||||
resize (_, __) {
|
||||
this.inputReadlineOutStream.emit('resize')
|
||||
}
|
||||
|
||||
kill (_?: string): void {
|
||||
this.serial.close()
|
||||
@ -104,8 +136,33 @@ export class SerialSession extends BaseSession {
|
||||
return null
|
||||
}
|
||||
|
||||
private onData (data: Buffer) {
|
||||
private onInput (data: Buffer) {
|
||||
if (this.serial) {
|
||||
this.serial.write(data.toString())
|
||||
}
|
||||
}
|
||||
|
||||
private onOutputSettled () {
|
||||
if (this.connection.inputMode == 'readline' && !this.inputPromptVisible) {
|
||||
this.resetInputPrompt()
|
||||
}
|
||||
}
|
||||
|
||||
private resetInputPrompt () {
|
||||
this.emitOutput(new Buffer('\r\n'))
|
||||
this.inputReadline.prompt(true)
|
||||
this.inputPromptVisible = true
|
||||
}
|
||||
|
||||
private onOutput (data: Buffer) {
|
||||
const dataString = data.toString()
|
||||
|
||||
if (this.connection.inputMode == 'readline') {
|
||||
if (this.inputPromptVisible) {
|
||||
clearLine(this.inputReadlineOutStream, 0)
|
||||
this.inputPromptVisible = false
|
||||
}
|
||||
}
|
||||
this.emitOutput(data)
|
||||
|
||||
if (this.scripts) {
|
||||
|
@ -11,6 +11,8 @@
|
||||
[(ngModel)]='connection.name',
|
||||
)
|
||||
|
||||
.row
|
||||
.col-6
|
||||
.form-group
|
||||
label Path
|
||||
input.form-control(
|
||||
@ -20,6 +22,7 @@
|
||||
[resultFormatter]='portsFormatter',
|
||||
)
|
||||
|
||||
.col-6
|
||||
.form-group
|
||||
label Baud Rate
|
||||
select.form-control(
|
||||
@ -27,6 +30,25 @@
|
||||
)
|
||||
option([value]='x', *ngFor='let x of baudRates') {{x}}
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Input mode
|
||||
|
||||
.d-flex(ngbDropdown)
|
||||
button.btn.btn-secondary.btn-tab-bar(
|
||||
ngbDropdownToggle,
|
||||
) {{getInputModeName(connection.inputMode)}}
|
||||
|
||||
div(ngbDropdownMenu)
|
||||
a.d-flex.flex-column(
|
||||
*ngFor='let mode of inputModes',
|
||||
(click)='connection.inputMode = mode.key',
|
||||
ngbDropdownItem
|
||||
)
|
||||
div {{mode.name}}
|
||||
.text-muted {{mode.description}}
|
||||
|
||||
|
||||
ngb-tab(id='advanced')
|
||||
ng-template(ngbTabTitle) Advanced
|
||||
ng-template(ngbTabContent)
|
||||
|
@ -15,6 +15,10 @@ export class EditConnectionModalComponent {
|
||||
connection: SerialConnection
|
||||
foundPorts: SerialPortInfo[]
|
||||
baudRates = BAUD_RATES
|
||||
inputModes = [
|
||||
{ key: null, name: 'Normal', description: 'Input is sent as you type' },
|
||||
{ key: 'readline', name: 'Line by line', description: 'Line editor, input is sent after you press Enter' },
|
||||
]
|
||||
|
||||
constructor (
|
||||
private modalInstance: NgbActiveModal,
|
||||
@ -24,6 +28,10 @@ export class EditConnectionModalComponent {
|
||||
) {
|
||||
}
|
||||
|
||||
getInputModeName (key) {
|
||||
return this.inputModes.find(x => x.key === key)?.name
|
||||
}
|
||||
|
||||
portsAutocomplete = text$ => text$.pipe(map(() => {
|
||||
return this.foundPorts.map(x => x.name)
|
||||
}))
|
||||
|
@ -34,6 +34,7 @@ export class SerialSettingsTabComponent {
|
||||
xany: false,
|
||||
xoff: false,
|
||||
xon: false,
|
||||
inputMode: null
|
||||
}
|
||||
|
||||
const modal = this.ngbModal.open(EditConnectionModalComponent)
|
||||
|
@ -114,7 +114,7 @@ export class SerialService {
|
||||
options.push({
|
||||
name: 'Manage connections',
|
||||
icon: 'cog',
|
||||
callback: () => this.app.openNewTab(SettingsTabComponent, { activeTab: 'serial' }),
|
||||
callback: () => this.app.openNewTabRaw(SettingsTabComponent, { activeTab: 'serial' }),
|
||||
})
|
||||
|
||||
options.push({
|
||||
|
@ -50,6 +50,8 @@ module.exports = {
|
||||
'path',
|
||||
'ngx-toastr',
|
||||
'serialport',
|
||||
'readline',
|
||||
'stream',
|
||||
'windows-process-tree/build/Release/windows_process_tree.node',
|
||||
/^rxjs/,
|
||||
/^@angular/,
|
||||
|
@ -383,7 +383,7 @@ export class SSHService {
|
||||
options.push({
|
||||
name: 'Manage connections',
|
||||
icon: 'cog',
|
||||
callback: () => this.app.openNewTab(SettingsTabComponent, { activeTab: 'ssh' }),
|
||||
callback: () => this.app.openNewTabRaw(SettingsTabComponent, { activeTab: 'ssh' }),
|
||||
})
|
||||
|
||||
options.push({
|
||||
|
Loading…
Reference in New Issue
Block a user