Compare commits
48 Commits
v1.0.0-alp
...
v1.0.0-alp
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c3c983daf6 | ||
![]() |
dce8647f55 | ||
![]() |
f947fe3f0f | ||
![]() |
b5f96a59f8 | ||
![]() |
c90a5678cf | ||
![]() |
663da34e6d | ||
![]() |
049f08b8f9 | ||
![]() |
3c3b14bf09 | ||
![]() |
5e07dd5442 | ||
![]() |
8f2d2cbe30 | ||
![]() |
bebde4799d | ||
![]() |
9cedeb3efb | ||
![]() |
63158ac6cd | ||
![]() |
4f44087989 | ||
![]() |
ab3c49b9b2 | ||
![]() |
28d01a1b56 | ||
![]() |
a979f0108e | ||
![]() |
3c74b8ec38 | ||
![]() |
9d7bf2ae44 | ||
![]() |
3b43b3914b | ||
![]() |
e9f22dd8b5 | ||
![]() |
e68cafdb70 | ||
![]() |
fde16b8699 | ||
![]() |
245c65d750 | ||
![]() |
c7d9f944d5 | ||
![]() |
4ca806e142 | ||
![]() |
0255985bc6 | ||
![]() |
104f1ee7aa | ||
![]() |
132d0553ae | ||
![]() |
b007ff6ff6 | ||
![]() |
2bea4b9d6c | ||
![]() |
4a76c12f15 | ||
![]() |
181f3e3d33 | ||
![]() |
ee2fadbf60 | ||
![]() |
4259d3b53d | ||
![]() |
65aaa131ef | ||
![]() |
46d9aabbdd | ||
![]() |
692045ce77 | ||
![]() |
9c257b0002 | ||
![]() |
15c23eb7dd | ||
![]() |
5fc67d3648 | ||
![]() |
571884f39c | ||
![]() |
ccbcd30813 | ||
![]() |
30666c2838 | ||
![]() |
953558a866 | ||
![]() |
ace81aced2 | ||
![]() |
dc781deeb0 | ||
![]() |
e3d1d5e61e |
@@ -14,11 +14,11 @@
|
|||||||
viewBox="0 0 150 150"
|
viewBox="0 0 150 150"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
id="svg8"
|
id="svg8"
|
||||||
inkscape:version="0.92.1 r15371"
|
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
|
||||||
sodipodi:docname="logo.svg"
|
sodipodi:docname="logo.svg"
|
||||||
inkscape:export-filename="/home/eugene/Work/term/build/icons/16x16.png"
|
inkscape:export-filename="/home/eugene/Work/term/build/icons/512x512.png"
|
||||||
inkscape:export-xdpi="2.7093334"
|
inkscape:export-xdpi="86.699997"
|
||||||
inkscape:export-ydpi="2.7093334">
|
inkscape:export-ydpi="86.699997">
|
||||||
<defs
|
<defs
|
||||||
id="defs2" />
|
id="defs2" />
|
||||||
<sodipodi:namedview
|
<sodipodi:namedview
|
||||||
@@ -29,8 +29,8 @@
|
|||||||
inkscape:pageopacity="0.0"
|
inkscape:pageopacity="0.0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:zoom="0.49497475"
|
inkscape:zoom="0.49497475"
|
||||||
inkscape:cx="134.39743"
|
inkscape:cx="85.897128"
|
||||||
inkscape:cy="340.43068"
|
inkscape:cy="375.72042"
|
||||||
inkscape:document-units="mm"
|
inkscape:document-units="mm"
|
||||||
inkscape:current-layer="layer1"
|
inkscape:current-layer="layer1"
|
||||||
showgrid="false"
|
showgrid="false"
|
||||||
@@ -43,7 +43,9 @@
|
|||||||
fit-margin-top="0"
|
fit-margin-top="0"
|
||||||
fit-margin-left="0"
|
fit-margin-left="0"
|
||||||
fit-margin-right="0"
|
fit-margin-right="0"
|
||||||
fit-margin-bottom="0" />
|
fit-margin-bottom="0"
|
||||||
|
inkscape:snap-intersection-paths="true"
|
||||||
|
inkscape:object-paths="true" />
|
||||||
<metadata
|
<metadata
|
||||||
id="metadata5">
|
id="metadata5">
|
||||||
<rdf:RDF>
|
<rdf:RDF>
|
||||||
@@ -64,24 +66,24 @@
|
|||||||
<path
|
<path
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
id="path138"
|
id="path138"
|
||||||
style="opacity:0.9;fill:#ccccff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
style="opacity:0.9;fill:#bfd9f1;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.12037313px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||||
d="m 39.305965,108.47713 60.922105,35.13225 0.0945,21.68327 -61.016595,-37.11662 z"
|
d="M 33.048081,103.66303 101.30357,143.02426 80.80219,154.86063 33.048089,125.73315 Z"
|
||||||
sodipodi:nodetypes="ccccc" />
|
sodipodi:nodetypes="ccccc" />
|
||||||
<path
|
<path
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
id="path116"
|
id="path116"
|
||||||
style="opacity:0.9;fill:#6666cc;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
style="opacity:0.9;fill:#6666af;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.12037313px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||||
d="m 136.19445,144.4429 0.0455,20.67266 -78.028381,44.11611 -0.0031,-19.78119 z"
|
d="m 141.59934,143.95811 0.051,23.16109 -87.420905,49.42651 -0.0034,-22.16232 z"
|
||||||
sodipodi:nodetypes="ccccc" />
|
sodipodi:nodetypes="ccccc" />
|
||||||
<path
|
<path
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
id="path118"
|
id="path118"
|
||||||
style="opacity:0.9;fill:#ccccff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
style="opacity:0.9;fill:#bfd9f1;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.12037313px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||||
d="m 39.471179,178.6501 18.737341,10.818 0.0031,19.78099 -18.740409,-10.88245 z"
|
d="m 33.233182,182.28294 20.992812,12.1202 0.0034,22.16208 -20.996251,-12.19239 z"
|
||||||
sodipodi:nodetypes="ccccc" />
|
sodipodi:nodetypes="ccccc" />
|
||||||
<path
|
<path
|
||||||
style="opacity:0.9;fill:#b4e2ff;fill-rule:evenodd;stroke:none;stroke-width:1.00546169px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
style="opacity:0.9;fill:#9dbef0;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.12649226px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||||
d="m 56.43263,98.242186 -17.391087,10.041014 61.186527,35.32618 -61.020778,35.23005 18.839694,10.87703 61.020784,-35.23005 17.39108,-10.04102 z"
|
d="m 52.236336,92.196079 -19.484508,11.249681 68.551742,39.5785 -68.366041,39.4708 21.107487,12.18633 68.366044,-39.4708 19.48451,-11.24968 z"
|
||||||
id="path134"
|
id="path134"
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
sodipodi:nodetypes="cccccccc" />
|
sodipodi:nodetypes="cccccccc" />
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.5 KiB |
BIN
app/assets/tray-darwinHighlightTemplate.png
Normal file
After Width: | Height: | Size: 415 B |
BIN
app/assets/tray-darwinHighlightTemplate@2x.png
Normal file
After Width: | Height: | Size: 955 B |
BIN
app/assets/tray-darwinTemplate.png
Normal file
After Width: | Height: | Size: 365 B |
BIN
app/assets/tray-darwinTemplate@2x.png
Normal file
After Width: | Height: | Size: 894 B |
BIN
app/assets/tray.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
50
app/bufferizedPTY.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
module.exports = function patchPTYModule (path) {
|
||||||
|
const mod = require(path)
|
||||||
|
const oldSpawn = mod.spawn
|
||||||
|
if (mod.patched) {
|
||||||
|
return mod
|
||||||
|
}
|
||||||
|
mod.patched = true
|
||||||
|
mod.spawn = (file, args, opt) => {
|
||||||
|
let terminal = oldSpawn(file, args, opt)
|
||||||
|
let timeout = null
|
||||||
|
let buffer = ''
|
||||||
|
let lastFlush = 0
|
||||||
|
let nextTimeout = 0
|
||||||
|
|
||||||
|
const maxWindow = 250
|
||||||
|
const minWindow = 50
|
||||||
|
|
||||||
|
function flush () {
|
||||||
|
if (buffer) {
|
||||||
|
terminal.emit('data-buffered', buffer)
|
||||||
|
}
|
||||||
|
lastFlush = Date.now()
|
||||||
|
buffer = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function reschedule () {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
nextTimeout = Date.now() + minWindow
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
timeout = null
|
||||||
|
flush()
|
||||||
|
}, minWindow)
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal.on('data', data => {
|
||||||
|
buffer += data
|
||||||
|
if (Date.now() - lastFlush > maxWindow) {
|
||||||
|
flush()
|
||||||
|
} else {
|
||||||
|
if (Date.now() > nextTimeout - (minWindow / 10)) {
|
||||||
|
reschedule()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return terminal
|
||||||
|
}
|
||||||
|
return mod
|
||||||
|
}
|
68
app/main.js
@@ -1,11 +1,9 @@
|
|||||||
if (process.platform == 'win32' && require('electron-squirrel-startup')) process.exit(0)
|
if (process.platform == 'win32' && require('electron-squirrel-startup')) process.exit(0)
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
if (process.argv.indexOf('--debug') !== -1) {
|
if (process.argv.indexOf('--debug') !== -1) {
|
||||||
require('electron-debug')({enabled: true, showDevTools: 'undocked'})
|
require('electron-debug')({enabled: true, showDevTools: 'undocked'})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let app = electron.app
|
let app = electron.app
|
||||||
|
|
||||||
let secondInstance = app.makeSingleInstance((argv, cwd) => {
|
let secondInstance = app.makeSingleInstance((argv, cwd) => {
|
||||||
@@ -32,8 +30,21 @@ if (!process.env.TERMINUS_PLUGINS) {
|
|||||||
setupWindowManagement = () => {
|
setupWindowManagement = () => {
|
||||||
app.window.on('show', () => {
|
app.window.on('show', () => {
|
||||||
app.window.webContents.send('host:window-shown')
|
app.window.webContents.send('host:window-shown')
|
||||||
|
if (app.tray) {
|
||||||
|
app.tray.destroy()
|
||||||
|
app.tray = null
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.window.on('hide', (e) => {
|
||||||
|
if (!app.tray) {
|
||||||
|
setupTray()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.window.on('enter-full-screen', () => app.window.webContents.send('host:window-enter-full-screen'))
|
||||||
|
app.window.on('leave-full-screen', () => app.window.webContents.send('host:window-leave-full-screen'))
|
||||||
|
|
||||||
app.window.on('close', (e) => {
|
app.window.on('close', (e) => {
|
||||||
windowConfig.set('windowBoundaries', app.window.getBounds())
|
windowConfig.set('windowBoundaries', app.window.getBounds())
|
||||||
})
|
})
|
||||||
@@ -46,14 +57,6 @@ setupWindowManagement = () => {
|
|||||||
app.window.focus()
|
app.window.focus()
|
||||||
})
|
})
|
||||||
|
|
||||||
electron.ipcMain.on('window-toggle-focus', () => {
|
|
||||||
if (app.window.isFocused()) {
|
|
||||||
app.window.minimize()
|
|
||||||
} else {
|
|
||||||
app.window.focus()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
electron.ipcMain.on('window-maximize', () => {
|
electron.ipcMain.on('window-maximize', () => {
|
||||||
app.window.maximize()
|
app.window.maximize()
|
||||||
})
|
})
|
||||||
@@ -75,20 +78,7 @@ setupWindowManagement = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
electron.ipcMain.on('window-set-bounds', (event, bounds) => {
|
electron.ipcMain.on('window-set-bounds', (event, bounds) => {
|
||||||
let actualBounds = app.window.getBounds()
|
app.window.setBounds(bounds)
|
||||||
actualBounds.width -= bounds.x - actualBounds.x
|
|
||||||
actualBounds.height -= bounds.y - actualBounds.y
|
|
||||||
actualBounds.x = bounds.x
|
|
||||||
actualBounds.y = bounds.y
|
|
||||||
app.window.setBounds(actualBounds)
|
|
||||||
setTimeout(() => {
|
|
||||||
actualBounds = app.window.getBounds()
|
|
||||||
bounds.width += bounds.x - actualBounds.x
|
|
||||||
bounds.height += bounds.y - actualBounds.y
|
|
||||||
bounds.x = actualBounds.x
|
|
||||||
bounds.y = actualBounds.y
|
|
||||||
app.window.setBounds(bounds)
|
|
||||||
}, 100)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
electron.ipcMain.on('window-set-always-on-top', (event, flag) => {
|
electron.ipcMain.on('window-set-always-on-top', (event, flag) => {
|
||||||
@@ -97,6 +87,35 @@ setupWindowManagement = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setupTray = () => {
|
||||||
|
if (process.platform == 'darwin') {
|
||||||
|
app.tray = new electron.Tray(`${app.getAppPath()}/assets/tray-darwinTemplate.png`)
|
||||||
|
app.tray.setPressedImage(`${app.getAppPath()}/assets/tray-darwinHighlightTemplate.png`)
|
||||||
|
} else {
|
||||||
|
app.tray = new electron.Tray(`${app.getAppPath()}/assets/tray.png`)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.tray.on('click', () => {
|
||||||
|
app.window.show()
|
||||||
|
app.window.focus()
|
||||||
|
})
|
||||||
|
|
||||||
|
const contextMenu = electron.Menu.buildFromTemplate([{
|
||||||
|
label: 'Show',
|
||||||
|
click () {
|
||||||
|
app.window.show()
|
||||||
|
app.window.focus()
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
|
||||||
|
if (process.platform != 'darwin') {
|
||||||
|
app.tray.setContextMenu(contextMenu)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.tray.setToolTip(`Terminus ${app.getVersion()}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
setupMenu = () => {
|
setupMenu = () => {
|
||||||
let template = [{
|
let template = [{
|
||||||
label: "Application",
|
label: "Application",
|
||||||
@@ -157,7 +176,6 @@ setupMenu = () => {
|
|||||||
{
|
{
|
||||||
role: 'window',
|
role: 'window',
|
||||||
submenu: [
|
submenu: [
|
||||||
{role: 'close'},
|
|
||||||
{role: 'minimize'},
|
{role: 'minimize'},
|
||||||
{role: 'zoom'},
|
{role: 'zoom'},
|
||||||
{type: 'separator'},
|
{type: 'separator'},
|
||||||
|
@@ -27,6 +27,7 @@
|
|||||||
"electron-squirrel-startup": "^1.0.0",
|
"electron-squirrel-startup": "^1.0.0",
|
||||||
"js-yaml": "3.8.2",
|
"js-yaml": "3.8.2",
|
||||||
"mz": "^2.6.0",
|
"mz": "^2.6.0",
|
||||||
|
"ngx-toastr": "^8.0.0",
|
||||||
"path": "0.12.7",
|
"path": "0.12.7",
|
||||||
"rxjs": "5.3.0",
|
"rxjs": "5.3.0",
|
||||||
"zone.js": "0.8.12"
|
"zone.js": "0.8.12"
|
||||||
|
@@ -1,12 +1,18 @@
|
|||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { BrowserModule } from '@angular/platform-browser'
|
import { BrowserModule } from '@angular/platform-browser'
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { ToastrModule } from 'ngx-toastr'
|
||||||
|
|
||||||
export function getRootModule (plugins: any[]) {
|
export function getRootModule (plugins: any[]) {
|
||||||
let imports = [
|
let imports = [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
...plugins,
|
...plugins,
|
||||||
NgbModule.forRoot(),
|
NgbModule.forRoot(),
|
||||||
|
ToastrModule.forRoot({
|
||||||
|
positionClass: 'toast-bottom-center',
|
||||||
|
preventDuplicates: true,
|
||||||
|
extendedTimeOut: 5000,
|
||||||
|
}),
|
||||||
]
|
]
|
||||||
let bootstrap = [
|
let bootstrap = [
|
||||||
...(plugins.filter(x => x.bootstrap).map(x => x.bootstrap)),
|
...(plugins.filter(x => x.bootstrap).map(x => x.bootstrap)),
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import 'source-sans-pro'
|
import 'source-sans-pro'
|
||||||
import 'font-awesome/css/font-awesome.css'
|
import 'font-awesome/css/font-awesome.css'
|
||||||
|
import 'ngx-toastr/toastr.css'
|
||||||
import './preload.scss'
|
import './preload.scss'
|
||||||
|
|
||||||
import * as Raven from 'raven-js'
|
import * as Raven from 'raven-js'
|
||||||
|
@@ -2,6 +2,7 @@ import 'zone.js'
|
|||||||
import 'core-js/es7/reflect'
|
import 'core-js/es7/reflect'
|
||||||
import 'core-js/core/delay'
|
import 'core-js/core/delay'
|
||||||
import 'rxjs'
|
import 'rxjs'
|
||||||
|
import './toastr.scss'
|
||||||
|
|
||||||
// Always land on the start view
|
// Always land on the start view
|
||||||
location.hash = ''
|
location.hash = ''
|
||||||
|
@@ -59,6 +59,7 @@ const builtinModules = [
|
|||||||
'@angular/platform-browser',
|
'@angular/platform-browser',
|
||||||
'@angular/platform-browser-dynamic',
|
'@angular/platform-browser-dynamic',
|
||||||
'@ng-bootstrap/ng-bootstrap',
|
'@ng-bootstrap/ng-bootstrap',
|
||||||
|
'ngx-toastr',
|
||||||
'rxjs',
|
'rxjs',
|
||||||
'terminus-core',
|
'terminus-core',
|
||||||
'terminus-settings',
|
'terminus-settings',
|
||||||
|
@@ -39,7 +39,7 @@
|
|||||||
.terminus-logo {
|
.terminus-logo {
|
||||||
width: 160px;
|
width: 160px;
|
||||||
height: 160px;
|
height: 160px;
|
||||||
background: url('./logo.svg');
|
background: url('../assets/logo.svg');
|
||||||
background-repeat: none;
|
background-repeat: none;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
16
app/src/toastr.scss
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#toast-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
box-shadow: 0 1px 0 rgba(0,0,0,.25);
|
||||||
|
padding: 10px;
|
||||||
|
background-image: none;
|
||||||
|
width: auto;
|
||||||
|
|
||||||
|
&.toast-info {
|
||||||
|
background-color: #555;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -58,6 +58,7 @@ module.exports = {
|
|||||||
'child_process': 'commonjs child_process',
|
'child_process': 'commonjs child_process',
|
||||||
'electron': 'commonjs electron',
|
'electron': 'commonjs electron',
|
||||||
'electron-is-dev': 'commonjs electron-is-dev',
|
'electron-is-dev': 'commonjs electron-is-dev',
|
||||||
|
'ngx-toastr': 'commonjs ngx-toastr',
|
||||||
'module': 'commonjs module',
|
'module': 'commonjs module',
|
||||||
'mz': 'commonjs mz',
|
'mz': 'commonjs mz',
|
||||||
'path': 'commonjs path',
|
'path': 'commonjs path',
|
||||||
|
@@ -195,6 +195,10 @@ mz@^2.6.0:
|
|||||||
object-assign "^4.0.1"
|
object-assign "^4.0.1"
|
||||||
thenify-all "^1.0.0"
|
thenify-all "^1.0.0"
|
||||||
|
|
||||||
|
ngx-toastr@^8.0.0:
|
||||||
|
version "8.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ngx-toastr/-/ngx-toastr-8.0.0.tgz#f3bc53146b2f7da3eabf3daa1b1bbdf65cb49697"
|
||||||
|
|
||||||
object-assign@^4.0.1:
|
object-assign@^4.0.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 655 B After Width: | Height: | Size: 644 B |
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -15,19 +15,22 @@
|
|||||||
viewBox="0 0 150 150"
|
viewBox="0 0 150 150"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
id="svg8"
|
id="svg8"
|
||||||
inkscape:version="0.92.1 r15371"
|
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
|
||||||
sodipodi:docname="icon.svg">
|
sodipodi:docname="icon.svg"
|
||||||
|
inkscape:export-filename="/home/eugene/Work/term/build/icons/512x512.png"
|
||||||
|
inkscape:export-xdpi="86.699997"
|
||||||
|
inkscape:export-ydpi="86.699997">
|
||||||
<defs
|
<defs
|
||||||
id="defs2">
|
id="defs2">
|
||||||
<linearGradient
|
<linearGradient
|
||||||
inkscape:collect="always"
|
inkscape:collect="always"
|
||||||
id="linearGradient4649">
|
id="linearGradient4649">
|
||||||
<stop
|
<stop
|
||||||
style="stop-color:#000916;stop-opacity:1"
|
style="stop-color:#000316;stop-opacity:1"
|
||||||
offset="0"
|
offset="0"
|
||||||
id="stop4645" />
|
id="stop4645" />
|
||||||
<stop
|
<stop
|
||||||
style="stop-color:#004565;stop-opacity:1"
|
style="stop-color:#190065;stop-opacity:1"
|
||||||
offset="1"
|
offset="1"
|
||||||
id="stop4647" />
|
id="stop4647" />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
@@ -39,7 +42,8 @@
|
|||||||
y1="85.146751"
|
y1="85.146751"
|
||||||
x2="89.26284"
|
x2="89.26284"
|
||||||
y2="229.47229"
|
y2="229.47229"
|
||||||
gradientUnits="userSpaceOnUse" />
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(0.82182032,0,0,0.82182032,15.208802,28.029361)" />
|
||||||
</defs>
|
</defs>
|
||||||
<sodipodi:namedview
|
<sodipodi:namedview
|
||||||
id="base"
|
id="base"
|
||||||
@@ -49,8 +53,8 @@
|
|||||||
inkscape:pageopacity="0.0"
|
inkscape:pageopacity="0.0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:zoom="0.49497475"
|
inkscape:zoom="0.49497475"
|
||||||
inkscape:cx="134.39743"
|
inkscape:cx="85.897128"
|
||||||
inkscape:cy="340.43068"
|
inkscape:cy="375.72042"
|
||||||
inkscape:document-units="mm"
|
inkscape:document-units="mm"
|
||||||
inkscape:current-layer="layer1"
|
inkscape:current-layer="layer1"
|
||||||
showgrid="false"
|
showgrid="false"
|
||||||
@@ -63,7 +67,9 @@
|
|||||||
fit-margin-top="0"
|
fit-margin-top="0"
|
||||||
fit-margin-left="0"
|
fit-margin-left="0"
|
||||||
fit-margin-right="0"
|
fit-margin-right="0"
|
||||||
fit-margin-bottom="0" />
|
fit-margin-bottom="0"
|
||||||
|
inkscape:snap-intersection-paths="true"
|
||||||
|
inkscape:object-paths="true" />
|
||||||
<metadata
|
<metadata
|
||||||
id="metadata5">
|
id="metadata5">
|
||||||
<rdf:RDF>
|
<rdf:RDF>
|
||||||
@@ -83,34 +89,34 @@
|
|||||||
transform="translate(-10.356544,-82.309525)">
|
transform="translate(-10.356544,-82.309525)">
|
||||||
<rect
|
<rect
|
||||||
id="rect168"
|
id="rect168"
|
||||||
width="150"
|
width="123.27305"
|
||||||
height="150"
|
height="123.27305"
|
||||||
x="10.356544"
|
x="23.72002"
|
||||||
y="82.309525"
|
y="95.673004"
|
||||||
style="fill:url(#linearGradient4651);fill-opacity:1;stroke-width:0.26458332"
|
style="fill:url(#linearGradient4651);fill-opacity:1;stroke-width:0.21743995"
|
||||||
rx="10"
|
rx="8.2182035"
|
||||||
ry="10" />
|
ry="8.2182035" />
|
||||||
<path
|
<path
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
id="path138"
|
id="path138"
|
||||||
style="opacity:0.9;fill:#ccccff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
style="opacity:0.9;fill:#bfd9f1;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.82182032px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
||||||
d="m 39.305965,108.47713 60.922105,35.13225 0.0945,21.68327 -61.016595,-37.11662 z"
|
d="m 47.511243,117.17807 50.067023,28.8724 -15.038249,8.68226 -35.028768,-21.3657 z"
|
||||||
sodipodi:nodetypes="ccccc" />
|
sodipodi:nodetypes="ccccc" />
|
||||||
<path
|
<path
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
id="path116"
|
id="path116"
|
||||||
style="opacity:0.9;fill:#6666cc;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
style="opacity:0.9;fill:#6666af;fill-rule:evenodd;stroke:none;stroke-width:0.82182032px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;fill-opacity:1"
|
||||||
d="m 136.19445,144.4429 0.0455,20.67266 -78.028381,44.11611 -0.0031,-19.78119 z"
|
d="m 127.13617,146.73547 0.0374,16.98921 -64.125308,36.25552 -0.0025,-16.25659 z"
|
||||||
sodipodi:nodetypes="ccccc" />
|
sodipodi:nodetypes="ccccc" />
|
||||||
<path
|
<path
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
id="path118"
|
id="path118"
|
||||||
style="opacity:0.9;fill:#ccccff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
style="opacity:0.9;fill:#bfd9f1;fill-rule:evenodd;stroke:none;stroke-width:0.82182032px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;fill-opacity:1"
|
||||||
d="m 39.471179,178.6501 18.737341,10.818 0.0031,19.78099 -18.740409,-10.88245 z"
|
d="m 47.647019,174.84764 15.398727,8.89046 0.0025,16.25641 -15.401249,-8.94341 z"
|
||||||
sodipodi:nodetypes="ccccc" />
|
sodipodi:nodetypes="ccccc" />
|
||||||
<path
|
<path
|
||||||
style="opacity:0.9;fill:#b4e2ff;fill-rule:evenodd;stroke:none;stroke-width:1.00546169px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
|
style="opacity:0.9;fill:#9dbef0;fill-rule:evenodd;stroke:none;stroke-width:0.82630885px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;fill-opacity:1"
|
||||||
d="m 56.43263,98.242186 -17.391087,10.041014 61.186527,35.32618 -61.020778,35.23005 18.839694,10.87703 61.020784,-35.23005 17.39108,-10.04102 z"
|
d="m 61.586284,108.76679 -14.292349,8.25191 50.284331,29.03177 -50.148115,28.95277 15.482843,8.93896 50.148116,-28.95277 14.29235,-8.25191 z"
|
||||||
id="path134"
|
id="path134"
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
sodipodi:nodetypes="cccccccc" />
|
sodipodi:nodetypes="cccccccc" />
|
||||||
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 361 KiB After Width: | Height: | Size: 361 KiB |
@@ -9,7 +9,7 @@
|
|||||||
"core-js": "2.4.1",
|
"core-js": "2.4.1",
|
||||||
"cross-env": "4.0.0",
|
"cross-env": "4.0.0",
|
||||||
"css-loader": "0.28.0",
|
"css-loader": "0.28.0",
|
||||||
"electron": "1.6.11",
|
"electron": "1.8.4",
|
||||||
"electron-builder": "17.1.1",
|
"electron-builder": "17.1.1",
|
||||||
"electron-builder-squirrel-windows": "17.0.1",
|
"electron-builder-squirrel-windows": "17.0.1",
|
||||||
"electron-rebuild": "1.5.11",
|
"electron-rebuild": "1.5.11",
|
||||||
@@ -98,7 +98,8 @@
|
|||||||
},
|
},
|
||||||
"rpm": {
|
"rpm": {
|
||||||
"depends": [
|
"depends": [
|
||||||
"screen"
|
"screen",
|
||||||
|
"gnome-python2-gnomekeyring"
|
||||||
],
|
],
|
||||||
"artifactName": "terminus-${version}-${os}-${arch}.rpm"
|
"artifactName": "terminus-${version}-${os}-${arch}.rpm"
|
||||||
}
|
}
|
||||||
|
@@ -20,3 +20,14 @@ vars.builtinPlugins.forEach(plugin => {
|
|||||||
sh.exec(`${npx} yarn install`)
|
sh.exec(`${npx} yarn install`)
|
||||||
sh.cd('..')
|
sh.cd('..')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (['darwin', 'linux'].includes(process.platform)) {
|
||||||
|
sh.cd('node_modules')
|
||||||
|
for (let x of vars.builtinPlugins) {
|
||||||
|
sh.ln('-fs', '../' + x, x)
|
||||||
|
}
|
||||||
|
for (let x of vars.bundledModules) {
|
||||||
|
sh.ln('-fs', '../app/node_modules/' + x, x)
|
||||||
|
}
|
||||||
|
sh.cd('..')
|
||||||
|
}
|
||||||
|
@@ -16,5 +16,9 @@ exports.builtinPlugins = [
|
|||||||
'terminus-plugin-manager',
|
'terminus-plugin-manager',
|
||||||
'terminus-ssh',
|
'terminus-ssh',
|
||||||
]
|
]
|
||||||
|
exports.bundledModules = [
|
||||||
|
'@angular',
|
||||||
|
'@ng-bootstrap',
|
||||||
|
]
|
||||||
exports.nativeModules = ['node-pty-tmp', 'font-manager', 'xkeychain']
|
exports.nativeModules = ['node-pty-tmp', 'font-manager', 'xkeychain']
|
||||||
exports.electronVersion = pkgInfo.devDependencies.electron
|
exports.electronVersion = pkgInfo.devDependencies.electron
|
||||||
|
@@ -1,44 +1,54 @@
|
|||||||
!
|
! Base16 Default Dark
|
||||||
! Generated with :
|
! Scheme: Chris Kempson (http://chriskempson.com)
|
||||||
! XRDB2Xreources.py
|
|
||||||
!
|
#define base00 #181818
|
||||||
*.foreground: #d8d8d8
|
#define base01 #282828
|
||||||
*.background: #181818
|
#define base02 #383838
|
||||||
*.cursorColor: #d8d8d8
|
#define base03 #585858
|
||||||
!
|
#define base04 #b8b8b8
|
||||||
! Black
|
#define base05 #d8d8d8
|
||||||
*.color0: #181818
|
#define base06 #e8e8e8
|
||||||
*.color8: #585858
|
#define base07 #f8f8f8
|
||||||
!
|
#define base08 #ab4642
|
||||||
! Red
|
#define base09 #dc9656
|
||||||
*.color1: #ab4642
|
#define base0A #f7ca88
|
||||||
*.color9: #ab4642
|
#define base0B #a1b56c
|
||||||
!
|
#define base0C #86c1b9
|
||||||
! Green
|
#define base0D #7cafc2
|
||||||
*.color2: #a1b56c
|
#define base0E #ba8baf
|
||||||
*.color10: #a1b56c
|
#define base0F #a16946
|
||||||
!
|
|
||||||
! Yellow
|
*.foreground: base05
|
||||||
*.color3: #f7ca88
|
#ifdef background_opacity
|
||||||
*.color11: #f7ca88
|
*.background: [background_opacity]base00
|
||||||
!
|
#else
|
||||||
! Blue
|
*.background: base00
|
||||||
*.color4: #7cafc2
|
#endif
|
||||||
*.color12: #7cafc2
|
*.cursorColor: base05
|
||||||
!
|
|
||||||
! Magenta
|
*.color0: base00
|
||||||
*.color5: #ba8baf
|
*.color1: base08
|
||||||
*.color13: #ba8baf
|
*.color2: base0B
|
||||||
!
|
*.color3: base0A
|
||||||
! Cyan
|
*.color4: base0D
|
||||||
*.color6: #86c1b9
|
*.color5: base0E
|
||||||
*.color14: #86c1b9
|
*.color6: base0C
|
||||||
!
|
*.color7: base05
|
||||||
! White
|
|
||||||
*.color7: #d8d8d8
|
*.color8: base03
|
||||||
*.color15: #f8f8f8
|
*.color9: base08
|
||||||
!
|
*.color10: base0B
|
||||||
! Bold, Italic, Underline
|
*.color11: base0A
|
||||||
*.colorBD: #d8d8d8
|
*.color12: base0D
|
||||||
!*.colorIT:
|
*.color13: base0E
|
||||||
!*.colorUL:
|
*.color14: base0C
|
||||||
|
*.color15: base07
|
||||||
|
|
||||||
|
! Note: colors beyond 15 might not be loaded (e.g., xterm, urxvt),
|
||||||
|
! use 'shell' template to set these if necessary
|
||||||
|
*.color16: base09
|
||||||
|
*.color17: base0F
|
||||||
|
*.color18: base01
|
||||||
|
*.color19: base02
|
||||||
|
*.color20: base04
|
||||||
|
*.color21: base06
|
||||||
|
@@ -10,13 +10,23 @@ export class ColorSchemes extends TerminalColorSchemeProvider {
|
|||||||
|
|
||||||
schemeContents.keys().forEach(schemeFile => {
|
schemeContents.keys().forEach(schemeFile => {
|
||||||
let lines = (schemeContents(schemeFile) as string).split('\n')
|
let lines = (schemeContents(schemeFile) as string).split('\n')
|
||||||
|
|
||||||
|
// process #define variables
|
||||||
|
let variables: any = {}
|
||||||
|
lines
|
||||||
|
.filter(x => x.startsWith('#define'))
|
||||||
|
.map(x => x.split(' ').map(v => v.trim()))
|
||||||
|
.forEach(([ignore, variableName, variableValue]) => {
|
||||||
|
variables[variableName] = variableValue
|
||||||
|
})
|
||||||
|
|
||||||
let values: any = {}
|
let values: any = {}
|
||||||
lines
|
lines
|
||||||
.filter(x => x.startsWith('*.'))
|
.filter(x => x.startsWith('*.'))
|
||||||
.map(x => x.substring(2))
|
.map(x => x.substring(2))
|
||||||
.map(x => x.split(':').map(v => v.trim()))
|
.map(x => x.split(':').map(v => v.trim()))
|
||||||
.forEach(([key, value]) => {
|
.forEach(([key, value]) => {
|
||||||
values[key] = value
|
values[key] = variables[value] ? variables[value] : value
|
||||||
})
|
})
|
||||||
|
|
||||||
let colors: string[] = []
|
let colors: string[] = []
|
||||||
|
@@ -1,12 +1,13 @@
|
|||||||
title-bar(
|
title-bar(
|
||||||
*ngIf='config.store.appearance.frame == "full" && config.store.appearance.dock == "off"',
|
*ngIf='!hostApp.isFullScreen && config.store.appearance.frame == "full" && config.store.appearance.dock == "off"',
|
||||||
[class.inset]='hostApp.platform == Platform.macOS'
|
[class.inset]='hostApp.platform == Platform.macOS'
|
||||||
)
|
)
|
||||||
|
|
||||||
.content(
|
.content(
|
||||||
[class.tabs-on-top]='config.store.appearance.tabsLocation == "top"'
|
[class.tabs-on-top]='config.store.appearance.tabsLocation == "top"'
|
||||||
)
|
)
|
||||||
.tab-bar(
|
.tab-bar(
|
||||||
|
*ngIf='!hostApp.isFullScreen',
|
||||||
[class.inset]='hostApp.platform == Platform.macOS && config.store.appearance.frame == "thin" && config.store.appearance.tabsLocation == "top"'
|
[class.inset]='hostApp.platform == Platform.macOS && config.store.appearance.frame == "thin" && config.store.appearance.tabsLocation == "top"'
|
||||||
)
|
)
|
||||||
.tabs
|
.tabs
|
||||||
@@ -20,7 +21,7 @@ title-bar(
|
|||||||
@animateTab,
|
@animateTab,
|
||||||
(click)='app.selectTab(tab)',
|
(click)='app.selectTab(tab)',
|
||||||
)
|
)
|
||||||
|
|
||||||
.btn-group
|
.btn-group
|
||||||
button.btn.btn-secondary.btn-tab-bar(
|
button.btn.btn-secondary.btn-tab-bar(
|
||||||
*ngFor='let button of leftToolbarButtons',
|
*ngFor='let button of leftToolbarButtons',
|
||||||
@@ -28,9 +29,9 @@ title-bar(
|
|||||||
(click)='button.click()',
|
(click)='button.click()',
|
||||||
)
|
)
|
||||||
i.fa([class]='"fa fa-" + button.icon')
|
i.fa([class]='"fa fa-" + button.icon')
|
||||||
|
|
||||||
.drag-space(*ngIf='config.store.appearance.frame == "thin" && hostApp.platform != Platform.macOS')
|
.drag-space([class.persistent]='config.store.appearance.frame == "thin" && hostApp.platform != Platform.macOS')
|
||||||
|
|
||||||
.btn-group
|
.btn-group
|
||||||
button.btn.btn-secondary.btn-tab-bar(
|
button.btn.btn-secondary.btn-tab-bar(
|
||||||
*ngFor='let button of rightToolbarButtons',
|
*ngFor='let button of rightToolbarButtons',
|
||||||
@@ -53,7 +54,7 @@ title-bar(
|
|||||||
start-page(*ngIf='ready && app.tabs.length == 0')
|
start-page(*ngIf='ready && app.tabs.length == 0')
|
||||||
|
|
||||||
tab-body(
|
tab-body(
|
||||||
*ngFor='let tab of app.tabs; trackBy: tab?.id',
|
*ngFor='let tab of app.tabs; trackBy: tab?.id',
|
||||||
[active]='tab == app.activeTab',
|
[active]='tab == app.activeTab',
|
||||||
[tab]='tab',
|
[tab]='tab',
|
||||||
[scrollable]='tab.scrollable',
|
[scrollable]='tab.scrollable',
|
||||||
|
@@ -7,6 +7,7 @@
|
|||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-webkit-user-drag: none;
|
-webkit-user-drag: none;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
will-change: transform;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
animation: 0.5s ease-out fadeIn;
|
animation: 0.5s ease-out fadeIn;
|
||||||
}
|
}
|
||||||
@@ -55,15 +56,20 @@ $tab-border-radius: 4px;
|
|||||||
}
|
}
|
||||||
|
|
||||||
&>.drag-space {
|
&>.drag-space {
|
||||||
min-width: 100px;
|
min-width: 1px;
|
||||||
flex: 1 0 25%;
|
flex: 1 0 1%;
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
|
|
||||||
|
&.persistent {
|
||||||
|
min-width: 100px;
|
||||||
|
flex: 1 0 25%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.inset {
|
&.inset {
|
||||||
padding-left: 85px;
|
padding-left: 85px;
|
||||||
}
|
}
|
||||||
|
|
||||||
window-controls {
|
window-controls {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,7 @@ import { DockingService } from '../services/docking.service'
|
|||||||
import { TabRecoveryService } from '../services/tabRecovery.service'
|
import { TabRecoveryService } from '../services/tabRecovery.service'
|
||||||
import { ThemesService } from '../services/themes.service'
|
import { ThemesService } from '../services/themes.service'
|
||||||
import { UpdaterService, Update } from '../services/updater.service'
|
import { UpdaterService, Update } from '../services/updater.service'
|
||||||
|
import { TouchbarService } from '../services/touchbar.service'
|
||||||
|
|
||||||
import { SafeModeModalComponent } from './safeModeModal.component'
|
import { SafeModeModalComponent } from './safeModeModal.component'
|
||||||
import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api'
|
import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api'
|
||||||
@@ -62,6 +63,7 @@ export class AppRootComponent {
|
|||||||
private tabRecovery: TabRecoveryService,
|
private tabRecovery: TabRecoveryService,
|
||||||
private hotkeys: HotkeysService,
|
private hotkeys: HotkeysService,
|
||||||
private updater: UpdaterService,
|
private updater: UpdaterService,
|
||||||
|
private touchbar: TouchbarService,
|
||||||
public hostApp: HostAppService,
|
public hostApp: HostAppService,
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
public app: AppService,
|
public app: AppService,
|
||||||
@@ -97,6 +99,9 @@ export class AppRootComponent {
|
|||||||
this.app.previousTab()
|
this.app.previousTab()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (hotkey === 'toggle-fullscreen') {
|
||||||
|
this.hostApp.toggleFullscreen()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.docking.dock()
|
this.docking.dock()
|
||||||
@@ -118,16 +123,20 @@ export class AppRootComponent {
|
|||||||
this.updater.check().then(update => {
|
this.updater.check().then(update => {
|
||||||
this.appUpdate = update
|
this.appUpdate = update
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.touchbar.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
onGlobalHotkey () {
|
onGlobalHotkey () {
|
||||||
if (this.electron.app.window.isFocused()) {
|
if (this.electron.app.window.isFocused()) {
|
||||||
// focused
|
// focused
|
||||||
|
this.electron.loseFocus()
|
||||||
this.electron.app.window.hide()
|
this.electron.app.window.hide()
|
||||||
} else {
|
} else {
|
||||||
if (!this.electron.app.window.isVisible()) {
|
if (!this.electron.app.window.isVisible()) {
|
||||||
// unfocused, invisible
|
// unfocused, invisible
|
||||||
this.electron.app.window.show()
|
this.electron.app.window.show()
|
||||||
|
this.electron.app.window.focus()
|
||||||
} else {
|
} else {
|
||||||
if (this.config.store.appearance.dock === 'off') {
|
if (this.config.store.appearance.dock === 'off') {
|
||||||
// not docked, visible
|
// not docked, visible
|
||||||
|
@@ -5,6 +5,7 @@ export abstract class BaseTabComponent {
|
|||||||
private static lastTabID = 0
|
private static lastTabID = 0
|
||||||
id: number
|
id: number
|
||||||
title: string
|
title: string
|
||||||
|
titleChange$ = new Subject<string>()
|
||||||
customTitle: string
|
customTitle: string
|
||||||
scrollable: boolean
|
scrollable: boolean
|
||||||
hasActivity = false
|
hasActivity = false
|
||||||
@@ -23,6 +24,13 @@ export abstract class BaseTabComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setTitle (title: string) {
|
||||||
|
this.title = title
|
||||||
|
if (!this.customTitle) {
|
||||||
|
this.titleChange$.next(title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
displayActivity (): void {
|
displayActivity (): void {
|
||||||
this.hasActivity = true
|
this.hasActivity = true
|
||||||
}
|
}
|
||||||
|
@@ -14,8 +14,6 @@ $tabs-height: 36px;
|
|||||||
|
|
||||||
transition: 0.125s ease-out all;
|
transition: 0.125s ease-out all;
|
||||||
|
|
||||||
border-top: 1px solid transparent;
|
|
||||||
|
|
||||||
.index {
|
.index {
|
||||||
flex: none;
|
flex: none;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
@@ -69,6 +69,7 @@ export class TabHeaderComponent {
|
|||||||
let modal = this.ngbModal.open(RenameTabModalComponent)
|
let modal = this.ngbModal.open(RenameTabModalComponent)
|
||||||
modal.componentInstance.value = this.tab.customTitle || this.tab.title
|
modal.componentInstance.value = this.tab.customTitle || this.tab.title
|
||||||
modal.result.then(result => {
|
modal.result.then(result => {
|
||||||
|
this.tab.setTitle(result)
|
||||||
this.tab.customTitle = result
|
this.tab.customTitle = result
|
||||||
}).catch(() => null)
|
}).catch(() => null)
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
hotkeys:
|
hotkeys:
|
||||||
toggle-window:
|
toggle-window:
|
||||||
- 'Ctrl+Space'
|
- 'Ctrl+Space'
|
||||||
|
toggle-fullscreen:
|
||||||
|
- 'F11'
|
||||||
close-tab:
|
close-tab:
|
||||||
- 'Ctrl-Shift-W'
|
- 'Ctrl-Shift-W'
|
||||||
- ['Ctrl-A', 'K']
|
- ['Ctrl-A', 'K']
|
||||||
|
@@ -1,48 +1,33 @@
|
|||||||
hotkeys:
|
hotkeys:
|
||||||
toggle-window:
|
toggle-window:
|
||||||
- 'Ctrl+Space'
|
- 'Ctrl+Space'
|
||||||
|
toggle-fullscreen:
|
||||||
|
- 'Ctrl+⌘+F'
|
||||||
close-tab:
|
close-tab:
|
||||||
- '⌘-W'
|
- '⌘-W'
|
||||||
- ['Ctrl-A', 'K']
|
toggle-last-tab: []
|
||||||
toggle-last-tab:
|
|
||||||
- ['Ctrl-A', 'A']
|
|
||||||
- ['Ctrl-A', 'Ctrl-A']
|
|
||||||
next-tab:
|
next-tab:
|
||||||
- '⌘-ArrowRight'
|
|
||||||
- ['Ctrl-A', 'N']
|
|
||||||
- 'Ctrl-Tab'
|
- 'Ctrl-Tab'
|
||||||
previous-tab:
|
previous-tab:
|
||||||
- '⌘-ArrowLeft'
|
|
||||||
- ['Ctrl-A', 'P']
|
|
||||||
- 'Ctrl-Shift-Tab'
|
- 'Ctrl-Shift-Tab'
|
||||||
tab-1:
|
tab-1:
|
||||||
- '⌘-1'
|
- '⌘-1'
|
||||||
- ['Ctrl-A', '1']
|
|
||||||
tab-2:
|
tab-2:
|
||||||
- '⌘-2'
|
- '⌘-2'
|
||||||
- ['Ctrl-A', '2']
|
|
||||||
tab-3:
|
tab-3:
|
||||||
- '⌘-3'
|
- '⌘-3'
|
||||||
- ['Ctrl-A', '3']
|
|
||||||
tab-4:
|
tab-4:
|
||||||
- '⌘-4'
|
- '⌘-4'
|
||||||
- ['Ctrl-A', '4']
|
|
||||||
tab-5:
|
tab-5:
|
||||||
- '⌘-5'
|
- '⌘-5'
|
||||||
- ['Ctrl-A', '5']
|
|
||||||
tab-6:
|
tab-6:
|
||||||
- '⌘-6'
|
- '⌘-6'
|
||||||
- ['Ctrl-A', '6']
|
|
||||||
tab-7:
|
tab-7:
|
||||||
- '⌘-7'
|
- '⌘-7'
|
||||||
- ['Ctrl-A', '7']
|
|
||||||
tab-8:
|
tab-8:
|
||||||
- '⌘-8'
|
- '⌘-8'
|
||||||
- ['Ctrl-A', '8']
|
|
||||||
tab-9:
|
tab-9:
|
||||||
- '⌘-9'
|
- '⌘-9'
|
||||||
- ['Ctrl-A', '9']
|
|
||||||
tab-10:
|
tab-10:
|
||||||
- '⌘-0'
|
- '⌘-0'
|
||||||
- ['Ctrl-A', '0']
|
|
||||||
pluginBlacklist: ['ssh']
|
pluginBlacklist: ['ssh']
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
hotkeys:
|
hotkeys:
|
||||||
toggle-window:
|
toggle-window:
|
||||||
- 'Ctrl+Space'
|
- 'Ctrl+Space'
|
||||||
|
toggle-fullscreen:
|
||||||
|
- 'F11'
|
||||||
close-tab:
|
close-tab:
|
||||||
- 'Ctrl-Shift-W'
|
- 'Ctrl-Shift-W'
|
||||||
- ['Ctrl-A', 'K']
|
- ['Ctrl-A', 'K']
|
||||||
|
@@ -3,6 +3,7 @@ appearance:
|
|||||||
dockScreen: current
|
dockScreen: current
|
||||||
dockFill: 50
|
dockFill: 50
|
||||||
tabsLocation: top
|
tabsLocation: top
|
||||||
|
cycleTabs: true
|
||||||
theme: Standard
|
theme: Standard
|
||||||
frame: thin
|
frame: thin
|
||||||
css: '/* * { color: blue !important; } */'
|
css: '/* * { color: blue !important; } */'
|
||||||
|
@@ -14,6 +14,7 @@ import { HotkeysService, AppHotkeyProvider } from './services/hotkeys.service'
|
|||||||
import { DockingService } from './services/docking.service'
|
import { DockingService } from './services/docking.service'
|
||||||
import { TabRecoveryService } from './services/tabRecovery.service'
|
import { TabRecoveryService } from './services/tabRecovery.service'
|
||||||
import { ThemesService } from './services/themes.service'
|
import { ThemesService } from './services/themes.service'
|
||||||
|
import { TouchbarService } from './services/touchbar.service'
|
||||||
import { UpdaterService } from './services/updater.service'
|
import { UpdaterService } from './services/updater.service'
|
||||||
|
|
||||||
import { AppRootComponent } from './components/appRoot.component'
|
import { AppRootComponent } from './components/appRoot.component'
|
||||||
@@ -44,6 +45,7 @@ const PROVIDERS = [
|
|||||||
LogService,
|
LogService,
|
||||||
TabRecoveryService,
|
TabRecoveryService,
|
||||||
ThemesService,
|
ThemesService,
|
||||||
|
TouchbarService,
|
||||||
UpdaterService,
|
UpdaterService,
|
||||||
{ provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
|
{ provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
|
||||||
{ provide: Theme, useClass: StandardTheme, multi: true },
|
{ provide: Theme, useClass: StandardTheme, multi: true },
|
||||||
|
@@ -2,7 +2,8 @@ import { Subject, AsyncSubject } from 'rxjs'
|
|||||||
import { Injectable, ComponentFactoryResolver, Injector, Optional } from '@angular/core'
|
import { Injectable, ComponentFactoryResolver, Injector, Optional } from '@angular/core'
|
||||||
import { DefaultTabProvider } from '../api/defaultTabProvider'
|
import { DefaultTabProvider } from '../api/defaultTabProvider'
|
||||||
import { BaseTabComponent } from '../components/baseTab.component'
|
import { BaseTabComponent } from '../components/baseTab.component'
|
||||||
import { Logger, LogService } from '../services/log.service'
|
import { Logger, LogService } from './log.service'
|
||||||
|
import { ConfigService } from './config.service'
|
||||||
|
|
||||||
export declare type TabComponentType = new (...args: any[]) => BaseTabComponent
|
export declare type TabComponentType = new (...args: any[]) => BaseTabComponent
|
||||||
|
|
||||||
@@ -10,14 +11,18 @@ export declare type TabComponentType = new (...args: any[]) => BaseTabComponent
|
|||||||
export class AppService {
|
export class AppService {
|
||||||
tabs: BaseTabComponent[] = []
|
tabs: BaseTabComponent[] = []
|
||||||
activeTab: BaseTabComponent
|
activeTab: BaseTabComponent
|
||||||
|
activeTabChange$ = new Subject<BaseTabComponent>()
|
||||||
lastTabIndex = 0
|
lastTabIndex = 0
|
||||||
logger: Logger
|
logger: Logger
|
||||||
tabsChanged$ = new Subject<void>()
|
tabsChanged$ = new Subject<void>()
|
||||||
|
tabOpened$ = new Subject<BaseTabComponent>()
|
||||||
|
tabClosed$ = new Subject<BaseTabComponent>()
|
||||||
ready$ = new AsyncSubject<void>()
|
ready$ = new AsyncSubject<void>()
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private componentFactoryResolver: ComponentFactoryResolver,
|
private componentFactoryResolver: ComponentFactoryResolver,
|
||||||
@Optional() private defaultTabProvider: DefaultTabProvider,
|
@Optional() private defaultTabProvider: DefaultTabProvider,
|
||||||
|
private config: ConfigService,
|
||||||
private injector: Injector,
|
private injector: Injector,
|
||||||
log: LogService,
|
log: LogService,
|
||||||
) {
|
) {
|
||||||
@@ -33,6 +38,7 @@ export class AppService {
|
|||||||
this.tabs.push(componentRef.instance)
|
this.tabs.push(componentRef.instance)
|
||||||
this.selectTab(componentRef.instance)
|
this.selectTab(componentRef.instance)
|
||||||
this.tabsChanged$.next()
|
this.tabsChanged$.next()
|
||||||
|
this.tabOpened$.next(componentRef.instance)
|
||||||
|
|
||||||
return componentRef.instance
|
return componentRef.instance
|
||||||
}
|
}
|
||||||
@@ -57,6 +63,7 @@ export class AppService {
|
|||||||
this.activeTab.blurred$.next()
|
this.activeTab.blurred$.next()
|
||||||
}
|
}
|
||||||
this.activeTab = tab
|
this.activeTab = tab
|
||||||
|
this.activeTabChange$.next(tab)
|
||||||
if (this.activeTab) {
|
if (this.activeTab) {
|
||||||
this.activeTab.focused$.next()
|
this.activeTab.focused$.next()
|
||||||
}
|
}
|
||||||
@@ -70,16 +77,24 @@ export class AppService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nextTab () {
|
nextTab () {
|
||||||
let tabIndex = this.tabs.indexOf(this.activeTab)
|
if (this.tabs.length > 1) {
|
||||||
if (tabIndex < this.tabs.length - 1) {
|
let tabIndex = this.tabs.indexOf(this.activeTab)
|
||||||
this.selectTab(this.tabs[tabIndex + 1])
|
if (tabIndex < this.tabs.length - 1) {
|
||||||
|
this.selectTab(this.tabs[tabIndex + 1])
|
||||||
|
} else if (this.config.store.appearance.cycleTabs) {
|
||||||
|
this.selectTab(this.tabs[0])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
previousTab () {
|
previousTab () {
|
||||||
let tabIndex = this.tabs.indexOf(this.activeTab)
|
if (this.tabs.length > 1) {
|
||||||
if (tabIndex > 0) {
|
let tabIndex = this.tabs.indexOf(this.activeTab)
|
||||||
this.selectTab(this.tabs[tabIndex - 1])
|
if (tabIndex > 0) {
|
||||||
|
this.selectTab(this.tabs[tabIndex - 1])
|
||||||
|
} else if (this.config.store.appearance.cycleTabs) {
|
||||||
|
this.selectTab(this.tabs[this.tabs.length - 1])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +112,7 @@ export class AppService {
|
|||||||
this.selectTab(this.tabs[newIndex])
|
this.selectTab(this.tabs[newIndex])
|
||||||
}
|
}
|
||||||
this.tabsChanged$.next()
|
this.tabsChanged$.next()
|
||||||
|
this.tabClosed$.next(tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
emitReady () {
|
emitReady () {
|
||||||
|
@@ -29,18 +29,19 @@ export class DockingService {
|
|||||||
let dockSide = this.config.store.appearance.dock
|
let dockSide = this.config.store.appearance.dock
|
||||||
let newBounds: Bounds = { x: 0, y: 0, width: 0, height: 0 }
|
let newBounds: Bounds = { x: 0, y: 0, width: 0, height: 0 }
|
||||||
let fill = this.config.store.appearance.dockFill
|
let fill = this.config.store.appearance.dockFill
|
||||||
|
let [minWidth, minHeight] = this.hostApp.getWindow().getMinimumSize()
|
||||||
|
|
||||||
if (dockSide === 'off') {
|
if (dockSide === 'off') {
|
||||||
this.hostApp.setAlwaysOnTop(false)
|
this.hostApp.setAlwaysOnTop(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (dockSide === 'left' || dockSide === 'right') {
|
if (dockSide === 'left' || dockSide === 'right') {
|
||||||
newBounds.width = Math.round(fill * display.bounds.width)
|
newBounds.width = Math.max(minWidth, Math.round(fill * display.bounds.width))
|
||||||
newBounds.height = display.bounds.height
|
newBounds.height = display.bounds.height
|
||||||
}
|
}
|
||||||
if (dockSide === 'top' || dockSide === 'bottom') {
|
if (dockSide === 'top' || dockSide === 'bottom') {
|
||||||
newBounds.width = display.bounds.width
|
newBounds.width = display.bounds.width
|
||||||
newBounds.height = Math.round(fill * display.bounds.height)
|
newBounds.height = Math.max(minHeight, Math.round(fill * display.bounds.height))
|
||||||
}
|
}
|
||||||
if (dockSide === 'right') {
|
if (dockSide === 'right') {
|
||||||
newBounds.x = display.bounds.x + display.bounds.width - newBounds.width
|
newBounds.x = display.bounds.x + display.bounds.width - newBounds.width
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
|
import { TouchBar } from 'electron'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ElectronService {
|
export class ElectronService {
|
||||||
@@ -10,6 +11,7 @@ export class ElectronService {
|
|||||||
globalShortcut: any
|
globalShortcut: any
|
||||||
screen: any
|
screen: any
|
||||||
remote: any
|
remote: any
|
||||||
|
TouchBar: typeof TouchBar
|
||||||
private electron: any
|
private electron: any
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
@@ -22,6 +24,7 @@ export class ElectronService {
|
|||||||
this.clipboard = this.electron.clipboard
|
this.clipboard = this.electron.clipboard
|
||||||
this.ipcRenderer = this.electron.ipcRenderer
|
this.ipcRenderer = this.electron.ipcRenderer
|
||||||
this.globalShortcut = this.remote.globalShortcut
|
this.globalShortcut = this.remote.globalShortcut
|
||||||
|
this.TouchBar = this.remote.TouchBar
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteRequire (name: string): any {
|
remoteRequire (name: string): any {
|
||||||
@@ -29,6 +32,16 @@ export class ElectronService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remoteRequirePluginModule (plugin: string, module: string, globals: any): any {
|
remoteRequirePluginModule (plugin: string, module: string, globals: any): any {
|
||||||
return this.remoteRequire(globals.require.resolve(`${plugin}/node_modules/${module}`))
|
return this.remoteRequire(this.remoteResolvePluginModule(plugin, module, globals))
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteResolvePluginModule (plugin: string, module: string, globals: any): any {
|
||||||
|
return globals.require.resolve(`${plugin}/node_modules/${module}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
loseFocus () {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
this.remote.Menu.sendActionToFirstResponder('hide:')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,7 +22,7 @@ export class HostAppService {
|
|||||||
ready = new EventEmitter<any>()
|
ready = new EventEmitter<any>()
|
||||||
shown = new EventEmitter<any>()
|
shown = new EventEmitter<any>()
|
||||||
secondInstance$ = new Subject<{ argv: string[], cwd: string }>()
|
secondInstance$ = new Subject<{ argv: string[], cwd: string }>()
|
||||||
|
isFullScreen = false
|
||||||
private logger: Logger
|
private logger: Logger
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
@@ -44,6 +44,14 @@ export class HostAppService {
|
|||||||
this.logger.error('Unhandled exception:', err)
|
this.logger.error('Unhandled exception:', err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
electron.ipcRenderer.on('host:window-enter-full-screen', () => this.zone.run(() => {
|
||||||
|
this.isFullScreen = true
|
||||||
|
}))
|
||||||
|
|
||||||
|
electron.ipcRenderer.on('host:window-leave-full-screen', () => this.zone.run(() => {
|
||||||
|
this.isFullScreen = false
|
||||||
|
}))
|
||||||
|
|
||||||
electron.ipcRenderer.on('host:window-shown', () => {
|
electron.ipcRenderer.on('host:window-shown', () => {
|
||||||
this.zone.run(() => this.shown.emit())
|
this.zone.run(() => this.shown.emit())
|
||||||
})
|
})
|
||||||
@@ -73,18 +81,19 @@ export class HostAppService {
|
|||||||
return this.electron.app.getPath(type)
|
return this.electron.app.getPath(type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleFullscreen () {
|
||||||
|
let window = this.getWindow()
|
||||||
|
window.setFullScreen(!window.isFullScreen())
|
||||||
|
}
|
||||||
|
|
||||||
openDevTools () {
|
openDevTools () {
|
||||||
this.getWindow().webContents.openDevTools()
|
this.getWindow().webContents.openDevTools({ mode: 'undocked' })
|
||||||
}
|
}
|
||||||
|
|
||||||
focusWindow () {
|
focusWindow () {
|
||||||
this.electron.ipcRenderer.send('window-focus')
|
this.electron.ipcRenderer.send('window-focus')
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleWindow () {
|
|
||||||
this.electron.ipcRenderer.send('window-toggle-focus')
|
|
||||||
}
|
|
||||||
|
|
||||||
minimize () {
|
minimize () {
|
||||||
this.electron.ipcRenderer.send('window-minimize')
|
this.electron.ipcRenderer.send('window-minimize')
|
||||||
}
|
}
|
||||||
|
@@ -178,6 +178,10 @@ export class AppHotkeyProvider extends HotkeyProvider {
|
|||||||
id: 'toggle-window',
|
id: 'toggle-window',
|
||||||
name: 'Toggle terminal window',
|
name: 'Toggle terminal window',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'toggle-fullscreen',
|
||||||
|
name: 'Toggle fullscreen mode',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'close-tab',
|
id: 'close-tab',
|
||||||
name: 'Close tab',
|
name: 'Close tab',
|
||||||
|
@@ -5,7 +5,7 @@ export const metaKeyName = {
|
|||||||
}[process.platform]
|
}[process.platform]
|
||||||
|
|
||||||
export const altKeyName = {
|
export const altKeyName = {
|
||||||
darwin: 'Option',
|
darwin: '⌥',
|
||||||
win32: 'Alt',
|
win32: 'Alt',
|
||||||
linux: 'Alt',
|
linux: 'Alt',
|
||||||
}[process.platform]
|
}[process.platform]
|
||||||
|
@@ -41,7 +41,9 @@ export class Logger {
|
|||||||
|
|
||||||
doLog (level: string, ...args: any[]) {
|
doLog (level: string, ...args: any[]) {
|
||||||
console[level](`%c[${this.name}]`, 'color: #aaa', ...args)
|
console[level](`%c[${this.name}]`, 'color: #aaa', ...args)
|
||||||
this.winstonLogger[level](...args)
|
if (this.winstonLogger) {
|
||||||
|
this.winstonLogger[level](...args)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug (...args: any[]) { this.doLog('debug', ...args) }
|
debug (...args: any[]) { this.doLog('debug', ...args) }
|
||||||
|
70
terminus-core/src/services/touchbar.service.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { Injectable, Inject, NgZone } from '@angular/core'
|
||||||
|
import { TouchBarSegmentedControl, SegmentedControlSegment } from 'electron'
|
||||||
|
import { Subject, Subscription } from 'rxjs'
|
||||||
|
import { AppService } from './app.service'
|
||||||
|
import { ConfigService } from './config.service'
|
||||||
|
import { ElectronService } from './electron.service'
|
||||||
|
import { BaseTabComponent } from '../components/baseTab.component'
|
||||||
|
import { IToolbarButton, ToolbarButtonProvider } from '../api'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TouchbarService {
|
||||||
|
tabSelected$ = new Subject<number>()
|
||||||
|
private titleSubscriptions = new Map<BaseTabComponent, Subscription>()
|
||||||
|
private tabsSegmentedControl: TouchBarSegmentedControl
|
||||||
|
private tabSegments: SegmentedControlSegment[] = []
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private app: AppService,
|
||||||
|
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
|
||||||
|
private config: ConfigService,
|
||||||
|
private electron: ElectronService,
|
||||||
|
private zone: NgZone,
|
||||||
|
) {
|
||||||
|
app.tabsChanged$.subscribe(() => this.update())
|
||||||
|
app.activeTabChange$.subscribe(() => this.update())
|
||||||
|
app.tabOpened$.subscribe(tab => {
|
||||||
|
let sub = tab.titleChange$.subscribe(title => {
|
||||||
|
this.tabSegments[app.tabs.indexOf(tab)].label = title
|
||||||
|
this.tabsSegmentedControl.segments = this.tabSegments
|
||||||
|
})
|
||||||
|
this.titleSubscriptions.set(tab, sub)
|
||||||
|
})
|
||||||
|
app.tabClosed$.subscribe(tab => {
|
||||||
|
this.titleSubscriptions.get(tab).unsubscribe()
|
||||||
|
this.titleSubscriptions.delete(tab)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
update () {
|
||||||
|
let buttons: IToolbarButton[] = []
|
||||||
|
this.config.enabledServices(this.toolbarButtonProviders).forEach(provider => {
|
||||||
|
buttons = buttons.concat(provider.provide())
|
||||||
|
})
|
||||||
|
buttons.sort((a, b) => (a.weight || 0) - (b.weight || 0))
|
||||||
|
this.tabSegments = this.app.tabs.map(tab => ({
|
||||||
|
label: tab.title,
|
||||||
|
}))
|
||||||
|
this.tabsSegmentedControl = new this.electron.TouchBar.TouchBarSegmentedControl({
|
||||||
|
segments: this.tabSegments,
|
||||||
|
selectedIndex: this.app.tabs.indexOf(this.app.activeTab),
|
||||||
|
change: (selectedIndex) => this.zone.run(() => {
|
||||||
|
this.app.selectTab(this.app.tabs[selectedIndex])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
let touchBar = new this.electron.TouchBar({
|
||||||
|
items: [
|
||||||
|
this.tabsSegmentedControl,
|
||||||
|
new this.electron.TouchBar.TouchBarSpacer({size: 'flexible'}),
|
||||||
|
new this.electron.TouchBar.TouchBarSpacer({size: 'small'}),
|
||||||
|
...buttons.map(button => new this.electron.TouchBar.TouchBarButton({
|
||||||
|
label: button.title,
|
||||||
|
// backgroundColor: '#0022cc',
|
||||||
|
click: () => this.zone.run(() => button.click()),
|
||||||
|
}))
|
||||||
|
]
|
||||||
|
})
|
||||||
|
this.electron.app.window.setTouchBar(touchBar)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -105,7 +105,7 @@ window-controls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$border-color: #141414;
|
$border-color: #111;
|
||||||
|
|
||||||
app-root {
|
app-root {
|
||||||
&> .content {
|
&> .content {
|
||||||
@@ -131,7 +131,7 @@ app-root {
|
|||||||
background: $body-bg2;
|
background: $body-bg2;
|
||||||
border-left: 1px solid transparent;
|
border-left: 1px solid transparent;
|
||||||
border-right: 1px solid transparent;
|
border-right: 1px solid transparent;
|
||||||
border-top: 1px solid transparent;
|
transition: 0.25s all;
|
||||||
|
|
||||||
.index {
|
.index {
|
||||||
color: #555;
|
color: #555;
|
||||||
@@ -159,17 +159,15 @@ app-root {
|
|||||||
border-bottom: 1px solid $border-color;
|
border-bottom: 1px solid $border-color;
|
||||||
|
|
||||||
tab-header {
|
tab-header {
|
||||||
border-top: 1px solid transparent;
|
|
||||||
border-bottom: 1px solid $border-color;
|
border-bottom: 1px solid $border-color;
|
||||||
margin-bottom: -1px;
|
margin-bottom: -1px;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
border-top: 1px solid $teal;
|
|
||||||
border-bottom-color: transparent;
|
border-bottom-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.has-activity:not(.active) {
|
&.has-activity:not(.active) {
|
||||||
border-top: 1px solid $green;
|
background: linear-gradient(to bottom, rgba(208, 0, 0, 0) 95%, #1aa99c 100%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,17 +176,15 @@ app-root {
|
|||||||
border-top: 1px solid $border-color;
|
border-top: 1px solid $border-color;
|
||||||
|
|
||||||
tab-header {
|
tab-header {
|
||||||
border-bottom: 1px solid transparent;
|
|
||||||
border-top: 1px solid $border-color;
|
border-top: 1px solid $border-color;
|
||||||
margin-top: -1px;
|
margin-top: -1px;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
border-bottom: 1px solid $teal;
|
|
||||||
margin-top: -1px;
|
margin-top: -1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.has-activity:not(.active) {
|
&.has-activity:not(.active) {
|
||||||
border-bottom: 1px solid $green;
|
background: linear-gradient(to top, rgba(208, 0, 0, 0) 95%, #1aa99c 100%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -45,7 +45,7 @@ export class PluginManagerService {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.hostApp.platform !== Platform.Windows) {
|
if (this.hostApp.platform !== Platform.Windows) {
|
||||||
let searchPaths = (await exec('bash -c -l "echo $PATH"'))[0].toString().trim().split(':')
|
let searchPaths = (await exec('$SHELL -c -i \'echo $PATH\''))[0].toString().trim().split(':')
|
||||||
for (let searchPath of searchPaths) {
|
for (let searchPath of searchPaths) {
|
||||||
if (await fs.exists(path.join(searchPath, 'npm'))) {
|
if (await fs.exists(path.join(searchPath, 'npm'))) {
|
||||||
this.logger.debug('Found npm in', searchPath)
|
this.logger.debug('Found npm in', searchPath)
|
||||||
@@ -69,7 +69,11 @@ export class PluginManagerService {
|
|||||||
listAvailable (query?: string): Observable<IPluginInfo[]> {
|
listAvailable (query?: string): Observable<IPluginInfo[]> {
|
||||||
return Observable
|
return Observable
|
||||||
.fromPromise(
|
.fromPromise(
|
||||||
axios.get(`https://www.npmjs.com/-/search?text=keywords:${KEYWORD}+${encodeURIComponent(query || '')}&from=0&size=1000`)
|
axios.get(`https://www.npmjs.com/search?q=keywords%3A${KEYWORD}+${encodeURIComponent(query || '')}&from=0&size=1000`, {
|
||||||
|
headers: {
|
||||||
|
'x-spiferack': '1',
|
||||||
|
}
|
||||||
|
})
|
||||||
)
|
)
|
||||||
.map(response => response.data.objects.map(item => ({
|
.map(response => response.data.objects.map(item => ({
|
||||||
name: item.package.name.substring(NAME_PREFIX.length),
|
name: item.package.name.substring(NAME_PREFIX.length),
|
||||||
|
@@ -28,7 +28,7 @@ export class SettingsTabComponent extends BaseTabComponent {
|
|||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.hotkeyDescriptions = config.enabledServices(hotkeyProviders).map(x => x.hotkeys).reduce((a, b) => a.concat(b))
|
this.hotkeyDescriptions = config.enabledServices(hotkeyProviders).map(x => x.hotkeys).reduce((a, b) => a.concat(b))
|
||||||
this.title = 'Settings'
|
this.setTitle('Settings')
|
||||||
this.scrollable = true
|
this.scrollable = true
|
||||||
this.screens = this.docking.getScreens()
|
this.screens = this.docking.getScreens()
|
||||||
this.settingsProviders = config.enabledServices(this.settingsProviders)
|
this.settingsProviders = config.enabledServices(this.settingsProviders)
|
||||||
|
@@ -22,6 +22,7 @@
|
|||||||
"apply-loader": "^2.0.0",
|
"apply-loader": "^2.0.0",
|
||||||
"awesome-typescript-loader": "^3.1.2",
|
"awesome-typescript-loader": "^3.1.2",
|
||||||
"electron": "^1.6.11",
|
"electron": "^1.6.11",
|
||||||
|
"ngx-toastr": "^8.0.0",
|
||||||
"pug": "^2.0.0-rc.3",
|
"pug": "^2.0.0-rc.3",
|
||||||
"pug-loader": "^2.3.0",
|
"pug-loader": "^2.3.0",
|
||||||
"rxjs": "^5.4.0",
|
"rxjs": "^5.4.0",
|
||||||
|
@@ -3,6 +3,7 @@ import { BaseSession } from 'terminus-terminal'
|
|||||||
export interface SSHConnection {
|
export interface SSHConnection {
|
||||||
name?: string
|
name?: string
|
||||||
host: string
|
host: string
|
||||||
|
port: number
|
||||||
user: string
|
user: string
|
||||||
password?: string
|
password?: string
|
||||||
privateKey?: string
|
privateKey?: string
|
||||||
|
@@ -18,17 +18,14 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
activate () {
|
activate () {
|
||||||
let modal = this.ngbModal.open(SSHModalComponent)
|
this.ngbModal.open(SSHModalComponent)
|
||||||
modal.result.then(() => {
|
|
||||||
//this.terminal.openTab(shell)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
provide (): IToolbarButton[] {
|
provide (): IToolbarButton[] {
|
||||||
return [{
|
return [{
|
||||||
icon: 'globe',
|
icon: 'globe',
|
||||||
weight: 5,
|
weight: 5,
|
||||||
title: 'SSH connections',
|
title: 'SSH',
|
||||||
click: async () => {
|
click: async () => {
|
||||||
this.activate()
|
this.activate()
|
||||||
}
|
}
|
||||||
|
@@ -13,6 +13,14 @@
|
|||||||
[(ngModel)]='connection.host',
|
[(ngModel)]='connection.host',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
label Port
|
||||||
|
input.form-control(
|
||||||
|
type='number',
|
||||||
|
placeholder='22',
|
||||||
|
[(ngModel)]='connection.port',
|
||||||
|
)
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
label Username
|
label Username
|
||||||
input.form-control(
|
input.form-control(
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
type='text',
|
type='text',
|
||||||
[(ngModel)]='quickTarget',
|
[(ngModel)]='quickTarget',
|
||||||
autofocus,
|
autofocus,
|
||||||
placeholder='Quick connect: [user@]host',
|
placeholder='Quick connect: [user@]host[:port]',
|
||||||
(keyup.enter)='quickConnect()'
|
(keyup.enter)='quickConnect()'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { ToastrService } from 'ngx-toastr'
|
||||||
import { ConfigService, AppService } from 'terminus-core'
|
import { ConfigService, AppService } from 'terminus-core'
|
||||||
import { SettingsTabComponent } from 'terminus-settings'
|
import { SettingsTabComponent } from 'terminus-settings'
|
||||||
import { SSHService } from '../services/ssh.service'
|
import { SSHService } from '../services/ssh.service'
|
||||||
@@ -19,6 +20,7 @@ export class SSHModalComponent {
|
|||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
private ssh: SSHService,
|
private ssh: SSHService,
|
||||||
private app: AppService,
|
private app: AppService,
|
||||||
|
private toastr: ToastrService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
@@ -31,12 +33,18 @@ export class SSHModalComponent {
|
|||||||
quickConnect () {
|
quickConnect () {
|
||||||
let user = 'root'
|
let user = 'root'
|
||||||
let host = this.quickTarget
|
let host = this.quickTarget
|
||||||
|
let port = 22
|
||||||
if (host.includes('@')) {
|
if (host.includes('@')) {
|
||||||
[user, host] = host.split('@')
|
[user, host] = host.split('@')
|
||||||
}
|
}
|
||||||
|
if (host.includes(':')) {
|
||||||
|
port = parseInt(host.split(':')[1])
|
||||||
|
host = host.split(':')[0]
|
||||||
|
}
|
||||||
|
|
||||||
let connection: SSHConnection = {
|
let connection: SSHConnection = {
|
||||||
name: this.quickTarget,
|
name: this.quickTarget,
|
||||||
host, user,
|
host, user, port
|
||||||
}
|
}
|
||||||
window.localStorage.lastConnection = JSON.stringify(connection)
|
window.localStorage.lastConnection = JSON.stringify(connection)
|
||||||
this.connect(connection)
|
this.connect(connection)
|
||||||
@@ -45,7 +53,7 @@ export class SSHModalComponent {
|
|||||||
connect (connection: SSHConnection) {
|
connect (connection: SSHConnection) {
|
||||||
this.close()
|
this.close()
|
||||||
this.ssh.connect(connection).catch(error => {
|
this.ssh.connect(connection).catch(error => {
|
||||||
alert(`Could not connect: ${error}`)
|
this.toastr.error(`Could not connect: ${error}`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,6 +21,7 @@ export class SSHSettingsTabComponent {
|
|||||||
let connection: SSHConnection = {
|
let connection: SSHConnection = {
|
||||||
name: '',
|
name: '',
|
||||||
host: '',
|
host: '',
|
||||||
|
port: 22,
|
||||||
user: 'root',
|
user: 'root',
|
||||||
}
|
}
|
||||||
let modal = this.ngbModal.open(EditConnectionModalComponent)
|
let modal = this.ngbModal.open(EditConnectionModalComponent)
|
||||||
|
@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core'
|
|||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { FormsModule } from '@angular/forms'
|
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 { ToolbarButtonProvider, ConfigProvider } from 'terminus-core'
|
import { ToolbarButtonProvider, ConfigProvider } from 'terminus-core'
|
||||||
import { SettingsTabProvider } from 'terminus-settings'
|
import { SettingsTabProvider } from 'terminus-settings'
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ import { SSHModalComponent } from './components/sshModal.component'
|
|||||||
import { PromptModalComponent } from './components/promptModal.component'
|
import { PromptModalComponent } from './components/promptModal.component'
|
||||||
import { SSHSettingsTabComponent } from './components/sshSettingsTab.component'
|
import { SSHSettingsTabComponent } from './components/sshSettingsTab.component'
|
||||||
import { SSHService } from './services/ssh.service'
|
import { SSHService } from './services/ssh.service'
|
||||||
|
import { PasswordStorageService } from './services/passwordStorage.service'
|
||||||
|
|
||||||
import { ButtonProvider } from './buttonProvider'
|
import { ButtonProvider } from './buttonProvider'
|
||||||
import { SSHConfigProvider } from './config'
|
import { SSHConfigProvider } from './config'
|
||||||
@@ -20,8 +22,10 @@ import { SSHSettingsTabProvider } from './settings'
|
|||||||
NgbModule,
|
NgbModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
|
ToastrModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
PasswordStorageService,
|
||||||
SSHService,
|
SSHService,
|
||||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||||
{ provide: ConfigProvider, useClass: SSHConfigProvider, multi: true },
|
{ provide: ConfigProvider, useClass: SSHConfigProvider, multi: true },
|
||||||
|
73
terminus-ssh/src/services/passwordStorage.service.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { Injectable, NgZone } from '@angular/core'
|
||||||
|
import { SSHConnection } from '../api'
|
||||||
|
|
||||||
|
let xkeychain
|
||||||
|
let wincredmgr
|
||||||
|
try {
|
||||||
|
xkeychain = require('xkeychain')
|
||||||
|
} catch (error) {
|
||||||
|
try {
|
||||||
|
wincredmgr = require('wincredmgr')
|
||||||
|
} catch (error2) {
|
||||||
|
console.warn('No keychain manager available')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PasswordStorageService {
|
||||||
|
constructor (
|
||||||
|
private zone: NgZone,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
savePassword (connection: SSHConnection, password: string) {
|
||||||
|
if (xkeychain) {
|
||||||
|
xkeychain.setPassword({
|
||||||
|
account: connection.user,
|
||||||
|
service: `ssh@${connection.host}`,
|
||||||
|
password
|
||||||
|
}, () => null)
|
||||||
|
} else {
|
||||||
|
wincredmgr.WriteCredentials(
|
||||||
|
'user',
|
||||||
|
password,
|
||||||
|
`ssh:${connection.user}@${connection.host}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deletePassword (connection: SSHConnection) {
|
||||||
|
if (xkeychain) {
|
||||||
|
xkeychain.deletePassword({
|
||||||
|
account: connection.user,
|
||||||
|
service: `ssh@${connection.host}`,
|
||||||
|
}, () => null)
|
||||||
|
} else {
|
||||||
|
wincredmgr.DeleteCredentials(
|
||||||
|
`ssh:${connection.user}@${connection.host}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPassword (connection: SSHConnection): Promise<string> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
if (!wincredmgr && !xkeychain.isSupported()) {
|
||||||
|
return resolve(null)
|
||||||
|
}
|
||||||
|
if (xkeychain) {
|
||||||
|
xkeychain.getPassword(
|
||||||
|
{
|
||||||
|
account: connection.user,
|
||||||
|
service: `ssh@${connection.host}`,
|
||||||
|
},
|
||||||
|
(_, result) => this.zone.run(() => resolve(result))
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
resolve(wincredmgr.ReadCredentials(`ssh:${connection.user}@${connection.host}`).password)
|
||||||
|
} catch (error) {
|
||||||
|
resolve(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -3,92 +3,61 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
|||||||
import { Client } from 'ssh2'
|
import { Client } from 'ssh2'
|
||||||
import * as fs from 'mz/fs'
|
import * as fs from 'mz/fs'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import { AppService, HostAppService, Platform } from 'terminus-core'
|
import { ToastrService } from 'ngx-toastr'
|
||||||
|
import { AppService, HostAppService, Platform, Logger, LogService } from 'terminus-core'
|
||||||
import { TerminalTabComponent } from 'terminus-terminal'
|
import { TerminalTabComponent } from 'terminus-terminal'
|
||||||
import { SSHConnection, SSHSession } from '../api'
|
import { SSHConnection, SSHSession } from '../api'
|
||||||
import { PromptModalComponent } from '../components/promptModal.component'
|
import { PromptModalComponent } from '../components/promptModal.component'
|
||||||
|
import { PasswordStorageService } from './passwordStorage.service'
|
||||||
const { SSH2Stream } = require('ssh2-streams')
|
const { SSH2Stream } = require('ssh2-streams')
|
||||||
|
|
||||||
let xkeychain
|
|
||||||
let wincredmgr
|
|
||||||
try {
|
|
||||||
xkeychain = require('xkeychain')
|
|
||||||
} catch (error) {
|
|
||||||
try {
|
|
||||||
wincredmgr = require('wincredmgr')
|
|
||||||
} catch (error2) {
|
|
||||||
console.warn('No keychain manager available')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SSHService {
|
export class SSHService {
|
||||||
|
private logger: Logger
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
|
log: LogService,
|
||||||
private app: AppService,
|
private app: AppService,
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
private ngbModal: NgbModal,
|
private ngbModal: NgbModal,
|
||||||
private hostApp: HostAppService,
|
private hostApp: HostAppService,
|
||||||
|
private passwordStorage: PasswordStorageService,
|
||||||
|
private toastr: ToastrService,
|
||||||
) {
|
) {
|
||||||
}
|
this.logger = log.create('ssh')
|
||||||
|
|
||||||
savePassword (connection: SSHConnection, password: string) {
|
|
||||||
if (xkeychain) {
|
|
||||||
xkeychain.setPassword({
|
|
||||||
account: connection.user,
|
|
||||||
service: `ssh@${connection.host}`,
|
|
||||||
password
|
|
||||||
}, () => null)
|
|
||||||
} else {
|
|
||||||
wincredmgr.WriteCredentials(
|
|
||||||
'user',
|
|
||||||
password,
|
|
||||||
`ssh:${connection.user}@${connection.host}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deletePassword (connection: SSHConnection) {
|
|
||||||
if (xkeychain) {
|
|
||||||
xkeychain.deletePassword({
|
|
||||||
account: connection.user,
|
|
||||||
service: `ssh@${connection.host}`,
|
|
||||||
}, () => null)
|
|
||||||
} else {
|
|
||||||
wincredmgr.DeleteCredentials(
|
|
||||||
`ssh:${connection.user}@${connection.host}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadPassword (connection: SSHConnection): Promise<string> {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
if (xkeychain) {
|
|
||||||
xkeychain.getPassword({
|
|
||||||
account: connection.user,
|
|
||||||
service: `ssh@${connection.host}`,
|
|
||||||
}, (_, result) => resolve(result))
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
resolve(wincredmgr.ReadCredentials(`ssh:${connection.user}@${connection.host}`).password)
|
|
||||||
} catch (error) {
|
|
||||||
resolve(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect (connection: SSHConnection): Promise<TerminalTabComponent> {
|
async connect (connection: SSHConnection): Promise<TerminalTabComponent> {
|
||||||
let privateKey: string = null
|
let privateKey: string = null
|
||||||
let keyPath = path.join(process.env.HOME, '.ssh', 'id_rsa')
|
let privateKeyPassphrase: string = null
|
||||||
if (!connection.privateKey && await fs.exists(keyPath)) {
|
let privateKeyPath = connection.privateKey
|
||||||
connection.privateKey = keyPath
|
if (!privateKeyPath) {
|
||||||
|
let userKeyPath = path.join(process.env.HOME, '.ssh', 'id_rsa')
|
||||||
|
if (await fs.exists(userKeyPath)) {
|
||||||
|
this.logger.info('Using user\'s default private key:', userKeyPath)
|
||||||
|
privateKeyPath = userKeyPath
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connection.privateKey) {
|
if (privateKeyPath) {
|
||||||
try {
|
try {
|
||||||
privateKey = (await fs.readFile(connection.privateKey)).toString()
|
privateKey = (await fs.readFile(privateKeyPath)).toString()
|
||||||
} catch (error) { }
|
} catch (error) {
|
||||||
|
this.toastr.warning('Could not read the private key file')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (privateKey) {
|
||||||
|
this.logger.info('Loaded private key from', privateKeyPath)
|
||||||
|
|
||||||
|
if (privateKey.includes('ENCRYPTED')) {
|
||||||
|
let modal = this.ngbModal.open(PromptModalComponent)
|
||||||
|
modal.componentInstance.prompt = 'Private key passphrase'
|
||||||
|
modal.componentInstance.password = true
|
||||||
|
try {
|
||||||
|
privateKeyPassphrase = await modal.result
|
||||||
|
} catch (_err) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ssh = new Client()
|
let ssh = new Client()
|
||||||
@@ -98,15 +67,15 @@ export class SSHService {
|
|||||||
ssh.on('ready', () => {
|
ssh.on('ready', () => {
|
||||||
connected = true
|
connected = true
|
||||||
if (savedPassword) {
|
if (savedPassword) {
|
||||||
this.savePassword(connection, savedPassword)
|
this.passwordStorage.savePassword(connection, savedPassword)
|
||||||
}
|
}
|
||||||
this.zone.run(resolve)
|
this.zone.run(resolve)
|
||||||
})
|
})
|
||||||
ssh.on('error', error => {
|
ssh.on('error', error => {
|
||||||
this.deletePassword(connection)
|
this.passwordStorage.deletePassword(connection)
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
if (connected) {
|
if (connected) {
|
||||||
alert(`SSH error: ${error}`)
|
this.toastr.error(error.toString())
|
||||||
} else {
|
} else {
|
||||||
reject(error)
|
reject(error)
|
||||||
}
|
}
|
||||||
@@ -133,9 +102,11 @@ export class SSHService {
|
|||||||
|
|
||||||
ssh.connect({
|
ssh.connect({
|
||||||
host: connection.host,
|
host: connection.host,
|
||||||
|
port: connection.port || 22,
|
||||||
username: connection.user,
|
username: connection.user,
|
||||||
password: privateKey ? undefined : '',
|
password: connection.privateKey ? undefined : '',
|
||||||
privateKey,
|
privateKey,
|
||||||
|
passphrase: privateKeyPassphrase,
|
||||||
tryKeyboard: true,
|
tryKeyboard: true,
|
||||||
agent,
|
agent,
|
||||||
agentForward: !!agent,
|
agentForward: !!agent,
|
||||||
@@ -145,12 +116,14 @@ export class SSHService {
|
|||||||
|
|
||||||
;(ssh as any).config.password = () => this.zone.run(async () => {
|
;(ssh as any).config.password = () => this.zone.run(async () => {
|
||||||
if (connection.password) {
|
if (connection.password) {
|
||||||
|
this.logger.info('Using preset password')
|
||||||
return connection.password
|
return connection.password
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!keychainPasswordUsed && (wincredmgr || xkeychain.isSupported())) {
|
if (!keychainPasswordUsed) {
|
||||||
let password = await this.loadPassword(connection)
|
let password = await this.passwordStorage.loadPassword(connection)
|
||||||
if (password) {
|
if (password) {
|
||||||
|
this.logger.info('Using saved password')
|
||||||
keychainPasswordUsed = true
|
keychainPasswordUsed = true
|
||||||
return password
|
return password
|
||||||
}
|
}
|
||||||
|
@@ -40,6 +40,7 @@ module.exports = {
|
|||||||
'xkeychain',
|
'xkeychain',
|
||||||
'wincredmgr',
|
'wincredmgr',
|
||||||
'path',
|
'path',
|
||||||
|
'ngx-toastr',
|
||||||
/^rxjs/,
|
/^rxjs/,
|
||||||
/^@angular/,
|
/^@angular/,
|
||||||
/^@ng-bootstrap/,
|
/^@ng-bootstrap/,
|
||||||
|
@@ -2,9 +2,13 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
"@types/node@*", "@types/node@^8.0.24":
|
"@types/node@*":
|
||||||
version "8.0.53"
|
version "9.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.53.tgz#396b35af826fa66aad472c8cb7b8d5e277f4e6d8"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-9.3.0.tgz#3a129cda7c4e5df2409702626892cb4b96546dd5"
|
||||||
|
|
||||||
|
"@types/node@^7.0.18":
|
||||||
|
version "7.0.52"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.52.tgz#8990d3350375542b0c21a83cd0331e6a8fc86716"
|
||||||
|
|
||||||
"@types/ssh2-streams@*":
|
"@types/ssh2-streams@*":
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
@@ -20,8 +24,8 @@
|
|||||||
"@types/ssh2-streams" "*"
|
"@types/ssh2-streams" "*"
|
||||||
|
|
||||||
"@types/webpack-env@^1.13.0":
|
"@types/webpack-env@^1.13.0":
|
||||||
version "1.13.2"
|
version "1.13.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.13.2.tgz#c290b99dbef74df21b06671aea36e355bf3b27e1"
|
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.13.3.tgz#0ecbe70f87341767793774d3683b51aa3246434c"
|
||||||
|
|
||||||
abbrev@1:
|
abbrev@1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
@@ -48,8 +52,8 @@ acorn@^4.0.3, acorn@^4.0.4, acorn@~4.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
|
||||||
|
|
||||||
acorn@^5.0.0:
|
acorn@^5.0.0:
|
||||||
version "5.2.1"
|
version "5.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.2.1.tgz#317ac7821826c22c702d66189ab8359675f135d7"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.3.0.tgz#7446d39459c54fb49a80e6ee6478149b940ec822"
|
||||||
|
|
||||||
ajv-keywords@^1.1.1:
|
ajv-keywords@^1.1.1:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
@@ -63,8 +67,8 @@ ajv@^4.7.0, ajv@^4.9.1:
|
|||||||
json-stable-stringify "^1.0.1"
|
json-stable-stringify "^1.0.1"
|
||||||
|
|
||||||
ajv@^5.1.0:
|
ajv@^5.1.0:
|
||||||
version "5.5.0"
|
version "5.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.0.tgz#eb2840746e9dc48bd5e063a36e3fd400c5eab5a9"
|
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
|
||||||
dependencies:
|
dependencies:
|
||||||
co "^4.6.0"
|
co "^4.6.0"
|
||||||
fast-deep-equal "^1.0.0"
|
fast-deep-equal "^1.0.0"
|
||||||
@@ -171,6 +175,10 @@ assert@^1.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
util "0.10.3"
|
util "0.10.3"
|
||||||
|
|
||||||
|
assign-symbols@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
|
||||||
|
|
||||||
async-each@^1.0.0:
|
async-each@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
|
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
|
||||||
@@ -190,8 +198,8 @@ atob@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d"
|
resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d"
|
||||||
|
|
||||||
awesome-typescript-loader@^3.1.2:
|
awesome-typescript-loader@^3.1.2:
|
||||||
version "3.4.0"
|
version "3.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/awesome-typescript-loader/-/awesome-typescript-loader-3.4.0.tgz#aed2c83af614d617d11e3ec368ac3befb55d002f"
|
resolved "https://registry.yarnpkg.com/awesome-typescript-loader/-/awesome-typescript-loader-3.4.1.tgz#22fa49800f0619ec18ab15383aef93b95378dea9"
|
||||||
dependencies:
|
dependencies:
|
||||||
colors "^1.1.2"
|
colors "^1.1.2"
|
||||||
enhanced-resolve "3.3.0"
|
enhanced-resolve "3.3.0"
|
||||||
@@ -456,13 +464,12 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
|
|||||||
safe-buffer "^5.0.1"
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
class-utils@^0.3.5:
|
class-utils@^0.3.5:
|
||||||
version "0.3.5"
|
version "0.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.5.tgz#17e793103750f9627b2176ea34cfd1b565903c80"
|
resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
|
||||||
dependencies:
|
dependencies:
|
||||||
arr-union "^3.1.0"
|
arr-union "^3.1.0"
|
||||||
define-property "^0.2.5"
|
define-property "^0.2.5"
|
||||||
isobject "^3.0.0"
|
isobject "^3.0.0"
|
||||||
lazy-cache "^2.0.2"
|
|
||||||
static-extend "^0.1.1"
|
static-extend "^0.1.1"
|
||||||
|
|
||||||
clean-css@^3.3.0:
|
clean-css@^3.3.0:
|
||||||
@@ -721,10 +728,10 @@ electron-download@^3.0.1:
|
|||||||
sumchecker "^1.2.0"
|
sumchecker "^1.2.0"
|
||||||
|
|
||||||
electron@^1.6.11:
|
electron@^1.6.11:
|
||||||
version "1.8.1"
|
version "1.7.10"
|
||||||
resolved "https://registry.yarnpkg.com/electron/-/electron-1.8.1.tgz#19b6f39f2013e204a91a60bc3086dc7a4a07ed88"
|
resolved "https://registry.yarnpkg.com/electron/-/electron-1.7.10.tgz#3a3e83d965fd7fafe473be8ddf8f472561b6253d"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "^8.0.24"
|
"@types/node" "^7.0.18"
|
||||||
electron-download "^3.0.1"
|
electron-download "^3.0.1"
|
||||||
extract-zip "^1.0.3"
|
extract-zip "^1.0.3"
|
||||||
|
|
||||||
@@ -744,7 +751,7 @@ emojis-list@^2.0.0:
|
|||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
|
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
|
||||||
|
|
||||||
enhanced-resolve@3.3.0, enhanced-resolve@^3.3.0:
|
enhanced-resolve@3.3.0:
|
||||||
version "3.3.0"
|
version "3.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.3.0.tgz#950964ecc7f0332a42321b673b38dc8ff15535b3"
|
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.3.0.tgz#950964ecc7f0332a42321b673b38dc8ff15535b3"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -753,11 +760,20 @@ enhanced-resolve@3.3.0, enhanced-resolve@^3.3.0:
|
|||||||
object-assign "^4.0.1"
|
object-assign "^4.0.1"
|
||||||
tapable "^0.2.5"
|
tapable "^0.2.5"
|
||||||
|
|
||||||
errno@^0.1.3:
|
enhanced-resolve@^3.3.0:
|
||||||
version "0.1.4"
|
version "3.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d"
|
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e"
|
||||||
dependencies:
|
dependencies:
|
||||||
prr "~0.0.0"
|
graceful-fs "^4.1.2"
|
||||||
|
memory-fs "^0.4.0"
|
||||||
|
object-assign "^4.0.1"
|
||||||
|
tapable "^0.2.7"
|
||||||
|
|
||||||
|
errno@^0.1.3:
|
||||||
|
version "0.1.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.6.tgz#c386ce8a6283f14fc09563b71560908c9bf53026"
|
||||||
|
dependencies:
|
||||||
|
prr "~1.0.1"
|
||||||
|
|
||||||
error-ex@^1.2.0:
|
error-ex@^1.2.0:
|
||||||
version "1.3.1"
|
version "1.3.1"
|
||||||
@@ -766,8 +782,8 @@ error-ex@^1.2.0:
|
|||||||
is-arrayish "^0.2.1"
|
is-arrayish "^0.2.1"
|
||||||
|
|
||||||
es6-promise@^4.0.5:
|
es6-promise@^4.0.5:
|
||||||
version "4.1.1"
|
version "4.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.1.1.tgz#8811e90915d9a0dba36274f0b242dbda78f9c92a"
|
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.2.tgz#f722d7769af88bd33bc13ec6605e1f92966b82d9"
|
||||||
|
|
||||||
events@^1.0.0:
|
events@^1.0.0:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
@@ -811,9 +827,10 @@ extend-shallow@^2.0.1:
|
|||||||
is-extendable "^0.1.0"
|
is-extendable "^0.1.0"
|
||||||
|
|
||||||
extend-shallow@^3.0.0:
|
extend-shallow@^3.0.0:
|
||||||
version "3.0.1"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.1.tgz#4b6d8c49b147fee029dc9eb9484adb770f689844"
|
resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
assign-symbols "^1.0.0"
|
||||||
is-extendable "^1.0.1"
|
is-extendable "^1.0.1"
|
||||||
|
|
||||||
extend@~3.0.0, extend@~3.0.1:
|
extend@~3.0.0, extend@~3.0.1:
|
||||||
@@ -827,8 +844,8 @@ extglob@^0.3.1:
|
|||||||
is-extglob "^1.0.0"
|
is-extglob "^1.0.0"
|
||||||
|
|
||||||
extglob@^2.0.2:
|
extglob@^2.0.2:
|
||||||
version "2.0.2"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.2.tgz#3290f46208db1b2e8eb8be0c94ed9e6ad80edbe2"
|
resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543"
|
||||||
dependencies:
|
dependencies:
|
||||||
array-unique "^0.3.2"
|
array-unique "^0.3.2"
|
||||||
define-property "^1.0.0"
|
define-property "^1.0.0"
|
||||||
@@ -848,10 +865,14 @@ extract-zip@^1.0.3:
|
|||||||
mkdirp "0.5.0"
|
mkdirp "0.5.0"
|
||||||
yauzl "2.4.1"
|
yauzl "2.4.1"
|
||||||
|
|
||||||
extsprintf@1.3.0, extsprintf@^1.2.0:
|
extsprintf@1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
||||||
|
|
||||||
|
extsprintf@^1.2.0:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
||||||
|
|
||||||
fast-deep-equal@^1.0.0:
|
fast-deep-equal@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
|
||||||
@@ -1216,8 +1237,8 @@ ini@~1.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
||||||
|
|
||||||
interpret@^1.0.0:
|
interpret@^1.0.0:
|
||||||
version "1.0.4"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.4.tgz#820cdd588b868ffb191a809506d6c9c8f212b1b0"
|
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
|
||||||
|
|
||||||
invert-kv@^1.0.0:
|
invert-kv@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
@@ -1229,6 +1250,12 @@ is-accessor-descriptor@^0.1.6:
|
|||||||
dependencies:
|
dependencies:
|
||||||
kind-of "^3.0.2"
|
kind-of "^3.0.2"
|
||||||
|
|
||||||
|
is-accessor-descriptor@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656"
|
||||||
|
dependencies:
|
||||||
|
kind-of "^6.0.0"
|
||||||
|
|
||||||
is-arrayish@^0.2.1:
|
is-arrayish@^0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
|
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
|
||||||
@@ -1255,6 +1282,12 @@ is-data-descriptor@^0.1.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
kind-of "^3.0.2"
|
kind-of "^3.0.2"
|
||||||
|
|
||||||
|
is-data-descriptor@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7"
|
||||||
|
dependencies:
|
||||||
|
kind-of "^6.0.0"
|
||||||
|
|
||||||
is-descriptor@^0.1.0:
|
is-descriptor@^0.1.0:
|
||||||
version "0.1.6"
|
version "0.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
|
resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
|
||||||
@@ -1264,12 +1297,12 @@ is-descriptor@^0.1.0:
|
|||||||
kind-of "^5.0.0"
|
kind-of "^5.0.0"
|
||||||
|
|
||||||
is-descriptor@^1.0.0:
|
is-descriptor@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.1.tgz#2c6023599bde2de9d5d2c8b9a9d94082036b6ef2"
|
resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec"
|
||||||
dependencies:
|
dependencies:
|
||||||
is-accessor-descriptor "^0.1.6"
|
is-accessor-descriptor "^1.0.0"
|
||||||
is-data-descriptor "^0.1.4"
|
is-data-descriptor "^1.0.0"
|
||||||
kind-of "^5.0.0"
|
kind-of "^6.0.2"
|
||||||
|
|
||||||
is-dotfile@^1.0.0:
|
is-dotfile@^1.0.0:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
@@ -1475,9 +1508,9 @@ kind-of@^5.0.0, kind-of@^5.0.2:
|
|||||||
version "5.1.0"
|
version "5.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
|
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
|
||||||
|
|
||||||
kind-of@^6.0.0:
|
kind-of@^6.0.0, kind-of@^6.0.2:
|
||||||
version "6.0.1"
|
version "6.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.1.tgz#4948e6263553ac3712fc44d305b77851d9e40ea4"
|
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
|
||||||
|
|
||||||
klaw@^1.0.0:
|
klaw@^1.0.0:
|
||||||
version "1.3.1"
|
version "1.3.1"
|
||||||
@@ -1609,8 +1642,8 @@ micromatch@^2.1.5:
|
|||||||
regex-cache "^0.4.2"
|
regex-cache "^0.4.2"
|
||||||
|
|
||||||
micromatch@^3.0.3:
|
micromatch@^3.0.3:
|
||||||
version "3.1.4"
|
version "3.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.4.tgz#bb812e741a41f982c854e42b421a7eac458796f4"
|
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.5.tgz#d05e168c206472dfbca985bfef4f57797b4cd4ba"
|
||||||
dependencies:
|
dependencies:
|
||||||
arr-diff "^4.0.0"
|
arr-diff "^4.0.0"
|
||||||
array-unique "^0.3.2"
|
array-unique "^0.3.2"
|
||||||
@@ -1666,11 +1699,11 @@ minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||||
|
|
||||||
mixin-deep@^1.2.0:
|
mixin-deep@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.2.0.tgz#d02b8c6f8b6d4b8f5982d3fd009c4919851c3fe2"
|
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.0.tgz#47a8732ba97799457c8c1eca28f95132d7e8150a"
|
||||||
dependencies:
|
dependencies:
|
||||||
for-in "^1.0.2"
|
for-in "^1.0.2"
|
||||||
is-extendable "^0.1.1"
|
is-extendable "^1.0.1"
|
||||||
|
|
||||||
mkdirp@0.5.0:
|
mkdirp@0.5.0:
|
||||||
version "0.5.0"
|
version "0.5.0"
|
||||||
@@ -1693,8 +1726,8 @@ nan@^2.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
|
||||||
|
|
||||||
nanomatch@^1.2.5:
|
nanomatch@^1.2.5:
|
||||||
version "1.2.5"
|
version "1.2.7"
|
||||||
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.5.tgz#5c9ab02475c76676275731b0bf0a7395c624a9c4"
|
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.7.tgz#53cd4aa109ff68b7f869591fdc9d10daeeea3e79"
|
||||||
dependencies:
|
dependencies:
|
||||||
arr-diff "^4.0.0"
|
arr-diff "^4.0.0"
|
||||||
array-unique "^0.3.2"
|
array-unique "^0.3.2"
|
||||||
@@ -1708,6 +1741,10 @@ nanomatch@^1.2.5:
|
|||||||
snapdragon "^0.8.1"
|
snapdragon "^0.8.1"
|
||||||
to-regex "^3.0.1"
|
to-regex "^3.0.1"
|
||||||
|
|
||||||
|
ngx-toastr@^8.0.0:
|
||||||
|
version "8.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ngx-toastr/-/ngx-toastr-8.1.0.tgz#3a0742e62895f88e232607843d61373d6f0d44d3"
|
||||||
|
|
||||||
node-libs-browser@^2.0.0:
|
node-libs-browser@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df"
|
resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df"
|
||||||
@@ -2000,9 +2037,9 @@ promise@^7.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
asap "~2.0.3"
|
asap "~2.0.3"
|
||||||
|
|
||||||
prr@~0.0.0:
|
prr@~1.0.1:
|
||||||
version "0.0.0"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"
|
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
|
||||||
|
|
||||||
public-encrypt@^4.0.0:
|
public-encrypt@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
@@ -2147,8 +2184,8 @@ randomatic@^1.1.3:
|
|||||||
kind-of "^4.0.0"
|
kind-of "^4.0.0"
|
||||||
|
|
||||||
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
|
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
|
||||||
version "2.0.5"
|
version "2.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.5.tgz#dc009a246b8d09a177b4b7a0ae77bc570f4b1b79"
|
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80"
|
||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "^5.1.0"
|
safe-buffer "^5.1.0"
|
||||||
|
|
||||||
@@ -2160,8 +2197,8 @@ randomfill@^1.0.3:
|
|||||||
safe-buffer "^5.1.0"
|
safe-buffer "^5.1.0"
|
||||||
|
|
||||||
rc@^1.1.2, rc@^1.1.7:
|
rc@^1.1.2, rc@^1.1.7:
|
||||||
version "1.2.2"
|
version "1.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.2.tgz#d8ce9cb57e8d64d9c7badd9876c7c34cbe3c7077"
|
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.4.tgz#a0f606caae2a3b862bbd0ef85482c0125b315fa3"
|
||||||
dependencies:
|
dependencies:
|
||||||
deep-extend "~0.4.0"
|
deep-extend "~0.4.0"
|
||||||
ini "~1.3.0"
|
ini "~1.3.0"
|
||||||
@@ -2183,7 +2220,7 @@ read-pkg@^1.0.0:
|
|||||||
normalize-package-data "^2.3.2"
|
normalize-package-data "^2.3.2"
|
||||||
path-type "^1.0.0"
|
path-type "^1.0.0"
|
||||||
|
|
||||||
readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.2.6, readable-stream@^2.3.3:
|
readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.3.3:
|
||||||
version "2.3.3"
|
version "2.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c"
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2342,18 +2379,18 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
|
|||||||
inherits "^2.0.1"
|
inherits "^2.0.1"
|
||||||
|
|
||||||
rxjs@^5.4.0:
|
rxjs@^5.4.0:
|
||||||
version "5.5.2"
|
version "5.5.6"
|
||||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.2.tgz#28d403f0071121967f18ad665563255d54236ac3"
|
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.6.tgz#e31fb96d6fd2ff1fd84bcea8ae9c02d007179c02"
|
||||||
dependencies:
|
dependencies:
|
||||||
symbol-observable "^1.0.1"
|
symbol-observable "1.0.1"
|
||||||
|
|
||||||
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
|
||||||
|
|
||||||
"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0:
|
"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0:
|
||||||
version "5.4.1"
|
version "5.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
|
||||||
|
|
||||||
set-blocking@^2.0.0, set-blocking@~2.0.0:
|
set-blocking@^2.0.0, set-blocking@~2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
@@ -2552,12 +2589,12 @@ stream-browserify@^2.0.1:
|
|||||||
readable-stream "^2.0.2"
|
readable-stream "^2.0.2"
|
||||||
|
|
||||||
stream-http@^2.7.2:
|
stream-http@^2.7.2:
|
||||||
version "2.7.2"
|
version "2.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.7.2.tgz#40a050ec8dc3b53b33d9909415c02c0bf1abfbad"
|
resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.0.tgz#fd86546dac9b1c91aff8fc5d287b98fafb41bc10"
|
||||||
dependencies:
|
dependencies:
|
||||||
builtin-status-codes "^3.0.0"
|
builtin-status-codes "^3.0.0"
|
||||||
inherits "^2.0.1"
|
inherits "^2.0.1"
|
||||||
readable-stream "^2.2.6"
|
readable-stream "^2.3.3"
|
||||||
to-arraybuffer "^1.0.0"
|
to-arraybuffer "^1.0.0"
|
||||||
xtend "^4.0.0"
|
xtend "^4.0.0"
|
||||||
|
|
||||||
@@ -2622,11 +2659,11 @@ supports-color@^3.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
has-flag "^1.0.0"
|
has-flag "^1.0.0"
|
||||||
|
|
||||||
symbol-observable@^1.0.1:
|
symbol-observable@1.0.1:
|
||||||
version "1.0.4"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d"
|
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4"
|
||||||
|
|
||||||
tapable@^0.2.5, tapable@~0.2.5:
|
tapable@^0.2.5, tapable@^0.2.7, tapable@~0.2.5:
|
||||||
version "0.2.8"
|
version "0.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22"
|
resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22"
|
||||||
|
|
||||||
@@ -2726,8 +2763,8 @@ typedarray@^0.0.6:
|
|||||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||||
|
|
||||||
typescript@^2.2.2:
|
typescript@^2.2.2:
|
||||||
version "2.6.1"
|
version "2.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.1.tgz#ef39cdea27abac0b500242d6726ab90e0c846631"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4"
|
||||||
|
|
||||||
uglify-js@^2.6.1, uglify-js@^2.8.27:
|
uglify-js@^2.6.1, uglify-js@^2.8.27:
|
||||||
version "2.8.29"
|
version "2.8.29"
|
||||||
@@ -2792,8 +2829,8 @@ util@0.10.3, util@^0.10.3:
|
|||||||
inherits "2.0.1"
|
inherits "2.0.1"
|
||||||
|
|
||||||
uuid@^3.0.0, uuid@^3.1.0:
|
uuid@^3.0.0, uuid@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
|
||||||
|
|
||||||
validate-npm-package-license@^3.0.1:
|
validate-npm-package-license@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
|
@@ -16,7 +16,13 @@ export class HyperColorSchemes extends TerminalColorSchemeProvider {
|
|||||||
try {
|
try {
|
||||||
let module = (global as any).require(path.join(pluginsPath, plugin))
|
let module = (global as any).require(path.join(pluginsPath, plugin))
|
||||||
if (module.decorateConfig) {
|
if (module.decorateConfig) {
|
||||||
let config = module.decorateConfig({})
|
let config: any
|
||||||
|
try {
|
||||||
|
config = module.decorateConfig({})
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Could not load Hyper theme:', plugin)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (config.colors) {
|
if (config.colors) {
|
||||||
themes.push({
|
themes.push({
|
||||||
name: plugin,
|
name: plugin,
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { BehaviorSubject, Subject, Subscription } from 'rxjs'
|
import { BehaviorSubject, Subject, Subscription } from 'rxjs'
|
||||||
import 'rxjs/add/operator/bufferTime'
|
import { ToastrService } from 'ngx-toastr'
|
||||||
import { Component, NgZone, Inject, Optional, ViewChild, HostBinding, Input } from '@angular/core'
|
import { Component, NgZone, Inject, Optional, ViewChild, HostBinding, Input } from '@angular/core'
|
||||||
import { AppService, ConfigService, BaseTabComponent, ElectronService, ThemesService, HostAppService, HotkeysService, Platform } from 'terminus-core'
|
import { AppService, ConfigService, BaseTabComponent, ElectronService, ThemesService, HostAppService, HotkeysService, Platform } from 'terminus-core'
|
||||||
|
|
||||||
@@ -54,11 +54,12 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
private electron: ElectronService,
|
private electron: ElectronService,
|
||||||
private terminalService: TerminalService,
|
private terminalService: TerminalService,
|
||||||
public config: ConfigService,
|
public config: ConfigService,
|
||||||
|
private toastr: ToastrService,
|
||||||
@Optional() @Inject(TerminalDecorator) private decorators: TerminalDecorator[],
|
@Optional() @Inject(TerminalDecorator) private decorators: TerminalDecorator[],
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.decorators = this.decorators || []
|
this.decorators = this.decorators || []
|
||||||
this.title = 'Terminal'
|
this.setTitle('Terminal')
|
||||||
this.resize$.first().subscribe(async (resizeEvent) => {
|
this.resize$.first().subscribe(async (resizeEvent) => {
|
||||||
if (!this.session) {
|
if (!this.session) {
|
||||||
this.session = this.sessions.addSession(
|
this.session = this.sessions.addSession(
|
||||||
@@ -88,20 +89,50 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
if (!this.hasFocus) {
|
if (!this.hasFocus) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (hotkey === 'copy') {
|
switch (hotkey) {
|
||||||
|
case 'ctrl-c':
|
||||||
|
if (this.hterm.getSelectionText()) {
|
||||||
|
this.hterm.copySelectionToClipboard()
|
||||||
|
} else {
|
||||||
|
this.sendInput('\x03')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'copy':
|
||||||
this.hterm.copySelectionToClipboard()
|
this.hterm.copySelectionToClipboard()
|
||||||
}
|
break
|
||||||
if (hotkey === 'clear') {
|
case 'paste':
|
||||||
|
this.paste()
|
||||||
|
break
|
||||||
|
case 'clear':
|
||||||
this.clear()
|
this.clear()
|
||||||
}
|
break
|
||||||
if (hotkey === 'zoom-in') {
|
case 'zoom-in':
|
||||||
this.zoomIn()
|
this.zoomIn()
|
||||||
}
|
break
|
||||||
if (hotkey === 'zoom-out') {
|
case 'zoom-out':
|
||||||
this.zoomOut()
|
this.zoomOut()
|
||||||
}
|
break
|
||||||
if (hotkey === 'reset-zoom') {
|
case 'reset-zoom':
|
||||||
this.resetZoom()
|
this.resetZoom()
|
||||||
|
break
|
||||||
|
case 'home':
|
||||||
|
this.sendInput('\x1bOH')
|
||||||
|
break
|
||||||
|
case 'end':
|
||||||
|
this.sendInput('\x1bOF')
|
||||||
|
break
|
||||||
|
case 'previous-word':
|
||||||
|
this.sendInput('\x1bb')
|
||||||
|
break
|
||||||
|
case 'next-word':
|
||||||
|
this.sendInput('\x1bf')
|
||||||
|
break
|
||||||
|
case 'delete-previous-word':
|
||||||
|
this.sendInput('\x1b\x7f')
|
||||||
|
break
|
||||||
|
case 'delete-next-word':
|
||||||
|
this.sendInput('\x1bd')
|
||||||
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.bellPlayer = document.createElement('audio')
|
this.bellPlayer = document.createElement('audio')
|
||||||
@@ -191,11 +222,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
attachHTermHandlers (hterm: any) {
|
attachHTermHandlers (hterm: any) {
|
||||||
hterm.setWindowTitle = (title) => {
|
hterm.setWindowTitle = title => this.zone.run(() => this.setTitle(title))
|
||||||
this.zone.run(() => {
|
|
||||||
this.title = title
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const _setAlternateMode = hterm.setAlternateMode.bind(hterm)
|
const _setAlternateMode = hterm.setAlternateMode.bind(hterm)
|
||||||
hterm.setAlternateMode = (state) => {
|
hterm.setAlternateMode = (state) => {
|
||||||
@@ -203,15 +230,18 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
this.alternateScreenActive$.next(state)
|
this.alternateScreenActive$.next(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _copySelectionToClipboard = hterm.copySelectionToClipboard.bind(hterm)
|
||||||
|
hterm.copySelectionToClipboard = () => {
|
||||||
|
_copySelectionToClipboard()
|
||||||
|
this.toastr.info('Copied')
|
||||||
|
}
|
||||||
|
|
||||||
hterm.primaryScreen_.syncSelectionCaret = () => null
|
hterm.primaryScreen_.syncSelectionCaret = () => null
|
||||||
hterm.alternateScreen_.syncSelectionCaret = () => null
|
hterm.alternateScreen_.syncSelectionCaret = () => null
|
||||||
hterm.primaryScreen_.terminal = hterm
|
hterm.primaryScreen_.terminal = hterm
|
||||||
hterm.alternateScreen_.terminal = hterm
|
hterm.alternateScreen_.terminal = hterm
|
||||||
|
|
||||||
const _onPaste = hterm.scrollPort_.onPaste_.bind(hterm.scrollPort_)
|
|
||||||
hterm.scrollPort_.onPaste_ = (event) => {
|
hterm.scrollPort_.onPaste_ = (event) => {
|
||||||
hterm.scrollPort_.pasteTarget_.value = event.clipboardData.getData('text/plain').trim()
|
|
||||||
_onPaste()
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,7 +343,12 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
paste () {
|
paste () {
|
||||||
this.sendInput(this.electron.clipboard.readText())
|
let data = this.electron.clipboard.readText()
|
||||||
|
data = this.hterm.keyboard.encode(data)
|
||||||
|
if (this.hterm.options_.bracketedPaste) {
|
||||||
|
data = '\x1b[200~' + data + '\x1b[201~'
|
||||||
|
}
|
||||||
|
this.sendInput(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
clear () {
|
clear () {
|
||||||
@@ -321,7 +356,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
this.hterm.onVTKeystroke('\f')
|
this.hterm.onVTKeystroke('\f')
|
||||||
}
|
}
|
||||||
|
|
||||||
async configure (): Promise<void> {
|
configure (): void {
|
||||||
let config = this.config.store
|
let config = this.config.store
|
||||||
preferenceManager.set('font-family', `"${config.terminal.font}", "monospace-fallback", monospace`)
|
preferenceManager.set('font-family', `"${config.terminal.font}", "monospace-fallback", monospace`)
|
||||||
this.setFontSize()
|
this.setFontSize()
|
||||||
@@ -338,6 +373,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
|||||||
preferenceManager.set('alt-gr-mode', 'ctrl-alt')
|
preferenceManager.set('alt-gr-mode', 'ctrl-alt')
|
||||||
preferenceManager.set('pass-alt-number', true)
|
preferenceManager.set('pass-alt-number', true)
|
||||||
preferenceManager.set('cursor-blink', config.terminal.cursorBlink)
|
preferenceManager.set('cursor-blink', config.terminal.cursorBlink)
|
||||||
|
preferenceManager.set('clear-selection-after-copy', true)
|
||||||
|
|
||||||
if (config.terminal.colorScheme.foreground) {
|
if (config.terminal.colorScheme.foreground) {
|
||||||
preferenceManager.set('foreground-color', config.terminal.colorScheme.foreground)
|
preferenceManager.set('foreground-color', config.terminal.colorScheme.foreground)
|
||||||
|
@@ -53,9 +53,13 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
persistence: 'screen',
|
persistence: 'screen',
|
||||||
},
|
},
|
||||||
hotkeys: {
|
hotkeys: {
|
||||||
|
'ctrl-c': ['Ctrl-C'],
|
||||||
'copy': [
|
'copy': [
|
||||||
'⌘-C',
|
'⌘-C',
|
||||||
],
|
],
|
||||||
|
'paste': [
|
||||||
|
'⌘-V',
|
||||||
|
],
|
||||||
'clear': [
|
'clear': [
|
||||||
'⌘-K',
|
'⌘-K',
|
||||||
],
|
],
|
||||||
@@ -75,7 +79,13 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
['Ctrl-A', 'Ctrl-C'],
|
['Ctrl-A', 'Ctrl-C'],
|
||||||
'⌘-T',
|
'⌘-T',
|
||||||
'⌘-N',
|
'⌘-N',
|
||||||
]
|
],
|
||||||
|
'home': ['⌘-ArrowLeft', 'Home'],
|
||||||
|
'end': ['⌘-ArrowRight', 'End'],
|
||||||
|
'previous-word': ['⌥-ArrowLeft'],
|
||||||
|
'next-word': ['⌥-ArrowRight'],
|
||||||
|
'delete-previous-word': ['⌥-Backspace'],
|
||||||
|
'delete-next-word': ['⌥-Delete'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[Platform.Windows]: {
|
[Platform.Windows]: {
|
||||||
@@ -87,9 +97,13 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
copyOnSelect: true,
|
copyOnSelect: true,
|
||||||
},
|
},
|
||||||
hotkeys: {
|
hotkeys: {
|
||||||
|
'ctrl-c': ['Ctrl-C'],
|
||||||
'copy': [
|
'copy': [
|
||||||
'Ctrl-Shift-C',
|
'Ctrl-Shift-C',
|
||||||
],
|
],
|
||||||
|
'paste': [
|
||||||
|
'Ctrl-Shift-V',
|
||||||
|
],
|
||||||
'clear': [
|
'clear': [
|
||||||
'Ctrl-L',
|
'Ctrl-L',
|
||||||
],
|
],
|
||||||
@@ -108,7 +122,13 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
['Ctrl-A', 'C'],
|
['Ctrl-A', 'C'],
|
||||||
['Ctrl-A', 'Ctrl-C'],
|
['Ctrl-A', 'Ctrl-C'],
|
||||||
'Ctrl-Shift-T',
|
'Ctrl-Shift-T',
|
||||||
]
|
],
|
||||||
|
'home': ['Home'],
|
||||||
|
'end': ['End'],
|
||||||
|
'previous-word': ['Ctrl-ArrowLeft'],
|
||||||
|
'next-word': ['Ctrl-ArrowRight'],
|
||||||
|
'delete-previous-word': ['Ctrl-Backspace'],
|
||||||
|
'delete-next-word': ['Ctrl-Delete'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[Platform.Linux]: {
|
[Platform.Linux]: {
|
||||||
@@ -118,9 +138,13 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
persistence: 'tmux',
|
persistence: 'tmux',
|
||||||
},
|
},
|
||||||
hotkeys: {
|
hotkeys: {
|
||||||
|
'ctrl-c': ['Ctrl-C'],
|
||||||
'copy': [
|
'copy': [
|
||||||
'Ctrl-Shift-C',
|
'Ctrl-Shift-C',
|
||||||
],
|
],
|
||||||
|
'paste': [
|
||||||
|
'Ctrl-Shift-V',
|
||||||
|
],
|
||||||
'clear': [
|
'clear': [
|
||||||
'Ctrl-L',
|
'Ctrl-L',
|
||||||
],
|
],
|
||||||
@@ -139,7 +163,13 @@ export class TerminalConfigProvider extends ConfigProvider {
|
|||||||
['Ctrl-A', 'C'],
|
['Ctrl-A', 'C'],
|
||||||
['Ctrl-A', 'Ctrl-C'],
|
['Ctrl-A', 'Ctrl-C'],
|
||||||
'Ctrl-Shift-T',
|
'Ctrl-Shift-T',
|
||||||
]
|
],
|
||||||
|
'home': ['Home'],
|
||||||
|
'end': ['End'],
|
||||||
|
'previous-word': ['Ctrl-ArrowLeft'],
|
||||||
|
'next-word': ['Ctrl-ArrowRight'],
|
||||||
|
'delete-previous-word': ['Ctrl-Backspace'],
|
||||||
|
'delete-next-word': ['Ctrl-Delete'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,34 @@ export class TerminalHotkeyProvider extends HotkeyProvider {
|
|||||||
id: 'copy',
|
id: 'copy',
|
||||||
name: 'Copy to clipboard',
|
name: 'Copy to clipboard',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'paste',
|
||||||
|
name: 'Paste from clipboard',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'home',
|
||||||
|
name: 'Beginning of the line',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'end',
|
||||||
|
name: 'End of the line',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'previous-word',
|
||||||
|
name: 'Jump to previous word',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'next-word',
|
||||||
|
name: 'Jump to next word',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'delete-previous-word',
|
||||||
|
name: 'Delete previous word',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'delete-next-word',
|
||||||
|
name: 'Delete next word',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'clear',
|
id: 'clear',
|
||||||
name: 'Clear terminal',
|
name: 'Clear terminal',
|
||||||
|
@@ -8,6 +8,15 @@ a:hover {
|
|||||||
|
|
||||||
x-screen {
|
x-screen {
|
||||||
transition: 0.125s ease background;
|
transition: 0.125s ease background;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 3px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
x-row > span {
|
x-row > span {
|
||||||
|
@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core'
|
|||||||
import { BrowserModule } from '@angular/platform-browser'
|
import { BrowserModule } from '@angular/platform-browser'
|
||||||
import { FormsModule } from '@angular/forms'
|
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 { ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider, AppService, ConfigService } from 'terminus-core'
|
import { ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider, AppService, ConfigService } from 'terminus-core'
|
||||||
import { SettingsTabProvider } from 'terminus-settings'
|
import { SettingsTabProvider } from 'terminus-settings'
|
||||||
@@ -41,6 +42,7 @@ import { hterm } from './hterm'
|
|||||||
BrowserModule,
|
BrowserModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
NgbModule,
|
NgbModule,
|
||||||
|
ToastrModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SessionsService,
|
SessionsService,
|
||||||
|
@@ -3,6 +3,8 @@ import { execFileSync } from 'child_process'
|
|||||||
import * as AsyncLock from 'async-lock'
|
import * as AsyncLock from 'async-lock'
|
||||||
import { ConnectableObservable, AsyncSubject, Subject } from 'rxjs'
|
import { ConnectableObservable, AsyncSubject, Subject } from 'rxjs'
|
||||||
import * as childProcess from 'child_process'
|
import * as childProcess from 'child_process'
|
||||||
|
|
||||||
|
import { Logger } from 'terminus-core'
|
||||||
import { SessionOptions, SessionPersistenceProvider } from '../api'
|
import { SessionOptions, SessionPersistenceProvider } from '../api'
|
||||||
|
|
||||||
declare function delay (ms: number): Promise<void>
|
declare function delay (ms: number): Promise<void>
|
||||||
@@ -52,10 +54,11 @@ export class TMuxCommandProcess {
|
|||||||
private block$ = new Subject<TMuxBlock>()
|
private block$ = new Subject<TMuxBlock>()
|
||||||
private response$: ConnectableObservable<TMuxBlock>
|
private response$: ConnectableObservable<TMuxBlock>
|
||||||
private lock = new AsyncLock({ timeout: 1000 })
|
private lock = new AsyncLock({ timeout: 1000 })
|
||||||
|
private logger = new Logger(null, 'tmuxProcess')
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
this.process = childProcess.spawn('tmux', ['-C', '-f', '/dev/null', '-L', 'terminus', 'new-session', '-A', '-D', '-s', 'control'])
|
this.process = childProcess.spawn('tmux', ['-C', '-f', '/dev/null', '-L', 'terminus', 'new-session', '-A', '-D', '-s', 'control'])
|
||||||
console.log('[tmux] started')
|
this.logger.log('started')
|
||||||
this.process.stdout.on('data', data => {
|
this.process.stdout.on('data', data => {
|
||||||
// console.debug('tmux says:', data.toString())
|
// console.debug('tmux says:', data.toString())
|
||||||
this.rawOutput$.next(data.toString())
|
this.rawOutput$.next(data.toString())
|
||||||
@@ -103,18 +106,18 @@ export class TMuxCommandProcess {
|
|||||||
this.response$.connect()
|
this.response$.connect()
|
||||||
|
|
||||||
this.block$.subscribe(block => {
|
this.block$.subscribe(block => {
|
||||||
console.debug('[tmux] block:', block)
|
this.logger.debug('block:', block)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.message$.subscribe(message => {
|
this.message$.subscribe(message => {
|
||||||
console.debug('[tmux] message:', message)
|
this.logger.debug('message:', message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
command (command: string): Promise<TMuxBlock> {
|
command (command: string): Promise<TMuxBlock> {
|
||||||
return this.lock.acquire('key', () => {
|
return this.lock.acquire('key', () => {
|
||||||
let p = this.response$.take(1).toPromise()
|
let p = this.response$.take(1).toPromise()
|
||||||
console.debug('[tmux] command:', command)
|
this.logger.debug('command:', command)
|
||||||
this.process.stdin.write(command + '\n')
|
this.process.stdin.write(command + '\n')
|
||||||
return p
|
return p
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
@@ -137,13 +140,18 @@ export class TMuxCommandProcess {
|
|||||||
export class TMux {
|
export class TMux {
|
||||||
private process: TMuxCommandProcess
|
private process: TMuxCommandProcess
|
||||||
private ready: Promise<void>
|
private ready: Promise<void>
|
||||||
|
private logger = new Logger(null, 'tmux')
|
||||||
|
|
||||||
constructor () {
|
constructor () {
|
||||||
this.process = new TMuxCommandProcess()
|
this.process = new TMuxCommandProcess()
|
||||||
this.ready = (async () => {
|
this.ready = (async () => {
|
||||||
for (let line of TMUX_CONFIG.split('\n')) {
|
for (let line of TMUX_CONFIG.split('\n')) {
|
||||||
if (line) {
|
if (line) {
|
||||||
await this.process.command(line)
|
try {
|
||||||
|
await this.process.command(line)
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.warn('Skipping failing config line:', line)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Tmux sometimes sends a stray response block at start
|
// Tmux sometimes sends a stray response block at start
|
||||||
|
@@ -100,7 +100,7 @@ export class Session extends BaseSession {
|
|||||||
|
|
||||||
this.open = true
|
this.open = true
|
||||||
|
|
||||||
this.pty.on('data', data => {
|
this.pty.on('data-buffered', data => {
|
||||||
this.emitOutput(data)
|
this.emitOutput(data)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -200,7 +200,8 @@ export class SessionsService {
|
|||||||
electron: ElectronService,
|
electron: ElectronService,
|
||||||
log: LogService,
|
log: LogService,
|
||||||
) {
|
) {
|
||||||
nodePTY = electron.remoteRequirePluginModule('terminus-terminal', 'node-pty-tmp', global as any)
|
const nodePTYPath = electron.remoteResolvePluginModule('terminus-terminal', 'node-pty-tmp', global as any)
|
||||||
|
nodePTY = electron.remoteRequire('./bufferizedPTY')(nodePTYPath)
|
||||||
this.logger = log.create('sessions')
|
this.logger = log.create('sessions')
|
||||||
this.persistenceProviders = this.config.enabledServices(this.persistenceProviders).filter(x => x.isAvailable())
|
this.persistenceProviders = this.config.enabledServices(this.persistenceProviders).filter(x => x.isAvailable())
|
||||||
}
|
}
|
||||||
|
@@ -34,7 +34,7 @@ export class TerminalService {
|
|||||||
|
|
||||||
async openTab (shell?: IShell, cwd?: string): Promise<TerminalTabComponent> {
|
async openTab (shell?: IShell, cwd?: string): Promise<TerminalTabComponent> {
|
||||||
if (!cwd) {
|
if (!cwd) {
|
||||||
if (this.app.activeTab instanceof TerminalTabComponent) {
|
if (this.app.activeTab instanceof TerminalTabComponent && this.app.activeTab.session) {
|
||||||
cwd = await this.app.activeTab.session.getWorkingDirectory()
|
cwd = await this.app.activeTab.session.getWorkingDirectory()
|
||||||
} else {
|
} else {
|
||||||
cwd = this.config.store.terminal.workingDirectory || null
|
cwd = this.config.store.terminal.workingDirectory || null
|
||||||
|
@@ -36,12 +36,20 @@ export class WindowsStockShellsProvider extends ShellProvider {
|
|||||||
{ id: 'cmd', name: 'CMD (stock)', command: 'cmd.exe' },
|
{ id: 'cmd', name: 'CMD (stock)', command: 'cmd.exe' },
|
||||||
{
|
{
|
||||||
id: 'powershell',
|
id: 'powershell',
|
||||||
name: 'PowerShell',
|
name: 'Windows PowerShell',
|
||||||
command: 'powershell.exe',
|
command: 'powershell.exe',
|
||||||
env: {
|
env: {
|
||||||
TERM: 'cygwin',
|
TERM: 'cygwin',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'powershell-core',
|
||||||
|
name: 'PowerShell Core',
|
||||||
|
command: 'pwsh.exe',
|
||||||
|
env: {
|
||||||
|
TERM: 'cygwin',
|
||||||
|
}
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -55,6 +55,7 @@ module.exports = {
|
|||||||
/^rxjs/,
|
/^rxjs/,
|
||||||
/^@angular/,
|
/^@angular/,
|
||||||
/^@ng-bootstrap/,
|
/^@ng-bootstrap/,
|
||||||
|
'ngx-toastr',
|
||||||
/^terminus-/,
|
/^terminus-/,
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
|