mirror of
https://github.com/Eugeny/tabby.git
synced 2025-06-10 06:20:03 +00:00
parent
5bd1bfd565
commit
73574374f0
@ -95,3 +95,7 @@ input[type=range] {
|
|||||||
&::-moz-range-track { @include track(); }
|
&::-moz-range-track { @include track(); }
|
||||||
&::-ms-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 { BaseSession } from 'terminus-terminal'
|
||||||
import { SerialPort } from 'serialport'
|
import { SerialPort } from 'serialport'
|
||||||
import { Logger } from 'terminus-core'
|
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 {
|
export interface LoginScript {
|
||||||
expect: string
|
expect: string
|
||||||
@ -24,6 +27,7 @@ export interface SerialConnection {
|
|||||||
xany: boolean
|
xany: boolean
|
||||||
scripts?: LoginScript[]
|
scripts?: LoginScript[]
|
||||||
color?: string
|
color?: string
|
||||||
|
inputMode?: InputMode
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BAUD_RATES = [
|
export const BAUD_RATES = [
|
||||||
@ -35,6 +39,8 @@ export interface SerialPortInfo {
|
|||||||
description?: string
|
description?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type InputMode = null | 'readline'
|
||||||
|
|
||||||
export class SerialSession extends BaseSession {
|
export class SerialSession extends BaseSession {
|
||||||
scripts?: LoginScript[]
|
scripts?: LoginScript[]
|
||||||
serial: SerialPort
|
serial: SerialPort
|
||||||
@ -42,17 +48,38 @@ export class SerialSession extends BaseSession {
|
|||||||
|
|
||||||
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
|
get serviceMessage$ (): Observable<string> { return this.serviceMessage }
|
||||||
private serviceMessage = new Subject<string>()
|
private serviceMessage = new Subject<string>()
|
||||||
|
private inputReadline: ReadLine
|
||||||
|
private inputPromptVisible = true
|
||||||
|
private inputReadlineInStream: Readable & Writable
|
||||||
|
private inputReadlineOutStream: Readable & Writable
|
||||||
|
|
||||||
constructor (public connection: SerialConnection) {
|
constructor (public connection: SerialConnection) {
|
||||||
super()
|
super()
|
||||||
this.scripts = connection.scripts ?? []
|
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> {
|
async start (): Promise<void> {
|
||||||
this.open = true
|
this.open = true
|
||||||
|
|
||||||
this.serial.on('readable', () => {
|
this.serial.on('readable', () => {
|
||||||
this.onData(this.serial.read())
|
this.onOutput(this.serial.read())
|
||||||
})
|
})
|
||||||
|
|
||||||
this.serial.on('end', () => {
|
this.serial.on('end', () => {
|
||||||
@ -66,18 +93,23 @@ export class SerialSession extends BaseSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
write (data: Buffer): void {
|
write (data: Buffer): void {
|
||||||
if (this.serial) {
|
if (this.connection.inputMode == 'readline') {
|
||||||
this.serial.write(data.toString())
|
this.inputReadlineInStream.write(data)
|
||||||
|
} else {
|
||||||
|
this.onInput(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async destroy (): Promise<void> {
|
async destroy (): Promise<void> {
|
||||||
this.serviceMessage.complete()
|
this.serviceMessage.complete()
|
||||||
|
this.inputReadline.close()
|
||||||
await super.destroy()
|
await super.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-empty-function
|
// 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 {
|
kill (_?: string): void {
|
||||||
this.serial.close()
|
this.serial.close()
|
||||||
@ -104,8 +136,33 @@ export class SerialSession extends BaseSession {
|
|||||||
return null
|
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()
|
const dataString = data.toString()
|
||||||
|
|
||||||
|
if (this.connection.inputMode == 'readline') {
|
||||||
|
if (this.inputPromptVisible) {
|
||||||
|
clearLine(this.inputReadlineOutStream, 0)
|
||||||
|
this.inputPromptVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
this.emitOutput(data)
|
this.emitOutput(data)
|
||||||
|
|
||||||
if (this.scripts) {
|
if (this.scripts) {
|
||||||
|
@ -11,21 +11,43 @@
|
|||||||
[(ngModel)]='connection.name',
|
[(ngModel)]='connection.name',
|
||||||
)
|
)
|
||||||
|
|
||||||
.form-group
|
.row
|
||||||
label Path
|
.col-6
|
||||||
input.form-control(
|
.form-group
|
||||||
type='text',
|
label Path
|
||||||
[(ngModel)]='connection.port',
|
input.form-control(
|
||||||
[ngbTypeahead]='portsAutocomplete',
|
type='text',
|
||||||
[resultFormatter]='portsFormatter',
|
[(ngModel)]='connection.port',
|
||||||
)
|
[ngbTypeahead]='portsAutocomplete',
|
||||||
|
[resultFormatter]='portsFormatter',
|
||||||
|
)
|
||||||
|
|
||||||
|
.col-6
|
||||||
|
.form-group
|
||||||
|
label Baud Rate
|
||||||
|
select.form-control(
|
||||||
|
[(ngModel)]='connection.baudrate',
|
||||||
|
)
|
||||||
|
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}}
|
||||||
|
|
||||||
.form-group
|
|
||||||
label Baud Rate
|
|
||||||
select.form-control(
|
|
||||||
[(ngModel)]='connection.baudrate',
|
|
||||||
)
|
|
||||||
option([value]='x', *ngFor='let x of baudRates') {{x}}
|
|
||||||
|
|
||||||
ngb-tab(id='advanced')
|
ngb-tab(id='advanced')
|
||||||
ng-template(ngbTabTitle) Advanced
|
ng-template(ngbTabTitle) Advanced
|
||||||
|
@ -15,6 +15,10 @@ export class EditConnectionModalComponent {
|
|||||||
connection: SerialConnection
|
connection: SerialConnection
|
||||||
foundPorts: SerialPortInfo[]
|
foundPorts: SerialPortInfo[]
|
||||||
baudRates = BAUD_RATES
|
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 (
|
constructor (
|
||||||
private modalInstance: NgbActiveModal,
|
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(() => {
|
portsAutocomplete = text$ => text$.pipe(map(() => {
|
||||||
return this.foundPorts.map(x => x.name)
|
return this.foundPorts.map(x => x.name)
|
||||||
}))
|
}))
|
||||||
|
@ -34,6 +34,7 @@ export class SerialSettingsTabComponent {
|
|||||||
xany: false,
|
xany: false,
|
||||||
xoff: false,
|
xoff: false,
|
||||||
xon: false,
|
xon: false,
|
||||||
|
inputMode: null
|
||||||
}
|
}
|
||||||
|
|
||||||
const modal = this.ngbModal.open(EditConnectionModalComponent)
|
const modal = this.ngbModal.open(EditConnectionModalComponent)
|
||||||
|
@ -114,7 +114,7 @@ export class SerialService {
|
|||||||
options.push({
|
options.push({
|
||||||
name: 'Manage connections',
|
name: 'Manage connections',
|
||||||
icon: 'cog',
|
icon: 'cog',
|
||||||
callback: () => this.app.openNewTab(SettingsTabComponent, { activeTab: 'serial' }),
|
callback: () => this.app.openNewTabRaw(SettingsTabComponent, { activeTab: 'serial' }),
|
||||||
})
|
})
|
||||||
|
|
||||||
options.push({
|
options.push({
|
||||||
|
@ -50,6 +50,8 @@ module.exports = {
|
|||||||
'path',
|
'path',
|
||||||
'ngx-toastr',
|
'ngx-toastr',
|
||||||
'serialport',
|
'serialport',
|
||||||
|
'readline',
|
||||||
|
'stream',
|
||||||
'windows-process-tree/build/Release/windows_process_tree.node',
|
'windows-process-tree/build/Release/windows_process_tree.node',
|
||||||
/^rxjs/,
|
/^rxjs/,
|
||||||
/^@angular/,
|
/^@angular/,
|
||||||
|
@ -383,7 +383,7 @@ export class SSHService {
|
|||||||
options.push({
|
options.push({
|
||||||
name: 'Manage connections',
|
name: 'Manage connections',
|
||||||
icon: 'cog',
|
icon: 'cog',
|
||||||
callback: () => this.app.openNewTab(SettingsTabComponent, { activeTab: 'ssh' }),
|
callback: () => this.app.openNewTabRaw(SettingsTabComponent, { activeTab: 'ssh' }),
|
||||||
})
|
})
|
||||||
|
|
||||||
options.push({
|
options.push({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user