mirror of
https://github.com/Eugeny/tabby.git
synced 2025-09-03 15:11:51 +00:00
Compare commits
59 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ccc34ae4d9 | ||
![]() |
4362b5c50b | ||
![]() |
2d6023446c | ||
![]() |
dcd43dc019 | ||
![]() |
d8e70f9693 | ||
![]() |
7a26e8bd65 | ||
![]() |
d56287587c | ||
![]() |
8793613117 | ||
![]() |
92afec75e7 | ||
![]() |
ca71ec24f8 | ||
![]() |
524550f6e3 | ||
![]() |
fe31131fc1 | ||
![]() |
a7c1fe3425 | ||
![]() |
d7b305bf29 | ||
![]() |
0bd0c850da | ||
![]() |
88bb40f94b | ||
![]() |
120e2a2cd5 | ||
![]() |
cbb6821814 | ||
![]() |
75bf374a8f | ||
![]() |
bf995981d3 | ||
![]() |
a6fdabcd2f | ||
![]() |
0e6886d00a | ||
![]() |
459d6aadd9 | ||
![]() |
21d533c7cf | ||
![]() |
211566488d | ||
![]() |
282aab2e55 | ||
![]() |
6f41865474 | ||
![]() |
e4bcfd8f39 | ||
![]() |
504cfcf8ff | ||
![]() |
6e13914712 | ||
![]() |
9aaf670092 | ||
![]() |
c204f6d5a4 | ||
![]() |
91bba042b5 | ||
![]() |
2ca6135c06 | ||
![]() |
9ef3cbc177 | ||
![]() |
8a3906687a | ||
![]() |
3192a14c9d | ||
![]() |
b510a86f4d | ||
![]() |
fcf14eaa8b | ||
![]() |
137dd0bbe8 | ||
![]() |
4b5b75a57a | ||
![]() |
68c497e5fc | ||
![]() |
1da7c85973 | ||
![]() |
fe75aab724 | ||
![]() |
85bcac1fb7 | ||
![]() |
72287cc7cb | ||
![]() |
1f1d212c1d | ||
![]() |
cded1284de | ||
![]() |
df97e7ebb5 | ||
![]() |
d80c9a27d3 | ||
![]() |
3469ec9b6b | ||
![]() |
d4db8f4b18 | ||
![]() |
384744ec44 | ||
![]() |
76633db25e | ||
![]() |
6b823d0fa0 | ||
![]() |
798dda5236 | ||
![]() |
2b90a17d5e | ||
![]() |
6387539980 | ||
![]() |
cb17fd0866 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,6 +5,7 @@ node_modules
|
||||
|
||||
build/files.wxs
|
||||
dist
|
||||
*/dist
|
||||
|
||||
*.xcworkspacedata
|
||||
*.xcuserstate
|
||||
|
@@ -6,7 +6,7 @@ matrix:
|
||||
env: BUILD_FOR=macos
|
||||
|
||||
language: node_js
|
||||
node_js: 8
|
||||
node_js: 10
|
||||
|
||||
cache:
|
||||
directories:
|
||||
@@ -14,8 +14,7 @@ cache:
|
||||
- app/node_modules
|
||||
|
||||
before_install:
|
||||
- yarn install
|
||||
- scripts/install-deps.js
|
||||
- yarn
|
||||
|
||||
script:
|
||||
- scripts/build-native.js
|
||||
|
@@ -103,7 +103,7 @@ export class Application {
|
||||
{
|
||||
label: 'Preferences',
|
||||
accelerator: 'Cmd+,',
|
||||
async click () {
|
||||
click: async () => {
|
||||
if (!this.hasWindows()) {
|
||||
await this.newWindow()
|
||||
}
|
||||
|
@@ -13,6 +13,9 @@ export function parseArgs (argv, cwd) {
|
||||
.command('run [command...]', 'run a command in the terminal', {
|
||||
command: { type: 'string' },
|
||||
})
|
||||
.command('profile [profileName]', 'open a tab with specified profile', {
|
||||
profileName: { type: 'string' },
|
||||
})
|
||||
.command('paste [text]', 'paste stdin into the active tab', yargs => {
|
||||
return yargs.option('escape', {
|
||||
alias: 'e',
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { Subject, Observable } from 'rxjs'
|
||||
import { debounceTime } from 'rxjs/operators'
|
||||
import { BrowserWindow, app, ipcMain, Rectangle } from 'electron'
|
||||
import ElectronConfig = require('electron-config')
|
||||
import * as os from 'os'
|
||||
@@ -143,6 +144,16 @@ export class Window {
|
||||
this.visible.next(false)
|
||||
})
|
||||
|
||||
let moveSubscription = new Observable<void>(observer => {
|
||||
this.window.on('move', () => observer.next())
|
||||
}).pipe(debounceTime(250)).subscribe(() => {
|
||||
this.window.webContents.send('host:window-moved')
|
||||
})
|
||||
|
||||
this.window.on('closed', () => {
|
||||
moveSubscription.unsubscribe()
|
||||
})
|
||||
|
||||
this.window.on('enter-full-screen', () => this.window.webContents.send('host:window-enter-full-screen'))
|
||||
this.window.on('leave-full-screen', () => this.window.webContents.send('host:window-leave-full-screen'))
|
||||
|
||||
@@ -250,7 +261,10 @@ export class Window {
|
||||
this.window.moveTop()
|
||||
})
|
||||
|
||||
ipcMain.on('window-close', () => {
|
||||
ipcMain.on('window-close', event => {
|
||||
if (event.sender !== this.window.webContents) {
|
||||
return
|
||||
}
|
||||
this.closing = true
|
||||
this.window.close()
|
||||
})
|
||||
|
@@ -13,13 +13,13 @@
|
||||
"watch": "webpack --progress --color --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "7.0.0",
|
||||
"@angular/common": "7.0.0",
|
||||
"@angular/compiler": "7.0.0",
|
||||
"@angular/core": "7.0.0",
|
||||
"@angular/forms": "7.0.0",
|
||||
"@angular/platform-browser": "7.0.0",
|
||||
"@angular/platform-browser-dynamic": "7.0.0",
|
||||
"@angular/animations": "7.2.0-rc.0",
|
||||
"@angular/common": "7.2.0-rc.0",
|
||||
"@angular/compiler": "7.2.0-rc.0",
|
||||
"@angular/core": "7.2.0-rc.0",
|
||||
"@angular/forms": "7.2.0-rc.0",
|
||||
"@angular/platform-browser": "7.2.0-rc.0",
|
||||
"@angular/platform-browser-dynamic": "7.2.0-rc.0",
|
||||
"@ng-bootstrap/ng-bootstrap": "^3.3.1",
|
||||
"devtron": "1.4.0",
|
||||
"electron-config": "0.2.1",
|
||||
|
@@ -1,7 +1,9 @@
|
||||
import '../lib/lru'
|
||||
import 'source-sans-pro'
|
||||
import 'source-code-pro/source-code-pro.css'
|
||||
import 'font-awesome/css/font-awesome.css'
|
||||
import '@fortawesome/fontawesome-free/css/solid.css'
|
||||
import '@fortawesome/fontawesome-free/css/brands.css'
|
||||
import '@fortawesome/fontawesome-free/css/fontawesome.css'
|
||||
import 'ngx-toastr/toastr.css'
|
||||
import './preload.scss'
|
||||
|
||||
|
@@ -34,7 +34,7 @@ async function bootstrap (plugins: IPluginInfo[], safeMode = false): Promise<NgM
|
||||
})
|
||||
let module = getRootModule(pluginsModules)
|
||||
window['rootModule'] = module
|
||||
return await platformBrowserDynamic().bootstrapModule(module)
|
||||
return platformBrowserDynamic().bootstrapModule(module)
|
||||
}
|
||||
|
||||
findPlugins().then(async plugins => {
|
||||
|
@@ -118,7 +118,7 @@ export async function findPlugins (): Promise<IPluginInfo[]> {
|
||||
}
|
||||
|
||||
try {
|
||||
let info = JSON.parse(await fs.readFile(infoPath, {encoding: 'utf-8'}))
|
||||
let info = JSON.parse(await fs.readFile(infoPath, { encoding: 'utf-8' }))
|
||||
if (!info.keywords || !(info.keywords.includes('terminus-plugin') || info.keywords.includes('terminus-builtin-plugin'))) {
|
||||
continue
|
||||
}
|
||||
|
@@ -2,52 +2,52 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@angular/animations@7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-7.0.0.tgz#5c9e1683063c29df10253b7dc5bb9b13694ee396"
|
||||
integrity sha512-IYdryQXdYfPvhJpExLSAr0o9rlUeyVS++a6h/sjqN1dkUt/yJBHLRreuHx8Udvlj2nH70raHJgevk8FwhAkTdA==
|
||||
"@angular/animations@7.2.0-rc.0":
|
||||
version "7.2.0-rc.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-7.2.0-rc.0.tgz#12849f8ab104d309ec99c0ceb170a895c15d3d44"
|
||||
integrity sha512-CRQNQ6QVTuf4nCHVLVpKQx7YPpNPfnTF79KVWzHefkkyS3URRuEgvE4jCED4oTJ4BEsmkjXyt51VeDV0FgqQFg==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
"@angular/common@7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/common/-/common-7.0.0.tgz#29206614d2b8dc79e5207b7dc6f9fc559e9a24f2"
|
||||
integrity sha512-jp6MA6EOq/a1m+F0c1aZC345pAYYYFpN1m7GMM91JlqkjzJMhyYVk+Bod9xQOEWadcpY+RFudG+jRsPCMO8bvQ==
|
||||
"@angular/common@7.2.0-rc.0":
|
||||
version "7.2.0-rc.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/common/-/common-7.2.0-rc.0.tgz#60d3540c6cdcf3440f67e2c15cf8f1c7b1160d9d"
|
||||
integrity sha512-Xv60KEP1kpF74kpN1xtps0W++PUXLUMK/0tDblUZH7tBWvS0XwEwtuK5B6wcs+I5nqZkPgvlvOyiVZvOLraWOg==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
"@angular/compiler@7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-7.0.0.tgz#f953a213a01e4736e94fe1a370b07e13e2393b71"
|
||||
integrity sha512-4fkohfGyG1BEpeYenOartuJmduyZ/R3XQx46hDDiR/9A8/Go4qLGkgr9Bd/JL/gPIR1XAHH9D5ii2sh+28ZEmA==
|
||||
"@angular/compiler@7.2.0-rc.0":
|
||||
version "7.2.0-rc.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-7.2.0-rc.0.tgz#603dbec25d6c2beea08a293c68c39b40e2ea81e2"
|
||||
integrity sha512-tvgGJx0urSz/qn6upmcjX3N3dyWQ9m5mQOwJxmN4qekxjOtSRml5yt2KtlaUTkGsjkEmEVfSHel+X1TwzBdhYw==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
"@angular/core@7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/core/-/core-7.0.0.tgz#01e9db9074a1db1c47a32f745b787d1c86f5d61a"
|
||||
integrity sha512-DjVyWNGBWKEeBvxeXy8FGBNlnr/W/tNygOZEd6/uCktcXTG4DNyNQrWuNZUKEpr7RuIT3YVMj+UNwgTq0jB/9g==
|
||||
"@angular/core@7.2.0-rc.0":
|
||||
version "7.2.0-rc.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/core/-/core-7.2.0-rc.0.tgz#57c0e26130288c3b58466f079828c028bdf6221f"
|
||||
integrity sha512-2u11TNlLorw3JhuczCPwl8UmxE+ja2Q/ghBl8iYi4SIBWiBO1K0wVT13Ts7eojk63yZcg60lyYYCegXBmHLTuw==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
"@angular/forms@7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-7.0.0.tgz#672c306b13e94a20b72c096214642a326c43699a"
|
||||
integrity sha512-rTg1UHq9gHR6zY3Kkip1KCm/YTck/rlR8CvVFIMwF0bdQxUCT51SXVn58nXts9yDaieABcGaQHNkQn1mARslgw==
|
||||
"@angular/forms@7.2.0-rc.0":
|
||||
version "7.2.0-rc.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-7.2.0-rc.0.tgz#4eb473018084bb81be3e2e1ae8afa8d2b2117a6c"
|
||||
integrity sha512-OWP1zzYQiuqtoltdlhkcVjHxg78exbt7z1lr8RSjybr/Snc5zSFhnZF6byasd/4lzVySuujsMXkTK7D8x6hedA==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
"@angular/platform-browser-dynamic@7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.0.0.tgz#2b2a50b5a8176bee257f90ee47b1d873502f7182"
|
||||
integrity sha512-lH2KuH+Em1y/mTOE6yTJmsOxYkMbYKzKLP9gYzc9vZu3er1df6Jx6jxefeBmAr9v+kNCLnpnHWHz2y4GzAesJA==
|
||||
"@angular/platform-browser-dynamic@7.2.0-rc.0":
|
||||
version "7.2.0-rc.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.2.0-rc.0.tgz#5ea47d094c53a0ba34ecbb0dfdcef452fa05dc9a"
|
||||
integrity sha512-uqT27oh9m58L6MUjgvT+7NpAFbigOnnTUWMsCLijNUKd7i37T6UxTVKPvuqNHlaLXsmDRxVHN3INI0IrWZ3R+w==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
"@angular/platform-browser@7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-7.0.0.tgz#8c13a6380cf465b3628e5b576a1313e9b4976093"
|
||||
integrity sha512-XyvL30d6meJ+SXlOmdR+sxoLdSvkQdmVNvpdvUzAHC/EqwA/byg4V3bTe5lpZmypclgFCjkGoTsz6uOnnwlQhw==
|
||||
"@angular/platform-browser@7.2.0-rc.0":
|
||||
version "7.2.0-rc.0"
|
||||
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-7.2.0-rc.0.tgz#c6d1f0b2328b1d81649bea70c23edc33de729015"
|
||||
integrity sha512-r0ak7SVLWrivd4S0MXWmqNLeF6NNOBAopnjrhUu2j5I00u7/QfLrX0E5zRlJ8JkARVjer6Wm+D1ztlOWw5jHag==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
|
@@ -7,18 +7,18 @@ environment:
|
||||
nodejs_version: "10"
|
||||
|
||||
cache:
|
||||
- '%USERPROFILE%\.electron'
|
||||
- "%USERPROFILE%\\.electron"
|
||||
- "%LOCALAPPDATA%\\Yarn"
|
||||
|
||||
version: "{build}"
|
||||
|
||||
install:
|
||||
- ps: Install-Product node $env:nodejs_version $env:platform
|
||||
- npm install
|
||||
- node scripts/install-deps.js
|
||||
- yarn
|
||||
- node scripts/build-native.js
|
||||
|
||||
build_script:
|
||||
- npm run build
|
||||
- yarn run build
|
||||
- node scripts/prepackage-plugins.js
|
||||
- node scripts/build-windows.js
|
||||
|
||||
|
BIN
extras/UAC.exe
Normal file
BIN
extras/UAC.exe
Normal file
Binary file not shown.
23
package.json
23
package.json
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "term",
|
||||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.6.3",
|
||||
"@types/electron-config": "^0.2.1",
|
||||
"@types/electron-debug": "^1.1.0",
|
||||
"@types/fs-promise": "1.0.1",
|
||||
@@ -13,20 +14,17 @@
|
||||
"core-js": "2.4.1",
|
||||
"cross-env": "4.0.0",
|
||||
"css-loader": "0.28.0",
|
||||
"electron": "4.0.0-beta.8",
|
||||
"electron-builder": "^20.38.2",
|
||||
"electron": "4.0.0",
|
||||
"electron-builder": "^20.38.4",
|
||||
"electron-builder-squirrel-windows": "^20.28.3",
|
||||
"electron-installer-snap": "^3.0.0",
|
||||
"electron-rebuild": "^1.8.2",
|
||||
"file-loader": "^1.1.11",
|
||||
"font-awesome": "4.7.0",
|
||||
"graceful-fs": "^4.1.11",
|
||||
"html-loader": "0.4.4",
|
||||
"json-loader": "0.5.4",
|
||||
"less": "2.7.1",
|
||||
"less-loader": "2.2.3",
|
||||
"node-abi": "^2.4.4",
|
||||
"node-gyp": "^3.6.2",
|
||||
"node-gyp": "^3.8.0",
|
||||
"node-sass": "^4.5.3",
|
||||
"npmlog": "4.1.0",
|
||||
"npx": "^10.2.0",
|
||||
@@ -44,17 +42,20 @@
|
||||
"style-loader": "0.13.1",
|
||||
"svg-inline-loader": "^0.8.0",
|
||||
"to-string-loader": "1.1.5",
|
||||
"tslint": "5.1.0",
|
||||
"tslint-config-standard": "5.0.2",
|
||||
"tslint-eslint-rules": "4.0.0",
|
||||
"tslint": "^5.12.0",
|
||||
"tslint-config-standard": "^8.0.1",
|
||||
"tslint-eslint-rules": "^5.4.0",
|
||||
"typescript": "^3.1.3",
|
||||
"url-loader": "^1.1.1",
|
||||
"val-loader": "0.5.0",
|
||||
"webpack": "^4.22.0",
|
||||
"webpack": "^4.27.1",
|
||||
"webpack-cli": "^3.1.2",
|
||||
"yaml-loader": "0.4.0",
|
||||
"yarn": "^1.10.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"*/node-abi": "^2.5.0"
|
||||
},
|
||||
"build": {
|
||||
"appId": "org.terminus",
|
||||
"productName": "Terminus",
|
||||
@@ -128,7 +129,7 @@
|
||||
"start": "cross-env DEV=1 electron app --debug",
|
||||
"prod": "cross-env DEV=1 electron app",
|
||||
"lint": "tslint -c tslint.json -t stylish terminus-*/src/**/*.ts terminus-*/src/*.ts app/src/*.ts",
|
||||
"postinstall": "install-app-deps"
|
||||
"postinstall": "node ./scripts/install-deps.js"
|
||||
},
|
||||
"repository": "eugeny/terminus"
|
||||
}
|
||||
|
@@ -4,24 +4,18 @@ const path = require('path')
|
||||
const vars = require('./vars')
|
||||
|
||||
lifecycles = []
|
||||
lifecycles.push(rebuild({
|
||||
buildPath: path.resolve(__dirname, '../app'),
|
||||
electronVersion: vars.electronVersion,
|
||||
force: true,
|
||||
}).lifecycle)
|
||||
lifecycles.push(rebuild({
|
||||
buildPath: path.resolve(__dirname, '../terminus-ssh'),
|
||||
electronVersion: vars.electronVersion,
|
||||
force: true,
|
||||
}).lifecycle)
|
||||
lifecycles.push(rebuild({
|
||||
buildPath: path.resolve(__dirname, '../terminus-terminal'),
|
||||
electronVersion: vars.electronVersion,
|
||||
force: true,
|
||||
}).lifecycle)
|
||||
for (let dir of ['app', 'terminus-ssh', 'terminus-terminal']) {
|
||||
lifecycles.push([rebuild({
|
||||
buildPath: path.resolve(__dirname, '../' + dir),
|
||||
electronVersion: vars.electronVersion,
|
||||
force: true,
|
||||
}).lifecycle, dir])
|
||||
}
|
||||
|
||||
for (let lc of lifecycles) {
|
||||
console.info('Building against Electron', vars.electronVersion)
|
||||
|
||||
for (let [lc, dir] of lifecycles) {
|
||||
lc.on('module-found', name => {
|
||||
console.info('Rebuilding', name)
|
||||
console.info('Rebuilding', dir + '/' + name)
|
||||
})
|
||||
}
|
||||
|
@@ -8,7 +8,6 @@ const localBinPath = path.resolve(__dirname, '../node_modules/.bin');
|
||||
const npx = `${localBinPath}/npx`;
|
||||
|
||||
log.info('deps', 'app')
|
||||
sh.exec(`${npx} yarn install`)
|
||||
|
||||
sh.cd('app')
|
||||
sh.exec(`${npx} yarn install`)
|
||||
|
@@ -3,10 +3,11 @@ const fs = require('fs')
|
||||
const childProcess = require('child_process')
|
||||
|
||||
const appInfo = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../app/package.json')))
|
||||
const pkgInfo = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../package.json')))
|
||||
const electronInfo = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../node_modules/electron/package.json')))
|
||||
|
||||
exports.version = childProcess.execSync('git describe --tags', {encoding:'utf-8'})
|
||||
exports.version = exports.version.substring(1, exports.version.length - 1)
|
||||
exports.version = exports.version.substring(1).trim()
|
||||
exports.version = exports.version.replace('-', '-c')
|
||||
|
||||
exports.builtinPlugins = [
|
||||
'terminus-core',
|
||||
@@ -20,4 +21,4 @@ exports.bundledModules = [
|
||||
'@angular',
|
||||
'@ng-bootstrap',
|
||||
]
|
||||
exports.electronVersion = pkgInfo.devDependencies.electron
|
||||
exports.electronVersion = electronInfo.version
|
||||
|
@@ -27,7 +27,7 @@
|
||||
"electron-updater": "^2.8.9",
|
||||
"ng2-dnd": "^5.0.2",
|
||||
"ngx-perfect-scrollbar": "^6.0.0",
|
||||
"rage-edit-tmp": "^1.1.0",
|
||||
"rage-edit": "^1.2.0",
|
||||
"shell-escape": "^0.2.0",
|
||||
"universal-analytics": "^0.4.17"
|
||||
},
|
||||
|
@@ -6,5 +6,5 @@ export interface RecoveredTab {
|
||||
}
|
||||
|
||||
export abstract class TabRecoveryProvider {
|
||||
abstract async recover (recoveryToken: any): Promise<RecoveredTab|null>
|
||||
abstract async recover (recoveryToken: any): Promise<RecoveredTab | null>
|
||||
}
|
||||
|
@@ -69,7 +69,7 @@ export abstract class BaseTabComponent {
|
||||
this.activity.next(false)
|
||||
}
|
||||
|
||||
getRecoveryToken (): any {
|
||||
async getRecoveryToken (): Promise<any> {
|
||||
return null
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
.icon(tabindex='0', [class.active]='model', (keyup.space)='click()')
|
||||
i.fa.fa-square-o.off
|
||||
i.fa.fa-check-square.on
|
||||
i.fas.fa-square.off
|
||||
i.fas.fa-check-square.on
|
||||
.text {{text}}
|
||||
|
@@ -16,6 +16,7 @@ export class RenameTabModalComponent {
|
||||
ngOnInit () {
|
||||
setTimeout(() => {
|
||||
this.input.nativeElement.focus()
|
||||
this.input.nativeElement.select()
|
||||
}, 250)
|
||||
}
|
||||
|
||||
|
@@ -14,10 +14,10 @@ div
|
||||
footer.d-flex.align-items-center
|
||||
.btn-group.mr-auto
|
||||
button.btn.btn-secondary((click)='homeBase.openGitHub()')
|
||||
i.fa.fa-github
|
||||
i.fab.fa-github
|
||||
span GitHub
|
||||
button.btn.btn-secondary((click)='homeBase.reportBug()')
|
||||
i.fa.fa-bug
|
||||
i.fas.fa-bug
|
||||
span Report a problem
|
||||
|
||||
.form-control-static.selectable.no-drag Version: {{homeBase.appVersion}}
|
||||
|
@@ -17,7 +17,7 @@ import { BaseTabComponent } from '../components/baseTab.component'
|
||||
export class TabBodyComponent implements OnChanges {
|
||||
@Input() @HostBinding('class.active') active: boolean
|
||||
@Input() tab: BaseTabComponent
|
||||
@ViewChild('placeholder', {read: ViewContainerRef}) placeholder: ViewContainerRef
|
||||
@ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef
|
||||
|
||||
ngOnChanges (changes) {
|
||||
if (changes.tab) {
|
||||
|
@@ -3,6 +3,7 @@ import { SortableComponent } from 'ng2-dnd'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { BaseTabComponent } from './baseTab.component'
|
||||
import { RenameTabModalComponent } from './renameTabModal.component'
|
||||
import { HotkeysService } from '../services/hotkeys.service'
|
||||
import { ElectronService } from '../services/electron.service'
|
||||
import { AppService } from '../services/app.service'
|
||||
import { HostAppService, Platform } from '../services/hostApp.service'
|
||||
@@ -38,8 +39,17 @@ export class TabHeaderComponent {
|
||||
private zone: NgZone,
|
||||
private hostApp: HostAppService,
|
||||
private ngbModal: NgbModal,
|
||||
private hotkeys: HotkeysService,
|
||||
private parentDraggable: SortableComponent,
|
||||
) { }
|
||||
) {
|
||||
this.hotkeys.matchedHotkey.subscribe((hotkey) => {
|
||||
if (this.app.activeTab === this.tab) {
|
||||
if (hotkey === 'rename-tab') {
|
||||
this.showRenameTabModal()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
if (this.hostApp.platform === Platform.macOS) {
|
||||
@@ -50,7 +60,7 @@ export class TabHeaderComponent {
|
||||
})
|
||||
}
|
||||
|
||||
@HostListener('dblclick') onDoubleClick (): void {
|
||||
showRenameTabModal (): void {
|
||||
let modal = this.ngbModal.open(RenameTabModalComponent)
|
||||
modal.componentInstance.value = this.tab.customTitle || this.tab.title
|
||||
modal.result.then(result => {
|
||||
@@ -59,6 +69,10 @@ export class TabHeaderComponent {
|
||||
}).catch(() => null)
|
||||
}
|
||||
|
||||
@HostListener('dblclick') onDoubleClick (): void {
|
||||
this.showRenameTabModal()
|
||||
}
|
||||
|
||||
@HostListener('auxclick', ['$event']) async onAuxClick ($event: MouseEvent) {
|
||||
if ($event.which === 2) {
|
||||
this.app.closeTab(this.tab, true)
|
||||
@@ -97,6 +111,10 @@ export class TabHeaderComponent {
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
label: 'Rename',
|
||||
click: () => this.zone.run(() => this.showRenameTabModal())
|
||||
},
|
||||
{
|
||||
label: 'Color',
|
||||
sublabel: COLORS.find(x => x.value === this.tab.color).name,
|
||||
@@ -111,6 +129,13 @@ export class TabHeaderComponent {
|
||||
}
|
||||
])
|
||||
|
||||
if ((this.tab as any).saveAsProfile) {
|
||||
contextMenu.append(new this.electron.MenuItem({
|
||||
label: 'Save as a profile',
|
||||
click: () => this.zone.run(() => (this.tab as any).saveAsProfile())
|
||||
}))
|
||||
}
|
||||
|
||||
let process = await this.tab.getCurrentProcess()
|
||||
if (process) {
|
||||
contextMenu.append(new this.electron.MenuItem({
|
||||
|
@@ -7,46 +7,33 @@ hotkeys:
|
||||
- 'F11'
|
||||
close-tab:
|
||||
- 'Ctrl-Shift-W'
|
||||
- ['Ctrl-A', 'K']
|
||||
toggle-last-tab:
|
||||
- ['Ctrl-A', 'A']
|
||||
- ['Ctrl-A', 'Ctrl-A']
|
||||
toggle-last-tab: []
|
||||
rename-tab:
|
||||
- 'Ctrl-Shift-R'
|
||||
next-tab:
|
||||
- 'Ctrl-Shift-ArrowRight'
|
||||
- ['Ctrl-A', 'N']
|
||||
- 'Ctrl-Tab'
|
||||
previous-tab:
|
||||
- 'Ctrl-Shift-ArrowLeft'
|
||||
- ['Ctrl-A', 'P']
|
||||
- 'Ctrl-Shift-Tab'
|
||||
tab-1:
|
||||
- 'Alt-1'
|
||||
- ['Ctrl-A', '1']
|
||||
tab-2:
|
||||
- 'Alt-2'
|
||||
- ['Ctrl-A', '2']
|
||||
tab-3:
|
||||
- 'Alt-3'
|
||||
- ['Ctrl-A', '3']
|
||||
tab-4:
|
||||
- 'Alt-4'
|
||||
- ['Ctrl-A', '4']
|
||||
tab-5:
|
||||
- 'Alt-5'
|
||||
- ['Ctrl-A', '5']
|
||||
tab-6:
|
||||
- 'Alt-6'
|
||||
- ['Ctrl-A', '6']
|
||||
tab-7:
|
||||
- 'Alt-7'
|
||||
- ['Ctrl-A', '7']
|
||||
tab-8:
|
||||
- 'Alt-8'
|
||||
- ['Ctrl-A', '8']
|
||||
tab-9:
|
||||
- 'Alt-9'
|
||||
- ['Ctrl-A', '9']
|
||||
tab-10:
|
||||
- 'Alt-0'
|
||||
- ['Ctrl-A', '0']
|
||||
pluginBlacklist: ['ssh']
|
||||
|
@@ -8,6 +8,8 @@ hotkeys:
|
||||
close-tab:
|
||||
- '⌘-W'
|
||||
toggle-last-tab: []
|
||||
rename-tab:
|
||||
- '⌘-R'
|
||||
next-tab:
|
||||
- 'Ctrl-Tab'
|
||||
previous-tab:
|
||||
|
@@ -7,46 +7,33 @@ hotkeys:
|
||||
- 'F11'
|
||||
close-tab:
|
||||
- 'Ctrl-Shift-W'
|
||||
- ['Ctrl-A', 'K']
|
||||
toggle-last-tab:
|
||||
- ['Ctrl-A', 'A']
|
||||
- ['Ctrl-A', 'Ctrl-A']
|
||||
toggle-last-tab: []
|
||||
rename-tab:
|
||||
- 'Ctrl-Shift-R'
|
||||
next-tab:
|
||||
- 'Ctrl-Shift-ArrowRight'
|
||||
- ['Ctrl-A', 'N']
|
||||
- 'Ctrl-Tab'
|
||||
previous-tab:
|
||||
- 'Ctrl-Shift-ArrowLeft'
|
||||
- ['Ctrl-A', 'P']
|
||||
- 'Ctrl-Shift-Tab'
|
||||
tab-1:
|
||||
- 'Alt-1'
|
||||
- ['Ctrl-A', '1']
|
||||
tab-2:
|
||||
- 'Alt-2'
|
||||
- ['Ctrl-A', '2']
|
||||
tab-3:
|
||||
- 'Alt-3'
|
||||
- ['Ctrl-A', '3']
|
||||
tab-4:
|
||||
- 'Alt-4'
|
||||
- ['Ctrl-A', '4']
|
||||
tab-5:
|
||||
- 'Alt-5'
|
||||
- ['Ctrl-A', '5']
|
||||
tab-6:
|
||||
- 'Alt-6'
|
||||
- ['Ctrl-A', '6']
|
||||
tab-7:
|
||||
- 'Alt-7'
|
||||
- ['Ctrl-A', '7']
|
||||
tab-8:
|
||||
- 'Alt-8'
|
||||
- ['Ctrl-A', '8']
|
||||
tab-9:
|
||||
- 'Alt-9'
|
||||
- ['Ctrl-A', '9']
|
||||
tab-10:
|
||||
- 'Alt-0'
|
||||
- ['Ctrl-A', '0']
|
||||
pluginBlacklist: []
|
||||
|
@@ -6,19 +6,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { PerfectScrollbarModule, PERFECT_SCROLLBAR_CONFIG } from 'ngx-perfect-scrollbar'
|
||||
import { DndModule } from 'ng2-dnd'
|
||||
|
||||
import { AppService } from './services/app.service'
|
||||
import { ConfigService } from './services/config.service'
|
||||
import { ElectronService } from './services/electron.service'
|
||||
import { HostAppService } from './services/hostApp.service'
|
||||
import { LogService } from './services/log.service'
|
||||
import { HomeBaseService } from './services/homeBase.service'
|
||||
import { HotkeysService, AppHotkeyProvider } from './services/hotkeys.service'
|
||||
import { DockingService } from './services/docking.service'
|
||||
import { ShellIntegrationService } from './services/shellIntegration.service'
|
||||
import { TabRecoveryService } from './services/tabRecovery.service'
|
||||
import { ThemesService } from './services/themes.service'
|
||||
import { TouchbarService } from './services/touchbar.service'
|
||||
import { UpdaterService } from './services/updater.service'
|
||||
import { AppHotkeyProvider } from './services/hotkeys.service'
|
||||
|
||||
import { AppRootComponent } from './components/appRoot.component'
|
||||
import { CheckboxComponent } from './components/checkbox.component'
|
||||
@@ -44,25 +32,12 @@ import 'perfect-scrollbar/css/perfect-scrollbar.css'
|
||||
import 'ng2-dnd/bundles/style.css'
|
||||
|
||||
const PROVIDERS = [
|
||||
AppService,
|
||||
ConfigService,
|
||||
DockingService,
|
||||
ElectronService,
|
||||
HomeBaseService,
|
||||
HostAppService,
|
||||
HotkeysService,
|
||||
LogService,
|
||||
ShellIntegrationService,
|
||||
TabRecoveryService,
|
||||
ThemesService,
|
||||
TouchbarService,
|
||||
UpdaterService,
|
||||
{ provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
|
||||
{ provide: Theme, useClass: StandardTheme, multi: true },
|
||||
{ provide: Theme, useClass: StandardCompactTheme, multi: true },
|
||||
{ provide: Theme, useClass: PaperTheme, multi: true },
|
||||
{ provide: ConfigProvider, useClass: CoreConfigProvider, multi: true },
|
||||
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true }}
|
||||
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } }
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
|
@@ -35,7 +35,7 @@ class CompletionObserver {
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AppService {
|
||||
tabs: BaseTabComponent[] = []
|
||||
activeTab: BaseTabComponent
|
||||
|
@@ -56,7 +56,7 @@ export class ConfigProxy {
|
||||
return real[key]
|
||||
} else {
|
||||
if (isNonStructuralObjectMember(defaults[key])) {
|
||||
real[key] = {...defaults[key]}
|
||||
real[key] = { ...defaults[key] }
|
||||
delete real[key].__nonStructural
|
||||
return real[key]
|
||||
} else {
|
||||
@@ -74,7 +74,7 @@ export class ConfigProxy {
|
||||
setValue (key: string, value: any) { } // tslint:disable-line
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ConfigService {
|
||||
store: any
|
||||
restartRequested: boolean
|
||||
|
@@ -8,7 +8,7 @@ export interface IScreen {
|
||||
name: string
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DockingService {
|
||||
constructor (
|
||||
private electron: ElectronService,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { TouchBar, BrowserWindow, Menu, MenuItem } from 'electron'
|
||||
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ElectronService {
|
||||
app: any
|
||||
ipcRenderer: any
|
||||
|
@@ -5,7 +5,7 @@ import { ConfigService } from './config.service'
|
||||
import ua = require('universal-analytics')
|
||||
import uuidv4 = require('uuid/v4')
|
||||
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class HomeBaseService {
|
||||
appVersion: string
|
||||
|
||||
|
@@ -16,7 +16,7 @@ export interface Bounds {
|
||||
height: number
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class HostAppService {
|
||||
platform: Platform
|
||||
nodePlatform: string
|
||||
@@ -27,8 +27,10 @@ export class HostAppService {
|
||||
private cliOpenDirectory = new Subject<string>()
|
||||
private cliRunCommand = new Subject<string[]>()
|
||||
private cliPaste = new Subject<string>()
|
||||
private cliOpenProfile = new Subject<string>()
|
||||
private configChangeBroadcast = new Subject<void>()
|
||||
private windowCloseRequest = new Subject<void>()
|
||||
private windowMoved = new Subject<void>()
|
||||
private logger: Logger
|
||||
private windowId: number
|
||||
|
||||
@@ -37,8 +39,10 @@ export class HostAppService {
|
||||
get cliOpenDirectory$ (): Observable<string> { return this.cliOpenDirectory }
|
||||
get cliRunCommand$ (): Observable<string[]> { return this.cliRunCommand }
|
||||
get cliPaste$ (): Observable<string> { return this.cliPaste }
|
||||
get cliOpenProfile$ (): Observable<string> { return this.cliOpenProfile }
|
||||
get configChangeBroadcast$ (): Observable<void> { return this.configChangeBroadcast }
|
||||
get windowCloseRequest$ (): Observable<void> { return this.windowCloseRequest }
|
||||
get windowMoved$ (): Observable<void> { return this.windowMoved }
|
||||
|
||||
constructor (
|
||||
private zone: NgZone,
|
||||
@@ -78,6 +82,10 @@ export class HostAppService {
|
||||
this.zone.run(() => this.windowCloseRequest.next())
|
||||
})
|
||||
|
||||
electron.ipcRenderer.on('host:window-moved', () => {
|
||||
this.zone.run(() => this.windowMoved.next())
|
||||
})
|
||||
|
||||
electron.ipcRenderer.on('host:second-instance', (_$event, argv: any, cwd: string) => this.zone.run(() => {
|
||||
this.logger.info('Second instance', argv)
|
||||
const op = argv._[0]
|
||||
@@ -91,6 +99,8 @@ export class HostAppService {
|
||||
text = shellEscape([text])
|
||||
}
|
||||
this.cliPaste.next(text)
|
||||
} else if (op === 'profile') {
|
||||
this.cliOpenProfile.next(argv.profileName)
|
||||
} else {
|
||||
this.secondInstance.next()
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ interface EventBufferEntry {
|
||||
time: number,
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class HotkeysService {
|
||||
key = new EventEmitter<NativeKeyEvent>()
|
||||
matchedHotkey = new EventEmitter<string>()
|
||||
@@ -80,8 +80,8 @@ export class HotkeysService {
|
||||
}
|
||||
|
||||
getCurrentKeystrokes (): string[] {
|
||||
this.currentKeystrokes = this.currentKeystrokes.filter((x) => performance.now() - x.time < KEY_TIMEOUT )
|
||||
return stringifyKeySequence(this.currentKeystrokes.map((x) => x.event))
|
||||
this.currentKeystrokes = this.currentKeystrokes.filter(x => performance.now() - x.time < KEY_TIMEOUT)
|
||||
return stringifyKeySequence(this.currentKeystrokes.map(x => x.event))
|
||||
}
|
||||
|
||||
registerGlobalHotkey () {
|
||||
@@ -215,6 +215,10 @@ export class AppHotkeyProvider extends HotkeyProvider {
|
||||
id: 'toggle-fullscreen',
|
||||
name: 'Toggle fullscreen mode',
|
||||
},
|
||||
{
|
||||
id: 'rename-tab',
|
||||
name: 'Rename Tab',
|
||||
},
|
||||
{
|
||||
id: 'close-tab',
|
||||
name: 'Close tab',
|
||||
|
@@ -53,7 +53,7 @@ export class Logger {
|
||||
log (...args: any[]) { this.doLog('log', ...args) }
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class LogService {
|
||||
private log: any
|
||||
|
||||
|
@@ -1,12 +1,12 @@
|
||||
import * as path from 'path'
|
||||
import * as fs from 'mz/fs'
|
||||
import { Registry } from 'rage-edit-tmp'
|
||||
import { Registry } from 'rage-edit'
|
||||
import { exec } from 'mz/child_process'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ElectronService } from './electron.service'
|
||||
import { HostAppService, Platform } from './hostApp.service'
|
||||
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ShellIntegrationService {
|
||||
private automatorWorkflows = ['Open Terminus here.workflow', 'Paste path into Terminus.workflow']
|
||||
private automatorWorkflowsLocation: string
|
||||
@@ -48,9 +48,9 @@ export class ShellIntegrationService {
|
||||
|
||||
async isInstalled (): Promise<boolean> {
|
||||
if (this.hostApp.platform === Platform.macOS) {
|
||||
return await fs.exists(path.join(this.automatorWorkflowsDestination, this.automatorWorkflows[0]))
|
||||
return fs.exists(path.join(this.automatorWorkflowsDestination, this.automatorWorkflows[0]))
|
||||
} else if (this.hostApp.platform === Platform.Windows) {
|
||||
return await Registry.has(this.registryKeys[0].path)
|
||||
return Registry.has(this.registryKeys[0].path)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ import { Logger, LogService } from '../services/log.service'
|
||||
import { AppService } from '../services/app.service'
|
||||
import { ConfigService } from '../services/config.service'
|
||||
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class TabRecoveryService {
|
||||
logger: Logger
|
||||
|
||||
@@ -19,13 +19,18 @@ export class TabRecoveryService {
|
||||
app.tabsChanged$.subscribe(() => {
|
||||
this.saveTabs(app.tabs)
|
||||
})
|
||||
setInterval(() => {
|
||||
this.saveTabs(app.tabs)
|
||||
}, 30000)
|
||||
}
|
||||
|
||||
saveTabs (tabs: BaseTabComponent[]) {
|
||||
async saveTabs (tabs: BaseTabComponent[]) {
|
||||
window.localStorage.tabsRecovery = JSON.stringify(
|
||||
tabs
|
||||
.map((tab) => tab.getRecoveryToken())
|
||||
.filter((token) => !!token)
|
||||
await Promise.all(
|
||||
tabs
|
||||
.map(tab => tab.getRecoveryToken())
|
||||
.filter(token => !!token)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,7 @@ import { Inject, Injectable } from '@angular/core'
|
||||
import { ConfigService } from '../services/config.service'
|
||||
import { Theme } from '../api/theme'
|
||||
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ThemesService {
|
||||
private styleElement: HTMLElement = null
|
||||
|
||||
|
@@ -6,7 +6,7 @@ import { ElectronService } from './electron.service'
|
||||
import { HostAppService } from './hostApp.service'
|
||||
import { IToolbarButton, ToolbarButtonProvider } from '../api'
|
||||
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class TouchbarService {
|
||||
private tabsSegmentedControl: TouchBarSegmentedControl
|
||||
private tabSegments: SegmentedControlSegment[] = []
|
||||
@@ -49,8 +49,8 @@ export class TouchbarService {
|
||||
let touchBar = new this.electron.TouchBar({
|
||||
items: [
|
||||
this.tabsSegmentedControl,
|
||||
new this.electron.TouchBar.TouchBarSpacer({size: 'flexible'}),
|
||||
new this.electron.TouchBar.TouchBarSpacer({size: 'small'}),
|
||||
new this.electron.TouchBar.TouchBarSpacer({ size: 'flexible' }),
|
||||
new this.electron.TouchBar.TouchBarSpacer({ size: 'small' }),
|
||||
...buttons.map(button => this.getButton(button))
|
||||
]
|
||||
})
|
||||
|
@@ -6,7 +6,7 @@ import { ElectronService } from './electron.service'
|
||||
|
||||
const UPDATES_URL = 'https://api.github.com/repos/eugeny/terminus/releases/latest'
|
||||
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class UpdaterService {
|
||||
private logger: Logger
|
||||
private downloaded: Promise<boolean>
|
||||
|
@@ -51,6 +51,7 @@ $input-disabled-bg: #333;
|
||||
$input-color: $body-color;
|
||||
$input-color-placeholder: #333;
|
||||
$input-border-color: #344;
|
||||
$input-border-width: 0;
|
||||
//$input-box-shadow: inset 0 1px 1px rgba($black,.075);
|
||||
$input-border-radius: 0;
|
||||
$custom-select-border-radius: 0;
|
||||
@@ -70,7 +71,7 @@ $popover-bg: $body-bg;
|
||||
|
||||
$dropdown-bg: $body-bg;
|
||||
$dropdown-link-color: $body-color;
|
||||
$dropdown-link-hover-color: #333;
|
||||
$dropdown-link-hover-color: white;
|
||||
$dropdown-link-hover-bg: $body-bg2;
|
||||
//$dropdown-link-active-color: $component-active-color;
|
||||
//$dropdown-link-active-bg: $component-active-bg;
|
||||
@@ -346,6 +347,15 @@ ngb-tabset .tab-content {
|
||||
}
|
||||
}
|
||||
|
||||
.list-group.list-group-flush .list-group-item:not(.list-group-item-action) {
|
||||
background: transparent;
|
||||
border-color: rgba(0, 0, 0, 0.2);
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
select.form-control {
|
||||
-webkit-appearance: none;
|
||||
background-image: url("data:image/svg+xml;utf8,<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='0 0 24 24'><path fill='#444' d='M7.406 7.828l4.594 4.594 4.594-4.594 1.406 1.406-6 6-6-6z'></path></svg>");
|
||||
@@ -362,6 +372,15 @@ toggle.active .body .toggle {
|
||||
background: $blue;
|
||||
}
|
||||
|
||||
.modal .modal-footer {
|
||||
background: rgba(0, 0, 0, .25);
|
||||
|
||||
.btn {
|
||||
font-weight: bold;
|
||||
padding: 0.375rem 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.list-group-item svg {
|
||||
fill: white;
|
||||
fill-opacity: 0.75;
|
||||
|
@@ -486,10 +486,10 @@ qs@~6.5.1:
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
|
||||
|
||||
rage-edit-tmp@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/rage-edit-tmp/-/rage-edit-tmp-1.1.0.tgz#fc5d76716d2fe2cf97dcafbf3e26753e3a08e3b2"
|
||||
integrity sha512-lR97QHY5WSf9orInMJhPqUbenkdiy7QbXUoRMI+wBZGyAPkxNwgo7h6ojq634QrBf/kQo3mVXYjuD3ZYraNaZQ==
|
||||
rage-edit@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/rage-edit/-/rage-edit-1.2.0.tgz#991860a60fef934d8a6d0f057e55786b02f94a2b"
|
||||
integrity sha512-0RspBRc2s6We4g7hRCvT5mu7YPEnfjvQK8Tt354a2uUNJCMC7MKLvo/1mLvHUCQ/zbP6siQyp5VRZN7UCpMFZg==
|
||||
|
||||
request@2.86.0:
|
||||
version "2.86.0"
|
||||
|
@@ -2,57 +2,61 @@
|
||||
strong Error in {{erroredPlugin}}:
|
||||
pre {{errorMessage}}
|
||||
|
||||
button.btn.btn-outline-info.btn-sm.pull-right((click)='openPluginsFolder()')
|
||||
i.fa.fa-folder
|
||||
span Plugins folder
|
||||
|
||||
.d-flex
|
||||
h3.mb-1 Installed
|
||||
button.btn.btn-outline-info.btn-sm.ml-auto((click)='openPluginsFolder()')
|
||||
i.fas.fa-folder
|
||||
span Plugins folder
|
||||
|
||||
h3.mb-1 Installed
|
||||
.list-group.list-group-flush.mt-2
|
||||
.list-group-item.d-flex.align-items-center(*ngFor='let plugin of pluginManager.installedPlugins|orderBy:"name"')
|
||||
.mr-auto.d-flex.flex-column
|
||||
div
|
||||
strong {{plugin.name}}
|
||||
small.text-muted.ml-1(*ngIf='!plugin.isBuiltin') {{plugin.version}} / {{plugin.author}}
|
||||
small.text-warning.ml-1(*ngIf='config.store.pluginBlacklist.includes(plugin.name)') Disabled
|
||||
a.text-muted.mb-0((click)='showPluginInfo(plugin)')
|
||||
small {{plugin.description}}
|
||||
|
||||
.mb-3.d-flex.w-100.align-items-center(*ngFor='let plugin of pluginManager.installedPlugins|orderBy:"name"')
|
||||
button.btn.btn-outline-danger.active.mr-2(
|
||||
*ngIf='config.store.pluginBlacklist.includes(plugin.name)',
|
||||
(click)='enablePlugin(plugin)'
|
||||
)
|
||||
i.fa.fa-fw.fa-pause
|
||||
button.btn.btn-outline-secondary.mr-2(
|
||||
*ngIf='!config.store.pluginBlacklist.includes(plugin.name)',
|
||||
(click)='disablePlugin(plugin)'
|
||||
)
|
||||
i.fa.fa-fw.fa-check
|
||||
button.btn.btn-primary.ml-2(
|
||||
*ngIf='npmInstalled && knownUpgrades[plugin.name]',
|
||||
(click)='upgradePlugin(plugin)',
|
||||
[disabled]='busy[plugin.name] != undefined'
|
||||
)
|
||||
i.fas.fa-fw.fa-arrow-up(*ngIf='busy[plugin.name] != BusyState.Installing')
|
||||
i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Installing')
|
||||
span Upgrade ({{knownUpgrades[plugin.name].version}})
|
||||
|
||||
.mr-auto.d-flex.flex-column
|
||||
div
|
||||
strong {{plugin.name}}
|
||||
small.text-muted.ml-1 {{plugin.version}} / {{plugin.author}}
|
||||
a.text-muted.mb-0((click)='showPluginInfo(plugin)')
|
||||
small {{plugin.description}}
|
||||
|
||||
button.btn.btn-primary.ml-2(
|
||||
*ngIf='npmInstalled && knownUpgrades[plugin.name]',
|
||||
(click)='upgradePlugin(plugin)',
|
||||
[disabled]='busy[plugin.name] != undefined'
|
||||
)
|
||||
i.fa.fa-fw.fa-arrow-up(*ngIf='busy[plugin.name] != BusyState.Installing')
|
||||
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Installing')
|
||||
span Upgrade ({{knownUpgrades[plugin.name].version}})
|
||||
|
||||
button.btn.btn-outline-danger.ml-2(
|
||||
(click)='uninstallPlugin(plugin)',
|
||||
*ngIf='!plugin.isBuiltin && npmInstalled',
|
||||
[disabled]='busy[plugin.name] != undefined'
|
||||
)
|
||||
i.fa.fa-fw.fa-trash-o(*ngIf='busy[plugin.name] != BusyState.Uninstalling')
|
||||
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Uninstalling')
|
||||
button.btn.btn-primary.ml-2(
|
||||
*ngIf='config.store.pluginBlacklist.includes(plugin.name)',
|
||||
(click)='enablePlugin(plugin)'
|
||||
)
|
||||
i.fas.fa-fw.fa-play
|
||||
|
||||
button.btn.btn-secondary.ml-2(
|
||||
*ngIf='!config.store.pluginBlacklist.includes(plugin.name)',
|
||||
(click)='disablePlugin(plugin)'
|
||||
)
|
||||
i.fas.fa-fw.fa-pause
|
||||
|
||||
button.btn.btn-danger.ml-2(
|
||||
(click)='uninstallPlugin(plugin)',
|
||||
*ngIf='!plugin.isBuiltin && npmInstalled',
|
||||
[disabled]='busy[plugin.name] != undefined'
|
||||
)
|
||||
i.fas.fa-fw.fa-trash(*ngIf='busy[plugin.name] != BusyState.Uninstalling')
|
||||
i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Uninstalling')
|
||||
|
||||
.text-center.mt-5(*ngIf='npmMissing')
|
||||
h4 npm not installed
|
||||
p.mb-2 npm is required to install Terminus plugins.
|
||||
.btn-group
|
||||
button.btn.btn-outline-primary((click)='downloadNPM()')
|
||||
i.fa.fa-download
|
||||
i.fas.fa-download
|
||||
span Get npm
|
||||
button.btn.btn-outline-info((click)='checkNPM()')
|
||||
i.fa.fa-refresh
|
||||
i.fas.fa-refresh
|
||||
span Try again
|
||||
|
||||
div(*ngIf='npmInstalled')
|
||||
@@ -61,8 +65,8 @@ div(*ngIf='npmInstalled')
|
||||
.input-group.mb-3
|
||||
.input-group-prepend
|
||||
.input-group-text
|
||||
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='!availablePluginsReady')
|
||||
i.fa.fa-fw.fa-search(*ngIf='availablePluginsReady')
|
||||
i.fas.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='!availablePluginsReady')
|
||||
i.fas.fa-fw.fa-search(*ngIf='availablePluginsReady')
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='_1',
|
||||
@@ -71,19 +75,19 @@ div(*ngIf='npmInstalled')
|
||||
)
|
||||
|
||||
|
||||
.mb-4(*ngIf='availablePlugins$')
|
||||
.list-group.list-group-flush.mb-4(*ngIf='availablePlugins$')
|
||||
ng-container(*ngFor='let plugin of (availablePlugins$|async|orderBy:"name")')
|
||||
.d-flex.w-100.align-items-center.mb-3(*ngIf='!isAlreadyInstalled(plugin)')
|
||||
button.btn.btn-primary.mr-2(
|
||||
.list-group-item.d-flex.align-items-center(*ngIf='!isAlreadyInstalled(plugin)')
|
||||
button.btn.btn-primary.mr-3(
|
||||
(click)='installPlugin(plugin)',
|
||||
[disabled]='busy[plugin.name] != undefined'
|
||||
)
|
||||
i.fa.fa-fw.fa-download(*ngIf='busy[plugin.name] != BusyState.Installing')
|
||||
i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Installing')
|
||||
i.fas.fa-fw.fa-download(*ngIf='busy[plugin.name] != BusyState.Installing')
|
||||
i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Installing')
|
||||
|
||||
div((click)='showPluginInfo(plugin)')
|
||||
div
|
||||
strong {{plugin.name}}
|
||||
small.text-muted.ml-1 {{plugin.version}} / {{plugin.author}}
|
||||
i.fa.fa-check.text-success.ml-1(*ngIf='plugin.isOfficial', title='Official')
|
||||
i.fas.fa-check.text-success.ml-1(*ngIf='plugin.isOfficial', title='Official')
|
||||
small.text-muted {{plugin.description}}
|
||||
|
@@ -117,7 +117,7 @@ export class PluginsSettingsTabComponent {
|
||||
}
|
||||
|
||||
disablePlugin (plugin: IPluginInfo) {
|
||||
this.config.store.pluginBlacklist.push(plugin.name)
|
||||
this.config.store.pluginBlacklist = [...this.config.store.pluginBlacklist, plugin.name]
|
||||
this.config.save()
|
||||
this.config.requestRestart()
|
||||
}
|
||||
|
@@ -22,7 +22,6 @@ import { PluginsSettingsTabProvider } from './settings'
|
||||
providers: [
|
||||
{ provide: SettingsTabProvider, useClass: PluginsSettingsTabProvider, multi: true },
|
||||
{ provide: ConfigProvider, useClass: PluginsConfigProvider, multi: true },
|
||||
PluginManagerService,
|
||||
],
|
||||
entryComponents: [
|
||||
PluginsSettingsTabComponent,
|
||||
|
@@ -23,7 +23,7 @@ export interface IPluginInfo {
|
||||
path?: string
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PluginManagerService {
|
||||
logger: Logger
|
||||
builtinPluginsPath: string = (window as any).builtinPluginsPath
|
||||
|
@@ -12,11 +12,11 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
||||
.text-muted.mr-auto {{homeBase.appVersion}}
|
||||
|
||||
button.btn.btn-secondary.mr-3((click)='homeBase.openGitHub()')
|
||||
i.fa.fa-github
|
||||
i.fab.fa-github
|
||||
span GitHub
|
||||
|
||||
button.btn.btn-secondary((click)='homeBase.reportBug()')
|
||||
i.fa.fa-bug
|
||||
i.fas.fa-bug
|
||||
span Report a problem
|
||||
|
||||
.form-line(*ngIf='!isShellIntegrationInstalled')
|
||||
@@ -24,7 +24,7 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
||||
.title Shell integration
|
||||
.description Allows quickly opening a terminal in the selected folder
|
||||
button.btn.btn-primary((click)='installShellIntegration()')
|
||||
i.fa.fa-check
|
||||
i.fas.fa-check
|
||||
span Install
|
||||
|
||||
.form-line
|
||||
@@ -225,7 +225,7 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
||||
.title Debugging
|
||||
|
||||
button.btn.btn-secondary((click)='hostApp.openDevTools()')
|
||||
i.fa.fa-bug
|
||||
i.fas.fa-bug
|
||||
span Open DevTools
|
||||
|
||||
.form-line
|
||||
@@ -254,7 +254,7 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
||||
.input-group.mb-4
|
||||
.input-group-prepend
|
||||
.input-group-text
|
||||
i.fa.fa-fw.fa-search
|
||||
i.fas.fa-fw.fa-search
|
||||
input.form-control(type='search', placeholder='Search hotkeys', [(ngModel)]='hotkeyFilter')
|
||||
|
||||
.form-group
|
||||
@@ -298,8 +298,8 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
|
||||
)
|
||||
.mt-3
|
||||
button.btn.btn-primary((click)='saveConfigFile()', *ngIf='isConfigFileValid()')
|
||||
i.fa.fa-check.mr-2
|
||||
i.fas.fa-check.mr-2
|
||||
| Save and apply
|
||||
button.btn.btn-primary(disabled, *ngIf='!isConfigFileValid()')
|
||||
i.fa.fa-warning.mr-2
|
||||
i.fas.fa-exclamation-triangle.mr-2
|
||||
| Invalid syntax
|
||||
|
@@ -2,12 +2,12 @@ import * as yaml from 'js-yaml'
|
||||
import * as os from 'os'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { Component, Inject, Input, HostBinding } from '@angular/core'
|
||||
import { HotkeysService } from 'terminus-core'
|
||||
import {
|
||||
ElectronService,
|
||||
DockingService,
|
||||
ConfigService,
|
||||
IHotkeyDescription,
|
||||
HotkeysService,
|
||||
BaseTabComponent,
|
||||
Theme,
|
||||
HostAppService,
|
||||
@@ -80,7 +80,7 @@ export class SettingsTabComponent extends BaseTabComponent {
|
||||
this.isShellIntegrationInstalled = await this.shellIntegration.isInstalled()
|
||||
}
|
||||
|
||||
getRecoveryToken (): any {
|
||||
async getRecoveryToken (): Promise<any> {
|
||||
return { type: 'app:settings' }
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,7 @@ import { SettingsTabProvider } from '../api'
|
||||
})
|
||||
export class SettingsTabBodyComponent {
|
||||
@Input() provider: SettingsTabProvider
|
||||
@ViewChild('placeholder', {read: ViewContainerRef}) placeholder: ViewContainerRef
|
||||
@ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef
|
||||
component: ComponentRef<Component>
|
||||
|
||||
constructor (private componentFactoryResolver: ComponentFactoryResolver) { }
|
||||
|
@@ -4,8 +4,7 @@ import { FormsModule } from '@angular/forms'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgPipesModule } from 'ngx-pipes'
|
||||
|
||||
import { ToolbarButtonProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider } from 'terminus-core'
|
||||
import TerminusCorePlugin from 'terminus-core'
|
||||
import TerminusCorePlugin, { ToolbarButtonProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider } from 'terminus-core'
|
||||
|
||||
import { HotkeyInputModalComponent } from './components/hotkeyInputModal.component'
|
||||
import { MultiHotkeyInputComponent } from './components/multiHotkeyInput.component'
|
||||
|
@@ -36,7 +36,8 @@
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"wincredmgr": "^2.0.0",
|
||||
"xkeychain": "^0.0.6"
|
||||
"xkeychain": "^0.0.6",
|
||||
"windows-process-tree": "^0.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"ssh2": "^0.5.5"
|
||||
|
@@ -56,7 +56,7 @@
|
||||
)
|
||||
.input-group-btn
|
||||
button.btn.btn-secondary((click)='selectPrivateKey()')
|
||||
i.fa.fa-folder-open
|
||||
i.fas.fa-folder-open
|
||||
|
||||
ngb-tab(id='advanced')
|
||||
ng-template(ngbTabTitle)
|
||||
@@ -119,11 +119,11 @@
|
||||
td
|
||||
.input-group.flex-nowrap
|
||||
button.btn.btn-outline-info.ml-0((click)='moveScriptUp(script)')
|
||||
i.fa.fa-arrow-up
|
||||
i.fas.fa-arrow-up
|
||||
button.btn.btn-outline-info.ml-0((click)='moveScriptDown(script)')
|
||||
i.fa.fa-arrow-down
|
||||
i.fas.fa-arrow-down
|
||||
button.btn.btn-outline-danger.ml-0((click)='deleteScript(script)')
|
||||
i.fa.fa-trash-o
|
||||
i.fas.fa-trash
|
||||
tr
|
||||
td
|
||||
input.form-control(
|
||||
@@ -148,9 +148,9 @@
|
||||
td
|
||||
.input-group.flex-nowrap
|
||||
button.btn.btn-outline-info.ml-0((click)='addScript()')
|
||||
i.fa.fa-check
|
||||
i.fas.fa-check
|
||||
button.btn.btn-outline-danger.ml-0((click)='clearScript()')
|
||||
i.fa.fa-trash-o
|
||||
i.fas.fa-trash
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-outline-primary((click)='save()') Save
|
||||
|
@@ -76,7 +76,7 @@ export class EditConnectionModalComponent {
|
||||
if (!this.connection.scripts) {
|
||||
this.connection.scripts = []
|
||||
}
|
||||
this.connection.scripts.push({...this.newScript})
|
||||
this.connection.scripts.push({ ...this.newScript })
|
||||
this.clearScript()
|
||||
}
|
||||
|
||||
|
@@ -10,10 +10,10 @@
|
||||
|
||||
.list-group.mt-3(*ngIf='lastConnection')
|
||||
a.list-group-item.list-group-item-action.d-flex.align-items-center((click)='connect(lastConnection)')
|
||||
i.fa.fa-fw.fa-history
|
||||
i.fas.fa-fw.fa-history
|
||||
.mr-auto {{lastConnection.name}}
|
||||
button.btn.btn-outline-danger.btn-sm((click)='clearLastConnection(); $event.stopPropagation()')
|
||||
i.fa.fa-trash-o
|
||||
i.fas.fa-trash
|
||||
|
||||
.list-group.mt-3.connections-list(*ngIf='childGroups.length')
|
||||
ng-container(*ngFor='let group of childGroups')
|
||||
@@ -27,4 +27,6 @@
|
||||
.list-group-item.list-group-item-action.pl-5.d-flex.align-items-center(
|
||||
*ngFor='let connection of group.connections',
|
||||
(click)='connect(connection)'
|
||||
) {{connection.name}}
|
||||
)
|
||||
.mr-2 {{connection.name}}
|
||||
.text-muted {{connection.host}}
|
||||
|
@@ -1,25 +1,28 @@
|
||||
h3 Connections
|
||||
|
||||
.list-group.mt-3.mb-3
|
||||
.list-group.list-group-flush.mt-3.mb-3
|
||||
ng-container(*ngFor='let group of childGroups')
|
||||
.list-group-item.list-group-item-action.d-flex.align-items-center((click)='groupCollapsed[group.name] = !groupCollapsed[group.name]')
|
||||
.list-group-item.list-group-item-action.d-flex.align-items-center(
|
||||
(click)='groupCollapsed[group.name] = !groupCollapsed[group.name]'
|
||||
)
|
||||
.fa.fa-fw.fa-chevron-right(*ngIf='groupCollapsed[group.name]')
|
||||
.fa.fa-fw.fa-chevron-down(*ngIf='!groupCollapsed[group.name]')
|
||||
span.ml-3.mr-auto {{group.name || "Ungrouped"}}
|
||||
button.btn.btn-outline-info.ml-2((click)='editGroup(group)')
|
||||
i.fa.fa-pencil
|
||||
i.fas.fa-edit
|
||||
button.btn.btn-outline-danger.ml-1((click)='deleteGroup(group)')
|
||||
i.fa.fa-trash-o
|
||||
i.fas.fa-trash
|
||||
ng-container(*ngIf='!groupCollapsed[group.name]')
|
||||
.list-group-item.pl-5.d-flex.align-items-center(*ngFor='let connection of group.connections')
|
||||
.list-group-item.list-group-item-action.pl-5.d-flex.align-items-center(
|
||||
*ngFor='let connection of group.connections',
|
||||
(click)='editConnection(connection)'
|
||||
)
|
||||
.mr-auto
|
||||
div {{connection.name}}
|
||||
.text-muted {{connection.host}}
|
||||
button.btn.btn-outline-info.ml-2((click)='editConnection(connection)')
|
||||
i.fa.fa-pencil
|
||||
button.btn.btn-outline-danger.ml-1((click)='deleteConnection(connection)')
|
||||
i.fa.fa-trash-o
|
||||
button.btn.btn-outline-danger.ml-1((click)='$event.stopPropagation(); deleteConnection(connection)')
|
||||
i.fas.fa-trash
|
||||
|
||||
button.btn.btn-outline-primary((click)='createConnection()')
|
||||
div.fa.fa-fw.fa-globe
|
||||
button.btn.btn-primary((click)='createConnection()')
|
||||
i.fas.fa-fw.fa-plus
|
||||
span.ml-2 Add connection
|
||||
|
@@ -3,16 +3,13 @@ import { CommonModule } from '@angular/common'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { ToastrModule } from 'ngx-toastr'
|
||||
import { ToolbarButtonProvider, ConfigProvider } from 'terminus-core'
|
||||
import TerminusCoreModule from 'terminus-core'
|
||||
import TerminusCoreModule, { ToolbarButtonProvider, ConfigProvider } from 'terminus-core'
|
||||
import { SettingsTabProvider } from 'terminus-settings'
|
||||
|
||||
import { EditConnectionModalComponent } from './components/editConnectionModal.component'
|
||||
import { SSHModalComponent } from './components/sshModal.component'
|
||||
import { PromptModalComponent } from './components/promptModal.component'
|
||||
import { SSHSettingsTabComponent } from './components/sshSettingsTab.component'
|
||||
import { SSHService } from './services/ssh.service'
|
||||
import { PasswordStorageService } from './services/passwordStorage.service'
|
||||
|
||||
import { ButtonProvider } from './buttonProvider'
|
||||
import { SSHConfigProvider } from './config'
|
||||
@@ -27,8 +24,6 @@ import { SSHSettingsTabProvider } from './settings'
|
||||
TerminusCoreModule,
|
||||
],
|
||||
providers: [
|
||||
PasswordStorageService,
|
||||
SSHService,
|
||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||
{ provide: ConfigProvider, useClass: SSHConfigProvider, multi: true },
|
||||
{ provide: SettingsTabProvider, useClass: SSHSettingsTabProvider, multi: true },
|
||||
|
@@ -13,7 +13,7 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PasswordStorageService {
|
||||
constructor (
|
||||
private zone: NgZone,
|
||||
|
@@ -11,7 +11,13 @@ import { PromptModalComponent } from '../components/promptModal.component'
|
||||
import { PasswordStorageService } from './passwordStorage.service'
|
||||
const { SSH2Stream } = require('ssh2-streams')
|
||||
|
||||
@Injectable()
|
||||
let windowsProcessTree
|
||||
try {
|
||||
windowsProcessTree = require('windows-process-tree/build/Release/windows_process_tree.node')
|
||||
} catch (e) {
|
||||
} // tslint:disable-line
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SSHService {
|
||||
private logger: Logger
|
||||
|
||||
@@ -67,7 +73,7 @@ export class SSHService {
|
||||
let ssh = new Client()
|
||||
let connected = false
|
||||
let savedPassword: string = null
|
||||
await new Promise((resolve, reject) => {
|
||||
await new Promise(async (resolve, reject) => {
|
||||
ssh.on('ready', () => {
|
||||
connected = true
|
||||
if (savedPassword) {
|
||||
@@ -99,7 +105,14 @@ export class SSHService {
|
||||
|
||||
let agent: string = null
|
||||
if (this.hostApp.platform === Platform.Windows) {
|
||||
agent = 'pageant'
|
||||
let pageantRunning = new Promise<boolean>(resolve => {
|
||||
windowsProcessTree.getProcessList(list => {
|
||||
resolve(list.some(x => x.name === 'pageant.exe'))
|
||||
}, 0)
|
||||
})
|
||||
if (await pageantRunning) {
|
||||
agent = 'pageant'
|
||||
}
|
||||
} else {
|
||||
agent = process.env.SSH_AUTH_SOCK
|
||||
}
|
||||
|
@@ -48,6 +48,7 @@ module.exports = {
|
||||
'wincredmgr',
|
||||
'path',
|
||||
'ngx-toastr',
|
||||
'windows-process-tree/build/Release/windows_process_tree.node',
|
||||
/^rxjs/,
|
||||
/^@angular/,
|
||||
/^@ng-bootstrap/,
|
||||
|
@@ -1507,6 +1507,11 @@ ms@2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||
|
||||
nan@^2.10.0:
|
||||
version "2.11.1"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.1.tgz#90e22bccb8ca57ea4cd37cc83d3819b52eea6766"
|
||||
integrity sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==
|
||||
|
||||
nan@^2.3.0:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
|
||||
@@ -2520,6 +2525,13 @@ window-size@0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
|
||||
integrity sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=
|
||||
|
||||
windows-process-tree@^0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/windows-process-tree/-/windows-process-tree-0.2.3.tgz#6b781f0a320e8a0d6434c9399add4389c709cf6e"
|
||||
integrity sha512-SzPJSubVVsToz1g5lr2P+4mQT70gvJ9u/nlnpfkOeQcAhOuhKz5DiO1TARgR0OnVsv21LPzxbA2m/4JQkGh1wA==
|
||||
dependencies:
|
||||
nan "^2.10.0"
|
||||
|
||||
wordwrap@0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
|
||||
|
@@ -25,7 +25,7 @@
|
||||
"dataurl": "0.1.0",
|
||||
"deep-equal": "1.0.1",
|
||||
"file-loader": "^0.11.2",
|
||||
"rage-edit-tmp": "^1.1.0",
|
||||
"rage-edit": "1.2.0",
|
||||
"uuid": "^3.3.2",
|
||||
"xterm-addon-ligatures-tmp": "^0.1.0-beta-1"
|
||||
},
|
||||
@@ -40,12 +40,10 @@
|
||||
"terminus-settings": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/async-lock": "0.0.19",
|
||||
"async-lock": "^1.0.0",
|
||||
"@terminus-term/node-pty": "0.8.0-1",
|
||||
"font-manager": "0.3.0",
|
||||
"hterm-umdjs": "1.4.1",
|
||||
"mz": "^2.6.0",
|
||||
"node-pty-tmp": "0.7.2",
|
||||
"ps-node": "^0.1.6",
|
||||
"runes": "^0.4.2"
|
||||
},
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { Observable } from 'rxjs'
|
||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||
|
||||
export abstract class TerminalDecorator {
|
||||
@@ -15,25 +14,19 @@ export interface ResizeEvent {
|
||||
|
||||
export interface SessionOptions {
|
||||
name?: string
|
||||
command?: string
|
||||
args?: string[]
|
||||
command: string
|
||||
args: string[]
|
||||
cwd?: string
|
||||
env?: any
|
||||
width?: number
|
||||
height?: number
|
||||
recoveryId?: string
|
||||
recoveredTruePID$?: Observable<number>
|
||||
pauseAfterExit?: boolean
|
||||
runAsAdministrator?: boolean
|
||||
}
|
||||
|
||||
export abstract class SessionPersistenceProvider {
|
||||
abstract id: string
|
||||
abstract displayName: string
|
||||
|
||||
abstract isAvailable (): boolean
|
||||
abstract async attachSession (recoveryId: any): Promise<SessionOptions>
|
||||
abstract async startSession (options: SessionOptions): Promise<any>
|
||||
abstract async terminateSession (recoveryId: string): Promise<void>
|
||||
export interface Profile {
|
||||
name: string,
|
||||
sessionOptions: SessionOptions,
|
||||
}
|
||||
|
||||
export interface ITerminalColorScheme {
|
||||
@@ -48,6 +41,12 @@ export abstract class TerminalColorSchemeProvider {
|
||||
abstract async getSchemes (): Promise<ITerminalColorScheme[]>
|
||||
}
|
||||
|
||||
export abstract class TerminalContextMenuItemProvider {
|
||||
weight: number
|
||||
|
||||
abstract async getItems (tab: TerminalTabComponent): Promise<Electron.MenuItemConstructorOptions[]>
|
||||
}
|
||||
|
||||
export interface IShell {
|
||||
id: string
|
||||
name?: string
|
||||
|
@@ -57,7 +57,7 @@ h3.mb-3 Appearance
|
||||
(click)='deleteScheme(config.store.terminal.colorScheme)',
|
||||
*ngIf='isCustomScheme(config.store.terminal.colorScheme)'
|
||||
)
|
||||
i.fa.fa-trash-o
|
||||
i.fas.fa-trash
|
||||
|
||||
.form-group(*ngIf='editingColorScheme')
|
||||
label Editing
|
||||
|
@@ -0,0 +1,58 @@
|
||||
.modal-body
|
||||
.form-group
|
||||
label Name
|
||||
input.form-control(
|
||||
type='text',
|
||||
autofocus,
|
||||
[(ngModel)]='profile.name',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Command
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='profile.sessionOptions.command',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Arguments
|
||||
.input-group(
|
||||
*ngFor='let arg of profile.sessionOptions.args; index as i; trackBy: trackByIndex',
|
||||
)
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='profile.sessionOptions.args[i]',
|
||||
)
|
||||
.input-group-btn
|
||||
button.btn.btn-secondary((click)='profile.sessionOptions.args.splice(i, 1)')
|
||||
i.fas.fa-trash
|
||||
|
||||
.mt-2
|
||||
button.btn.btn-secondary((click)='profile.sessionOptions.args.push("")')
|
||||
i.fas.fa-plus.mr-2
|
||||
| Add
|
||||
|
||||
.form-line(*ngIf='uac.isAvailable')
|
||||
.header
|
||||
.title Run as administrator
|
||||
toggle(
|
||||
[(ngModel)]='profile.sessionOptions.runAsAdministrator',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Working directory
|
||||
input.form-control(
|
||||
type='text',
|
||||
[(ngModel)]='profile.sessionOptions.cwd',
|
||||
)
|
||||
|
||||
.form-group
|
||||
label Environment
|
||||
environment-editor(
|
||||
type='text',
|
||||
[(model)]='profile.sessionOptions.env',
|
||||
)
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-outline-primary((click)='save()') Save
|
||||
button.btn.btn-outline-danger((click)='cancel()') Cancel
|
@@ -0,0 +1,34 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { UACService } from '../services/uac.service'
|
||||
import { Profile } from '../api'
|
||||
|
||||
@Component({
|
||||
template: require('./editProfileModal.component.pug'),
|
||||
})
|
||||
export class EditProfileModalComponent {
|
||||
profile: Profile
|
||||
|
||||
constructor (
|
||||
public uac: UACService,
|
||||
private modalInstance: NgbActiveModal,
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
this.profile.sessionOptions.env = this.profile.sessionOptions.env || {}
|
||||
this.profile.sessionOptions.args = this.profile.sessionOptions.args || []
|
||||
}
|
||||
|
||||
save () {
|
||||
this.modalInstance.close(this.profile)
|
||||
}
|
||||
|
||||
cancel () {
|
||||
this.modalInstance.dismiss()
|
||||
}
|
||||
|
||||
trackByIndex (index) {
|
||||
return index
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
.mb-2.d-flex.align-items-center(*ngFor='let pair of vars')
|
||||
.input-group.w-50
|
||||
input.form-control([(ngModel)]='pair.key', (blur)='emitUpdate()', placeholder='Variable name')
|
||||
.input-group-append
|
||||
.input-group-text =
|
||||
input.form-control.w-50.mr-1([(ngModel)]='pair.value', (blur)='emitUpdate()', placeholder='Value')
|
||||
button.btn.btn-secondary((click)='removeEnvironmentVar(pair.key)')
|
||||
i.fas.fa-trash
|
||||
|
||||
button.btn.btn-secondary((click)='addEnvironmentVar()')
|
||||
i.fas.fa-plus.mr-2
|
||||
span Add
|
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
display: block;
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
import { Component, Output, Input } from '@angular/core'
|
||||
import { Subject } from 'rxjs'
|
||||
|
||||
@Component({
|
||||
selector: 'environment-editor',
|
||||
template: require('./environmentEditor.component.pug'),
|
||||
styles: [require('./environmentEditor.component.scss')],
|
||||
})
|
||||
export class EnvironmentEditorComponent {
|
||||
@Output() modelChange = new Subject<any>()
|
||||
vars: {key: string, value: string}[] = []
|
||||
private cachedModel: any
|
||||
|
||||
@Input() get model (): any {
|
||||
return this.cachedModel
|
||||
}
|
||||
|
||||
set model (value) {
|
||||
this.vars = Object.entries(value).map(([k, v]) => ({ key: k, value: v as string }))
|
||||
this.cachedModel = this.getModel()
|
||||
}
|
||||
|
||||
getModel () {
|
||||
let model = {}
|
||||
for (let pair of this.vars) {
|
||||
model[pair.key] = pair.value
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
emitUpdate () {
|
||||
this.cachedModel = this.getModel()
|
||||
this.modelChange.next(this.cachedModel)
|
||||
}
|
||||
|
||||
addEnvironmentVar () {
|
||||
this.vars.push({ key: '', value: '' })
|
||||
}
|
||||
|
||||
removeEnvironmentVar (key: string) {
|
||||
this.vars = this.vars.filter(x => x.key !== key)
|
||||
this.emitUpdate()
|
||||
}
|
||||
|
||||
}
|
@@ -14,9 +14,19 @@ h3.mb-3 Shell
|
||||
[ngValue]='shell.id'
|
||||
) {{shell.name}}
|
||||
|
||||
.alert.alert-info.d-flex.align-items-center(*ngIf='config.store.terminal.shell.startsWith("wsl")')
|
||||
.mr-auto WSL terminal only supports 16 colors until ConPTY is implemented in node-pty
|
||||
button.btn.btn-secondary((click)='openConPtyInfo()') More Information
|
||||
|
||||
.form-line(*ngIf='isConPTYAvailable')
|
||||
.header
|
||||
.title Use ConPTY
|
||||
.description Enables the experimental Windows ConPTY API
|
||||
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.useConPTY',
|
||||
(ngModelChange)='config.save()'
|
||||
)
|
||||
|
||||
.alert.alert-info.d-flex.align-items-center(*ngIf='config.store.terminal.shell.startsWith("wsl") && (config.store.terminal.frontend != "hterm" || !config.store.terminal.useConPTY)')
|
||||
.mr-auto WSL terminal only supports TrueColor with ConPTY and the hterm frontend
|
||||
|
||||
.form-line(*ngIf='config.store.terminal.shell == "custom"')
|
||||
.header
|
||||
@@ -28,20 +38,6 @@ h3.mb-3 Shell
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
|
||||
.form-line(*ngIf='persistenceProviders.length > 0')
|
||||
.header
|
||||
.title Session persistence
|
||||
.description Restores tabs when Terminus is restarted
|
||||
select.form-control(
|
||||
[(ngModel)]='config.store.terminal.persistence',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
option([ngValue]='null') Off
|
||||
option(
|
||||
*ngFor='let provider of persistenceProviders',
|
||||
[ngValue]='provider.id'
|
||||
) {{provider.displayName}}
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Working directory
|
||||
@@ -54,20 +50,31 @@ h3.mb-3 Shell
|
||||
)
|
||||
.input-group-btn
|
||||
button.btn.btn-secondary((click)='pickWorkingDirectory()')
|
||||
i.fa.fa-folder-open
|
||||
i.fas.fa-folder-open
|
||||
|
||||
.form-line
|
||||
.form-line.align-items-start
|
||||
.header
|
||||
.title Environment
|
||||
.description Inject additional environment variables
|
||||
|
||||
div
|
||||
.mb-2.d-flex.align-items-center(*ngFor='let pair of environmentVars')
|
||||
input.form-control.w-50([(ngModel)]='pair.key', (blur)='saveEnvironment()', placeholder='Variable name')
|
||||
input.form-control.w-50.mr-1([(ngModel)]='pair.value', (blur)='saveEnvironment()', placeholder='Value')
|
||||
button.btn.btn-secondary((click)='removeEnvironmentVar(pair.key)')
|
||||
i.fa.fa-trash-o
|
||||
|
||||
button.btn.btn-secondary((click)='addEnvironmentVar()')
|
||||
i.fa.fa-plus.mr-2
|
||||
span Add
|
||||
environment-editor([(model)]='this.config.store.terminal.environment')
|
||||
|
||||
h3.mt-3 Saved Profiles
|
||||
|
||||
.list-group.list-group-flush.mt-3.mb-3
|
||||
.list-group-item.list-group-item-action.d-flex.align-items-center(
|
||||
*ngFor='let profile of profiles',
|
||||
(click)='editProfile(profile)',
|
||||
)
|
||||
.mr-auto
|
||||
div {{profile.name}}
|
||||
.text-muted {{profile.sessionOptions.command}}
|
||||
button.btn.btn-outline-danger.ml-1((click)='$event.stopPropagation(); deleteProfile(profile)')
|
||||
i.fas.fa-trash
|
||||
|
||||
div(ngbDropdown, placement='top-left')
|
||||
button.btn.btn-primary(ngbDropdownToggle)
|
||||
i.fas.fa-fw.fa-plus
|
||||
| New profile
|
||||
div(ngbDropdownMenu)
|
||||
button.dropdown-item(*ngFor='let shell of shells', (click)='newProfile(shell)') {{shell.name}}
|
||||
|
@@ -1,43 +1,50 @@
|
||||
import { Component, Inject } from '@angular/core'
|
||||
import { Component } from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { ConfigService, ElectronService } from 'terminus-core'
|
||||
import { IShell, ShellProvider, SessionPersistenceProvider } from '../api'
|
||||
import { ConfigService, ElectronService, HostAppService, Platform } from 'terminus-core'
|
||||
import { EditProfileModalComponent } from './editProfileModal.component'
|
||||
import { IShell, Profile } from '../api'
|
||||
import { TerminalService } from '../services/terminal.service'
|
||||
import { UACService } from '../services/uac.service'
|
||||
|
||||
@Component({
|
||||
template: require('./shellSettingsTab.component.pug'),
|
||||
})
|
||||
export class ShellSettingsTabComponent {
|
||||
shells: IShell[] = []
|
||||
persistenceProviders: SessionPersistenceProvider[]
|
||||
|
||||
environmentVars: {key: string, value: string}[] = []
|
||||
profiles: Profile[] = []
|
||||
Platform = Platform
|
||||
isConPTYAvailable: boolean
|
||||
private configSubscription: Subscription
|
||||
|
||||
constructor (
|
||||
uac: UACService,
|
||||
public config: ConfigService,
|
||||
public hostApp: HostAppService,
|
||||
private electron: ElectronService,
|
||||
@Inject(ShellProvider) private shellProviders: ShellProvider[],
|
||||
@Inject(SessionPersistenceProvider) persistenceProviders: SessionPersistenceProvider[],
|
||||
private terminalService: TerminalService,
|
||||
private ngbModal: NgbModal,
|
||||
) {
|
||||
this.persistenceProviders = this.config.enabledServices(persistenceProviders).filter(x => x.isAvailable())
|
||||
|
||||
config.store.terminal.environment = config.store.terminal.environment || {}
|
||||
this.reloadEnvironment()
|
||||
this.configSubscription = config.changed$.subscribe(() => this.reloadEnvironment())
|
||||
this.configSubscription = this.config.changed$.subscribe(() => {
|
||||
this.reload()
|
||||
})
|
||||
this.reload()
|
||||
this.isConPTYAvailable = uac.isAvailable
|
||||
}
|
||||
|
||||
async ngOnInit () {
|
||||
this.shells = (await Promise.all(this.config.enabledServices(this.shellProviders).map(x => x.provide()))).reduce((a, b) => a.concat(b))
|
||||
}
|
||||
|
||||
openConPtyInfo() {
|
||||
this.electron.shell.openExternal('https://github.com/Microsoft/node-pty/issues/216')
|
||||
this.shells = await this.terminalService.shells$.toPromise()
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
this.configSubscription.unsubscribe()
|
||||
}
|
||||
|
||||
reload () {
|
||||
this.profiles = this.config.store.terminal.profiles
|
||||
}
|
||||
|
||||
pickWorkingDirectory () {
|
||||
let shell = this.shells.find(x => x.id === this.config.store.terminal.shell)
|
||||
console.log(shell)
|
||||
@@ -50,23 +57,28 @@ export class ShellSettingsTabComponent {
|
||||
}
|
||||
}
|
||||
|
||||
reloadEnvironment () {
|
||||
this.environmentVars = Object.entries(this.config.store.terminal.environment).map(([k, v]) => ({ key: k, value: v as string }))
|
||||
}
|
||||
|
||||
saveEnvironment () {
|
||||
this.config.store.terminal.environment = {}
|
||||
for (let pair of this.environmentVars) {
|
||||
this.config.store.terminal.environment[pair.key] = pair.value
|
||||
newProfile (shell: IShell) {
|
||||
let profile: Profile = {
|
||||
name: shell.name,
|
||||
sessionOptions: this.terminalService.optionsFromShell(shell),
|
||||
}
|
||||
this.profiles.push(profile)
|
||||
this.config.store.terminal.profiles = this.profiles
|
||||
this.config.save()
|
||||
}
|
||||
|
||||
addEnvironmentVar () {
|
||||
this.environmentVars.push({ key: '', value: '' })
|
||||
editProfile (profile: Profile) {
|
||||
let modal = this.ngbModal.open(EditProfileModalComponent)
|
||||
modal.componentInstance.profile = Object.assign({}, profile)
|
||||
modal.result.then(result => {
|
||||
Object.assign(profile, result)
|
||||
this.config.save()
|
||||
})
|
||||
}
|
||||
|
||||
removeEnvironmentVar (key: string) {
|
||||
this.environmentVars = this.environmentVars.filter(x => x.key !== key)
|
||||
this.saveEnvironment()
|
||||
deleteProfile (profile: Profile) {
|
||||
this.profiles = this.profiles.filter(x => x !== profile)
|
||||
this.config.store.terminal.profiles = this.profiles
|
||||
this.config.save()
|
||||
}
|
||||
}
|
||||
|
@@ -4,12 +4,10 @@ import { ToastrService } from 'ngx-toastr'
|
||||
import { Component, NgZone, Inject, Optional, ViewChild, HostBinding, Input } from '@angular/core'
|
||||
import { AppService, ConfigService, BaseTabComponent, BaseTabProcess, ElectronService, HostAppService, HotkeysService, Platform } from 'terminus-core'
|
||||
|
||||
import { IShell } from '../api'
|
||||
import { Session, SessionsService } from '../services/sessions.service'
|
||||
import { TerminalService } from '../services/terminal.service'
|
||||
import { TerminalFrontendService } from '../services/terminalFrontend.service'
|
||||
|
||||
import { TerminalDecorator, ResizeEvent, SessionOptions } from '../api'
|
||||
import { TerminalDecorator, ResizeEvent, SessionOptions, TerminalContextMenuItemProvider } from '../api'
|
||||
import { Frontend } from '../frontends/frontend'
|
||||
|
||||
@Component({
|
||||
@@ -33,10 +31,8 @@ export class TerminalTabComponent extends BaseTabComponent {
|
||||
sessionCloseSubscription: Subscription
|
||||
hotkeysSubscription: Subscription
|
||||
htermVisible = false
|
||||
shell: IShell
|
||||
private output = new Subject<string>()
|
||||
private bellPlayer: HTMLAudioElement
|
||||
private contextMenu: any
|
||||
private termContainerSubscriptions: Subscription[] = []
|
||||
|
||||
get input$ (): Observable<string> { return this.frontend.input$ }
|
||||
@@ -51,17 +47,17 @@ export class TerminalTabComponent extends BaseTabComponent {
|
||||
private hotkeys: HotkeysService,
|
||||
private sessions: SessionsService,
|
||||
private electron: ElectronService,
|
||||
private terminalService: TerminalService,
|
||||
private terminalContainersService: TerminalFrontendService,
|
||||
public config: ConfigService,
|
||||
private toastr: ToastrService,
|
||||
@Optional() @Inject(TerminalDecorator) private decorators: TerminalDecorator[],
|
||||
@Optional() @Inject(TerminalContextMenuItemProvider) private contextMenuProviders: TerminalContextMenuItemProvider[],
|
||||
) {
|
||||
super()
|
||||
this.decorators = this.decorators || []
|
||||
this.setTitle('Terminal')
|
||||
|
||||
this.session = new Session()
|
||||
this.session = new Session(this.config)
|
||||
|
||||
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
||||
if (!this.hasFocus) {
|
||||
@@ -118,6 +114,8 @@ export class TerminalTabComponent extends BaseTabComponent {
|
||||
})
|
||||
this.bellPlayer = document.createElement('audio')
|
||||
this.bellPlayer.src = require<string>('../bell.ogg')
|
||||
|
||||
this.contextMenuProviders.sort((a, b) => a.weight - b.weight)
|
||||
}
|
||||
|
||||
initializeSession (columns: number, rows: number) {
|
||||
@@ -143,10 +141,14 @@ export class TerminalTabComponent extends BaseTabComponent {
|
||||
})
|
||||
}
|
||||
|
||||
getRecoveryToken (): any {
|
||||
async getRecoveryToken (): Promise<any> {
|
||||
let cwd = this.session ? await this.session.getWorkingDirectory() : null
|
||||
return {
|
||||
type: 'app:terminal',
|
||||
recoveryId: this.sessionOptions.recoveryId,
|
||||
type: 'app:terminal-tab',
|
||||
sessionOptions: {
|
||||
...this.sessionOptions,
|
||||
cwd: cwd || this.sessionOptions.cwd,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +164,7 @@ export class TerminalTabComponent extends BaseTabComponent {
|
||||
this.htermVisible = true
|
||||
})
|
||||
|
||||
this.frontend.resize$.pipe(first()).subscribe(async ({columns, rows}) => {
|
||||
this.frontend.resize$.pipe(first()).subscribe(async ({ columns, rows }) => {
|
||||
if (!this.session.open) {
|
||||
this.initializeSession(columns, rows)
|
||||
}
|
||||
@@ -199,39 +201,19 @@ export class TerminalTabComponent extends BaseTabComponent {
|
||||
}
|
||||
})
|
||||
|
||||
this.contextMenu = [
|
||||
{
|
||||
label: 'New terminal',
|
||||
click: () => {
|
||||
this.zone.run(() => {
|
||||
this.terminalService.openTab(this.shell)
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Copy',
|
||||
click: () => {
|
||||
this.zone.run(() => {
|
||||
setTimeout(() => {
|
||||
this.frontend.copySelection()
|
||||
this.toastr.info('Copied')
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Paste',
|
||||
click: () => {
|
||||
this.zone.run(() => {
|
||||
this.paste()
|
||||
})
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
this.frontend.focus()
|
||||
}
|
||||
|
||||
async buildContextMenu (): Promise<Electron.MenuItemConstructorOptions[]> {
|
||||
let items: Electron.MenuItemConstructorOptions[] = []
|
||||
for (let section of await Promise.all(this.contextMenuProviders.map(x => x.getItems(this)))) {
|
||||
items = items.concat(section)
|
||||
items.push({ type: 'separator' })
|
||||
}
|
||||
items.splice(items.length - 1, 1)
|
||||
return items
|
||||
}
|
||||
|
||||
detachTermContainerHandlers () {
|
||||
for (let subscription of this.termContainerSubscriptions) {
|
||||
subscription.unsubscribe()
|
||||
@@ -247,11 +229,11 @@ export class TerminalTabComponent extends BaseTabComponent {
|
||||
this.focused$.subscribe(() => this.frontend.enableResizing = true),
|
||||
this.blurred$.subscribe(() => this.frontend.enableResizing = false),
|
||||
|
||||
this.frontend.mouseEvent$.subscribe(event => {
|
||||
this.frontend.mouseEvent$.subscribe(async event => {
|
||||
if (event.type === 'mousedown') {
|
||||
if (event.which === 3) {
|
||||
if (this.config.store.terminal.rightClick === 'menu') {
|
||||
this.hostApp.popupContextMenu(this.contextMenu)
|
||||
this.hostApp.popupContextMenu(await this.buildContextMenu())
|
||||
} else if (this.config.store.terminal.rightClick === 'paste') {
|
||||
this.paste()
|
||||
}
|
||||
@@ -264,9 +246,9 @@ export class TerminalTabComponent extends BaseTabComponent {
|
||||
let wheelDeltaY = 0
|
||||
|
||||
if ('wheelDeltaY' in event) {
|
||||
wheelDeltaY = (event as MouseWheelEvent).wheelDeltaY
|
||||
wheelDeltaY = (event as MouseWheelEvent)['wheelDeltaY']
|
||||
} else {
|
||||
wheelDeltaY = (event as MouseWheelEvent).deltaY
|
||||
wheelDeltaY = (event as MouseWheelEvent)['deltaY']
|
||||
}
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
|
||||
@@ -287,14 +269,18 @@ export class TerminalTabComponent extends BaseTabComponent {
|
||||
this.sendInput(data)
|
||||
}),
|
||||
|
||||
this.frontend.resize$.subscribe(({columns, rows}) => {
|
||||
this.frontend.resize$.subscribe(({ columns, rows }) => {
|
||||
console.log(`Resizing to ${columns}x${rows}`)
|
||||
this.zone.run(() => {
|
||||
if (this.session.open) {
|
||||
this.session.resize(columns, rows)
|
||||
}
|
||||
})
|
||||
})
|
||||
}),
|
||||
|
||||
this.hostApp.windowMoved$.subscribe(() => setTimeout(() => {
|
||||
this.configure()
|
||||
}, 250)),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -396,4 +382,20 @@ export class TerminalTabComponent extends BaseTabComponent {
|
||||
}
|
||||
return confirm(`"${children[0].command}" is still running. Close?`)
|
||||
}
|
||||
|
||||
async saveAsProfile () {
|
||||
let profile = {
|
||||
sessionOptions: {
|
||||
...this.sessionOptions,
|
||||
cwd: (await this.session.getWorkingDirectory()) || this.sessionOptions.cwd,
|
||||
},
|
||||
name: this.sessionOptions.command,
|
||||
}
|
||||
this.config.store.terminal.profiles = [
|
||||
...this.config.store.terminal.profiles,
|
||||
profile,
|
||||
]
|
||||
this.config.save()
|
||||
this.toastr.info('Saved')
|
||||
}
|
||||
}
|
||||
|
@@ -51,6 +51,8 @@ export class TerminalConfigProvider extends ConfigProvider {
|
||||
},
|
||||
customColorSchemes: [],
|
||||
environment: {},
|
||||
profiles: [],
|
||||
useConPTY: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -59,7 +61,6 @@ export class TerminalConfigProvider extends ConfigProvider {
|
||||
terminal: {
|
||||
font: 'Menlo',
|
||||
shell: 'default',
|
||||
persistence: 'screen',
|
||||
},
|
||||
hotkeys: {
|
||||
'ctrl-c': ['Ctrl-C'],
|
||||
@@ -83,8 +84,6 @@ export class TerminalConfigProvider extends ConfigProvider {
|
||||
'⌘-0',
|
||||
],
|
||||
'new-tab': [
|
||||
['Ctrl-A', 'C'],
|
||||
['Ctrl-A', 'Ctrl-C'],
|
||||
'⌘-T',
|
||||
'⌘-N',
|
||||
],
|
||||
@@ -100,7 +99,6 @@ export class TerminalConfigProvider extends ConfigProvider {
|
||||
terminal: {
|
||||
font: 'Consolas',
|
||||
shell: 'clink',
|
||||
persistence: null,
|
||||
rightClick: 'paste',
|
||||
copyOnSelect: true,
|
||||
},
|
||||
@@ -127,8 +125,6 @@ export class TerminalConfigProvider extends ConfigProvider {
|
||||
'Ctrl-0',
|
||||
],
|
||||
'new-tab': [
|
||||
['Ctrl-A', 'C'],
|
||||
['Ctrl-A', 'Ctrl-C'],
|
||||
'Ctrl-Shift-T',
|
||||
],
|
||||
'home': ['Home'],
|
||||
@@ -143,7 +139,6 @@ export class TerminalConfigProvider extends ConfigProvider {
|
||||
terminal: {
|
||||
font: 'Liberation Mono',
|
||||
shell: 'default',
|
||||
persistence: 'tmux',
|
||||
},
|
||||
hotkeys: {
|
||||
'ctrl-c': ['Ctrl-C'],
|
||||
@@ -168,8 +163,6 @@ export class TerminalConfigProvider extends ConfigProvider {
|
||||
'Ctrl-0',
|
||||
],
|
||||
'new-tab': [
|
||||
['Ctrl-A', 'C'],
|
||||
['Ctrl-A', 'Ctrl-C'],
|
||||
'Ctrl-Shift-T',
|
||||
],
|
||||
'home': ['Home'],
|
||||
|
108
terminus-terminal/src/contextMenu.ts
Normal file
108
terminus-terminal/src/contextMenu.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { NgZone, Injectable } from '@angular/core'
|
||||
import { ToastrService } from 'ngx-toastr'
|
||||
import { ConfigService } from 'terminus-core'
|
||||
import { UACService } from './services/uac.service'
|
||||
import { TerminalService } from './services/terminal.service'
|
||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||
import { TerminalContextMenuItemProvider } from './api'
|
||||
|
||||
@Injectable()
|
||||
export class NewTabContextMenu extends TerminalContextMenuItemProvider {
|
||||
weight = 0
|
||||
|
||||
constructor (
|
||||
public config: ConfigService,
|
||||
private zone: NgZone,
|
||||
private terminalService: TerminalService,
|
||||
private uac: UACService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
async getItems (tab: TerminalTabComponent): Promise<Electron.MenuItemConstructorOptions[]> {
|
||||
let shells = await this.terminalService.shells$.toPromise()
|
||||
|
||||
let items: Electron.MenuItemConstructorOptions[] = [
|
||||
{
|
||||
label: 'New terminal',
|
||||
click: () => this.zone.run(() => {
|
||||
this.terminalService.openTabWithOptions(tab.sessionOptions)
|
||||
})
|
||||
},
|
||||
{
|
||||
label: 'New with shell',
|
||||
submenu: shells.map(shell => ({
|
||||
label: shell.name,
|
||||
click: () => this.zone.run(async () => {
|
||||
this.terminalService.openTab(shell, await tab.session.getWorkingDirectory())
|
||||
}),
|
||||
})),
|
||||
},
|
||||
]
|
||||
|
||||
if (this.uac.isAvailable) {
|
||||
items.push({
|
||||
label: 'New as admin',
|
||||
submenu: shells.map(shell => ({
|
||||
label: shell.name,
|
||||
click: () => this.zone.run(async () => {
|
||||
let options = this.terminalService.optionsFromShell(shell)
|
||||
options.runAsAdministrator = true
|
||||
this.terminalService.openTabWithOptions(options)
|
||||
}),
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
items = items.concat([
|
||||
{
|
||||
label: 'New with profile',
|
||||
submenu: this.config.store.terminal.profiles.length ? this.config.store.terminal.profiles.map(profile => ({
|
||||
label: profile.name,
|
||||
click: () => this.zone.run(() => {
|
||||
this.terminalService.openTabWithOptions(profile.sessionOptions)
|
||||
}),
|
||||
})) : [{
|
||||
label: 'No profiles saved',
|
||||
enabled: false,
|
||||
}],
|
||||
},
|
||||
])
|
||||
|
||||
return items
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class CopyPasteContextMenu extends TerminalContextMenuItemProvider {
|
||||
weight = 1
|
||||
|
||||
constructor (
|
||||
private zone: NgZone,
|
||||
private toastr: ToastrService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
async getItems (tab: TerminalTabComponent): Promise<Electron.MenuItemConstructorOptions[]> {
|
||||
return [
|
||||
{
|
||||
label: 'Copy',
|
||||
click: () => {
|
||||
this.zone.run(() => {
|
||||
setTimeout(() => {
|
||||
tab.frontend.copySelection()
|
||||
this.toastr.info('Copied')
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Paste',
|
||||
click: () => {
|
||||
this.zone.run(() => tab.paste())
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
@@ -66,6 +66,7 @@ export class HTermFrontend extends Frontend {
|
||||
preferenceManager.set('ctrl-plus-minus-zero-zoom', false)
|
||||
preferenceManager.set('scrollbar-visible', process.platform === 'darwin')
|
||||
preferenceManager.set('copy-on-select', config.terminal.copyOnSelect)
|
||||
preferenceManager.set('pass-meta-v', false)
|
||||
preferenceManager.set('alt-is-meta', config.terminal.altIsMeta)
|
||||
preferenceManager.set('alt-sends-what', 'browser-key')
|
||||
preferenceManager.set('alt-gr-mode', 'ctrl-alt')
|
||||
@@ -153,7 +154,14 @@ export class HTermFrontend extends Frontend {
|
||||
}
|
||||
|
||||
private setFontSize () {
|
||||
preferenceManager.set('font-size', this.configuredFontSize * Math.pow(1.1, this.zoom))
|
||||
let size = this.configuredFontSize * Math.pow(1.1, this.zoom)
|
||||
preferenceManager.set('font-size', size)
|
||||
if (this.term) {
|
||||
setTimeout(() => {
|
||||
this.term.scrollPort_.characterSize = this.term.scrollPort_.measureCharacterSize()
|
||||
this.term.setFontSize(size)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private init () {
|
||||
|
@@ -100,7 +100,7 @@ export class XTermFrontend extends Frontend {
|
||||
this.xterm.setOption('bellStyle', config.terminal.bell)
|
||||
this.xterm.setOption('cursorStyle', {
|
||||
beam: 'bar'
|
||||
}[config.terminal.cuxrsor] || config.terminal.cursor)
|
||||
}[config.terminal.cursor] || config.terminal.cursor)
|
||||
this.xterm.setOption('cursorBlink', config.terminal.cursorBlink)
|
||||
this.xterm.setOption('macOptionIsMeta', config.terminal.altIsMeta)
|
||||
// this.xterm.setOption('colors', )
|
||||
|
@@ -52,7 +52,6 @@ hterm.hterm.Terminal.prototype.applyCursorShape = function () {
|
||||
[hterm.hterm.Terminal.cursorShape.BEAM, false],
|
||||
]
|
||||
let modeNumber = this.cursorMode || 1
|
||||
console.log('mode', modeNumber)
|
||||
if (modeNumber >= modes.length) {
|
||||
console.warn('Unknown cursor style: ' + modeNumber)
|
||||
return
|
||||
|
@@ -5,32 +5,32 @@ import { BrowserModule } from '@angular/platform-browser'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { ToastrModule } from 'ngx-toastr'
|
||||
import TerminusCorePlugin from 'terminus-core'
|
||||
import { HostAppService } from 'terminus-core'
|
||||
|
||||
import { ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider, AppService, ConfigService } from 'terminus-core'
|
||||
import TerminusCorePlugin, { HostAppService, ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider, HotkeysService, HotkeyProvider, AppService, ConfigService } from 'terminus-core'
|
||||
import { SettingsTabProvider } from 'terminus-settings'
|
||||
|
||||
import { AppearanceSettingsTabComponent } from './components/appearanceSettingsTab.component'
|
||||
import { ShellSettingsTabComponent } from './components/shellSettingsTab.component'
|
||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||
import { ShellSettingsTabComponent } from './components/shellSettingsTab.component'
|
||||
import { TerminalSettingsTabComponent } from './components/terminalSettingsTab.component'
|
||||
import { ColorPickerComponent } from './components/colorPicker.component'
|
||||
import { EditProfileModalComponent } from './components/editProfileModal.component'
|
||||
import { EnvironmentEditorComponent } from './components/environmentEditor.component'
|
||||
|
||||
import { SessionsService, BaseSession } from './services/sessions.service'
|
||||
import { BaseSession } from './services/sessions.service'
|
||||
import { TerminalFrontendService } from './services/terminalFrontend.service'
|
||||
import { TerminalService } from './services/terminal.service'
|
||||
import { DockMenuService } from './services/dockMenu.service'
|
||||
|
||||
import { ScreenPersistenceProvider } from './persistence/screen'
|
||||
import { TMuxPersistenceProvider } from './persistence/tmux'
|
||||
import { ButtonProvider } from './buttonProvider'
|
||||
import { RecoveryProvider } from './recoveryProvider'
|
||||
import { SessionPersistenceProvider, TerminalColorSchemeProvider, TerminalDecorator, ShellProvider } from './api'
|
||||
import { TerminalColorSchemeProvider, TerminalDecorator, ShellProvider, TerminalContextMenuItemProvider } from './api'
|
||||
import { TerminalSettingsTabProvider, AppearanceSettingsTabProvider, ShellSettingsTabProvider } from './settings'
|
||||
import { PathDropDecorator } from './pathDrop'
|
||||
import { TerminalConfigProvider } from './config'
|
||||
import { TerminalHotkeyProvider } from './hotkeys'
|
||||
import { HyperColorSchemes } from './colorSchemes'
|
||||
import { NewTabContextMenu, CopyPasteContextMenu } from './contextMenu'
|
||||
|
||||
import { CmderShellProvider } from './shells/cmder'
|
||||
import { CustomShellProvider } from './shells/custom'
|
||||
@@ -56,10 +56,6 @@ import { hterm } from './hterm'
|
||||
TerminusCorePlugin,
|
||||
],
|
||||
providers: [
|
||||
SessionsService,
|
||||
TerminalFrontendService,
|
||||
TerminalService,
|
||||
|
||||
{ provide: SettingsTabProvider, useClass: AppearanceSettingsTabProvider, multi: true },
|
||||
{ provide: SettingsTabProvider, useClass: ShellSettingsTabProvider, multi: true },
|
||||
{ provide: SettingsTabProvider, useClass: TerminalSettingsTabProvider, multi: true },
|
||||
@@ -71,9 +67,6 @@ import { hterm } from './hterm'
|
||||
{ provide: TerminalColorSchemeProvider, useClass: HyperColorSchemes, multi: true },
|
||||
{ provide: TerminalDecorator, useClass: PathDropDecorator, multi: true },
|
||||
|
||||
{ provide: SessionPersistenceProvider, useClass: ScreenPersistenceProvider, multi: true },
|
||||
{ provide: SessionPersistenceProvider, useClass: TMuxPersistenceProvider, multi: true },
|
||||
|
||||
{ provide: ShellProvider, useClass: WindowsDefaultShellProvider, multi: true },
|
||||
{ provide: ShellProvider, useClass: MacOSDefaultShellProvider, multi: true },
|
||||
{ provide: ShellProvider, useClass: LinuxDefaultShellProvider, multi: true },
|
||||
@@ -87,6 +80,9 @@ import { hterm } from './hterm'
|
||||
{ provide: ShellProvider, useClass: PowerShellCoreShellProvider, multi: true },
|
||||
{ provide: ShellProvider, useClass: WSLShellProvider, multi: true },
|
||||
|
||||
{ provide: TerminalContextMenuItemProvider, useClass: NewTabContextMenu, multi: true },
|
||||
{ provide: TerminalContextMenuItemProvider, useClass: CopyPasteContextMenu, multi: true },
|
||||
|
||||
// For WindowsDefaultShellProvider
|
||||
PowerShellCoreShellProvider,
|
||||
WSLShellProvider,
|
||||
@@ -97,6 +93,7 @@ import { hterm } from './hterm'
|
||||
AppearanceSettingsTabComponent,
|
||||
ShellSettingsTabComponent,
|
||||
TerminalSettingsTabComponent,
|
||||
EditProfileModalComponent,
|
||||
],
|
||||
declarations: [
|
||||
ColorPickerComponent,
|
||||
@@ -104,6 +101,12 @@ import { hterm } from './hterm'
|
||||
AppearanceSettingsTabComponent,
|
||||
ShellSettingsTabComponent,
|
||||
TerminalSettingsTabComponent,
|
||||
EditProfileModalComponent,
|
||||
EnvironmentEditorComponent,
|
||||
],
|
||||
exports: [
|
||||
ColorPickerComponent,
|
||||
EnvironmentEditorComponent,
|
||||
],
|
||||
})
|
||||
export default class TerminalModule {
|
||||
@@ -113,6 +116,7 @@ export default class TerminalModule {
|
||||
hotkeys: HotkeysService,
|
||||
terminal: TerminalService,
|
||||
hostApp: HostAppService,
|
||||
dockMenu: DockMenuService,
|
||||
) {
|
||||
let events = [
|
||||
{
|
||||
@@ -159,6 +163,7 @@ export default class TerminalModule {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
hostApp.cliOpenDirectory$.subscribe(async directory => {
|
||||
if (await fs.exists(directory)) {
|
||||
if ((await fs.stat(directory)).isDirectory()) {
|
||||
@@ -167,6 +172,7 @@ export default class TerminalModule {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
hostApp.cliRunCommand$.subscribe(async command => {
|
||||
terminal.openTab({
|
||||
id: '',
|
||||
@@ -175,12 +181,25 @@ export default class TerminalModule {
|
||||
}, null, true)
|
||||
hostApp.bringToFront()
|
||||
})
|
||||
|
||||
hostApp.cliPaste$.subscribe(text => {
|
||||
if (app.activeTab instanceof TerminalTabComponent && app.activeTab.session) {
|
||||
(app.activeTab as TerminalTabComponent).sendInput(text)
|
||||
hostApp.bringToFront()
|
||||
}
|
||||
})
|
||||
|
||||
hostApp.cliOpenProfile$.subscribe(async profileName => {
|
||||
let profile = config.store.terminal.profiles.find(x => x.name === profileName)
|
||||
if (!profile) {
|
||||
console.error('Requested profile', profileName, 'not found')
|
||||
return
|
||||
}
|
||||
terminal.openTabWithOptions(profile.sessionOptions)
|
||||
hostApp.bringToFront()
|
||||
})
|
||||
|
||||
dockMenu.update()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,142 +0,0 @@
|
||||
import * as fs from 'mz/fs'
|
||||
import * as path from 'path'
|
||||
import { exec, spawn } from 'mz/child_process'
|
||||
import { exec as execAsync, execFileSync } from 'child_process'
|
||||
|
||||
import { AsyncSubject } from 'rxjs'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Logger, LogService, ElectronService } from 'terminus-core'
|
||||
import { SessionOptions, SessionPersistenceProvider } from '../api'
|
||||
|
||||
declare function delay (ms: number): Promise<void>
|
||||
|
||||
interface IChildProcess {
|
||||
pid: number
|
||||
ppid: number
|
||||
command: string
|
||||
}
|
||||
|
||||
async function listProcesses (): Promise<IChildProcess[]> {
|
||||
return (await exec(`ps -A -o pid,ppid,command`))[0].toString()
|
||||
.split('\n')
|
||||
.slice(1)
|
||||
.map(line => line.split(' ').filter(x => x).slice(0, 3))
|
||||
.map(([pid, ppid, command]) => {
|
||||
return {
|
||||
pid: parseInt(pid), ppid: parseInt(ppid), command
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ScreenPersistenceProvider extends SessionPersistenceProvider {
|
||||
id = 'screen'
|
||||
displayName = 'GNU Screen'
|
||||
private logger: Logger
|
||||
|
||||
constructor (
|
||||
log: LogService,
|
||||
private electron: ElectronService,
|
||||
) {
|
||||
super()
|
||||
this.logger = log.create('main')
|
||||
}
|
||||
|
||||
isAvailable () {
|
||||
try {
|
||||
execFileSync('sh', ['-c', 'which screen'])
|
||||
return true
|
||||
} catch (_) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async attachSession (recoveryId: any): Promise<SessionOptions> {
|
||||
let lines = await new Promise<string[]>(resolve => {
|
||||
execAsync('screen -list', (_err, stdout) => {
|
||||
// returns an error code on macOS
|
||||
resolve(stdout.split('\n'))
|
||||
})
|
||||
})
|
||||
let screenPID = lines
|
||||
.filter(line => line.indexOf('.' + recoveryId) !== -1)
|
||||
.map(line => parseInt(line.trim().split('.')[0]))[0]
|
||||
|
||||
if (!screenPID) {
|
||||
return null
|
||||
}
|
||||
|
||||
let truePID$ = new AsyncSubject<number>()
|
||||
|
||||
this.extractShellPID(screenPID).then(pid => {
|
||||
truePID$.next(pid)
|
||||
truePID$.complete()
|
||||
})
|
||||
|
||||
return {
|
||||
recoveryId,
|
||||
recoveredTruePID$: truePID$.asObservable(),
|
||||
command: 'screen',
|
||||
args: ['-d', '-r', recoveryId, '-c', await this.prepareConfig()],
|
||||
}
|
||||
}
|
||||
|
||||
async extractShellPID (screenPID: number): Promise<number> {
|
||||
let processes = await listProcesses()
|
||||
let child = processes.find(x => x.ppid === screenPID)
|
||||
|
||||
if (!child) {
|
||||
throw new Error(`Could not find any children of the screen process (PID ${screenPID})!`)
|
||||
}
|
||||
|
||||
if (child.command === 'login') {
|
||||
await delay(1000)
|
||||
child = processes.find(x => x.ppid === child.pid)
|
||||
}
|
||||
|
||||
return child.pid
|
||||
}
|
||||
|
||||
async startSession (options: SessionOptions): Promise<any> {
|
||||
let recoveryId = `term-tab-${Date.now()}`
|
||||
let args = ['-d', '-m', '-c', await this.prepareConfig(), '-U', '-S', recoveryId, '-T', 'xterm-256color', '--', '-' + options.command].concat(options.args || [])
|
||||
this.logger.debug('Spawning screen with', args.join(' '))
|
||||
await spawn('screen', args, {
|
||||
cwd: options.cwd,
|
||||
env: options.env || process.env,
|
||||
})
|
||||
return recoveryId
|
||||
}
|
||||
|
||||
async terminateSession (recoveryId: string): Promise<void> {
|
||||
try {
|
||||
await exec(`screen -S ${recoveryId} -X quit`)
|
||||
} catch (_) {
|
||||
// screen has already quit
|
||||
}
|
||||
}
|
||||
|
||||
private async prepareConfig (): Promise<string> {
|
||||
let configPath = path.join(this.electron.app.getPath('userData'), 'screen-config.tmp')
|
||||
await fs.writeFile(configPath, `
|
||||
escape ^^^
|
||||
vbell off
|
||||
deflogin on
|
||||
defflow off
|
||||
term xterm-color
|
||||
bindkey "^[OH" beginning-of-line
|
||||
bindkey "^[OF" end-of-line
|
||||
bindkey "^[[H" beginning-of-line
|
||||
bindkey "^[[F" end-of-line
|
||||
bindkey "\\027[?1049h" stuff ----alternate enter-----
|
||||
bindkey "\\027[?1049l" stuff ----alternate leave-----
|
||||
termcapinfo xterm* 'hs:ts=\\E]0;:fs=\\007:ds=\\E]0;\\007'
|
||||
defhstatus "^Et"
|
||||
hardstatus off
|
||||
altscreen on
|
||||
defutf8 on
|
||||
defencoding utf8
|
||||
`, 'utf-8')
|
||||
return configPath
|
||||
}
|
||||
}
|
@@ -1,248 +0,0 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { execFileSync } from 'child_process'
|
||||
import AsyncLock = require('async-lock')
|
||||
import { ConnectableObservable, AsyncSubject, Subject } from 'rxjs'
|
||||
import { first, publish } from 'rxjs/operators'
|
||||
import * as childProcess from 'child_process'
|
||||
|
||||
import { Logger } from 'terminus-core'
|
||||
import { SessionOptions, SessionPersistenceProvider } from '../api'
|
||||
|
||||
declare function delay (ms: number): Promise<void>
|
||||
|
||||
const TMUX_CONFIG = `
|
||||
set -g status off
|
||||
set -g focus-events on
|
||||
set -g bell-action any
|
||||
set -g bell-on-alert on
|
||||
set -g visual-bell off
|
||||
set -g set-titles on
|
||||
set -g set-titles-string "#W"
|
||||
set -g window-status-format '#I:#(pwd="#{pane_current_path}"; echo \${pwd####*/})#F'
|
||||
set -g window-status-current-format '#I:#(pwd="#{pane_current_path}"; echo \${pwd####*/})#F'
|
||||
set-option -g prefix C-^
|
||||
set-option -g status-interval 1
|
||||
`
|
||||
|
||||
export class TMuxBlock {
|
||||
time: number
|
||||
number: number
|
||||
error: boolean
|
||||
lines: string[]
|
||||
|
||||
constructor (line: string) {
|
||||
this.time = parseInt(line.split(' ')[1])
|
||||
this.number = parseInt(line.split(' ')[2])
|
||||
this.lines = []
|
||||
}
|
||||
}
|
||||
|
||||
export class TMuxMessage {
|
||||
type: string
|
||||
content: string
|
||||
|
||||
constructor (line: string) {
|
||||
this.type = line.substring(0, line.indexOf(' '))
|
||||
this.content = line.substring(line.indexOf(' ') + 1)
|
||||
}
|
||||
}
|
||||
|
||||
export class TMuxCommandProcess {
|
||||
private process: childProcess.ChildProcess
|
||||
private rawOutput$ = new Subject<string>()
|
||||
private line$ = new Subject<string>()
|
||||
private message$ = new Subject<string>()
|
||||
private block$ = new Subject<TMuxBlock>()
|
||||
private response$: ConnectableObservable<TMuxBlock>
|
||||
private lock = new AsyncLock({ timeout: 1000 })
|
||||
private logger = new Logger(null, 'tmuxProcess')
|
||||
|
||||
constructor () {
|
||||
this.process = childProcess.spawn('tmux', ['-C', '-f', '/dev/null', '-L', 'terminus', 'new-session', '-A', '-D', '-s', 'control'])
|
||||
this.logger.log('started')
|
||||
this.process.stdout.on('data', data => {
|
||||
// console.debug('tmux says:', data.toString())
|
||||
this.rawOutput$.next(data.toString())
|
||||
})
|
||||
|
||||
let rawBuffer = ''
|
||||
this.rawOutput$.subscribe(raw => {
|
||||
rawBuffer += raw
|
||||
if (rawBuffer.includes('\n')) {
|
||||
let lines = rawBuffer.split('\n')
|
||||
rawBuffer = lines.pop()
|
||||
lines.forEach(line => this.line$.next(line))
|
||||
}
|
||||
})
|
||||
|
||||
let currentBlock = null
|
||||
this.line$.subscribe(line => {
|
||||
if (currentBlock) {
|
||||
if (line.startsWith('%end ')) {
|
||||
let block = currentBlock
|
||||
currentBlock = null
|
||||
setImmediate(() => {
|
||||
this.block$.next(block)
|
||||
})
|
||||
} else if (line.startsWith('%error ')) {
|
||||
let block = currentBlock
|
||||
block.error = true
|
||||
currentBlock = null
|
||||
setImmediate(() => {
|
||||
this.block$.next(block)
|
||||
})
|
||||
} else {
|
||||
currentBlock.lines.push(line)
|
||||
}
|
||||
} else {
|
||||
if (line.startsWith('%begin ')) {
|
||||
currentBlock = new TMuxBlock(line)
|
||||
} else {
|
||||
this.message$.next(line)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.response$ = this.block$.asObservable().pipe(publish()) as ConnectableObservable<TMuxBlock>
|
||||
this.response$.connect()
|
||||
|
||||
this.block$.subscribe(block => {
|
||||
this.logger.debug('block:', block)
|
||||
})
|
||||
|
||||
this.message$.subscribe(message => {
|
||||
this.logger.debug('message:', message)
|
||||
})
|
||||
}
|
||||
|
||||
command (command: string): Promise<TMuxBlock> {
|
||||
return this.lock.acquire('key', () => {
|
||||
let p = this.response$.pipe(first()).toPromise()
|
||||
this.logger.debug('command:', command)
|
||||
this.process.stdin.write(command + '\n')
|
||||
return p
|
||||
}).then(response => {
|
||||
if (response.error) {
|
||||
throw response
|
||||
}
|
||||
return response
|
||||
}) as Promise<TMuxBlock>
|
||||
}
|
||||
|
||||
destroy () {
|
||||
this.rawOutput$.complete()
|
||||
this.line$.complete()
|
||||
this.block$.complete()
|
||||
this.message$.complete()
|
||||
this.process.kill('SIGTERM')
|
||||
}
|
||||
}
|
||||
|
||||
export class TMux {
|
||||
private process: TMuxCommandProcess
|
||||
private ready: Promise<void>
|
||||
private logger = new Logger(null, 'tmux')
|
||||
|
||||
constructor () {
|
||||
this.process = new TMuxCommandProcess()
|
||||
this.ready = (async () => {
|
||||
for (let line of TMUX_CONFIG.split('\n')) {
|
||||
if (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
|
||||
await delay(500)
|
||||
})()
|
||||
}
|
||||
|
||||
async create (id: string, options: SessionOptions): Promise<void> {
|
||||
await this.ready
|
||||
let args = [options.command].concat(options.args.slice(1))
|
||||
let cmd = args.map(x => `"${x.replace('"', '\\"')}"`).join(' ')
|
||||
await this.process.command(
|
||||
`new-session -s "${id}" -d`
|
||||
+ (options.cwd ? ` -c '${options.cwd.replace("'", "\\'")}'` : '')
|
||||
+ ` '${cmd}'`
|
||||
)
|
||||
}
|
||||
|
||||
async list (): Promise<string[]> {
|
||||
await this.ready
|
||||
let block = await this.process.command('list-sessions -F "#{session_name}"')
|
||||
return block.lines
|
||||
}
|
||||
|
||||
async getPID (id: string): Promise<number|null> {
|
||||
await this.ready
|
||||
let response = await this.process.command(`list-panes -t ${id} -F "#{pane_pid}"`)
|
||||
if (response.lines.length === 0) {
|
||||
return null
|
||||
} else {
|
||||
return parseInt(response.lines[0])
|
||||
}
|
||||
}
|
||||
|
||||
async terminate (id: string): Promise<void> {
|
||||
await this.ready
|
||||
this.process.command(`kill-session -t ${id}`).catch(() => {
|
||||
console.debug('Session already killed')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class TMuxPersistenceProvider extends SessionPersistenceProvider {
|
||||
id = 'tmux'
|
||||
displayName = 'Tmux'
|
||||
private tmux: TMux
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
if (this.isAvailable()) {
|
||||
this.tmux = new TMux()
|
||||
}
|
||||
}
|
||||
|
||||
isAvailable (): boolean {
|
||||
try {
|
||||
execFileSync('tmux', ['-V'])
|
||||
return true
|
||||
} catch (_) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async attachSession (recoveryId: any): Promise<SessionOptions> {
|
||||
let sessions = await this.tmux.list()
|
||||
if (!sessions.includes(recoveryId)) {
|
||||
return null
|
||||
}
|
||||
let truePID$ = new AsyncSubject<number>()
|
||||
this.tmux.getPID(recoveryId).then(pid => {
|
||||
truePID$.next(pid)
|
||||
truePID$.complete()
|
||||
})
|
||||
return {
|
||||
command: 'tmux',
|
||||
args: ['-L', 'terminus', 'attach-session', '-d', '-t', recoveryId, ';', 'refresh-client'],
|
||||
recoveredTruePID$: truePID$.asObservable(),
|
||||
recoveryId,
|
||||
}
|
||||
}
|
||||
|
||||
async startSession (options: SessionOptions): Promise<any> {
|
||||
// TODO env
|
||||
let recoveryId = Date.now().toString()
|
||||
await this.tmux.create(recoveryId, options)
|
||||
return recoveryId
|
||||
}
|
||||
|
||||
async terminateSession (recoveryId: string): Promise<void> {
|
||||
await this.tmux.terminate(recoveryId)
|
||||
}
|
||||
}
|
@@ -2,25 +2,20 @@ import { Injectable } from '@angular/core'
|
||||
import { TabRecoveryProvider, RecoveredTab } from 'terminus-core'
|
||||
|
||||
import { TerminalTabComponent } from './components/terminalTab.component'
|
||||
import { SessionsService } from './services/sessions.service'
|
||||
|
||||
@Injectable()
|
||||
export class RecoveryProvider extends TabRecoveryProvider {
|
||||
constructor (
|
||||
private sessions: SessionsService,
|
||||
// private sessions: SessionsService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
async recover (recoveryToken: any): Promise<RecoveredTab> {
|
||||
if (recoveryToken.type === 'app:terminal') {
|
||||
let sessionOptions = await this.sessions.recover(recoveryToken.recoveryId)
|
||||
if (!sessionOptions) {
|
||||
return null
|
||||
}
|
||||
if (recoveryToken.type === 'app:terminal-tab') {
|
||||
return {
|
||||
type: TerminalTabComponent,
|
||||
options: { sessionOptions },
|
||||
options: { sessionOptions: recoveryToken.sessionOptions },
|
||||
}
|
||||
}
|
||||
return null
|
||||
|
45
terminus-terminal/src/services/dockMenu.service.ts
Normal file
45
terminus-terminal/src/services/dockMenu.service.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { NgZone, Injectable } from '@angular/core'
|
||||
import { ElectronService, ConfigService, HostAppService, Platform } from 'terminus-core'
|
||||
import { TerminalService } from './terminal.service'
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DockMenuService {
|
||||
appVersion: string
|
||||
|
||||
constructor (
|
||||
private electron: ElectronService,
|
||||
private config: ConfigService,
|
||||
private hostApp: HostAppService,
|
||||
private zone: NgZone,
|
||||
private terminalService: TerminalService,
|
||||
) {
|
||||
config.changed$.subscribe(() => this.update())
|
||||
}
|
||||
|
||||
update () {
|
||||
if (this.hostApp.platform === Platform.Windows) {
|
||||
this.electron.app.setJumpList(this.config.store.terminal.profiles.length ? [{
|
||||
type: 'custom',
|
||||
name: 'Profiles',
|
||||
items: this.config.store.terminal.profiles.map(profile => ({
|
||||
type: 'task',
|
||||
program: process.execPath,
|
||||
args: `profile "${profile.name}"`,
|
||||
title: profile.name,
|
||||
iconPath: process.execPath,
|
||||
iconIndex: 0,
|
||||
}))
|
||||
}] : null)
|
||||
}
|
||||
if (this.hostApp.platform === Platform.macOS) {
|
||||
this.electron.app.dock.setMenu(this.electron.Menu.buildFromTemplate(
|
||||
this.config.store.terminal.profiles.map(profile => ({
|
||||
label: profile.name,
|
||||
click: () => this.zone.run(() => {
|
||||
this.terminalService.openTabWithOptions(profile.sessionOptions)
|
||||
}),
|
||||
}))
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,11 +3,10 @@ let nodePTY
|
||||
import * as fs from 'mz/fs'
|
||||
import { Observable, Subject } from 'rxjs'
|
||||
import { first } from 'rxjs/operators'
|
||||
import { Injectable, Inject } from '@angular/core'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Logger, LogService, ConfigService } from 'terminus-core'
|
||||
import { exec } from 'mz/child_process'
|
||||
|
||||
import { SessionOptions, SessionPersistenceProvider } from '../api'
|
||||
import { SessionOptions } from '../api'
|
||||
|
||||
let macOSNativeProcessList
|
||||
try {
|
||||
@@ -26,10 +25,11 @@ export interface IChildProcess {
|
||||
command: string
|
||||
}
|
||||
|
||||
const windowsDirectoryRegex = /([a-zA-Z]:[^\:\[\]\?\"\<\>\|]+)/mi // tslint:disable-line
|
||||
|
||||
export abstract class BaseSession {
|
||||
open: boolean
|
||||
name: string
|
||||
recoveryId: string
|
||||
truePID: number
|
||||
protected output = new Subject<string>()
|
||||
protected closed = new Subject<void>()
|
||||
@@ -77,15 +77,20 @@ export abstract class BaseSession {
|
||||
export class Session extends BaseSession {
|
||||
private pty: any
|
||||
private pauseAfterExit = false
|
||||
private guessedCWD: string
|
||||
|
||||
constructor (private config: ConfigService) {
|
||||
super()
|
||||
}
|
||||
|
||||
start (options: SessionOptions) {
|
||||
this.name = options.name
|
||||
this.recoveryId = options.recoveryId
|
||||
|
||||
let env = {
|
||||
...process.env,
|
||||
TERM: 'xterm-256color',
|
||||
...options.env,
|
||||
...this.config.store.terminal.environment || {},
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin' && !process.env.LC_ALL) {
|
||||
@@ -99,21 +104,26 @@ export class Session extends BaseSession {
|
||||
LC_MONETARY: locale,
|
||||
})
|
||||
}
|
||||
|
||||
let cwd = options.cwd || process.env.HOME
|
||||
|
||||
if (!fs.existsSync(cwd)) {
|
||||
console.warn('Ignoring non-existent CWD:', cwd)
|
||||
cwd = null
|
||||
}
|
||||
|
||||
this.pty = nodePTY.spawn(options.command, options.args || [], {
|
||||
name: 'xterm-256color',
|
||||
cols: options.width || 80,
|
||||
rows: options.height || 30,
|
||||
cwd: options.cwd || process.env.HOME,
|
||||
cwd,
|
||||
env: env,
|
||||
experimentalUseConpty: this.config.store.terminal.useConPTY,
|
||||
})
|
||||
|
||||
if (options.recoveredTruePID$) {
|
||||
options.recoveredTruePID$.subscribe(pid => {
|
||||
this.truePID = pid
|
||||
})
|
||||
} else {
|
||||
this.truePID = (this.pty as any).pid
|
||||
}
|
||||
this.guessedCWD = cwd
|
||||
|
||||
this.truePID = (this.pty as any).pid
|
||||
|
||||
setTimeout(async () => {
|
||||
// Retrieve any possible single children now that shell has fully started
|
||||
@@ -128,6 +138,9 @@ export class Session extends BaseSession {
|
||||
|
||||
this.pty.on('data-buffered', data => {
|
||||
this.emitOutput(data)
|
||||
if (process.platform === 'win32') {
|
||||
this.guessWindowsCWD(data)
|
||||
}
|
||||
})
|
||||
|
||||
this.pty.on('exit', () => {
|
||||
@@ -184,7 +197,7 @@ export class Session extends BaseSession {
|
||||
}))
|
||||
}
|
||||
if (process.platform === 'win32') {
|
||||
return await new Promise<IChildProcess[]>(resolve => {
|
||||
return new Promise<IChildProcess[]>(resolve => {
|
||||
windowsProcessTree.getProcessTree(this.truePID, tree => {
|
||||
resolve(tree ? tree.children.map(child => ({
|
||||
pid: child.pid,
|
||||
@@ -244,65 +257,52 @@ export class Session extends BaseSession {
|
||||
}
|
||||
}
|
||||
if (process.platform === 'linux') {
|
||||
return await fs.readlink(`/proc/${this.truePID}/cwd`)
|
||||
return fs.readlink(`/proc/${this.truePID}/cwd`)
|
||||
}
|
||||
if (process.platform === 'win32') {
|
||||
if (!this.guessedCWD) {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
fs.access(this.guessedCWD)
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
return this.guessedCWD
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private guessWindowsCWD (data: string) {
|
||||
let match = windowsDirectoryRegex.exec(data)
|
||||
if (match) {
|
||||
this.guessedCWD = match[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SessionsService {
|
||||
sessions: {[id: string]: BaseSession} = {}
|
||||
logger: Logger
|
||||
private lastID = 0
|
||||
|
||||
constructor (
|
||||
@Inject(SessionPersistenceProvider) private persistenceProviders: SessionPersistenceProvider[],
|
||||
private config: ConfigService,
|
||||
log: LogService,
|
||||
) {
|
||||
nodePTY = require('node-pty-tmp')
|
||||
nodePTY = require('@terminus-term/node-pty')
|
||||
nodePTY = require('../bufferizedPTY')(nodePTY)
|
||||
this.logger = log.create('sessions')
|
||||
this.persistenceProviders = this.config.enabledServices(this.persistenceProviders).filter(x => x.isAvailable())
|
||||
}
|
||||
|
||||
async prepareNewSession (options: SessionOptions): Promise<SessionOptions> {
|
||||
let persistence = this.getPersistence()
|
||||
if (persistence) {
|
||||
let recoveryId = await persistence.startSession(options)
|
||||
options = await persistence.attachSession(recoveryId)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
addSession (session: BaseSession, options: SessionOptions) {
|
||||
this.lastID++
|
||||
options.name = `session-${this.lastID}`
|
||||
session.start(options)
|
||||
let persistence = this.getPersistence()
|
||||
session.destroyed$.pipe(first()).subscribe(() => {
|
||||
delete this.sessions[session.name]
|
||||
if (persistence) {
|
||||
persistence.terminateSession(session.recoveryId)
|
||||
}
|
||||
})
|
||||
this.sessions[session.name] = session
|
||||
return session
|
||||
}
|
||||
|
||||
async recover (recoveryId: string): Promise<SessionOptions> {
|
||||
let persistence = this.getPersistence()
|
||||
if (persistence) {
|
||||
return await persistence.attachSession(recoveryId)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private getPersistence (): SessionPersistenceProvider {
|
||||
if (!this.config.store.terminal.persistence) {
|
||||
return null
|
||||
}
|
||||
return this.persistenceProviders.find(x => x.id === this.config.store.terminal.persistence) || null
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import { Observable, AsyncSubject } from 'rxjs'
|
||||
import { Injectable, Inject } from '@angular/core'
|
||||
import { AppService, Logger, LogService, ConfigService } from 'terminus-core'
|
||||
import { IShell, ShellProvider } from '../api'
|
||||
import { SessionsService } from './sessions.service'
|
||||
import { IShell, ShellProvider, SessionOptions } from '../api'
|
||||
import { TerminalTabComponent } from '../components/terminalTab.component'
|
||||
import { UACService } from './uac.service'
|
||||
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class TerminalService {
|
||||
private shells = new AsyncSubject<IShell[]>()
|
||||
private logger: Logger
|
||||
@@ -14,8 +14,8 @@ export class TerminalService {
|
||||
|
||||
constructor (
|
||||
private app: AppService,
|
||||
private sessions: SessionsService,
|
||||
private config: ConfigService,
|
||||
private uac: UACService,
|
||||
@Inject(ShellProvider) private shellProviders: ShellProvider[],
|
||||
log: LogService,
|
||||
) {
|
||||
@@ -52,22 +52,34 @@ export class TerminalService {
|
||||
let shells = await this.shells$.toPromise()
|
||||
shell = shells.find(x => x.id === this.config.store.terminal.shell) || shells[0]
|
||||
}
|
||||
let env: any = Object.assign({}, process.env, shell.env || {}, this.config.store.terminal.environment || {})
|
||||
|
||||
this.logger.log(`Starting shell ${shell.name}`, shell)
|
||||
let sessionOptions = await this.sessions.prepareNewSession({
|
||||
let sessionOptions = {
|
||||
...this.optionsFromShell(shell),
|
||||
pauseAfterExit: pause,
|
||||
cwd,
|
||||
}
|
||||
|
||||
return this.openTabWithOptions(sessionOptions)
|
||||
}
|
||||
|
||||
optionsFromShell (shell: IShell): SessionOptions {
|
||||
return {
|
||||
command: shell.command,
|
||||
args: shell.args || [],
|
||||
cwd,
|
||||
env,
|
||||
pauseAfterExit: pause,
|
||||
})
|
||||
env: shell.env,
|
||||
}
|
||||
}
|
||||
|
||||
openTabWithOptions (sessionOptions: SessionOptions): TerminalTabComponent {
|
||||
if (sessionOptions.runAsAdministrator && this.uac.isAvailable) {
|
||||
sessionOptions = this.uac.patchSessionOptionsForUAC(sessionOptions)
|
||||
}
|
||||
this.logger.log('Using session options:', sessionOptions)
|
||||
|
||||
return this.app.openNewTab(
|
||||
TerminalTabComponent,
|
||||
{ sessionOptions, shell }
|
||||
{ sessionOptions }
|
||||
) as TerminalTabComponent
|
||||
}
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ import { HTermFrontend } from '../frontends/htermFrontend'
|
||||
import { XTermFrontend } from '../frontends/xtermFrontend'
|
||||
import { BaseSession } from '../services/sessions.service'
|
||||
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class TerminalFrontendService {
|
||||
private containers = new WeakMap<BaseSession, Frontend>()
|
||||
|
||||
|
43
terminus-terminal/src/services/uac.service.ts
Normal file
43
terminus-terminal/src/services/uac.service.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import * as path from 'path'
|
||||
import * as os from 'os'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ElectronService, HostAppService, Platform } from 'terminus-core'
|
||||
import { SessionOptions } from '../api'
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class UACService {
|
||||
isAvailable = false
|
||||
|
||||
constructor (
|
||||
hostApp: HostAppService,
|
||||
private electron: ElectronService,
|
||||
) {
|
||||
this.isAvailable = hostApp.platform === Platform.Windows
|
||||
&& parseFloat(os.release()) >= 10
|
||||
&& parseInt(os.release().split('.')[2]) >= 17692
|
||||
}
|
||||
|
||||
patchSessionOptionsForUAC (sessionOptions: SessionOptions): SessionOptions {
|
||||
let helperPath = path.join(
|
||||
path.dirname(this.electron.app.getPath('exe')),
|
||||
'resources',
|
||||
'extras',
|
||||
'UAC.exe',
|
||||
)
|
||||
|
||||
if (process.env.DEV) {
|
||||
helperPath = path.join(
|
||||
path.dirname(this.electron.app.getPath('exe')),
|
||||
'..', '..', '..',
|
||||
'extras',
|
||||
'UAC.exe',
|
||||
)
|
||||
}
|
||||
|
||||
let options = { ...sessionOptions }
|
||||
options.args = [options.command, ...options.args]
|
||||
options.command = helperPath
|
||||
return options
|
||||
}
|
||||
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
import * as path from 'path'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Registry } from 'rage-edit-tmp'
|
||||
import { Registry } from 'rage-edit'
|
||||
import { HostAppService, Platform } from 'terminus-core'
|
||||
|
||||
import { ShellProvider, IShell } from '../api'
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import * as path from 'path'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Registry } from 'rage-edit-tmp'
|
||||
import { Registry } from 'rage-edit'
|
||||
import { HostAppService, Platform } from 'terminus-core'
|
||||
|
||||
import { ShellProvider, IShell } from '../api'
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import * as path from 'path'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Registry } from 'rage-edit-tmp'
|
||||
import { Registry } from 'rage-edit'
|
||||
import { HostAppService, Platform } from 'terminus-core'
|
||||
|
||||
import { ShellProvider, IShell } from '../api'
|
||||
|
@@ -16,7 +16,7 @@ export class MacOSDefaultShellProvider extends ShellProvider {
|
||||
if (this.hostApp.platform !== Platform.macOS) {
|
||||
return []
|
||||
}
|
||||
let shellEntry = (await exec(`dscl . -read /Users/${process.env.LOGNAME} UserShell`))[0].toString()
|
||||
let shellEntry = (await exec(`/usr/bin/dscl . -read /Users/${process.env.LOGNAME} UserShell`))[0].toString()
|
||||
return [{
|
||||
id: 'default',
|
||||
name: 'User default',
|
||||
|
@@ -24,7 +24,7 @@ export class POSIXShellsProvider extends ShellProvider {
|
||||
id: x,
|
||||
name: x,
|
||||
command: x,
|
||||
args: ['--login'],
|
||||
args: ['-l'],
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Registry } from 'rage-edit-tmp'
|
||||
import { Registry } from 'rage-edit'
|
||||
import { HostAppService, Platform } from 'terminus-core'
|
||||
import { ShellProvider, IShell } from '../api'
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import * as fs from 'mz/fs'
|
||||
import { Registry } from 'rage-edit-tmp'
|
||||
import { Registry } from 'rage-edit'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { HostAppService, Platform } from 'terminus-core'
|
||||
|
||||
|
@@ -57,11 +57,11 @@ module.exports = {
|
||||
'fs',
|
||||
'font-manager',
|
||||
'path',
|
||||
'node-pty-tmp',
|
||||
'macos-native-processlist',
|
||||
'windows-process-tree',
|
||||
'mz/fs',
|
||||
'mz/child_process',
|
||||
'@terminus-term/node-pty',
|
||||
/^rxjs/,
|
||||
/^@angular/,
|
||||
/^@ng-bootstrap/,
|
||||
|
@@ -2,16 +2,18 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@terminus-term/node-pty@0.8.0-1":
|
||||
version "0.8.0-1"
|
||||
resolved "https://registry.yarnpkg.com/@terminus-term/node-pty/-/node-pty-0.8.0-1.tgz#3cb682f2351179842d195c074acf7d5b54e96ffe"
|
||||
integrity sha512-mEP5zQC/yHtvCbYjdGmwzFkkdTqCe0Jfd1o35yzM9jfGrVpW9qlvo/ZrzyOLSH2tJlYRB5SqfdWlo/LVXrAEYA==
|
||||
dependencies:
|
||||
nan "2.10.0"
|
||||
|
||||
"@terminus-term/xterm@3.8.4":
|
||||
version "3.8.4"
|
||||
resolved "https://registry.yarnpkg.com/@terminus-term/xterm/-/xterm-3.8.4.tgz#c9a9d9e0d46dbd8a94e06384e2d7268d36f5b0c6"
|
||||
integrity sha512-DrxCjnJh9n3ivpldwI098PnuVYwg9e5lFlU8/1qfh/J/wFHbG3dX/bEtB4ynfTi3IXVJozFO2psD96+W2h3yeQ==
|
||||
|
||||
"@types/async-lock@0.0.19":
|
||||
version "0.0.19"
|
||||
resolved "https://registry.yarnpkg.com/@types/async-lock/-/async-lock-0.0.19.tgz#4bdb7f8d9ac2826588b98068903aedbd9d95dce8"
|
||||
integrity sha1-S9t/jZrCgmWIuYBokDrtvZ2V3Og=
|
||||
|
||||
"@types/deep-equal@^1.0.0":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.1.tgz#71cfabb247c22bcc16d536111f50c0ed12476b03"
|
||||
@@ -44,11 +46,6 @@ any-promise@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
|
||||
integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
|
||||
|
||||
async-lock@^1.0.0:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.1.3.tgz#e47f1cbb6bec765b73e27ed8961d58006457ec08"
|
||||
integrity sha512-nxlfFLGfCJ1r7p9zhR5OuL6jYkDd9P7FqSitfLji+C1NdyhCz4+rWW3kiPiyPASHhN7VlsKEvRWWbnME9lYngw==
|
||||
|
||||
big.js@^3.1.3:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
|
||||
@@ -153,18 +150,16 @@ mz@^2.6.0:
|
||||
object-assign "^4.0.1"
|
||||
thenify-all "^1.0.0"
|
||||
|
||||
nan@2.10.0:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
|
||||
integrity sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==
|
||||
|
||||
nan@>=2.10.0, nan@^2.10.0:
|
||||
version "2.11.1"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.1.tgz#90e22bccb8ca57ea4cd37cc83d3819b52eea6766"
|
||||
integrity sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==
|
||||
|
||||
node-pty-tmp@0.7.2:
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/node-pty-tmp/-/node-pty-tmp-0.7.2.tgz#d1528245a46ab193c54e34792ee0b89d0f557417"
|
||||
integrity sha512-/I0BluFKSy7SOnlR5TALVx+pQEJZQStsfkwCpmsxHymSUVc3dJeOLrbaaVtNsU0VchXF3KOZJmHk7EUdBmWbfQ==
|
||||
dependencies:
|
||||
nan "^2.10.0"
|
||||
|
||||
object-assign@^4.0.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
@@ -194,10 +189,10 @@ pseudomap@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
|
||||
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
|
||||
|
||||
rage-edit-tmp@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/rage-edit-tmp/-/rage-edit-tmp-1.1.0.tgz#fc5d76716d2fe2cf97dcafbf3e26753e3a08e3b2"
|
||||
integrity sha512-lR97QHY5WSf9orInMJhPqUbenkdiy7QbXUoRMI+wBZGyAPkxNwgo7h6ojq634QrBf/kQo3mVXYjuD3ZYraNaZQ==
|
||||
rage-edit@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/rage-edit/-/rage-edit-1.2.0.tgz#991860a60fef934d8a6d0f057e55786b02f94a2b"
|
||||
integrity sha512-0RspBRc2s6We4g7hRCvT5mu7YPEnfjvQK8Tt354a2uUNJCMC7MKLvo/1mLvHUCQ/zbP6siQyp5VRZN7UCpMFZg==
|
||||
|
||||
runes@^0.4.2:
|
||||
version "0.4.3"
|
||||
|
4
terminus-uac/.gitignore
vendored
Normal file
4
terminus-uac/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
Debug
|
||||
*.ipch
|
||||
*.suo
|
||||
v15
|
31
terminus-uac/UAC.sln
Normal file
31
terminus-uac/UAC.sln
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27703.2042
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UAC", "UAC\UAC.vcxproj", "{DCE3B955-DB20-4334-B11B-F6C040825A59}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{DCE3B955-DB20-4334-B11B-F6C040825A59}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{DCE3B955-DB20-4334-B11B-F6C040825A59}.Debug|x64.Build.0 = Debug|x64
|
||||
{DCE3B955-DB20-4334-B11B-F6C040825A59}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{DCE3B955-DB20-4334-B11B-F6C040825A59}.Debug|x86.Build.0 = Debug|Win32
|
||||
{DCE3B955-DB20-4334-B11B-F6C040825A59}.Release|x64.ActiveCfg = Release|x64
|
||||
{DCE3B955-DB20-4334-B11B-F6C040825A59}.Release|x64.Build.0 = Release|x64
|
||||
{DCE3B955-DB20-4334-B11B-F6C040825A59}.Release|x86.ActiveCfg = Release|Win32
|
||||
{DCE3B955-DB20-4334-B11B-F6C040825A59}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {77F43A07-B912-4CCD-9928-5C12F6D0112B}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
BIN
terminus-uac/UAC/UAC.cpp
Normal file
BIN
terminus-uac/UAC/UAC.cpp
Normal file
Binary file not shown.
165
terminus-uac/UAC/UAC.vcxproj
Normal file
165
terminus-uac/UAC/UAC.vcxproj
Normal file
@@ -0,0 +1,165 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{DCE3B955-DB20-4334-B11B-F6C040825A59}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>UAC</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.18298.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="stdafx.h" />
|
||||
<ClInclude Include="targetver.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="UAC.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
33
terminus-uac/UAC/UAC.vcxproj.filters
Normal file
33
terminus-uac/UAC/UAC.vcxproj.filters
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="stdafx.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="targetver.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="UAC.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user