Compare commits

..

23 Commits

Author SHA1 Message Date
Eugene Pankov
5a5cd09832 Merge branch 'master' of github.com:Eugeny/terminus 2018-10-08 11:31:36 +02:00
Eugene Pankov
c2f4c343a7 Revert "use npms.io instead of npmjs.org for plugin search"
This reverts commit 9c6f2747aa.
2018-10-08 11:30:52 +02:00
Eugene
153cd1ec9a Merge pull request #437 from Domain/master
Support regex and optional script
2018-10-08 08:43:15 +02:00
Domain
0254cabdde lint 2018-10-08 14:02:20 +08:00
Domain
9eee600ccc optional script 2018-10-08 14:02:19 +08:00
Domain
24288fac9a add regex support 2018-10-08 14:02:18 +08:00
Eugene Pankov
441164363f transparency support on Linux 2018-10-06 20:50:06 +02:00
Eugene Pankov
3d7a4a1e0e build fix 2018-10-06 20:31:50 +02:00
Eugene Pankov
7984313b06 dropped electron-vibrancy 2018-10-05 21:25:44 +02:00
Eugene Pankov
89cc6c0d20 build fix 2018-10-05 20:20:21 +02:00
Eugene Pankov
d104f5e771 reimplemented Windows vibrancy using windows-swca and windows-blurbehind (#383) 2018-10-05 11:12:06 -07:00
Eugene Pankov
5b5d145bd0 lint 2018-10-05 11:36:37 +01:00
Eugene Pankov
78212a9538 build fix 2018-10-05 11:36:15 +01:00
Eugene Pankov
1b1b2af545 possibly fixed cursor blink interval overlaps (fixes #216) 2018-10-05 10:24:28 +01:00
Eugene Pankov
f3f969a006 fixed cursor visibility (fixes #439) 2018-10-05 10:18:34 +01:00
Eugene Pankov
621a6d8127 cleanup 2018-10-05 10:10:02 +01:00
Eugene Pankov
87933edb96 fixed context menu and xterm mouse events (fixes #442) 2018-10-05 10:02:03 +01:00
Eugene Pankov
a931d47c23 auto-update terminus path in registry (fixes #447) 2018-10-05 09:47:04 +01:00
Eugene Pankov
1ae027f17c ci 2018-10-03 22:44:04 +01:00
Eugene
5360b2f292 Merge pull request #443 from vsailev/master
Restore Bash on Windows
2018-10-03 21:57:36 +01:00
vsailev
d806fb6e1e Restore Bash on Windows 2018-10-03 21:15:16 +01:00
Eugene Pankov
57de182013 Merge branch 'master' of github.com:Eugeny/terminus 2018-09-25 10:48:24 +02:00
Eugene Pankov
3fd57e81a6 install terminus into /usr/bin on Debian (fixes #433) 2018-09-25 10:48:00 +02:00
25 changed files with 187 additions and 96 deletions

View File

@@ -1,4 +1,5 @@
import { app, ipcMain, Menu, Tray, shell } from 'electron'
import { loadConfig } from './config'
import { Window } from './window'
export class Application {
@@ -9,6 +10,14 @@ export class Application {
ipcMain.on('app:config-change', () => {
this.broadcast('host:config-change')
})
const configData = loadConfig()
if (process.platform === 'linux' && ((configData.appearance || {}).opacity || 1) !== 1) {
app.commandLine.appendSwitch('enable-transparent-visuals')
app.disableHardwareAcceleration()
}
app.commandLine.appendSwitch('disable-http-cache')
}
async newWindow (): Promise<Window> {

13
app/lib/config.ts Normal file
View File

@@ -0,0 +1,13 @@
import * as fs from 'fs'
import * as path from 'path'
import * as yaml from 'js-yaml'
import { app } from 'electron'
export function loadConfig (): any {
let configPath = path.join(app.getPath('userData'), 'config.yaml')
if (fs.existsSync(configPath)) {
return yaml.safeLoad(fs.readFileSync(configPath, 'utf8'))
} else {
return {}
}
}

View File

@@ -11,8 +11,6 @@ if (!process.env.TERMINUS_PLUGINS) {
const application = new Application()
app.commandLine.appendSwitch('disable-http-cache')
ipcMain.on('app:new-window', () => {
console.log('new-window')
application.newWindow()

View File

@@ -1,33 +1,30 @@
import { Subject, Observable } from 'rxjs'
import { BrowserWindow, app, ipcMain, Rectangle, Menu } from 'electron'
import { BrowserWindow, app, ipcMain, Rectangle } from 'electron'
import ElectronConfig = require('electron-config')
import * as yaml from 'js-yaml'
import * as fs from 'fs'
import * as path from 'path'
import * as os from 'os'
let electronVibrancy: any
if (process.platform !== 'linux') {
electronVibrancy = require('electron-vibrancy')
import { loadConfig } from './config'
let SetWindowCompositionAttribute: any
let AccentState: any
let DwmEnableBlurBehindWindow: any
if (process.platform === 'win32') {
SetWindowCompositionAttribute = require('windows-swca').SetWindowCompositionAttribute
AccentState = require('windows-swca').AccentState
DwmEnableBlurBehindWindow = require('windows-blurbehind').DwmEnableBlurBehindWindow
}
export class Window {
ready: Promise<void>
private visible = new Subject<boolean>()
private window: BrowserWindow
private vibrancyViewID: number
private windowConfig: ElectronConfig
private windowBounds: Rectangle
get visible$ (): Observable<boolean> { return this.visible }
constructor () {
let configPath = path.join(app.getPath('userData'), 'config.yaml')
let configData
if (fs.existsSync(configPath)) {
configData = yaml.safeLoad(fs.readFileSync(configPath, 'utf8'))
} else {
configData = {}
}
let configData = loadConfig()
this.windowConfig = new ElectronConfig({ name: 'window' })
this.windowBounds = this.windowConfig.get('windowBoundaries')
@@ -42,6 +39,7 @@ export class Window {
webPreferences: { webSecurity: false },
frame: false,
show: false,
backgroundColor: '#00000000'
}
Object.assign(options, this.windowBounds)
@@ -53,10 +51,6 @@ export class Window {
}
}
if (process.platform === 'win32' && (configData.appearance || {}).vibrancy) {
options.transparent = true
}
if (process.platform === 'linux') {
options.backgroundColor = '#131d27'
}
@@ -95,11 +89,22 @@ export class Window {
}
setVibrancy (enabled: boolean) {
if (enabled && !this.vibrancyViewID) {
this.vibrancyViewID = electronVibrancy.SetVibrancy(this.window, 0)
} else if (!enabled && this.vibrancyViewID) {
electronVibrancy.RemoveView(this.window, this.vibrancyViewID)
this.vibrancyViewID = null
if (process.platform === 'win32') {
if (parseFloat(os.release()) >= 10) {
let attribValue = AccentState.ACCENT_DISABLED
let color = 0x00000000
if (enabled) {
if (parseInt(os.release().split('.')[2]) >= 17063) {
attribValue = AccentState.ACCENT_ENABLE_FLUENT
color = 0x01000000 // using a small alpha because acrylic bugs out at full transparency.
} else {
attribValue = AccentState.ACCENT_ENABLE_BLURBEHIND
}
}
SetWindowCompositionAttribute(this.window, attribValue, color)
} else {
DwmEnableBlurBehindWindow(this.window, enabled)
}
}
}
@@ -188,10 +193,6 @@ export class Window {
ipcMain.on('window-set-title', (_event, title) => {
this.window.setTitle(title)
})
ipcMain.on('window-popup-context-menu', (_event, menuDefinition) => {
Menu.buildFromTemplate(menuDefinition).popup({ window: this.window })
})
}
private destroy () {

View File

@@ -25,7 +25,6 @@
"electron-debug": "^2.0.0",
"electron-is-dev": "0.1.2",
"electron-squirrel-startup": "^1.0.0",
"electron-vibrancy": "^0.1.3",
"js-yaml": "3.8.2",
"mz": "^2.6.0",
"ngx-toastr": "^8.7.3",
@@ -34,6 +33,10 @@
"yargs": "^12.0.1",
"zone.js": "~0.8.26"
},
"optionalDependencies": {
"windows-blurbehind": "^1.0.0",
"windows-swca": "^1.1.1"
},
"devDependencies": {
"@types/mz": "0.0.31"
}

View File

@@ -41,6 +41,8 @@ module.exports = {
mz: 'commonjs mz',
path: 'commonjs path',
yargs: 'commonjs yargs',
'windows-swca': 'commonjs windows-swca',
'windows-blurbehind': 'commonjs windows-blurbehind',
},
plugins: [
new webpack.optimize.ModuleConcatenationPlugin(),

View File

@@ -80,10 +80,6 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
bindings@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7"
camelcase@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
@@ -183,13 +179,6 @@ electron-squirrel-startup@^1.0.0:
dependencies:
debug "^2.2.0"
electron-vibrancy@^0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/electron-vibrancy/-/electron-vibrancy-0.1.3.tgz#04382dd6e030e5ca5e60f8e024033738cb8479e3"
dependencies:
bindings "^1.2.1"
nan "^2.0.5"
env-paths@^0.3.0:
version "0.3.1"
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-0.3.1.tgz#c30ccfcbc30c890943dc08a85582517ef00da463"
@@ -336,10 +325,6 @@ mz@^2.6.0:
object-assign "^4.0.1"
thenify-all "^1.0.0"
nan@^2.0.5:
version "2.10.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
ngx-toastr@^8.7.3:
version "8.7.3"
resolved "https://registry.yarnpkg.com/ngx-toastr/-/ngx-toastr-8.7.3.tgz#d3b7a8077ba1c860dd8a44779ccad38c5ea15c92"
@@ -532,6 +517,14 @@ which@^1.2.9:
dependencies:
isexe "^2.0.0"
windows-blurbehind@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/windows-blurbehind/-/windows-blurbehind-1.0.0.tgz#050efb988704c44335bdc3efefd757f6e463b8ac"
windows-swca@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/windows-swca/-/windows-swca-1.1.1.tgz#0b3530278c67d408baac71c3a6aeb16d55318bf8"
wrap-ansi@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"

View File

@@ -4,7 +4,7 @@ platform:
- x64
environment:
nodejs_version: "7"
nodejs_version: "10"
cache:
- '%USERPROFILE%\.electron'

View File

@@ -0,0 +1,4 @@
#!/bin/bash
# Link to the binary
ln -sf '/opt/${productFilename}/${executable}' '/usr/bin/${executable}'

View File

@@ -120,7 +120,8 @@
"libxtst6",
"libnss3",
"tmux"
]
],
"afterInstall": "build/linux/after-install.tpl"
},
"rpm": {
"depends": [

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'
import { TouchBar, BrowserWindow } from 'electron'
import { TouchBar, BrowserWindow, Menu } from 'electron'
@Injectable()
export class ElectronService {
@@ -14,6 +14,7 @@ export class ElectronService {
remote: any
TouchBar: typeof TouchBar
BrowserWindow: typeof BrowserWindow
Menu: typeof Menu
private electron: any
constructor () {
@@ -29,6 +30,7 @@ export class ElectronService {
this.nativeImage = this.remote.nativeImage
this.TouchBar = this.remote.TouchBar
this.BrowserWindow = this.remote.BrowserWindow
this.Menu = this.remote.Menu
}
remoteRequire (name: string): any {

View File

@@ -169,7 +169,7 @@ export class HostAppService {
}
popupContextMenu (menuDefinition: Electron.MenuItemConstructorOptions[]) {
this.electron.ipcRenderer.send('window-popup-context-menu', menuDefinition)
this.electron.Menu.buildFromTemplate(menuDefinition).popup({})
}
broadcastConfigChange () {

View File

@@ -38,6 +38,16 @@ export class ShellIntegrationService {
)
this.automatorWorkflowsDestination = path.join(process.env.HOME, 'Library', 'Services')
}
this.updatePaths()
}
async updatePaths (): Promise<void> {
// Update paths in case of an update
if (this.hostApp.platform === Platform.Windows) {
if (await this.isInstalled()) {
await this.install()
}
}
}
async isInstalled (): Promise<boolean> {

View File

@@ -66,7 +66,7 @@ div(*ngIf='npmInstalled')
i.fa.fa-fw.fa-search(*ngIf='availablePluginsReady')
input.form-control(
type='text',
'[(ngModel)]'='_1',
[(ngModel)]='_1',
(ngModelChange)='searchAvailable(_1)',
placeholder='Search plugins'
)

View File

@@ -72,9 +72,13 @@ export class PluginManagerService {
listAvailable (query?: string): Observable<IPluginInfo[]> {
return from(
axios.get(`https://api.npms.io/v2/search?q=keywords%3A${KEYWORD}+${encodeURIComponent(query || '')}&from=0&size=250`, {})
axios.get(`https://www.npmjs.com/search?q=keywords%3A${KEYWORD}+${encodeURIComponent(query || '')}&from=0&size=1000`, {
headers: {
'x-spiferack': '1',
}
})
).pipe(
map(response => response.data.results.map(item => ({
map(response => response.data.objects.map(item => ({
name: item.package.name.substring(NAME_PREFIX.length),
packageName: item.package.name,
description: item.package.description,

View File

@@ -63,34 +63,19 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
.header
.title Vibrancy
.description Gives the window a blurred transparent background
.btn-group(
toggle(
[(ngModel)]='config.store.appearance.vibrancy',
(ngModelChange)='config.save(); (hostApp.platform === Platform.Windows && config.requestRestart())',
ngbRadioGroup
(ngModelChange)='config.save()'
)
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='true'
)
| Enable
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='false'
)
| Disable
.form-line(*ngIf='hostApp.platform !== Platform.Linux')
.form-line
.header
.title Window opacity
input(
type='range',
[(ngModel)]='config.store.appearance.opacity',
(ngModelChange)='config.save()',
(ngModelChange)='config.save(); (hostApp.platform === Platform.Linux && config.requestRestart())',
min='0.05',
max='1',
step='0.01'
@@ -259,8 +244,8 @@ ngb-tabset.vertical(type='pills', [activeId]='activeTab')
td {{hotkey.id}}
td
multi-hotkey-input(
'[(model)]'='config.store.hotkeys[hotkey.id]'
'(modelChange)'='config.save(); docking.dock()'
[(model)]='config.store.hotkeys[hotkey.id]',
(modelChange)='config.save(); docking.dock()'
)
ngb-tab(*ngFor='let provider of settingsProviders', [id]='provider.id')

View File

@@ -3,6 +3,8 @@ import { BaseSession } from 'terminus-terminal'
export interface LoginScript {
expect?: string
send: string
isRegex?: boolean
optional?: boolean
}
export interface SSHConnection {
@@ -37,13 +39,35 @@ export class SSHSession extends BaseSession {
if (this.scripts) {
let found = false
for (let script of this.scripts) {
if (dataString.includes(script.expect)) {
console.log('Executing script:', script.send)
this.shell.write(script.send + '\n')
this.scripts = this.scripts.filter(x => x !== script)
found = true
let match = false
let cmd = ''
if (script.isRegex) {
let re = new RegExp(script.expect, 'g')
if (dataString.match(re)) {
cmd = dataString.replace(re, script.send)
match = true
found = true
}
} else {
break
if (dataString.includes(script.expect)) {
cmd = script.send
match = true
found = true
}
}
if (match) {
console.log('Executing script: "' + cmd + '"')
this.shell.write(cmd + '\n')
this.scripts = this.scripts.filter(x => x !== script)
} else {
if (script.optional) {
console.log('Skip optional script: ' + script.expect)
found = true
this.scripts = this.scripts.filter(x => x !== script)
} else {
break
}
}
}

View File

@@ -94,18 +94,28 @@
tr
th String to expect
th String to be sent
th Regex
th Optional
th Actions
tr(*ngFor='let script of connection.scripts')
td
input.form-control(
type='text',
[(ngModel)]='script.expect'
)
type='text',
[(ngModel)]='script.expect'
)
td
input.form-control(
type='text',
[(ngModel)]='script.send'
)
type='text',
[(ngModel)]='script.send'
)
td
toggle(
[(ngModel)]='script.isRegex',
)
td
toggle(
[(ngModel)]='script.optional',
)
td
.input-group.flex-nowrap
button.btn.btn-outline-info.ml-0((click)='moveScriptUp(script)')
@@ -127,6 +137,14 @@
placeholder='Enter a string to be sent',
[(ngModel)]='newScript.send'
)
td
toggle(
[(ngModel)]='newScript.isRegex',
)
td
toggle(
[(ngModel)]='newScript.optional',
)
td
.input-group.flex-nowrap
button.btn.btn-outline-info.ml-0((click)='addScript()')

View File

@@ -83,5 +83,7 @@ export class EditConnectionModalComponent {
clearScript () {
this.newScript.expect = ''
this.newScript.send = ''
this.newScript.isRegex = false
this.newScript.optional = false
}
}

View File

@@ -3,7 +3,7 @@ ng-template(#content)
[style.width]='"100%"',
[style.background]='model',
)
input.form-control(type='text', '[(ngModel)]'='model', (ngModelChange)='onChange()', #input)
input.form-control(type='text', [(ngModel)]='model', (ngModelChange)='onChange()', #input)
div(
[ngbPopover]='content',

View File

@@ -70,23 +70,23 @@ h3.mb-3 Appearance
.form-group(*ngIf='editingColorScheme')
color-picker(
'[(model)]'='editingColorScheme.foreground',
[(model)]='editingColorScheme.foreground',
(modelChange)='config.save(); schemeChanged = true',
title='FG',
)
color-picker(
'[(model)]'='editingColorScheme.background',
[(model)]='editingColorScheme.background',
(modelChange)='config.save(); schemeChanged = true',
title='BG',
)
color-picker(
'[(model)]'='editingColorScheme.cursor',
[(model)]='editingColorScheme.cursor',
(modelChange)='config.save(); schemeChanged = true',
title='CU',
)
color-picker(
*ngFor='let _ of editingColorScheme.colors; let idx = index; trackBy: colorsTrackBy',
'[(model)]'='editingColorScheme.colors[idx]',
[(model)]='editingColorScheme.colors[idx]',
(modelChange)='config.save(); schemeChanged = true',
[title]='idx',
)

View File

@@ -43,6 +43,10 @@ export class XTermFrontend extends Frontend {
host.addEventListener('dragOver', (event: any) => this.dragOver.next(event))
host.addEventListener('drop', event => this.drop.next(event))
host.addEventListener('mousedown', event => this.mouseEvent.next(event))
host.addEventListener('mouseup', event => this.mouseEvent.next(event))
host.addEventListener('mousewheel', event => this.mouseEvent.next(event))
}
detach (host: HTMLElement): void {

View File

@@ -57,8 +57,13 @@ hterm.hterm.Terminal.prototype.applyCursorShape = function () {
console.warn('Unknown cursor style: ' + modeNumber)
return
}
this.setCursorShape(modes[modeNumber][0])
this.setCursorBlink(modes[modeNumber][1])
setTimeout(() => {
this.setCursorShape(modes[modeNumber][0])
this.setCursorBlink(modes[modeNumber][1])
})
setTimeout(() => {
this.setCursorVisible(true)
})
}
hterm.hterm.VT.CSI[' q'] = function (parseState) {

View File

@@ -7,7 +7,6 @@ import { WSLShellProvider } from './wsl'
import { PowerShellCoreShellProvider } from './powershellCore'
import { WindowsStockShellsProvider } from './windowsStock'
@Injectable()
export class WindowsDefaultShellProvider extends ShellProvider {
private providers: ShellProvider[]

View File

@@ -18,10 +18,24 @@ export class WSLShellProvider extends ShellProvider {
return []
}
const bashPath = `${process.env.windir}\\system32\\bash.exe`
const wslPath = `${process.env.windir}\\system32\\wsl.exe`
const wslConfigPath = `${process.env.windir}\\system32\\wslconfig.exe`
if (!await fs.exists(wslPath)) {
return []
if (await fs.exists(bashPath)) {
return [{
id: 'wsl',
name: 'WSL / Bash on Windows',
command: bashPath,
env: {
TERM: 'xterm-color',
COLORTERM: 'truecolor',
}
}]
} else {
return []
}
}
let lines = (await exec(`${wslConfigPath} /l`, { encoding: 'ucs2' }))[0].toString().split('\n').splice(1)