Compare commits

..

23 Commits

Author SHA1 Message Date
Eugene Pankov
329d0448d3 reconfigure terminals on DPI change (fixes #576) 2019-02-10 00:23:49 +01:00
Eugene Pankov
100436f511 set xterm as default frontend (fixes #542) 2019-02-09 22:40:31 +01:00
Eugene Pankov
22d3e35723 ignore events on destroyed windows 2019-02-09 22:38:45 +01:00
Eugene Pankov
9cdcc8d8e5 fixed #649 2019-02-09 22:10:42 +01:00
Eugene Pankov
168e6f17dc allow selecting ssh ciphers (fixes #645) 2019-02-09 18:52:09 +01:00
Eugene Pankov
a2c636fdbf console logging 2019-02-09 17:44:23 +01:00
Eugene Pankov
413ca70729 Warn when enabling ConPTY on older insider builds (fixes #609, fixes #594) 2019-02-09 17:44:17 +01:00
Eugene Pankov
6f99e6c14b Merge branch 'master' of github.com:Eugeny/terminus 2019-01-30 19:46:25 +01:00
Eugene Pankov
e65811786d bumped node-ssh2 (fixes #605) 2019-01-30 18:11:49 +01:00
Eugene
aecd381b25 Merge pull request #633 from sylveon/master
Update windows-swca dependency
2019-01-30 16:48:11 +01:00
Eugene Pankov
89465f57d5 bumped node-pty 2019-01-30 13:02:35 +01:00
Charles Milette
3bf0ac43ef Update yarn lockfile 2019-01-29 15:37:40 -05:00
Charles Milette
a66dd43e1e Update windows-swca dependency 2019-01-29 15:33:50 -05:00
Eugene Pankov
dd4566cf02 #618 fixes 2019-01-27 23:58:55 +01:00
Eugene Pankov
f2be34d137 limit max font size (fixes #618) 2019-01-27 23:40:33 +01:00
Eugene Pankov
e28c619bdc force en-us locale (#618) 2019-01-27 23:39:05 +01:00
Eugene Pankov
04bf5dbcfb fixed offset with tabs on bottom on macos (fixes #629) 2019-01-27 23:12:46 +01:00
Eugene Pankov
a2128ca1f2 use ssh connection name for the tab's title (fixes #621) 2019-01-27 22:56:50 +01:00
Eugene Pankov
bf0d02d1fc tab duplication (fixes #588) 2019-01-27 22:45:08 +01:00
Eugene Pankov
792de65696 properly recover tabs with xterm 2019-01-27 22:01:55 +01:00
Eugene Pankov
fab21f6859 mention save-output plugin 2019-01-27 22:01:46 +01:00
Eugene
b0b01b98be Update README.md 2019-01-21 15:52:55 +01:00
Eugene
24dff4b5b7 Update README.md 2019-01-18 13:56:08 +00:00
44 changed files with 298 additions and 491 deletions

View File

@@ -6,7 +6,7 @@
</p>
<p align="center">
<a href="https://github.com/Eugeny/terminus/releases/latest">Downloads</a> | <a href="https://t.me/joinchat/AAAAAEZuCv2WKKYcfyQ3QA">Community</a> | <a href="https://ci.appveyor.com/project/Eugeny/terminus/build/artifacts">Latest Windows nightly</a>
<a href="https://github.com/Eugeny/terminus/releases/latest">Downloads</a> | <a href="https://t.me/joinchat/HgLqPhRg9Inhmm7WD3H1BQ">Community</a> | <a href="https://ci.appveyor.com/project/Eugeny/terminus/build/artifacts">Latest Windows nightly</a>
</p>
----
@@ -19,8 +19,9 @@
* Full Unicode support including double-width characters
* Doesn't choke on fast-flowing outputs
* Proper shell-like experience on Windows including tab completion (via Clink)
* PowerShell Core, WSL (Bash on Windows), PowerShell, Git-Bash, Cygwin, Cmder and CMD support
* Tab persistence on macOS and Linux
* PowerShell (+Core), WSL (Bash on Windows), Git-Bash, Cygwin, Cmder and CMD support
* Remembers your tabs
* Integrated SSH client and connection manager
[![Buy me a coffee](https://github.com/Eugeny/terminus/raw/master/docs/kofi.png)](https://ko-fi.com/eugeny)
@@ -38,6 +39,7 @@ Plugins can be installed directly from the Settings view inside Terminus.
* [title-control](https://github.com/kbjr/terminus-title-control) - allows modifying the title of the terminal tabs by providing a prefix, suffix, and/or strings to be removed
* [scrollbar](https://github.com/kbjr/terminus-scrollbar) - adds a scrollbar to terminal tabs
* [quick-cmds](https://github.com/Domain/terminus-quick-cmds) - quickly send commands to one or all terminal tabs
* [save-output](https://github.com/Eugeny/terminus-save-output) - record terminal output into a file
---

View File

@@ -1,4 +1,5 @@
import { app, ipcMain, Menu, Tray, shell } from 'electron'
import * as electron from 'electron'
import { loadConfig } from './config'
import { Window, WindowOptions } from './window'
@@ -18,6 +19,11 @@ export class Application {
}
app.commandLine.appendSwitch('disable-http-cache')
app.commandLine.appendSwitch('lang', 'EN')
}
init () {
electron.screen.on('display-metrics-changed', () => this.broadcast('host:display-metrics-changed'))
}
async newWindow (options?: WindowOptions): Promise<Window> {

View File

@@ -12,7 +12,6 @@ if (!process.env.TERMINUS_PLUGINS) {
const application = new Application()
ipcMain.on('app:new-window', () => {
console.log('new-window')
application.newWindow()
})
@@ -59,5 +58,6 @@ app.on('ready', () => {
}
]))
}
application.init()
application.newWindow({ hidden: argv.hidden })
})

View File

@@ -11,7 +11,7 @@ let AccentState: any
let DwmEnableBlurBehindWindow: any
if (process.platform === 'win32') {
SetWindowCompositionAttribute = require('windows-swca').SetWindowCompositionAttribute
AccentState = require('windows-swca').AccentState
AccentState = require('windows-swca').ACCENT_STATE
DwmEnableBlurBehindWindow = require('windows-blurbehind').DwmEnableBlurBehindWindow
}
@@ -103,16 +103,14 @@ export class Window {
if (process.platform === 'win32') {
if (parseFloat(os.release()) >= 10) {
let attribValue = AccentState.ACCENT_DISABLED
let color = 0x00000000
if (enabled) {
if (parseInt(os.release().split('.')[2]) >= 17063 && type === 'fluent') {
attribValue = AccentState.ACCENT_ENABLE_FLUENT
color = 0x01000000 // using a small alpha because acrylic bugs out at full transparency.
attribValue = AccentState.ACCENT_ENABLE_ACRYLICBLURBEHIND
} else {
attribValue = AccentState.ACCENT_ENABLE_BLURBEHIND
}
}
SetWindowCompositionAttribute(this.window, attribValue, color)
SetWindowCompositionAttribute(this.window.getNativeWindowHandle(), attribValue, 0x00000000)
} else {
DwmEnableBlurBehindWindow(this.window, enabled)
}
@@ -184,28 +182,28 @@ export class Window {
})
ipcMain.on('window-focus', event => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.focus()
})
ipcMain.on('window-maximize', event => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.maximize()
})
ipcMain.on('window-unmaximize', event => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.unmaximize()
})
ipcMain.on('window-toggle-maximize', event => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
if (this.window.isMaximized()) {
@@ -216,42 +214,42 @@ export class Window {
})
ipcMain.on('window-minimize', event => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.minimize()
})
ipcMain.on('window-set-bounds', (event, bounds) => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.setBounds(bounds)
})
ipcMain.on('window-set-always-on-top', (event, flag) => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.setAlwaysOnTop(flag)
})
ipcMain.on('window-set-vibrancy', (event, enabled, type) => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.setVibrancy(enabled, type)
})
ipcMain.on('window-set-title', (event, title) => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.window.setTitle(title)
})
ipcMain.on('window-bring-to-front', event => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
if (this.window.isMinimized()) {
@@ -262,7 +260,7 @@ export class Window {
})
ipcMain.on('window-close', event => {
if (event.sender !== this.window.webContents) {
if (!this.window || event.sender !== this.window.webContents) {
return
}
this.closing = true

View File

@@ -36,7 +36,7 @@
},
"optionalDependencies": {
"windows-blurbehind": "^1.0.0",
"windows-swca": "^1.1.1"
"windows-swca": "^2.0.1"
},
"devDependencies": {
"@types/mz": "0.0.31"

View File

@@ -16,11 +16,11 @@ function normalizePath (path: string): string {
nodeRequire.main.paths.map(x => nodeModule.globalPaths.push(normalizePath(x)))
if (process.env.DEV) {
if (process.env.TERMINUS_DEV) {
nodeModule.globalPaths.unshift(path.dirname(require('electron').remote.app.getAppPath()))
}
const builtinPluginsPath = process.env.DEV ? path.dirname(require('electron').remote.app.getAppPath()) : path.join((process as any).resourcesPath, 'builtin-plugins')
const builtinPluginsPath = process.env.TERMINUS_DEV ? path.dirname(require('electron').remote.app.getAppPath()) : path.join((process as any).resourcesPath, 'builtin-plugins')
const userPluginsPath = path.join(
require('electron').remote.app.getPath('appData'),

View File

@@ -9,7 +9,6 @@
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedParameters": true,

View File

@@ -9,7 +9,7 @@ module.exports = {
preload: path.resolve(__dirname, 'src/entry.preload.ts'),
bundle: path.resolve(__dirname, 'src/entry.ts'),
},
mode: process.env.DEV ? 'development' : 'production',
mode: process.env.TERMINUS_DEV ? 'development' : 'production',
optimization:{
minimize: false,
},

View File

@@ -7,7 +7,7 @@ module.exports = {
entry: {
main: path.resolve(__dirname, 'lib/index.ts'),
},
mode: process.env.DEV ? 'development' : 'production',
mode: process.env.TERMINUS_DEV ? 'development' : 'production',
context: __dirname,
devtool: 'source-map',
output: {

View File

@@ -70,6 +70,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.13.tgz#530f0f9254209b0335bf5cc6387822594ef47093"
integrity sha512-Y3EAG7VA7NVNbZek/fjJtILnmTk/ZfpJuWZGDBqDZ1dVIxgJJJ82fXPW7pKnqyV9CD/9bcPOCi7eErUqGMHOrA==
"@types/node@^10.12.18":
version "10.12.18"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67"
integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==
accessibility-developer-tools@^2.11.0:
version "2.12.0"
resolved "https://registry.yarnpkg.com/accessibility-developer-tools/-/accessibility-developer-tools-2.12.0.tgz#3da0cce9d6ec6373964b84f35db7cfc3df7ab514"
@@ -620,10 +625,12 @@ windows-blurbehind@^1.0.0:
resolved "https://registry.yarnpkg.com/windows-blurbehind/-/windows-blurbehind-1.0.0.tgz#050efb988704c44335bdc3efefd757f6e463b8ac"
integrity sha512-lO+A7fhTHO7oy9zJM3o1AdzfSQrmtPkdwvleeuww840ghijjEA1f1Zp8bKA3mJu2DFNtVT40fwmqtgsCGat4UA==
windows-swca@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/windows-swca/-/windows-swca-1.1.1.tgz#0b3530278c67d408baac71c3a6aeb16d55318bf8"
integrity sha512-hKmHrNYJD72Kg0u35fjkiFIuMKuC+Tztmf3Obnf4aTkNjstEpbSEspEeSo3ZNixaVCETA1dLbDkVUQVF1QxtWA==
windows-swca@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/windows-swca/-/windows-swca-2.0.1.tgz#25d78ce25251292061494a0ad07c02282b28b4e3"
integrity sha512-flj+HD6RUemZUvKKguLLnMUOYkQSgDu9qrhUIO4cydvtb/x+sxU8XmpZUtugYuydcdikB9zsCOMgKnAqIQ+7nw==
dependencies:
"@types/node" "^10.12.18"
wrap-ansi@^2.0.0:
version "2.1.0"

View File

@@ -127,9 +127,9 @@
},
"scripts": {
"build": "webpack --color --config app/webpack.main.config.js && webpack --color --config app/webpack.config.js && webpack --color --config terminus-core/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-terminal/webpack.config.js && webpack --color --config terminus-settings/webpack.config.js && webpack --color --config terminus-plugin-manager/webpack.config.js && webpack --color --config terminus-community-color-schemes/webpack.config.js && webpack --color --config terminus-ssh/webpack.config.js",
"watch": "cross-env DEV=1 webpack --progress --color --watch",
"start": "cross-env DEV=1 electron app --debug",
"prod": "cross-env DEV=1 electron app",
"watch": "cross-env TERMINUS_DEV=1 webpack --progress --color --watch",
"start": "cross-env TERMINUS_DEV=1 electron app --debug",
"prod": "cross-env TERMINUS_DEV=1 electron app",
"lint": "tslint -c tslint.json -t stylish terminus-*/src/**/*.ts terminus-*/src/*.ts app/src/*.ts",
"postinstall": "node ./scripts/install-deps.js"
},

View File

@@ -13,7 +13,7 @@ module.exports = {
libraryTarget: 'umd',
devtoolModuleFilenameTemplate: 'webpack-terminus-community-color-schemes:///[resource-path]',
},
mode: process.env.DEV ? 'development' : 'production',
mode: process.env.TERMINUS_DEV ? 'development' : 'production',
optimization:{
minimize: false,
},

View File

@@ -9,7 +9,6 @@ import { HotkeysService } from '../services/hotkeys.service'
import { Logger, LogService } from '../services/log.service'
import { ConfigService } from '../services/config.service'
import { DockingService } from '../services/docking.service'
import { TabRecoveryService } from '../services/tabRecovery.service'
import { ThemesService } from '../services/themes.service'
import { UpdaterService } from '../services/updater.service'
import { TouchbarService } from '../services/touchbar.service'
@@ -69,7 +68,6 @@ export class AppRootComponent {
constructor (
private docking: DockingService,
private electron: ElectronService,
private tabRecovery: TabRecoveryService,
private hotkeys: HotkeysService,
private updater: UpdaterService,
private touchbar: TouchbarService,
@@ -199,9 +197,7 @@ export class AppRootComponent {
}
async ngOnInit () {
await this.tabRecovery.recoverTabs()
this.ready = true
this.tabRecovery.saveTabs(this.app.tabs)
this.app.emitReady()
}

View File

@@ -20,6 +20,10 @@
flex-direction: row;
align-items: center;
.off {
color: rgba(0, 0, 0, .5);
}
.icon {
position: relative;
flex: none;

View File

@@ -5,6 +5,7 @@ import { BaseTabComponent } from '../components/baseTab.component'
import { Logger, LogService } from './log.service'
import { ConfigService } from './config.service'
import { HostAppService } from './hostApp.service'
import { TabRecoveryService } from './tabRecovery.service'
export declare type TabComponentType = new (...args: any[]) => BaseTabComponent
@@ -61,11 +62,25 @@ export class AppService {
private config: ConfigService,
private hostApp: HostAppService,
private injector: Injector,
private tabRecovery: TabRecoveryService,
log: LogService,
) {
this.logger = log.create('app')
this.hostApp.windowCloseRequest$.subscribe(() => this.closeWindow())
this.tabRecovery.recoverTabs().then(tabs => {
for (let tab of tabs) {
this.openNewTab(tab.type, tab.options)
}
this.tabsChanged$.subscribe(() => {
tabRecovery.saveTabs(this.tabs)
})
setInterval(() => {
tabRecovery.saveTabs(this.tabs)
}, 30000)
})
}
openNewTab (type: TabComponentType, inputs?: any): BaseTabComponent {
@@ -105,7 +120,9 @@ export class AppService {
this.activeTab = tab
this.activeTabChange.next(tab)
if (this.activeTab) {
this.activeTab.emitFocused()
setImmediate(() => {
this.activeTab.emitFocused()
})
this.hostApp.setTitle(this.activeTab.title)
}
}
@@ -160,6 +177,17 @@ export class AppService {
this.tabClosed.next(tab)
}
async duplicateTab (tab: BaseTabComponent) {
let token = await tab.getRecoveryToken()
if (!token) {
return
}
let recoveredTab = await this.tabRecovery.recoverTab(token)
if (recoveredTab) {
this.openNewTab(recoveredTab.type, recoveredTab.options)
}
}
async closeWindow () {
for (let tab of this.tabs) {
if (!await tab.canClose()) {

View File

@@ -31,6 +31,7 @@ export class HostAppService {
private configChangeBroadcast = new Subject<void>()
private windowCloseRequest = new Subject<void>()
private windowMoved = new Subject<void>()
private displayMetricsChanged = new Subject<void>()
private logger: Logger
private windowId: number
@@ -43,6 +44,7 @@ export class HostAppService {
get configChangeBroadcast$ (): Observable<void> { return this.configChangeBroadcast }
get windowCloseRequest$ (): Observable<void> { return this.windowCloseRequest }
get windowMoved$ (): Observable<void> { return this.windowMoved }
get displayMetricsChanged$ (): Observable<void> { return this.displayMetricsChanged }
constructor (
private zone: NgZone,
@@ -86,6 +88,10 @@ export class HostAppService {
this.zone.run(() => this.windowMoved.next())
})
electron.ipcRenderer.on('host:display-metrics-changed', () => {
this.zone.run(() => this.displayMetricsChanged.next())
})
electron.ipcRenderer.on('host:second-instance', (_$event, argv: any, cwd: string) => this.zone.run(() => {
this.logger.info('Second instance', argv)
const op = argv._[0]

View File

@@ -23,7 +23,7 @@ const initializeWinston = (electron: ElectronService) => {
colorize: false
}),
new winston.transports.Console({
level: 'info',
level: 'debug',
handleExceptions: false,
json: false,
colorize: true

View File

@@ -2,7 +2,6 @@ import { Injectable, Inject } from '@angular/core'
import { TabRecoveryProvider, RecoveredTab } from '../api/tabRecovery'
import { BaseTabComponent } from '../components/baseTab.component'
import { Logger, LogService } from '../services/log.service'
import { AppService } from '../services/app.service'
import { ConfigService } from '../services/config.service'
@Injectable({ providedIn: 'root' })
@@ -11,17 +10,10 @@ export class TabRecoveryService {
constructor (
@Inject(TabRecoveryProvider) private tabRecoveryProviders: TabRecoveryProvider[],
private app: AppService,
private config: ConfigService,
log: LogService
) {
this.logger = log.create('tabRecovery')
app.tabsChanged$.subscribe(() => {
this.saveTabs(app.tabs)
})
setInterval(() => {
this.saveTabs(app.tabs)
}, 30000)
}
async saveTabs (tabs: BaseTabComponent[]) {
@@ -34,25 +26,32 @@ export class TabRecoveryService {
)
}
async recoverTabs (): Promise<void> {
async recoverTab (token: any): Promise<RecoveredTab> {
for (let provider of this.config.enabledServices(this.tabRecoveryProviders)) {
try {
let tab = await provider.recover(token)
if (tab) {
return tab
}
} catch (error) {
this.logger.warn('Tab recovery crashed:', token, provider, error)
}
}
return null
}
async recoverTabs (): Promise<RecoveredTab[]> {
if (window.localStorage.tabsRecovery) {
let tabs: RecoveredTab[] = []
for (let token of JSON.parse(window.localStorage.tabsRecovery)) {
for (let provider of this.config.enabledServices(this.tabRecoveryProviders)) {
try {
let tab = await provider.recover(token)
if (tab) {
tabs.push(tab)
}
} catch (error) {
this.logger.warn('Tab recovery crashed:', token, provider, error)
}
let tab = await this.recoverTab(token)
if (tab) {
tabs.push(tab)
}
}
tabs.forEach(tab => {
this.app.openNewTab(tab.type, tab.options)
})
return tabs
}
return []
}
}

View File

@@ -67,6 +67,7 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
constructor (
private zone: NgZone,
private app: AppService,
) {
super()
}
@@ -77,6 +78,10 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
label: 'Rename',
click: () => this.zone.run(() => tabHeader.showRenameTabModal())
},
{
label: 'Duplicate',
click: () => this.zone.run(() => this.app.duplicateTab(tab))
},
{
label: 'Color',
sublabel: COLORS.find(x => x.value === tab.color).name,

View File

@@ -14,7 +14,7 @@ module.exports = {
libraryTarget: 'umd',
devtoolModuleFilenameTemplate: 'webpack-terminus-core:///[resource-path]',
},
mode: process.env.DEV ? 'development' : 'production',
mode: process.env.TERMINUS_DEV ? 'development' : 'production',
optimization:{
minimize: false,
},

View File

@@ -65,7 +65,7 @@ div(*ngIf='npmInstalled')
.input-group.mb-3
.input-group-prepend
.input-group-text
i.fas.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='!availablePluginsReady')
i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='!availablePluginsReady')
i.fas.fa-fw.fa-search(*ngIf='availablePluginsReady')
input.form-control(
type='text',

View File

@@ -13,7 +13,7 @@ module.exports = {
libraryTarget: 'umd',
devtoolModuleFilenameTemplate: 'webpack-terminus-plugin-manager:///[resource-path]',
},
mode: process.env.DEV ? 'development' : 'production',
mode: process.env.TERMINUS_DEV ? 'development' : 'production',
optimization:{
minimize: false,
},

View File

@@ -14,7 +14,7 @@ module.exports = {
libraryTarget: 'umd',
devtoolModuleFilenameTemplate: 'webpack-terminus-settings:///[resource-path]',
},
mode: process.env.DEV ? 'development' : 'production',
mode: process.env.TERMINUS_DEV ? 'development' : 'production',
optimization:{
minimize: false,
},

View File

@@ -36,10 +36,11 @@
},
"optionalDependencies": {
"wincredmgr": "^2.0.0",
"xkeychain": "^0.0.6",
"windows-process-tree": "^0.2.3"
"windows-process-tree": "^0.2.3",
"xkeychain": "^0.0.6"
},
"dependencies": {
"ssh2": "^0.5.5"
"ssh2": "^0.8.2",
"ssh2-streams": "^0.4.2"
}
}

View File

@@ -7,6 +7,13 @@ export interface LoginScript {
optional?: boolean
}
export enum SSHAlgorithmType {
HMAC = 'hmac',
KEX = 'kex',
CIPHER = 'cipher',
HOSTKEY = 'serverHostKey'
}
export interface SSHConnection {
name?: string
host: string
@@ -19,6 +26,8 @@ export interface SSHConnection {
keepaliveInterval?: number
keepaliveCountMax?: number
readyTimeout?: number
algorithms?: {[t: string]: string[]}
}
export class SSHSession extends BaseSession {

View File

@@ -85,6 +85,27 @@
placeholder='20000',
[(ngModel)]='connection.readyTimeout',
)
.form-group
label Ciphers
div(*ngFor='let alg of supportedAlgorithms.cipher')
checkbox([text]='alg', [(ngModel)]='algorithms.cipher[alg]')
.form-group
label Key exchange
div(*ngFor='let alg of supportedAlgorithms.kex')
checkbox([text]='alg', [(ngModel)]='algorithms.kex[alg]')
.form-group
label HMAC
div(*ngFor='let alg of supportedAlgorithms.hmac')
checkbox([text]='alg', [(ngModel)]='algorithms.hmac[alg]')
.form-group
label Host key
div(*ngFor='let alg of supportedAlgorithms.serverHostKey')
checkbox([text]='alg', [(ngModel)]='algorithms.serverHostKey[alg]')
ngb-tab(id='scripts')
ng-template(ngbTabTitle)

View File

@@ -2,7 +2,8 @@ import { Component } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { ElectronService, HostAppService } from 'terminus-core'
import { PasswordStorageService } from '../services/passwordStorage.service'
import { SSHConnection, LoginScript } from '../api'
import { SSHConnection, LoginScript, SSHAlgorithmType } from '../api'
import { ALGORITHMS } from 'ssh2-streams/lib/constants'
@Component({
template: require('./editConnectionModal.component.pug'),
@@ -12,6 +13,10 @@ export class EditConnectionModalComponent {
newScript: LoginScript
hasSavedPassword: boolean
supportedAlgorithms: {[id: string]: string[]} = {}
defaultAlgorithms: {[id: string]: string[]} = {}
algorithms: {[id: string]: {[a: string]: boolean}} = {}
constructor (
private modalInstance: NgbActiveModal,
private electron: ElectronService,
@@ -19,10 +24,41 @@ export class EditConnectionModalComponent {
private passwordStorage: PasswordStorageService,
) {
this.newScript = { expect: '', send: '' }
for (let k of Object.values(SSHAlgorithmType)) {
this.supportedAlgorithms[k] = ALGORITHMS[
{
[SSHAlgorithmType.KEX]: 'SUPPORTED_KEX',
[SSHAlgorithmType.HOSTKEY]: 'SUPPORTED_SERVER_HOST_KEY',
[SSHAlgorithmType.CIPHER]: 'SUPPORTED_CIPHER',
[SSHAlgorithmType.HMAC]: 'SUPPORTED_HMAC',
}[k]
]
this.defaultAlgorithms[k] = ALGORITHMS[
{
[SSHAlgorithmType.KEX]: 'KEX',
[SSHAlgorithmType.HOSTKEY]: 'SERVER_HOST_KEY',
[SSHAlgorithmType.CIPHER]: 'CIPHER',
[SSHAlgorithmType.HMAC]: 'HMAC',
}[k]
]
}
console.log(this)
}
async ngOnInit () {
this.hasSavedPassword = !!(await this.passwordStorage.loadPassword(this.connection))
this.connection.algorithms = this.connection.algorithms || {}
for (let k of Object.values(SSHAlgorithmType)) {
if (!this.connection.algorithms[k]) {
this.connection.algorithms[k] = this.defaultAlgorithms[k]
}
this.algorithms[k] = {}
for (let alg of this.connection.algorithms[k]) {
this.algorithms[k][alg] = true
}
}
}
clearSavedPassword () {
@@ -43,6 +79,11 @@ export class EditConnectionModalComponent {
}
save () {
for (let k of Object.values(SSHAlgorithmType)) {
this.connection.algorithms[k] = Object.entries(this.algorithms[k])
.filter(([k, v]) => !!v)
.map(([k, v]) => k)
}
this.modalInstance.close(this.connection)
}

View File

@@ -26,6 +26,10 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
})
super.ngOnInit()
setImmediate(() => {
this.setTitle(this.connection.name)
})
}
async initializeSession () {

View File

@@ -164,6 +164,7 @@ export class SSHService {
return true
},
hostHash: 'sha256' as any,
algorithms: session.connection.algorithms,
})
} catch (e) {
this.toastr.error(e.message)

View File

@@ -12,7 +12,7 @@ module.exports = {
libraryTarget: 'umd',
devtoolModuleFilenameTemplate: 'webpack-terminus-ssh:///[resource-path]',
},
mode: process.env.DEV ? 'development' : 'production',
mode: process.env.TERMINUS_DEV ? 'development' : 'production',
optimization:{
minimize: false,
},
@@ -44,6 +44,7 @@ module.exports = {
externals: [
'fs',
'node-ssh',
'ssh2-streams',
'xkeychain',
'wincredmgr',
'path',

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
hotkeys: HotkeysService,
) {
super()
if (!electron.remote.process.env.DEV) {
if (!electron.remote.process.env.TERMINUS_DEV) {
setImmediate(async () => {
let argv: string[] = electron.remote.process.argv
for (let arg of argv.slice(1).concat([electron.remote.process.argv0])) {

View File

@@ -1,6 +1,6 @@
h3.mb-3 Appearance
.row
.col-md-6
.d-flex
.mr-5
.form-line
.header
.title Frontend
@@ -26,6 +26,7 @@ h3.mb-3 Appearance
)
input.form-control.w-25(
type='number',
max='48',
[(ngModel)]='config.store.terminal.fontSize',
(ngModelChange)='config.save()',
)
@@ -91,7 +92,7 @@ h3.mb-3 Appearance
[title]='idx',
)
.col-md-6
div
.form-group
.appearance-preview(
[style.font-family]='config.store.terminal.font',

View File

@@ -3,6 +3,9 @@
margin-left: 20px;
padding: 10px;
overflow: hidden;
max-width: 400px;
max-height: 400px;
span {
white-space: pre;
}

View File

@@ -24,6 +24,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
@Input() zoom = 0
@ViewChild('content') content
@HostBinding('style.background-color') backgroundColor: string
@HostBinding('class.top-padded') topPadded: boolean
frontend: Frontend
sessionCloseSubscription: Subscription
hotkeysSubscription: Subscription
@@ -145,7 +146,15 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
})
this.frontend.configure()
this.frontend.attach(this.content.nativeElement)
if (this.hasFocus) {
this.frontend.attach(this.content.nativeElement)
} else {
this.focused$.pipe(first()).subscribe(() => {
this.frontend.attach(this.content.nativeElement)
})
}
this.attachTermContainerHandlers()
this.configure()
@@ -244,7 +253,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
}),
this.frontend.resize$.subscribe(({ columns, rows }) => {
this.logger.info(`Resizing to ${columns}x${rows}`)
this.logger.debug(`Resizing to ${columns}x${rows}`)
this.size = { columns, rows }
this.zone.run(() => {
if (this.session && this.session.open) {
@@ -256,6 +265,10 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
this.hostApp.windowMoved$.subscribe(() => setTimeout(() => {
this.configure()
}, 250)),
this.hostApp.displayMetricsChanged$.subscribe(() => setTimeout(() => {
this.configure()
}, 250)),
]
}
@@ -296,6 +309,10 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
configure (): void {
this.frontend.configure()
this.topPadded = this.hostApp.platform === Platform.macOS
&& this.config.store.appearance.frame === 'thin'
&& this.config.store.appearance.tabsLocation === 'bottom'
if (this.config.store.terminal.background === 'colorScheme') {
if (this.config.store.terminal.colorScheme.background) {
this.backgroundColor = this.config.store.terminal.colorScheme.background

View File

@@ -25,6 +25,9 @@ h3.mb-3 Shell
(ngModelChange)='config.save()'
)
.alert.alert-info.d-flex.align-items-center(*ngIf='config.store.terminal.useConPTY && isConPTYAvailable && !isConPTYStable')
.mr-auto Windows 10 build 18309 or above is recommended for ConPTY
.alert.alert-info.d-flex.align-items-center(*ngIf='config.store.terminal.shell.startsWith("wsl") && (config.store.terminal.frontend != "hterm" || !config.store.terminal.useConPTY)')
.mr-auto WSL terminal only supports TrueColor with ConPTY and the hterm frontend

View File

@@ -1,3 +1,4 @@
import * as os from 'os'
import { Component } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { Subscription } from 'rxjs'
@@ -15,6 +16,7 @@ export class ShellSettingsTabComponent {
profiles: Profile[] = []
Platform = Platform
isConPTYAvailable: boolean
isConPTYStable: boolean
private configSubscription: Subscription
constructor (
@@ -31,6 +33,9 @@ export class ShellSettingsTabComponent {
})
this.reload()
this.isConPTYAvailable = uac.isAvailable
this.isConPTYStable = hostApp.platform === Platform.Windows
&& parseFloat(os.release()) >= 10
&& parseInt(os.release().split('.')[2]) >= 18309
}
async ngOnInit () {

View File

@@ -3,6 +3,10 @@
display: flex;
overflow: hidden;
&.top-padded {
padding-top: 20px;
}
&> .content {
flex: auto;
position: relative;

View File

@@ -8,7 +8,7 @@ export class TerminalConfigProvider extends ConfigProvider {
},
},
terminal: {
frontend: 'hterm',
frontend: 'xterm',
autoOpen: false,
fontSize: 14,
linePadding: 0,

View File

@@ -54,6 +54,14 @@ export class XTermFrontend extends Frontend {
this.xtermCore._scrollToBottom = this.xtermCore.scrollToBottom.bind(this.xtermCore)
this.xtermCore.scrollToBottom = () => null
this.resizeHandler = () => {
try {
(this.xterm as any).fit()
} catch {
// tends to throw when element wasn't shown yet
}
}
}
attach (host: HTMLElement): void {
@@ -61,7 +69,6 @@ export class XTermFrontend extends Frontend {
this.ready.next(null)
this.ready.complete()
this.resizeHandler = () => (this.xterm as any).fit()
window.addEventListener('resize', this.resizeHandler)
this.resizeHandler()

View File

@@ -118,7 +118,7 @@ export class Session extends BaseSession {
rows: options.height || 30,
cwd,
env: env,
experimentalUseConpty: this.config.store.terminal.useConPTY,
experimentalUseConpty: this.config.store.terminal.useConPTY && 1,
})
this.guessedCWD = cwd

View File

@@ -25,7 +25,7 @@ export class UACService {
'UAC.exe',
)
if (process.env.DEV) {
if (process.env.TERMINUS_DEV) {
helperPath = path.join(
path.dirname(this.electron.app.getPath('exe')),
'..', '..', '..',

View File

@@ -13,7 +13,7 @@ module.exports = {
libraryTarget: 'umd',
devtoolModuleFilenameTemplate: 'webpack-terminus-terminal:///[resource-path]',
},
mode: process.env.DEV ? 'development' : 'production',
mode: process.env.TERMINUS_DEV ? 'development' : 'production',
optimization: {
minimize: false,
},

View File

@@ -15,9 +15,9 @@
"@types/node" "*"
"@types/node@*":
version "10.12.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.0.tgz#ea6dcbddbc5b584c83f06c60e82736d8fbb0c235"
integrity sha512-3TUHC3jsBAB7qVRGxT6lWyYo2v96BMmD2PTcl47H25Lu7UXtFH/2qqmKiVrnel6Ne//0TFYf6uvNX+HW2FRkLQ==
version "10.12.19"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.19.tgz#ca1018c26be01f07e66ac7fefbeb6407e4490c61"
integrity sha512-2NVovndCjJQj6fUUn9jCgpP4WSqr+u1SoUZMZyJkhGeBFsm6dE46l31S7lPUYt9uQ28XI+ibrJA1f5XyH5HNtA==
"@types/node@7.0.12":
version "7.0.12"
@@ -34,10 +34,10 @@ any-promise@^1.0.0:
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
big.js@^3.1.3:
version "3.2.0"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
connected-domain@^1.0.0:
version "1.0.0"
@@ -100,24 +100,26 @@ hterm-umdjs@1.4.1:
resolved "https://registry.yarnpkg.com/hterm-umdjs/-/hterm-umdjs-1.4.1.tgz#0cd5352eaf927c70b83c36146cf2c2a281dba957"
integrity sha512-r5JOmdDK1bZCmp3cKcuGRLVeum33H+pzD119ZxmQou+QUVe6SAVSz03HvKWVhM2Ao1Biv+fkhFDmnsaRPq0tFg==
json5@^0.5.0:
version "0.5.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=
json5@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
dependencies:
minimist "^1.2.0"
loader-utils@^1.0.2:
version "1.1.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
integrity sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=
version "1.2.3"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
dependencies:
big.js "^3.1.3"
big.js "^5.2.2"
emojis-list "^2.0.0"
json5 "^0.5.0"
json5 "^1.0.1"
lru-cache@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c"
integrity sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==
version "4.1.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
dependencies:
pseudomap "^1.0.2"
yallist "^2.1.2"
@@ -129,6 +131,11 @@ macos-native-processlist@^1.0.0:
dependencies:
nan "^2.10.0"
minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
mz@^2.6.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
@@ -138,22 +145,17 @@ mz@^2.6.0:
object-assign "^4.0.1"
thenify-all "^1.0.0"
nan@2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
integrity sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==
nan@>=2.10.0, nan@^2.10.0:
version "2.11.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.1.tgz#90e22bccb8ca57ea4cd37cc83d3819b52eea6766"
integrity sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==
nan@2.12.1, nan@>=2.10.0, nan@^2.10.0:
version "2.12.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552"
integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==
node-pty@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.8.0.tgz#08bccb633f49e2e3f7245eb56ea6b40f37ccd64f"
integrity sha512-g5ggk3gN4gLrDmAllee5ScFyX3YzpOC/U8VJafha4pE7do0TIE1voiIxEbHSRUOPD1xYqmY+uHhOKAd3avbxGQ==
version "0.8.1"
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.8.1.tgz#94b457bec013e7a09b8d9141f63b0787fa25c23f"
integrity sha512-j+/g0Q5dR+vkELclpJpz32HcS3O/3EdPSGPvDXJZVJQLCvgG0toEbfmymxAEyQyZEpaoKHAcoL+PvKM+4N9nlw==
dependencies:
nan "2.10.0"
nan "2.12.1"
object-assign@^4.0.1:
version "4.1.1"