Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8116ab733f | ||
![]() |
92513814e4 | ||
![]() |
0f71618cb8 | ||
![]() |
8cba805522 | ||
![]() |
35ca7015c8 | ||
![]() |
2e72774548 | ||
![]() |
beb7c614bc | ||
![]() |
ef3c9e3be0 |
@@ -25,8 +25,8 @@ export const builtinPlugins = [
|
|||||||
'tabby-ssh',
|
'tabby-ssh',
|
||||||
'tabby-serial',
|
'tabby-serial',
|
||||||
'tabby-telnet',
|
'tabby-telnet',
|
||||||
'tabby-electron',
|
|
||||||
'tabby-local',
|
'tabby-local',
|
||||||
|
'tabby-electron',
|
||||||
'tabby-plugin-manager',
|
'tabby-plugin-manager',
|
||||||
'tabby-linkifier',
|
'tabby-linkifier',
|
||||||
]
|
]
|
||||||
|
@@ -176,6 +176,7 @@ export abstract class PlatformService {
|
|||||||
abstract setErrorHandler (handler: (_: any) => void): void
|
abstract setErrorHandler (handler: (_: any) => void): void
|
||||||
abstract popupContextMenu (menu: MenuItemOptions[], event?: MouseEvent): void
|
abstract popupContextMenu (menu: MenuItemOptions[], event?: MouseEvent): void
|
||||||
abstract showMessageBox (options: MessageBoxOptions): Promise<MessageBoxResult>
|
abstract showMessageBox (options: MessageBoxOptions): Promise<MessageBoxResult>
|
||||||
|
abstract pickDirectory (): Promise<string>
|
||||||
abstract quit (): void
|
abstract quit (): void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -192,6 +192,14 @@ export abstract class BaseTabComponent extends BaseComponent {
|
|||||||
this.viewContainer = undefined
|
this.viewContainer = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get topmostParent (): BaseTabComponent|null {
|
||||||
|
let parent = this.parent
|
||||||
|
while (parent?.parent) {
|
||||||
|
parent = parent.parent
|
||||||
|
}
|
||||||
|
return parent
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called before the tab is closed
|
* Called before the tab is closed
|
||||||
*/
|
*/
|
||||||
|
@@ -458,12 +458,18 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
tab.destroy()
|
tab.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let allTabs: BaseTabComponent[] = []
|
||||||
if (thing instanceof BaseTabComponent) {
|
if (thing instanceof BaseTabComponent) {
|
||||||
if (thing.parent instanceof SplitTabComponent) {
|
allTabs = [thing]
|
||||||
thing.parent.removeTab(thing)
|
} else if (thing instanceof SplitContainer) {
|
||||||
|
allTabs = thing.getAllTabs()
|
||||||
|
}
|
||||||
|
for (const tab of allTabs) {
|
||||||
|
if (tab.parent instanceof SplitTabComponent) {
|
||||||
|
tab.parent.removeTab(tab)
|
||||||
}
|
}
|
||||||
thing.removeFromContainer()
|
tab.removeFromContainer()
|
||||||
thing.parent = this
|
tab.parent = this
|
||||||
}
|
}
|
||||||
|
|
||||||
let target = relative ? this.getParentOf(relative) : null
|
let target = relative ? this.getParentOf(relative) : null
|
||||||
|
@@ -17,11 +17,14 @@
|
|||||||
"author": "Eugene Pankov",
|
"author": "Eugene Pankov",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/core": "^9.1.9"
|
"@angular/core": "^9.1.9",
|
||||||
|
"tabby-local": "*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron-promise-ipc": "^2.2.4",
|
"electron-promise-ipc": "^2.2.4",
|
||||||
|
"ps-node": "^0.1.6",
|
||||||
"tmp-promise": "^3.0.2",
|
"tmp-promise": "^3.0.2",
|
||||||
|
"hasbin": "^1.2.3",
|
||||||
"winston": "^3.3.3"
|
"winston": "^3.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 392 B |
Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 392 B |
Before Width: | Height: | Size: 567 B After Width: | Height: | Size: 567 B |
Before Width: | Height: | Size: 570 B After Width: | Height: | Size: 570 B |
Before Width: | Height: | Size: 315 B After Width: | Height: | Size: 315 B |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 205 KiB After Width: | Height: | Size: 205 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 287 B After Width: | Height: | Size: 287 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 551 B After Width: | Height: | Size: 551 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core'
|
|||||||
import { PlatformService, LogService, UpdaterService, DockingService, HostAppService, ThemesService, Platform, AppService, ConfigService, WIN_BUILD_FLUENT_BG_SUPPORTED, isWindowsBuild, HostWindowService, HotkeyProvider, ConfigProvider, FileProvider } from 'tabby-core'
|
import { PlatformService, LogService, UpdaterService, DockingService, HostAppService, ThemesService, Platform, AppService, ConfigService, WIN_BUILD_FLUENT_BG_SUPPORTED, isWindowsBuild, HostWindowService, HotkeyProvider, ConfigProvider, FileProvider } from 'tabby-core'
|
||||||
import { TerminalColorSchemeProvider } from 'tabby-terminal'
|
import { TerminalColorSchemeProvider } from 'tabby-terminal'
|
||||||
import { SFTPContextMenuItemProvider, SSHProfileImporter, AutoPrivateKeyLocator } from 'tabby-ssh'
|
import { SFTPContextMenuItemProvider, SSHProfileImporter, AutoPrivateKeyLocator } from 'tabby-ssh'
|
||||||
|
import { PTYInterface, ShellProvider, UACService } from 'tabby-local'
|
||||||
import { auditTime } from 'rxjs'
|
import { auditTime } from 'rxjs'
|
||||||
|
|
||||||
import { HyperColorSchemes } from './colorSchemes'
|
import { HyperColorSchemes } from './colorSchemes'
|
||||||
@@ -14,10 +15,28 @@ import { ElectronHostWindow } from './services/hostWindow.service'
|
|||||||
import { ElectronFileProvider } from './services/fileProvider.service'
|
import { ElectronFileProvider } from './services/fileProvider.service'
|
||||||
import { ElectronHostAppService } from './services/hostApp.service'
|
import { ElectronHostAppService } from './services/hostApp.service'
|
||||||
import { ElectronService } from './services/electron.service'
|
import { ElectronService } from './services/electron.service'
|
||||||
|
import { DockMenuService } from './services/dockMenu.service'
|
||||||
|
import { ElectronUACService } from './services/uac.service'
|
||||||
|
|
||||||
import { ElectronHotkeyProvider } from './hotkeys'
|
import { ElectronHotkeyProvider } from './hotkeys'
|
||||||
import { ElectronConfigProvider } from './config'
|
import { ElectronConfigProvider } from './config'
|
||||||
import { EditSFTPContextMenu } from './sftpContextMenu'
|
import { EditSFTPContextMenu } from './sftpContextMenu'
|
||||||
import { OpenSSHImporter, PrivateKeyLocator, StaticFileImporter } from './sshImporters'
|
import { OpenSSHImporter, PrivateKeyLocator, StaticFileImporter } from './sshImporters'
|
||||||
|
import { ElectronPTYInterface } from './pty'
|
||||||
|
|
||||||
|
import { CmderShellProvider } from './shells/cmder'
|
||||||
|
import { Cygwin32ShellProvider } from './shells/cygwin32'
|
||||||
|
import { Cygwin64ShellProvider } from './shells/cygwin64'
|
||||||
|
import { GitBashShellProvider } from './shells/gitBash'
|
||||||
|
import { LinuxDefaultShellProvider } from './shells/linuxDefault'
|
||||||
|
import { MacOSDefaultShellProvider } from './shells/macDefault'
|
||||||
|
import { MSYS2ShellProvider } from './shells/msys2'
|
||||||
|
import { POSIXShellsProvider } from './shells/posix'
|
||||||
|
import { PowerShellCoreShellProvider } from './shells/powershellCore'
|
||||||
|
import { WindowsDefaultShellProvider } from './shells/winDefault'
|
||||||
|
import { WindowsStockShellsProvider } from './shells/windowsStock'
|
||||||
|
import { WSLShellProvider } from './shells/wsl'
|
||||||
|
import { VSDevToolsProvider } from './shells/vs'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
providers: [
|
providers: [
|
||||||
@@ -35,6 +54,29 @@ import { OpenSSHImporter, PrivateKeyLocator, StaticFileImporter } from './sshImp
|
|||||||
{ provide: SSHProfileImporter, useExisting: OpenSSHImporter, multi: true },
|
{ provide: SSHProfileImporter, useExisting: OpenSSHImporter, multi: true },
|
||||||
{ provide: SSHProfileImporter, useExisting: StaticFileImporter, multi: true },
|
{ provide: SSHProfileImporter, useExisting: StaticFileImporter, multi: true },
|
||||||
{ provide: AutoPrivateKeyLocator, useExisting: PrivateKeyLocator, multi: true },
|
{ provide: AutoPrivateKeyLocator, useExisting: PrivateKeyLocator, multi: true },
|
||||||
|
|
||||||
|
{ provide: ShellProvider, useClass: WindowsDefaultShellProvider, multi: true },
|
||||||
|
{ provide: ShellProvider, useClass: MacOSDefaultShellProvider, multi: true },
|
||||||
|
{ provide: ShellProvider, useClass: LinuxDefaultShellProvider, multi: true },
|
||||||
|
{ provide: ShellProvider, useClass: WindowsStockShellsProvider, multi: true },
|
||||||
|
{ provide: ShellProvider, useClass: PowerShellCoreShellProvider, multi: true },
|
||||||
|
{ provide: ShellProvider, useClass: CmderShellProvider, multi: true },
|
||||||
|
{ provide: ShellProvider, useClass: Cygwin32ShellProvider, multi: true },
|
||||||
|
{ provide: ShellProvider, useClass: Cygwin64ShellProvider, multi: true },
|
||||||
|
{ provide: ShellProvider, useClass: GitBashShellProvider, multi: true },
|
||||||
|
{ provide: ShellProvider, useClass: POSIXShellsProvider, multi: true },
|
||||||
|
{ provide: ShellProvider, useClass: MSYS2ShellProvider, multi: true },
|
||||||
|
{ provide: ShellProvider, useClass: WSLShellProvider, multi: true },
|
||||||
|
{ provide: ShellProvider, useClass: VSDevToolsProvider, multi: true },
|
||||||
|
|
||||||
|
{ provide: UACService, useClass: ElectronUACService },
|
||||||
|
|
||||||
|
{ provide: PTYInterface, useClass: ElectronPTYInterface },
|
||||||
|
|
||||||
|
// For WindowsDefaultShellProvider
|
||||||
|
PowerShellCoreShellProvider,
|
||||||
|
WSLShellProvider,
|
||||||
|
WindowsStockShellsProvider,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export default class ElectronModule {
|
export default class ElectronModule {
|
||||||
@@ -47,6 +89,7 @@ export default class ElectronModule {
|
|||||||
docking: DockingService,
|
docking: DockingService,
|
||||||
themeService: ThemesService,
|
themeService: ThemesService,
|
||||||
app: AppService,
|
app: AppService,
|
||||||
|
dockMenu: DockMenuService,
|
||||||
) {
|
) {
|
||||||
config.ready$.toPromise().then(() => {
|
config.ready$.toPromise().then(() => {
|
||||||
touchbar.update()
|
touchbar.update()
|
||||||
@@ -87,6 +130,10 @@ export default class ElectronModule {
|
|||||||
})
|
})
|
||||||
|
|
||||||
config.changed$.subscribe(() => this.updateVibrancy())
|
config.changed$.subscribe(() => this.updateVibrancy())
|
||||||
|
|
||||||
|
config.ready$.toPromise().then(() => {
|
||||||
|
dockMenu.update()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerGlobalHotkey () {
|
private registerGlobalHotkey () {
|
||||||
|
140
tabby-electron/src/pty.ts
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import * as psNode from 'ps-node'
|
||||||
|
import { ipcRenderer } from 'electron'
|
||||||
|
import { ChildProcess, PTYInterface, PTYProxy } from 'tabby-local'
|
||||||
|
import { getWorkingDirectoryFromPID } from 'native-process-working-directory'
|
||||||
|
|
||||||
|
/* eslint-disable block-scoped-var */
|
||||||
|
|
||||||
|
try {
|
||||||
|
var macOSNativeProcessList = require('macos-native-processlist') // eslint-disable-line @typescript-eslint/no-var-requires, no-var
|
||||||
|
} catch { }
|
||||||
|
|
||||||
|
try {
|
||||||
|
var windowsProcessTree = require('windows-process-tree') // eslint-disable-line @typescript-eslint/no-var-requires, no-var
|
||||||
|
} catch { }
|
||||||
|
|
||||||
|
export class ElectronPTYInterface extends PTYInterface {
|
||||||
|
async spawn (...options: any[]): Promise<PTYProxy> {
|
||||||
|
const id = ipcRenderer.sendSync('pty:spawn', ...options)
|
||||||
|
return new ElectronPTYProxy(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
async restore (id: string): Promise<ElectronPTYProxy|null> {
|
||||||
|
if (ipcRenderer.sendSync('pty:exists', id)) {
|
||||||
|
return new ElectronPTYProxy(id)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
||||||
|
export class ElectronPTYProxy extends PTYProxy {
|
||||||
|
private subscriptions: Map<string, any> = new Map()
|
||||||
|
private truePID: Promise<number>
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private id: string,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
this.truePID = new Promise(async (resolve) => {
|
||||||
|
let pid = await this.getPID()
|
||||||
|
try {
|
||||||
|
await new Promise(r => setTimeout(r, 2000))
|
||||||
|
|
||||||
|
// Retrieve any possible single children now that shell has fully started
|
||||||
|
let processes = await this.getChildProcessesInternal(pid)
|
||||||
|
while (pid && processes.length === 1) {
|
||||||
|
if (!processes[0].pid) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pid = processes[0].pid
|
||||||
|
processes = await this.getChildProcessesInternal(pid)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
resolve(pid)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.truePID = this.truePID.catch(() => this.getPID())
|
||||||
|
}
|
||||||
|
|
||||||
|
getID (): string {
|
||||||
|
return this.id
|
||||||
|
}
|
||||||
|
|
||||||
|
getTruePID (): Promise<number> {
|
||||||
|
return this.truePID
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPID (): Promise<number> {
|
||||||
|
return ipcRenderer.sendSync('pty:get-pid', this.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe (event: string, handler: (..._: any[]) => void): void {
|
||||||
|
const key = `pty:${this.id}:${event}`
|
||||||
|
const newHandler = (_event, ...args) => handler(...args)
|
||||||
|
this.subscriptions.set(key, newHandler)
|
||||||
|
ipcRenderer.on(key, newHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
ackData (length: number): void {
|
||||||
|
ipcRenderer.send('pty:ack-data', this.id, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribeAll (): void {
|
||||||
|
for (const k of this.subscriptions.keys()) {
|
||||||
|
ipcRenderer.off(k, this.subscriptions.get(k))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async resize (columns: number, rows: number): Promise<void> {
|
||||||
|
ipcRenderer.send('pty:resize', this.id, columns, rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
async write (data: Buffer): Promise<void> {
|
||||||
|
ipcRenderer.send('pty:write', this.id, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
async kill (signal?: string): Promise<void> {
|
||||||
|
ipcRenderer.send('pty:kill', this.id, signal)
|
||||||
|
}
|
||||||
|
|
||||||
|
async getChildProcesses (): Promise<ChildProcess[]> {
|
||||||
|
return this.getChildProcessesInternal(await this.getTruePID())
|
||||||
|
}
|
||||||
|
|
||||||
|
async getChildProcessesInternal (truePID: number): Promise<ChildProcess[]> {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
const processes = await macOSNativeProcessList.getProcessList()
|
||||||
|
return processes.filter(x => x.ppid === truePID).map(p => ({
|
||||||
|
pid: p.pid,
|
||||||
|
ppid: p.ppid,
|
||||||
|
command: p.name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
return new Promise<ChildProcess[]>(resolve => {
|
||||||
|
windowsProcessTree.getProcessTree(truePID, tree => {
|
||||||
|
resolve(tree ? tree.children.map(child => ({
|
||||||
|
pid: child.pid,
|
||||||
|
ppid: tree.pid,
|
||||||
|
command: child.name,
|
||||||
|
})) : [])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return new Promise<ChildProcess[]>((resolve, reject) => {
|
||||||
|
psNode.lookup({ ppid: truePID }, (err, processes) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resolve(processes as ChildProcess[])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWorkingDirectory (): Promise<string|null> {
|
||||||
|
return getWorkingDirectoryFromPID(await this.getTruePID())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
import { NgZone, Injectable } from '@angular/core'
|
import { NgZone, Injectable } from '@angular/core'
|
||||||
import { ConfigService, HostAppService, Platform, ProfilesService, TranslateService } from 'tabby-core'
|
import { ConfigService, HostAppService, Platform, ProfilesService, TranslateService } from 'tabby-core'
|
||||||
import { ElectronService } from 'tabby-electron'
|
import { ElectronService } from './electron.service'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
@@ -234,6 +234,15 @@ export class ElectronPlatformService extends PlatformService {
|
|||||||
handler(err)
|
handler(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async pickDirectory (): Promise<string> {
|
||||||
|
return (await this.electron.dialog.showOpenDialog(
|
||||||
|
this.hostWindow.getWindow(),
|
||||||
|
{
|
||||||
|
properties: ['openDirectory', 'showHiddenFiles'],
|
||||||
|
},
|
||||||
|
)).filePaths[0]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ElectronFileUpload extends FileUpload {
|
class ElectronFileUpload extends FileUpload {
|
||||||
|
@@ -1,17 +1,16 @@
|
|||||||
import * as path from 'path'
|
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
|
import * as path from 'path'
|
||||||
import { WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild } from 'tabby-core'
|
import { WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild } from 'tabby-core'
|
||||||
import { ElectronService } from 'tabby-electron'
|
import { SessionOptions, UACService } from 'tabby-local'
|
||||||
import { SessionOptions } from '../api'
|
import { ElectronService } from './electron.service'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable()
|
||||||
export class UACService {
|
export class ElectronUACService extends UACService {
|
||||||
isAvailable = false
|
constructor (
|
||||||
|
|
||||||
private constructor (
|
|
||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
) {
|
) {
|
||||||
|
super()
|
||||||
this.isAvailable = isWindowsBuild(WIN_BUILD_CONPTY_SUPPORTED)
|
this.isAvailable = isWindowsBuild(WIN_BUILD_CONPTY_SUPPORTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,5 +36,4 @@ export class UACService {
|
|||||||
options.command = helperPath
|
options.command = helperPath
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -2,7 +2,7 @@ import * as path from 'path'
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { HostAppService, Platform } from 'tabby-core'
|
import { HostAppService, Platform } from 'tabby-core'
|
||||||
|
|
||||||
import { ShellProvider, Shell } from '../api'
|
import { ShellProvider, Shell } from 'tabby-local'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Injectable()
|
@Injectable()
|
@@ -2,7 +2,7 @@ import * as path from 'path'
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { HostAppService, Platform } from 'tabby-core'
|
import { HostAppService, Platform } from 'tabby-core'
|
||||||
|
|
||||||
import { ShellProvider, Shell } from '../api'
|
import { ShellProvider, Shell } from 'tabby-local'
|
||||||
|
|
||||||
/* eslint-disable block-scoped-var */
|
/* eslint-disable block-scoped-var */
|
||||||
|
|
@@ -2,7 +2,7 @@ import * as path from 'path'
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { HostAppService, Platform } from 'tabby-core'
|
import { HostAppService, Platform } from 'tabby-core'
|
||||||
|
|
||||||
import { ShellProvider, Shell } from '../api'
|
import { ShellProvider, Shell } from 'tabby-local'
|
||||||
|
|
||||||
/* eslint-disable block-scoped-var */
|
/* eslint-disable block-scoped-var */
|
||||||
|
|
@@ -2,7 +2,7 @@ import * as path from 'path'
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { Platform, ConfigService, HostAppService } from 'tabby-core'
|
import { Platform, ConfigService, HostAppService } from 'tabby-core'
|
||||||
|
|
||||||
import { Shell } from '../api'
|
import { Shell } from 'tabby-local'
|
||||||
import { WindowsBaseShellProvider } from './windowsBase'
|
import { WindowsBaseShellProvider } from './windowsBase'
|
||||||
|
|
||||||
/* eslint-disable block-scoped-var */
|
/* eslint-disable block-scoped-var */
|
@@ -2,7 +2,7 @@ import * as fs from 'mz/fs'
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { HostAppService, Platform, LogService, Logger, TranslateService } from 'tabby-core'
|
import { HostAppService, Platform, LogService, Logger, TranslateService } from 'tabby-core'
|
||||||
|
|
||||||
import { ShellProvider, Shell } from '../api'
|
import { ShellProvider, Shell } from 'tabby-local'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Injectable()
|
@Injectable()
|
@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'
|
|||||||
import promiseIpc, { RendererProcessType } from 'electron-promise-ipc'
|
import promiseIpc, { RendererProcessType } from 'electron-promise-ipc'
|
||||||
import { HostAppService, Platform, TranslateService } from 'tabby-core'
|
import { HostAppService, Platform, TranslateService } from 'tabby-core'
|
||||||
|
|
||||||
import { ShellProvider, Shell } from '../api'
|
import { ShellProvider, Shell } from 'tabby-local'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Injectable()
|
@Injectable()
|
@@ -3,7 +3,7 @@ import * as path from 'path'
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { HostAppService, Platform } from 'tabby-core'
|
import { HostAppService, Platform } from 'tabby-core'
|
||||||
|
|
||||||
import { ShellProvider, Shell } from '../api'
|
import { ShellProvider, Shell } from 'tabby-local'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Injectable()
|
@Injectable()
|
@@ -3,7 +3,7 @@ import slugify from 'slugify'
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { HostAppService, Platform } from 'tabby-core'
|
import { HostAppService, Platform } from 'tabby-core'
|
||||||
|
|
||||||
import { ShellProvider, Shell } from '../api'
|
import { ShellProvider, Shell } from 'tabby-local'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Injectable()
|
@Injectable()
|
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { HostAppService, ConfigService, Platform } from 'tabby-core'
|
import { HostAppService, ConfigService, Platform } from 'tabby-core'
|
||||||
|
|
||||||
import { Shell } from '../api'
|
import { Shell } from 'tabby-local'
|
||||||
import { WindowsBaseShellProvider } from './windowsBase'
|
import { WindowsBaseShellProvider } from './windowsBase'
|
||||||
|
|
||||||
/* eslint-disable block-scoped-var */
|
/* eslint-disable block-scoped-var */
|
@@ -3,7 +3,7 @@ import * as fs from 'fs/promises'
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { HostAppService, Platform } from 'tabby-core'
|
import { HostAppService, Platform } from 'tabby-core'
|
||||||
|
|
||||||
import { ShellProvider, Shell } from '../api'
|
import { ShellProvider, Shell } from 'tabby-local'
|
||||||
|
|
||||||
/* eslint-disable quote-props */
|
/* eslint-disable quote-props */
|
||||||
const vsIconMap: Record<string, string> = {
|
const vsIconMap: Record<string, string> = {
|
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { HostAppService, Platform, TranslateService } from 'tabby-core'
|
import { HostAppService, Platform, TranslateService } from 'tabby-core'
|
||||||
|
|
||||||
import { ShellProvider, Shell } from '../api'
|
import { ShellProvider, Shell } from 'tabby-local'
|
||||||
|
|
||||||
import { WSLShellProvider } from './wsl'
|
import { WSLShellProvider } from './wsl'
|
||||||
import { PowerShellCoreShellProvider } from './powershellCore'
|
import { PowerShellCoreShellProvider } from './powershellCore'
|
@@ -1,6 +1,6 @@
|
|||||||
import { ConfigService, HostAppService } from 'tabby-core'
|
import { ConfigService, HostAppService } from 'tabby-core'
|
||||||
|
|
||||||
import { ShellProvider } from '../api'
|
import { ShellProvider } from 'tabby-local'
|
||||||
|
|
||||||
export abstract class WindowsBaseShellProvider extends ShellProvider {
|
export abstract class WindowsBaseShellProvider extends ShellProvider {
|
||||||
constructor (
|
constructor (
|
@@ -3,9 +3,9 @@ import * as fs from 'fs/promises'
|
|||||||
import hasbin from 'hasbin'
|
import hasbin from 'hasbin'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { HostAppService, Platform, ConfigService } from 'tabby-core'
|
import { HostAppService, Platform, ConfigService } from 'tabby-core'
|
||||||
import { ElectronService } from 'tabby-electron'
|
import { ElectronService } from '../services/electron.service'
|
||||||
|
|
||||||
import { Shell } from '../api'
|
import { Shell } from 'tabby-local'
|
||||||
import { WindowsBaseShellProvider } from './windowsBase'
|
import { WindowsBaseShellProvider } from './windowsBase'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
@@ -4,7 +4,7 @@ import slugify from 'slugify'
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { HostAppService, Platform, isWindowsBuild, WIN_BUILD_WSL_EXE_DISTRO_FLAG } from 'tabby-core'
|
import { HostAppService, Platform, isWindowsBuild, WIN_BUILD_WSL_EXE_DISTRO_FLAG } from 'tabby-core'
|
||||||
|
|
||||||
import { ShellProvider, Shell } from '../api'
|
import { ShellProvider, Shell } from 'tabby-local'
|
||||||
|
|
||||||
/* eslint-disable block-scoped-var */
|
/* eslint-disable block-scoped-var */
|
||||||
|
|
@@ -21,6 +21,11 @@ async@^3.2.3:
|
|||||||
resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
|
resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
|
||||||
integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==
|
integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==
|
||||||
|
|
||||||
|
async@~1.5:
|
||||||
|
version "1.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
|
||||||
|
integrity sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==
|
||||||
|
|
||||||
balanced-match@^1.0.0:
|
balanced-match@^1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||||
@@ -88,6 +93,11 @@ concat-map@0.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||||
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
||||||
|
|
||||||
|
connected-domain@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/connected-domain/-/connected-domain-1.0.0.tgz#bfe77238c74be453a79f0cb6058deeb4f2358e93"
|
||||||
|
integrity sha512-lHlohUiJxlpunvDag2Y0pO20bnvarMjnrdciZeuJUqRwrf/5JHNhdpiPIr5GQ8IkqrFj5TDMQwcCjblGo1oeuA==
|
||||||
|
|
||||||
define-properties@^1.1.3:
|
define-properties@^1.1.3:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
|
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
|
||||||
@@ -199,6 +209,13 @@ has@^1.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind "^1.1.1"
|
function-bind "^1.1.1"
|
||||||
|
|
||||||
|
hasbin@^1.2.3:
|
||||||
|
version "1.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/hasbin/-/hasbin-1.2.3.tgz#78c5926893c80215c2b568ae1fd3fcab7a2696b0"
|
||||||
|
integrity sha512-CCd8e/w2w28G8DyZvKgiHnQJ/5XXDz6qiUHnthvtag/6T5acUeN5lqq+HMoBqcmgWueWDhiCplrw0Kb1zDACRg==
|
||||||
|
dependencies:
|
||||||
|
async "~1.5"
|
||||||
|
|
||||||
inflight@^1.0.4:
|
inflight@^1.0.4:
|
||||||
version "1.0.6"
|
version "1.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||||
@@ -356,6 +373,13 @@ path-is-absolute@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||||
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
||||||
|
|
||||||
|
ps-node@^0.1.6:
|
||||||
|
version "0.1.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/ps-node/-/ps-node-0.1.6.tgz#9af67a99d7b1d0132e51a503099d38a8d2ace2c3"
|
||||||
|
integrity sha512-w7QJhUTbu70hpDso0YXDRNKCPNuchV8UTUZsAv0m7Qj5g85oHOJfr9drA1EjvK4nQK/bG8P97W4L6PJ3IQLoOA==
|
||||||
|
dependencies:
|
||||||
|
table-parser "^0.1.3"
|
||||||
|
|
||||||
readable-stream@^3.4.0, readable-stream@^3.6.0:
|
readable-stream@^3.4.0, readable-stream@^3.6.0:
|
||||||
version "3.6.0"
|
version "3.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
|
||||||
@@ -424,6 +448,13 @@ string_decoder@^1.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.2.0"
|
safe-buffer "~5.2.0"
|
||||||
|
|
||||||
|
table-parser@^0.1.3:
|
||||||
|
version "0.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/table-parser/-/table-parser-0.1.3.tgz#0441cfce16a59481684c27d1b5a67ff15a43c7b0"
|
||||||
|
integrity sha512-LCYeuvqqoPII3lzzYaXKbC3Forb+d2u4bNwhk/9FlivuGRxPE28YEWAYcujeSlLLDlMfvy29+WPybFJZFiKMYg==
|
||||||
|
dependencies:
|
||||||
|
connected-domain "^1.0.0"
|
||||||
|
|
||||||
text-hex@1.0.x:
|
text-hex@1.0.x:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5"
|
resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5"
|
||||||
|
@@ -19,8 +19,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"ansi-colors": "^4.1.1",
|
"ansi-colors": "^4.1.1",
|
||||||
"dataurl": "0.1.0",
|
"dataurl": "0.1.0",
|
||||||
"hasbin": "^1.2.3",
|
|
||||||
"ps-node": "^0.1.6",
|
|
||||||
"runes": "^0.4.2"
|
"runes": "^0.4.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
@@ -53,3 +53,28 @@ export interface ChildProcess {
|
|||||||
ppid: number
|
ppid: number
|
||||||
command: string
|
command: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export abstract class UACService {
|
||||||
|
isAvailable = false
|
||||||
|
|
||||||
|
abstract patchSessionOptionsForUAC (sessionOptions: SessionOptions): SessionOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class PTYProxy {
|
||||||
|
abstract getID (): string
|
||||||
|
abstract getPID (): Promise<number>
|
||||||
|
abstract resize (columns: number, rows: number): Promise<void>
|
||||||
|
abstract write (data: Buffer): Promise<void>
|
||||||
|
abstract kill (signal?: string): Promise<void>
|
||||||
|
abstract ackData (length: number): void
|
||||||
|
abstract subscribe (event: string, handler: (..._: any[]) => void): void
|
||||||
|
abstract unsubscribeAll (): void
|
||||||
|
abstract getChildProcesses (): Promise<ChildProcess[]>
|
||||||
|
abstract getTruePID (): Promise<number>
|
||||||
|
abstract getWorkingDirectory (): Promise<string|null>
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class PTYInterface {
|
||||||
|
abstract spawn (...options: any[]): Promise<PTYProxy>
|
||||||
|
abstract restore (id: string): Promise<PTYProxy|null>
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
command-line-editor([model]='profile.options')
|
command-line-editor([model]='profile.options')
|
||||||
|
|
||||||
.form-line(*ngIf='uac.isAvailable')
|
.form-line(*ngIf='uac?.isAvailable')
|
||||||
.header
|
.header
|
||||||
.title(translate) Run as administrator
|
.title(translate) Run as administrator
|
||||||
toggle(
|
toggle(
|
||||||
|
@@ -1,9 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Component } from '@angular/core'
|
import { Component, Inject, Optional } from '@angular/core'
|
||||||
import { UACService } from '../services/uac.service'
|
import { LocalProfile, UACService } from '../api'
|
||||||
import { LocalProfile } from '../api'
|
import { PlatformService, ProfileSettingsComponent } from 'tabby-core'
|
||||||
import { ElectronHostWindow, ElectronService } from 'tabby-electron'
|
|
||||||
import { ProfileSettingsComponent } from 'tabby-core'
|
|
||||||
|
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@@ -14,9 +12,8 @@ export class LocalProfileSettingsComponent implements ProfileSettingsComponent<L
|
|||||||
profile: LocalProfile
|
profile: LocalProfile
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
public uac: UACService,
|
@Optional() @Inject(UACService) public uac: UACService|undefined,
|
||||||
private hostWindow: ElectronHostWindow,
|
private platform: PlatformService,
|
||||||
private electron: ElectronService,
|
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
@@ -30,14 +27,7 @@ export class LocalProfileSettingsComponent implements ProfileSettingsComponent<L
|
|||||||
// if (!shell) {
|
// if (!shell) {
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
const paths = (await this.electron.dialog.showOpenDialog(
|
|
||||||
this.hostWindow.getWindow(),
|
this.profile.options.cwd = await this.platform.pickDirectory()
|
||||||
{
|
|
||||||
// TODO
|
|
||||||
// defaultPath: shell.fsBase,
|
|
||||||
properties: ['openDirectory', 'showHiddenFiles'],
|
|
||||||
},
|
|
||||||
)).filePaths
|
|
||||||
this.profile.options.cwd = paths[0]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,9 @@
|
|||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'
|
||||||
import { Component, Input, Injector } from '@angular/core'
|
import { Component, Input, Injector, Inject, Optional } from '@angular/core'
|
||||||
import { BaseTabProcess, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild, GetRecoveryTokenOptions } from 'tabby-core'
|
import { BaseTabProcess, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild, GetRecoveryTokenOptions } from 'tabby-core'
|
||||||
import { BaseTerminalTabComponent } from 'tabby-terminal'
|
import { BaseTerminalTabComponent } from 'tabby-terminal'
|
||||||
import { LocalProfile, SessionOptions } from '../api'
|
import { LocalProfile, SessionOptions, UACService } from '../api'
|
||||||
import { Session } from '../session'
|
import { Session } from '../session'
|
||||||
import { UACService } from '../services/uac.service'
|
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Component({
|
@Component({
|
||||||
@@ -20,7 +19,7 @@ export class TerminalTabComponent extends BaseTerminalTabComponent<LocalProfile>
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
||||||
constructor (
|
constructor (
|
||||||
injector: Injector,
|
injector: Injector,
|
||||||
private uac: UACService,
|
@Optional() @Inject(UACService) private uac: UACService|undefined,
|
||||||
) {
|
) {
|
||||||
super(injector)
|
super(injector)
|
||||||
}
|
}
|
||||||
@@ -52,12 +51,12 @@ export class TerminalTabComponent extends BaseTerminalTabComponent<LocalProfile>
|
|||||||
|
|
||||||
protected onFrontendReady (): void {
|
protected onFrontendReady (): void {
|
||||||
this.initializeSession(this.size.columns, this.size.rows)
|
this.initializeSession(this.size.columns, this.size.rows)
|
||||||
this.savedStateIsLive = this.profile.options.restoreFromPTYID === this.session?.getPTYID()
|
this.savedStateIsLive = this.profile.options.restoreFromPTYID === this.session?.getID()
|
||||||
super.onFrontendReady()
|
super.onFrontendReady()
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeSession (columns: number, rows: number): void {
|
initializeSession (columns: number, rows: number): void {
|
||||||
if (this.profile.options.runAsAdministrator && this.uac.isAvailable) {
|
if (this.profile.options.runAsAdministrator && this.uac?.isAvailable) {
|
||||||
this.profile = {
|
this.profile = {
|
||||||
...this.profile,
|
...this.profile,
|
||||||
options: this.uac.patchSessionOptionsForUAC(this.profile.options),
|
options: this.uac.patchSessionOptionsForUAC(this.profile.options),
|
||||||
@@ -83,7 +82,7 @@ export class TerminalTabComponent extends BaseTerminalTabComponent<LocalProfile>
|
|||||||
options: {
|
options: {
|
||||||
...this.profile.options,
|
...this.profile.options,
|
||||||
cwd: cwd ?? this.profile.options.cwd,
|
cwd: cwd ?? this.profile.options.cwd,
|
||||||
restoreFromPTYID: options?.includeState && this.session?.getPTYID(),
|
restoreFromPTYID: options?.includeState && this.session?.getID(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
savedState: options?.includeState && this.frontend?.saveState(),
|
savedState: options?.includeState && this.frontend?.saveState(),
|
||||||
|
@@ -4,9 +4,8 @@ import { FormsModule } from '@angular/forms'
|
|||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { ToastrModule } from 'ngx-toastr'
|
import { ToastrModule } from 'ngx-toastr'
|
||||||
|
|
||||||
import TabbyCorePlugin, { HostAppService, ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider, TabContextMenuItemProvider, CLIHandler, ConfigService, ProfileProvider } from 'tabby-core'
|
import TabbyCorePlugin, { HostAppService, ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider, TabContextMenuItemProvider, CLIHandler, ProfileProvider } from 'tabby-core'
|
||||||
import TabbyTerminalModule from 'tabby-terminal'
|
import TabbyTerminalModule from 'tabby-terminal'
|
||||||
import TabbyElectronPlugin from 'tabby-electron'
|
|
||||||
import { SettingsTabProvider } from 'tabby-settings'
|
import { SettingsTabProvider } from 'tabby-settings'
|
||||||
|
|
||||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||||
@@ -16,30 +15,14 @@ import { LocalProfileSettingsComponent } from './components/localProfileSettings
|
|||||||
import { CommandLineEditorComponent } from './components/commandLineEditor.component'
|
import { CommandLineEditorComponent } from './components/commandLineEditor.component'
|
||||||
|
|
||||||
import { TerminalService } from './services/terminal.service'
|
import { TerminalService } from './services/terminal.service'
|
||||||
import { DockMenuService } from './services/dockMenu.service'
|
|
||||||
|
|
||||||
import { ButtonProvider } from './buttonProvider'
|
import { ButtonProvider } from './buttonProvider'
|
||||||
import { RecoveryProvider } from './recoveryProvider'
|
import { RecoveryProvider } from './recoveryProvider'
|
||||||
import { ShellProvider } from './api'
|
|
||||||
import { ShellSettingsTabProvider } from './settings'
|
import { ShellSettingsTabProvider } from './settings'
|
||||||
import { TerminalConfigProvider } from './config'
|
import { TerminalConfigProvider } from './config'
|
||||||
import { LocalTerminalHotkeyProvider } from './hotkeys'
|
import { LocalTerminalHotkeyProvider } from './hotkeys'
|
||||||
import { NewTabContextMenu, SaveAsProfileContextMenu } from './tabContextMenu'
|
import { NewTabContextMenu, SaveAsProfileContextMenu } from './tabContextMenu'
|
||||||
|
|
||||||
import { CmderShellProvider } from './shells/cmder'
|
|
||||||
import { Cygwin32ShellProvider } from './shells/cygwin32'
|
|
||||||
import { Cygwin64ShellProvider } from './shells/cygwin64'
|
|
||||||
import { GitBashShellProvider } from './shells/gitBash'
|
|
||||||
import { LinuxDefaultShellProvider } from './shells/linuxDefault'
|
|
||||||
import { MacOSDefaultShellProvider } from './shells/macDefault'
|
|
||||||
import { MSYS2ShellProvider } from './shells/msys2'
|
|
||||||
import { POSIXShellsProvider } from './shells/posix'
|
|
||||||
import { PowerShellCoreShellProvider } from './shells/powershellCore'
|
|
||||||
import { WindowsDefaultShellProvider } from './shells/winDefault'
|
|
||||||
import { WindowsStockShellsProvider } from './shells/windowsStock'
|
|
||||||
import { WSLShellProvider } from './shells/wsl'
|
|
||||||
import { VSDevToolsProvider } from './shells/vs'
|
|
||||||
|
|
||||||
import { AutoOpenTabCLIHandler, OpenPathCLIHandler, TerminalCLIHandler } from './cli'
|
import { AutoOpenTabCLIHandler, OpenPathCLIHandler, TerminalCLIHandler } from './cli'
|
||||||
import { LocalProfilesService } from './profiles'
|
import { LocalProfilesService } from './profiles'
|
||||||
|
|
||||||
@@ -51,7 +34,6 @@ import { LocalProfilesService } from './profiles'
|
|||||||
NgbModule,
|
NgbModule,
|
||||||
ToastrModule,
|
ToastrModule,
|
||||||
TabbyCorePlugin,
|
TabbyCorePlugin,
|
||||||
TabbyElectronPlugin,
|
|
||||||
TabbyTerminalModule,
|
TabbyTerminalModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
@@ -62,20 +44,6 @@ import { LocalProfilesService } from './profiles'
|
|||||||
{ provide: ConfigProvider, useClass: TerminalConfigProvider, multi: true },
|
{ provide: ConfigProvider, useClass: TerminalConfigProvider, multi: true },
|
||||||
{ provide: HotkeyProvider, useClass: LocalTerminalHotkeyProvider, multi: true },
|
{ provide: HotkeyProvider, useClass: LocalTerminalHotkeyProvider, multi: true },
|
||||||
|
|
||||||
{ provide: ShellProvider, useClass: WindowsDefaultShellProvider, multi: true },
|
|
||||||
{ provide: ShellProvider, useClass: MacOSDefaultShellProvider, multi: true },
|
|
||||||
{ provide: ShellProvider, useClass: LinuxDefaultShellProvider, multi: true },
|
|
||||||
{ provide: ShellProvider, useClass: WindowsStockShellsProvider, multi: true },
|
|
||||||
{ provide: ShellProvider, useClass: PowerShellCoreShellProvider, multi: true },
|
|
||||||
{ provide: ShellProvider, useClass: CmderShellProvider, multi: true },
|
|
||||||
{ provide: ShellProvider, useClass: Cygwin32ShellProvider, multi: true },
|
|
||||||
{ provide: ShellProvider, useClass: Cygwin64ShellProvider, multi: true },
|
|
||||||
{ provide: ShellProvider, useClass: GitBashShellProvider, multi: true },
|
|
||||||
{ provide: ShellProvider, useClass: POSIXShellsProvider, multi: true },
|
|
||||||
{ provide: ShellProvider, useClass: MSYS2ShellProvider, multi: true },
|
|
||||||
{ provide: ShellProvider, useClass: WSLShellProvider, multi: true },
|
|
||||||
{ provide: ShellProvider, useClass: VSDevToolsProvider, multi: true },
|
|
||||||
|
|
||||||
{ provide: ProfileProvider, useClass: LocalProfilesService, multi: true },
|
{ provide: ProfileProvider, useClass: LocalProfilesService, multi: true },
|
||||||
|
|
||||||
{ provide: TabContextMenuItemProvider, useClass: NewTabContextMenu, multi: true },
|
{ provide: TabContextMenuItemProvider, useClass: NewTabContextMenu, multi: true },
|
||||||
@@ -84,11 +52,6 @@ import { LocalProfilesService } from './profiles'
|
|||||||
{ provide: CLIHandler, useClass: TerminalCLIHandler, multi: true },
|
{ provide: CLIHandler, useClass: TerminalCLIHandler, multi: true },
|
||||||
{ provide: CLIHandler, useClass: OpenPathCLIHandler, multi: true },
|
{ provide: CLIHandler, useClass: OpenPathCLIHandler, multi: true },
|
||||||
{ provide: CLIHandler, useClass: AutoOpenTabCLIHandler, multi: true },
|
{ provide: CLIHandler, useClass: AutoOpenTabCLIHandler, multi: true },
|
||||||
|
|
||||||
// For WindowsDefaultShellProvider
|
|
||||||
PowerShellCoreShellProvider,
|
|
||||||
WSLShellProvider,
|
|
||||||
WindowsStockShellsProvider,
|
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
TerminalTabComponent,
|
TerminalTabComponent,
|
||||||
@@ -108,8 +71,6 @@ export default class LocalTerminalModule { // eslint-disable-line @typescript-es
|
|||||||
hotkeys: HotkeysService,
|
hotkeys: HotkeysService,
|
||||||
terminal: TerminalService,
|
terminal: TerminalService,
|
||||||
hostApp: HostAppService,
|
hostApp: HostAppService,
|
||||||
dockMenu: DockMenuService,
|
|
||||||
config: ConfigService,
|
|
||||||
) {
|
) {
|
||||||
hotkeys.hotkey$.subscribe(async (hotkey) => {
|
hotkeys.hotkey$.subscribe(async (hotkey) => {
|
||||||
if (hotkey === 'new-tab') {
|
if (hotkey === 'new-tab') {
|
||||||
@@ -119,10 +80,6 @@ export default class LocalTerminalModule { // eslint-disable-line @typescript-es
|
|||||||
hostApp.newWindow()
|
hostApp.newWindow()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
config.ready$.toPromise().then(() => {
|
|
||||||
dockMenu.update()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,87 +1,12 @@
|
|||||||
import * as psNode from 'ps-node'
|
|
||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
import * as fsSync from 'fs'
|
import * as fsSync from 'fs'
|
||||||
import { Injector } from '@angular/core'
|
import { Injector } from '@angular/core'
|
||||||
import { HostAppService, ConfigService, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild, Platform, BootstrapData, BOOTSTRAP_DATA, LogService } from 'tabby-core'
|
import { HostAppService, ConfigService, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild, Platform, BootstrapData, BOOTSTRAP_DATA, LogService } from 'tabby-core'
|
||||||
import { BaseSession } from 'tabby-terminal'
|
import { BaseSession } from 'tabby-terminal'
|
||||||
import { ipcRenderer } from 'electron'
|
import { SessionOptions, ChildProcess, PTYInterface, PTYProxy } from './api'
|
||||||
import { getWorkingDirectoryFromPID } from 'native-process-working-directory'
|
|
||||||
import { SessionOptions, ChildProcess } from './api'
|
|
||||||
|
|
||||||
/* eslint-disable block-scoped-var */
|
|
||||||
|
|
||||||
try {
|
|
||||||
var macOSNativeProcessList = require('macos-native-processlist') // eslint-disable-line @typescript-eslint/no-var-requires, no-var
|
|
||||||
} catch { }
|
|
||||||
|
|
||||||
try {
|
|
||||||
var windowsProcessTree = require('windows-process-tree') // eslint-disable-line @typescript-eslint/no-var-requires, no-var
|
|
||||||
} catch { }
|
|
||||||
|
|
||||||
const windowsDirectoryRegex = /([a-zA-Z]:[^\:\[\]\?\"\<\>\|]+)/mi
|
const windowsDirectoryRegex = /([a-zA-Z]:[^\:\[\]\?\"\<\>\|]+)/mi
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
|
||||||
export class PTYProxy {
|
|
||||||
private id: string
|
|
||||||
private subscriptions: Map<string, any> = new Map()
|
|
||||||
|
|
||||||
static spawn (...options: any[]): PTYProxy {
|
|
||||||
return new PTYProxy(null, ...options)
|
|
||||||
}
|
|
||||||
|
|
||||||
static restore (id: string): PTYProxy|null {
|
|
||||||
if (ipcRenderer.sendSync('pty:exists', id)) {
|
|
||||||
return new PTYProxy(id)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private constructor (id: string|null, ...options: any[]) {
|
|
||||||
if (id) {
|
|
||||||
this.id = id
|
|
||||||
} else {
|
|
||||||
this.id = ipcRenderer.sendSync('pty:spawn', ...options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getPTYID (): string {
|
|
||||||
return this.id
|
|
||||||
}
|
|
||||||
|
|
||||||
getPID (): number {
|
|
||||||
return ipcRenderer.sendSync('pty:get-pid', this.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe (event: string, handler: (..._: any[]) => void): void {
|
|
||||||
const key = `pty:${this.id}:${event}`
|
|
||||||
const newHandler = (_event, ...args) => handler(...args)
|
|
||||||
this.subscriptions.set(key, newHandler)
|
|
||||||
ipcRenderer.on(key, newHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
ackData (length: number): void {
|
|
||||||
ipcRenderer.send('pty:ack-data', this.id, length)
|
|
||||||
}
|
|
||||||
|
|
||||||
unsubscribeAll (): void {
|
|
||||||
for (const k of this.subscriptions.keys()) {
|
|
||||||
ipcRenderer.off(k, this.subscriptions.get(k))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resize (columns: number, rows: number): void {
|
|
||||||
ipcRenderer.send('pty:resize', this.id, columns, rows)
|
|
||||||
}
|
|
||||||
|
|
||||||
write (data: Buffer): void {
|
|
||||||
ipcRenderer.send('pty:write', this.id, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
kill (signal?: string): void {
|
|
||||||
ipcRenderer.send('pty:kill', this.id, signal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mergeEnv (...envs) {
|
function mergeEnv (...envs) {
|
||||||
const result = {}
|
const result = {}
|
||||||
const keyMap = {}
|
const keyMap = {}
|
||||||
@@ -121,19 +46,23 @@ export class Session extends BaseSession {
|
|||||||
private config: ConfigService
|
private config: ConfigService
|
||||||
private hostApp: HostAppService
|
private hostApp: HostAppService
|
||||||
private bootstrapData: BootstrapData
|
private bootstrapData: BootstrapData
|
||||||
|
private ptyInterface: PTYInterface
|
||||||
|
|
||||||
constructor (injector: Injector) {
|
constructor (
|
||||||
|
injector: Injector,
|
||||||
|
) {
|
||||||
super(injector.get(LogService).create('local'))
|
super(injector.get(LogService).create('local'))
|
||||||
this.config = injector.get(ConfigService)
|
this.config = injector.get(ConfigService)
|
||||||
this.hostApp = injector.get(HostAppService)
|
this.hostApp = injector.get(HostAppService)
|
||||||
|
this.ptyInterface = injector.get(PTYInterface)
|
||||||
this.bootstrapData = injector.get(BOOTSTRAP_DATA)
|
this.bootstrapData = injector.get(BOOTSTRAP_DATA)
|
||||||
}
|
}
|
||||||
|
|
||||||
start (options: SessionOptions): void {
|
async start (options: SessionOptions): Promise<void> {
|
||||||
let pty: PTYProxy|null = null
|
let pty: PTYProxy|null = null
|
||||||
|
|
||||||
if (options.restoreFromPTYID) {
|
if (options.restoreFromPTYID) {
|
||||||
pty = PTYProxy.restore(options.restoreFromPTYID)
|
pty = await this.ptyInterface.restore(options.restoreFromPTYID)
|
||||||
options.restoreFromPTYID = undefined
|
options.restoreFromPTYID = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +104,7 @@ export class Session extends BaseSession {
|
|||||||
cwd = undefined
|
cwd = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
pty = PTYProxy.spawn(options.command, options.args ?? [], {
|
pty = await this.ptyInterface.spawn(options.command, options.args ?? [], {
|
||||||
name: 'xterm-256color',
|
name: 'xterm-256color',
|
||||||
cols: options.width ?? 80,
|
cols: options.width ?? 80,
|
||||||
rows: options.height ?? 30,
|
rows: options.height ?? 30,
|
||||||
@@ -191,17 +120,9 @@ export class Session extends BaseSession {
|
|||||||
|
|
||||||
this.pty = pty
|
this.pty = pty
|
||||||
|
|
||||||
this.truePID = this.pty.getPID()
|
pty.getTruePID().then(async () => {
|
||||||
|
|
||||||
setTimeout(async () => {
|
|
||||||
// Retrieve any possible single children now that shell has fully started
|
|
||||||
let processes = await this.getChildProcesses()
|
|
||||||
while (processes.length === 1) {
|
|
||||||
this.truePID = processes[0].pid
|
|
||||||
processes = await this.getChildProcesses()
|
|
||||||
}
|
|
||||||
this.initialCWD = await this.getWorkingDirectory()
|
this.initialCWD = await this.getWorkingDirectory()
|
||||||
}, 2000)
|
})
|
||||||
|
|
||||||
this.open = true
|
this.open = true
|
||||||
|
|
||||||
@@ -236,8 +157,8 @@ export class Session extends BaseSession {
|
|||||||
this.destroyed$.subscribe(() => this.pty!.unsubscribeAll())
|
this.destroyed$.subscribe(() => this.pty!.unsubscribeAll())
|
||||||
}
|
}
|
||||||
|
|
||||||
getPTYID (): string|null {
|
getID (): string|null {
|
||||||
return this.pty?.getPTYID() ?? null
|
return this.pty?.getID() ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
resize (columns: number, rows: number): void {
|
resize (columns: number, rows: number): void {
|
||||||
@@ -258,37 +179,7 @@ export class Session extends BaseSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getChildProcesses (): Promise<ChildProcess[]> {
|
async getChildProcesses (): Promise<ChildProcess[]> {
|
||||||
if (!this.truePID) {
|
return this.pty?.getChildProcesses() ?? []
|
||||||
return []
|
|
||||||
}
|
|
||||||
if (this.hostApp.platform === Platform.macOS) {
|
|
||||||
const processes = await macOSNativeProcessList.getProcessList()
|
|
||||||
return processes.filter(x => x.ppid === this.truePID).map(p => ({
|
|
||||||
pid: p.pid,
|
|
||||||
ppid: p.ppid,
|
|
||||||
command: p.name,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
if (this.hostApp.platform === Platform.Windows) {
|
|
||||||
return new Promise<ChildProcess[]>(resolve => {
|
|
||||||
windowsProcessTree.getProcessTree(this.truePID, tree => {
|
|
||||||
resolve(tree ? tree.children.map(child => ({
|
|
||||||
pid: child.pid,
|
|
||||||
ppid: tree.pid,
|
|
||||||
command: child.name,
|
|
||||||
})) : [])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return new Promise<ChildProcess[]>((resolve, reject) => {
|
|
||||||
psNode.lookup({ ppid: this.truePID }, (err, processes) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resolve(processes as ChildProcess[])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async gracefullyKillProcess (): Promise<void> {
|
async gracefullyKillProcess (): Promise<void> {
|
||||||
@@ -297,9 +188,9 @@ export class Session extends BaseSession {
|
|||||||
} else {
|
} else {
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve) => {
|
||||||
this.kill('SIGTERM')
|
this.kill('SIGTERM')
|
||||||
setTimeout(() => {
|
setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
process.kill(this.pty!.getPID(), 0)
|
process.kill(await this.pty!.getPID(), 0)
|
||||||
// still alive
|
// still alive
|
||||||
this.kill('SIGKILL')
|
this.kill('SIGKILL')
|
||||||
resolve()
|
resolve()
|
||||||
@@ -312,19 +203,16 @@ export class Session extends BaseSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
supportsWorkingDirectory (): boolean {
|
supportsWorkingDirectory (): boolean {
|
||||||
return !!(this.truePID ?? this.reportedCWD ?? this.guessedCWD)
|
return !!(this.initialCWD ?? this.reportedCWD ?? this.guessedCWD)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getWorkingDirectory (): Promise<string|null> {
|
async getWorkingDirectory (): Promise<string|null> {
|
||||||
if (this.reportedCWD) {
|
if (this.reportedCWD) {
|
||||||
return this.reportedCWD
|
return this.reportedCWD
|
||||||
}
|
}
|
||||||
if (!this.truePID) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
let cwd: string|null = null
|
let cwd: string|null = null
|
||||||
try {
|
try {
|
||||||
cwd = getWorkingDirectoryFromPID(this.truePID)
|
cwd = await this.pty?.getWorkingDirectory() ?? null
|
||||||
} catch (exc) {
|
} catch (exc) {
|
||||||
console.info('Could not read working directory:', exc)
|
console.info('Could not read working directory:', exc)
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,9 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Inject, Injectable, Optional } from '@angular/core'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, NotificationsService, MenuItemOptions, ProfilesService, PromptModalComponent, TranslateService } from 'tabby-core'
|
import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, NotificationsService, MenuItemOptions, ProfilesService, PromptModalComponent, TranslateService } from 'tabby-core'
|
||||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||||
import { UACService } from './services/uac.service'
|
|
||||||
import { TerminalService } from './services/terminal.service'
|
import { TerminalService } from './services/terminal.service'
|
||||||
import { LocalProfile } from './api'
|
import { LocalProfile, UACService } from './api'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -64,7 +63,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
|
|||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
private profilesService: ProfilesService,
|
private profilesService: ProfilesService,
|
||||||
private terminalService: TerminalService,
|
private terminalService: TerminalService,
|
||||||
private uac: UACService,
|
@Optional() @Inject(UACService) private uac: UACService|undefined,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
@@ -99,7 +98,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
if (this.uac.isAvailable) {
|
if (this.uac?.isAvailable) {
|
||||||
items.push({
|
items.push({
|
||||||
label: this.translate.instant('New admin tab'),
|
label: this.translate.instant('New admin tab'),
|
||||||
submenu: profiles.map(profile => ({
|
submenu: profiles.map(profile => ({
|
||||||
@@ -117,7 +116,7 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tab instanceof TerminalTabComponent && tabHeader && this.uac.isAvailable) {
|
if (tab instanceof TerminalTabComponent && tabHeader && this.uac?.isAvailable) {
|
||||||
const terminalTab = tab
|
const terminalTab = tab
|
||||||
items.push({
|
items.push({
|
||||||
label: this.translate.instant('Duplicate as administrator'),
|
label: this.translate.instant('Duplicate as administrator'),
|
||||||
|
@@ -7,43 +7,12 @@ ansi-colors@^4.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
|
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
|
||||||
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
|
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
|
||||||
|
|
||||||
async@~1.5:
|
|
||||||
version "1.5.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
|
|
||||||
integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=
|
|
||||||
|
|
||||||
connected-domain@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/connected-domain/-/connected-domain-1.0.0.tgz#bfe77238c74be453a79f0cb6058deeb4f2358e93"
|
|
||||||
integrity sha1-v+dyOMdL5FOnnwy2BY3utPI1jpM=
|
|
||||||
|
|
||||||
dataurl@0.1.0:
|
dataurl@0.1.0:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/dataurl/-/dataurl-0.1.0.tgz#1f4734feddec05ffe445747978d86759c4b33199"
|
resolved "https://registry.yarnpkg.com/dataurl/-/dataurl-0.1.0.tgz#1f4734feddec05ffe445747978d86759c4b33199"
|
||||||
integrity sha1-H0c0/t3sBf/kRXR5eNhnWcSzMZk=
|
integrity sha1-H0c0/t3sBf/kRXR5eNhnWcSzMZk=
|
||||||
|
|
||||||
hasbin@^1.2.3:
|
|
||||||
version "1.2.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/hasbin/-/hasbin-1.2.3.tgz#78c5926893c80215c2b568ae1fd3fcab7a2696b0"
|
|
||||||
integrity sha1-eMWSaJPIAhXCtWiuH9P8q3omlrA=
|
|
||||||
dependencies:
|
|
||||||
async "~1.5"
|
|
||||||
|
|
||||||
ps-node@^0.1.6:
|
|
||||||
version "0.1.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/ps-node/-/ps-node-0.1.6.tgz#9af67a99d7b1d0132e51a503099d38a8d2ace2c3"
|
|
||||||
integrity sha1-mvZ6mdex0BMuUaUDCZ04qNKs4sM=
|
|
||||||
dependencies:
|
|
||||||
table-parser "^0.1.3"
|
|
||||||
|
|
||||||
runes@^0.4.2:
|
runes@^0.4.2:
|
||||||
version "0.4.3"
|
version "0.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/runes/-/runes-0.4.3.tgz#32f7738844bc767b65cc68171528e3373c7bb355"
|
resolved "https://registry.yarnpkg.com/runes/-/runes-0.4.3.tgz#32f7738844bc767b65cc68171528e3373c7bb355"
|
||||||
integrity sha512-K6p9y4ZyL9wPzA+PMDloNQPfoDGTiFYDvdlXznyGKgD10BJpcAosvATKrExRKOrNLgD8E7Um7WGW0lxsnOuNLg==
|
integrity sha512-K6p9y4ZyL9wPzA+PMDloNQPfoDGTiFYDvdlXznyGKgD10BJpcAosvATKrExRKOrNLgD8E7Um7WGW0lxsnOuNLg==
|
||||||
|
|
||||||
table-parser@^0.1.3:
|
|
||||||
version "0.1.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/table-parser/-/table-parser-0.1.3.tgz#0441cfce16a59481684c27d1b5a67ff15a43c7b0"
|
|
||||||
integrity sha1-BEHPzhallIFoTCfRtaZ/8VpDx7A=
|
|
||||||
dependencies:
|
|
||||||
connected-domain "^1.0.0"
|
|
||||||
|
@@ -46,6 +46,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-close svg {
|
.btn-link svg {
|
||||||
width: 12px;
|
width: 12px;
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,6 @@
|
|||||||
"hexer": "^1.5.0",
|
"hexer": "^1.5.0",
|
||||||
"ngx-colors": "^3.4.0",
|
"ngx-colors": "^3.4.0",
|
||||||
"patch-package": "^6.5.0",
|
"patch-package": "^6.5.0",
|
||||||
"ps-node": "^0.1.6",
|
|
||||||
"runes": "^0.4.2",
|
"runes": "^0.4.2",
|
||||||
"xterm": "^5.1.0",
|
"xterm": "^5.1.0",
|
||||||
"xterm-addon-canvas": "^0.3.0",
|
"xterm-addon-canvas": "^0.3.0",
|
||||||
|
@@ -28,24 +28,27 @@ ng-container(*ngIf='state.resultCount > 0')
|
|||||||
|
|
||||||
.me-2
|
.me-2
|
||||||
|
|
||||||
button.btn.btn-link(
|
button.btn(
|
||||||
(click)='options.caseSensitive = !options.caseSensitive; saveSearchOptions()',
|
(click)='options.caseSensitive = !options.caseSensitive; saveSearchOptions()',
|
||||||
|
[class.btn-link]='!options.caseSensitive',
|
||||||
[class.btn-info]='options.caseSensitive',
|
[class.btn-info]='options.caseSensitive',
|
||||||
ngbTooltip='Case sensitivity',
|
ngbTooltip='Case sensitivity',
|
||||||
placement='bottom',
|
placement='bottom',
|
||||||
[fastHtmlBind]='icons.case'
|
[fastHtmlBind]='icons.case'
|
||||||
)
|
)
|
||||||
|
|
||||||
button.btn.btn-link(
|
button.btn(
|
||||||
(click)='options.regex = !options.regex; saveSearchOptions()',
|
(click)='options.regex = !options.regex; saveSearchOptions()',
|
||||||
|
[class.btn-link]='!options.regex',
|
||||||
[class.btn-info]='options.regex',
|
[class.btn-info]='options.regex',
|
||||||
ngbTooltip='Regular expression',
|
ngbTooltip='Regular expression',
|
||||||
placement='bottom',
|
placement='bottom',
|
||||||
[fastHtmlBind]='icons.regexp'
|
[fastHtmlBind]='icons.regexp'
|
||||||
)
|
)
|
||||||
|
|
||||||
button.btn.btn-link(
|
button.btn(
|
||||||
(click)='options.wholeWord = !options.wholeWord; saveSearchOptions()',
|
(click)='options.wholeWord = !options.wholeWord; saveSearchOptions()',
|
||||||
|
[class.btn-link]='!options.wholeWord',
|
||||||
[class.btn-info]='options.wholeWord',
|
[class.btn-info]='options.wholeWord',
|
||||||
ngbTooltip='Whole word',
|
ngbTooltip='Whole word',
|
||||||
placement='bottom',
|
placement='bottom',
|
||||||
|
@@ -21,3 +21,7 @@
|
|||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
opacity: .5;
|
opacity: .5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::ng-deep svg {
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
padding: 5px 15px 5px 15px;
|
padding: 5px 15px 5px 15px;
|
||||||
display: flex;
|
display: flex;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
::ng-deep .btn {
|
::ng-deep .btn {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@@ -29,7 +29,7 @@ export class TerminalToolbarComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get shouldShowDragHandle (): boolean {
|
get shouldShowDragHandle (): boolean {
|
||||||
return this.tab.parent instanceof SplitTabComponent && this.tab.parent.getAllTabs().length > 1
|
return this.tab.topmostParent instanceof SplitTabComponent && this.tab.topmostParent.getAllTabs().length > 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('mouseenter') onMouseEnter () {
|
@HostListener('mouseenter') onMouseEnter () {
|
||||||
|
@@ -13,9 +13,6 @@ import { ImageAddon } from 'xterm-addon-image'
|
|||||||
import { CanvasAddon } from 'xterm-addon-canvas'
|
import { CanvasAddon } from 'xterm-addon-canvas'
|
||||||
import './xterm.css'
|
import './xterm.css'
|
||||||
import deepEqual from 'deep-equal'
|
import deepEqual from 'deep-equal'
|
||||||
import { Attributes } from 'xterm/src/common/buffer/Constants'
|
|
||||||
import { AttributeData } from 'xterm/src/common/buffer/AttributeData'
|
|
||||||
import { CellData } from 'xterm/src/common/buffer/CellData'
|
|
||||||
import { BaseTerminalProfile, TerminalColorScheme } from '../api/interfaces'
|
import { BaseTerminalProfile, TerminalColorScheme } from '../api/interfaces'
|
||||||
|
|
||||||
const COLOR_NAMES = [
|
const COLOR_NAMES = [
|
||||||
@@ -492,60 +489,7 @@ export class XTermFrontend extends Frontend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getSelectionAsHTML (): string {
|
private getSelectionAsHTML (): string {
|
||||||
let html = `<div style="font-family: '${this.configService.store.terminal.font}', monospace; white-space: pre">`
|
return this.serializeAddon.serializeAsHTML({ includeGlobalBackground: true, onlySelection: true })
|
||||||
const selection = this.xterm.getSelectionPosition()
|
|
||||||
if (!selection) {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
if (selection.start.y === selection.end.y) {
|
|
||||||
html += this.getLineAsHTML(selection.start.y, selection.start.x, selection.end.x)
|
|
||||||
} else {
|
|
||||||
html += this.getLineAsHTML(selection.start.y, selection.start.x, this.xterm.cols)
|
|
||||||
for (let y = selection.start.y + 1; y < selection.end.y; y++) {
|
|
||||||
html += this.getLineAsHTML(y, 0, this.xterm.cols)
|
|
||||||
}
|
|
||||||
html += this.getLineAsHTML(selection.end.y, 0, selection.end.x)
|
|
||||||
}
|
|
||||||
html += '</div>'
|
|
||||||
return html
|
|
||||||
}
|
|
||||||
|
|
||||||
private getHexColor (mode: number, color: number, def: string): string {
|
|
||||||
if (mode === Attributes.CM_RGB) {
|
|
||||||
const rgb = AttributeData.toColorRGB(color)
|
|
||||||
return rgb.map(x => x.toString(16).padStart(2, '0')).join('')
|
|
||||||
}
|
|
||||||
if (mode === Attributes.CM_P16 || mode === Attributes.CM_P256) {
|
|
||||||
return this.configService.store.terminal.colorScheme.colors[color]
|
|
||||||
}
|
|
||||||
return def
|
|
||||||
}
|
|
||||||
|
|
||||||
private getLineAsHTML (y: number, start: number, end: number): string {
|
|
||||||
let html = '<div>'
|
|
||||||
let lastStyle: string|null = null
|
|
||||||
const outerLine = this.xterm.buffer.active.getLine(y)
|
|
||||||
if (!outerLine) {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
const line = outerLine['_line']
|
|
||||||
const cell = new CellData()
|
|
||||||
for (let i = start; i < end; i++) {
|
|
||||||
line.loadCell(i, cell)
|
|
||||||
const fg = this.getHexColor(cell.getFgColorMode(), cell.getFgColor(), this.configService.store.terminal.colorScheme.foreground)
|
|
||||||
const bg = this.getHexColor(cell.getBgColorMode(), cell.getBgColor(), this.configService.store.terminal.colorScheme.background)
|
|
||||||
const style = `color: ${fg}; background: ${bg}; font-weight: ${cell.isBold() ? 'bold' : 'normal'}; font-style: ${cell.isItalic() ? 'italic' : 'normal'}; text-decoration: ${cell.isUnderline() ? 'underline' : 'none'}`
|
|
||||||
if (style !== lastStyle) {
|
|
||||||
if (lastStyle !== null) {
|
|
||||||
html += '</span>'
|
|
||||||
}
|
|
||||||
html += `<span style="${style}">`
|
|
||||||
lastStyle = style
|
|
||||||
}
|
|
||||||
html += line.getString(i) || ' '
|
|
||||||
}
|
|
||||||
html += '</span></div>'
|
|
||||||
return html
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,7 +10,6 @@ import { SessionMiddlewareStack } from './api/middleware'
|
|||||||
*/
|
*/
|
||||||
export abstract class BaseSession {
|
export abstract class BaseSession {
|
||||||
open: boolean
|
open: boolean
|
||||||
truePID?: number
|
|
||||||
readonly oscProcessor = new OSCProcessor()
|
readonly oscProcessor = new OSCProcessor()
|
||||||
readonly middleware = new SessionMiddlewareStack()
|
readonly middleware = new SessionMiddlewareStack()
|
||||||
protected output = new Subject<string>()
|
protected output = new Subject<string>()
|
||||||
@@ -85,7 +84,7 @@ export abstract class BaseSession {
|
|||||||
this.binaryOutput.complete()
|
this.binaryOutput.complete()
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract start (options: unknown): void
|
abstract start (options: unknown): Promise<void>
|
||||||
abstract resize (columns: number, rows: number): void
|
abstract resize (columns: number, rows: number): void
|
||||||
abstract write (data: Buffer): void
|
abstract write (data: Buffer): void
|
||||||
abstract kill (signal?: string): void
|
abstract kill (signal?: string): void
|
||||||
|
@@ -18,6 +18,5 @@ export default () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
cfg.resolve.modules.push('node_modules/xterm/src')
|
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
@@ -89,11 +89,6 @@ concat-map@0.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||||
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
||||||
|
|
||||||
connected-domain@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/connected-domain/-/connected-domain-1.0.0.tgz#bfe77238c74be453a79f0cb6058deeb4f2358e93"
|
|
||||||
integrity sha1-v+dyOMdL5FOnnwy2BY3utPI1jpM=
|
|
||||||
|
|
||||||
crc-32@^1.1.1:
|
crc-32@^1.1.1:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208"
|
resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.0.tgz#cb2db6e29b88508e32d9dd0ec1693e7b41a18208"
|
||||||
@@ -382,13 +377,6 @@ promise-stream-reader@^1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/promise-stream-reader/-/promise-stream-reader-1.0.1.tgz#4e793a79c9d49a73ccd947c6da9c127f12923649"
|
resolved "https://registry.yarnpkg.com/promise-stream-reader/-/promise-stream-reader-1.0.1.tgz#4e793a79c9d49a73ccd947c6da9c127f12923649"
|
||||||
integrity sha512-Tnxit5trUjBAqqZCGWwjyxhmgMN4hGrtpW3Oc/tRI4bpm/O2+ej72BB08l6JBnGQgVDGCLvHFGjGgQS6vzhwXg==
|
integrity sha512-Tnxit5trUjBAqqZCGWwjyxhmgMN4hGrtpW3Oc/tRI4bpm/O2+ej72BB08l6JBnGQgVDGCLvHFGjGgQS6vzhwXg==
|
||||||
|
|
||||||
ps-node@^0.1.6:
|
|
||||||
version "0.1.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/ps-node/-/ps-node-0.1.6.tgz#9af67a99d7b1d0132e51a503099d38a8d2ace2c3"
|
|
||||||
integrity sha1-mvZ6mdex0BMuUaUDCZ04qNKs4sM=
|
|
||||||
dependencies:
|
|
||||||
table-parser "^0.1.3"
|
|
||||||
|
|
||||||
rimraf@^2.6.3:
|
rimraf@^2.6.3:
|
||||||
version "2.7.1"
|
version "2.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
|
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
|
||||||
@@ -430,13 +418,6 @@ supports-color@^7.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
has-flag "^4.0.0"
|
has-flag "^4.0.0"
|
||||||
|
|
||||||
table-parser@^0.1.3:
|
|
||||||
version "0.1.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/table-parser/-/table-parser-0.1.3.tgz#0441cfce16a59481684c27d1b5a67ff15a43c7b0"
|
|
||||||
integrity sha1-BEHPzhallIFoTCfRtaZ/8VpDx7A=
|
|
||||||
dependencies:
|
|
||||||
connected-domain "^1.0.0"
|
|
||||||
|
|
||||||
tiny-inflate@^1.0.2:
|
tiny-inflate@^1.0.2:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4"
|
resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4"
|
||||||
|
@@ -138,6 +138,10 @@ export class WebPlatformService extends PlatformService {
|
|||||||
setErrorHandler (handler: (_: any) => void): void {
|
setErrorHandler (handler: (_: any) => void): void {
|
||||||
window.addEventListener('error', handler)
|
window.addEventListener('error', handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async pickDirectory (): Promise<string> {
|
||||||
|
throw new Error('Unsupported')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HTMLFileDownload extends FileDownload {
|
class HTMLFileDownload extends FileDownload {
|
||||||
|
@@ -33,9 +33,6 @@
|
|||||||
"../../app/node_modules/*",
|
"../../app/node_modules/*",
|
||||||
"./app/node_modules/*"
|
"./app/node_modules/*"
|
||||||
],
|
],
|
||||||
"common*": [
|
|
||||||
"../../tabby-terminal/node_modules/xterm/src/common*"
|
|
||||||
],
|
|
||||||
"tabby-*": ["../../tabby-*/src"],
|
"tabby-*": ["../../tabby-*/src"],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|