This commit is contained in:
Eugene Pankov
2017-03-20 17:46:25 +01:00
parent 2c2da1d697
commit f659a45532
19 changed files with 174 additions and 79 deletions

View File

@@ -16,7 +16,6 @@ import { PluginDispatcherService } from 'services/pluginDispatcher'
import { QuitterService } from 'services/quitter'
import { SessionsService } from 'services/sessions'
import { DockingService } from 'services/docking'
import { LocalStorageService } from 'angular2-localstorage/LocalStorageEmitter'
import { AppComponent } from 'components/app'
import { CheckboxComponent } from 'components/checkbox'
@@ -48,7 +47,6 @@ import { TerminalComponent } from 'components/terminal'
PluginDispatcherService,
QuitterService,
SessionsService,
LocalStorageService,
],
entryComponents: [
HotkeyInputModalComponent,

View File

@@ -185,6 +185,7 @@
&.active {
background: @title-bg;
box-shadow: 0px -1px 0px 0px blue;
.content-wrapper {
//border-bottom: 2px solid #69bbea;

View File

@@ -1,8 +1,8 @@
.titlebar(*ngIf='!config.store.appearance.useNativeFrame && config.store.appearance.dock == "off"')
.title((dblclick)='hostApp.maximizeWindow()') Term
button.btn.btn-secondary.btn-minimize((click)='hostApp.minimizeWindow()')
.title((dblclick)='hostApp.toggleMaximize()') Term
button.btn.btn-secondary.btn-minimize((click)='hostApp.minimize()')
i.fa.fa-window-minimize
button.btn.btn-secondary.btn-maximize((click)='hostApp.maximizeWindow()')
button.btn.btn-secondary.btn-maximize((click)='hostApp.toggleMaximize()')
i.fa.fa-window-maximize
button.btn.btn-secondary.btn-close((click)='hostApp.quit()')
i.fa.fa-close

View File

@@ -66,11 +66,11 @@ export class AppComponent {
private elementRef: ElementRef,
private sessions: SessionsService,
private docking: DockingService,
private electron: ElectronService,
public hostApp: HostAppService,
public hotkeys: HotkeysService,
public config: ConfigService,
log: LogService,
electron: ElectronService,
_quitter: QuitterService,
) {
console.timeStamp('AppComponent ctor')
@@ -135,10 +135,33 @@ export class AppComponent {
this.hostApp.shown.subscribe(() => {
this.docking.dock()
})
this.hostApp.secondInstance.subscribe(() => {
if (this.electron.app.window.isFocused()) {
// focused
this.electron.app.window.hide()
} else {
if (!this.electron.app.window.isVisible()) {
// unfocused, invisible
this.electron.app.window.show()
} else {
if (this.config.full().appearance.dock == 'off') {
// not docked, visible
setTimeout(() => {
this.electron.app.window.focus()
})
} else {
// docked, visible
this.electron.app.window.hide()
}
}
}
this.docking.dock()
})
}
newTab () {
this.addTerminalTab(this.sessions.createNewSession({command: 'bash'}))
this.addTerminalTab(this.sessions.createNewSession({shell: 'zsh'}))
}
addTerminalTab (session) {

View File

@@ -31,7 +31,7 @@ ngb-tabset(type='tabs')
label Dock the terminal
br
.row
.col-auto
.col.col-auto
div(
'[(ngModel)]'='config.store.appearance.dock'
'(ngModelChange)'='config.save(); docking.dock()'
@@ -71,10 +71,10 @@ ngb-tabset(type='tabs')
input(
type='range',
'[(ngModel)]'='config.store.appearance.dockFill',
'(ngModelChange)'='config.save(); docking.dock()',
min='1',
max='100',
step='1'
'(mouseup)'='config.save(); docking.dock()',
min='0.05',
max='1',
step='0.01'
)
br
div(

View File

@@ -1,5 +0,0 @@
:host {
position: relative;
display: block;
overflow: hidden;
}

View File

@@ -0,0 +1,10 @@
:host {
position: relative;
display: block;
overflow: hidden;
div[style]:last-child {
background: black !important;
color: white !important;
}
}

View File

@@ -25,19 +25,10 @@ hterm.hterm.VT.ESC['k'] = function(parseState) {
hterm.hterm.defaultStorage = new hterm.lib.Storage.Memory()
const preferenceManager = new hterm.hterm.PreferenceManager('default')
preferenceManager.set('user-css', dataurl.convert({
data: `
a {
cursor: pointer;
}
a:hover {
text-decoration: underline;
}
`,
data: require('./terminal.userCSS.scss'),
mimetype: 'text/css',
charset: 'utf8',
}))
preferenceManager.set('font-size', 12)
preferenceManager.set('background-color', '#1D272D')
preferenceManager.set('color-palette-overrides', {
0: '#1D272D',
@@ -49,11 +40,12 @@ hterm.hterm.ScrollPort.prototype.decorate = function (...args) {
this.screen_.style.cssText += `; padding-right: ${this.screen_.offsetWidth - this.screen_.clientWidth}px;`
}
hterm.hterm.Terminal.prototype.showOverlay = () => null
@Component({
selector: 'terminal',
template: '',
styles: [require('./terminal.less')],
styles: [require('./terminal.scss')],
})
export class TerminalComponent {
@Input() session: Session
@@ -115,6 +107,7 @@ export class TerminalComponent {
preferenceManager.set('font-size', config.appearance.fontSize)
preferenceManager.set('audible-bell-sound', '')
preferenceManager.set('desktop-notification-bell', config.terminal.bell == 'notification')
preferenceManager.set('enable-clipboard-notice', false)
}
ngOnDestroy () {

View File

@@ -0,0 +1,11 @@
a {
cursor: pointer;
}
a:hover {
text-decoration: underline;
}
* {
font-feature-settings: "liga" 0; // disable ligatures (they break monospacing)
}

View File

@@ -14,6 +14,7 @@ export interface IAppearanceData {
fontSize: number
dock: string
dockScreen: string
dockFill: number
}
export interface ITerminalData {

View File

@@ -26,18 +26,19 @@ export class DockingService {
let dockSide = this.config.full().appearance.dock
let newBounds: Electron.Rectangle = { x: 0, y: 0, width: 0, height: 0 }
let fill = 0.5
let fill = this.config.full().appearance.dockFill
if (dockSide == 'off') {
this.hostApp.setAlwaysOnTop(false)
return
}
if (dockSide == 'left' || dockSide == 'right') {
newBounds.width = fill * display.bounds.width
newBounds.width = Math.round(fill * display.bounds.width)
newBounds.height = display.bounds.height
}
if (dockSide == 'top' || dockSide == 'bottom') {
newBounds.width = display.bounds.width
newBounds.height = fill * display.bounds.height
newBounds.height = Math.round(fill * display.bounds.height)
}
if (dockSide == 'right') {
newBounds.x = display.bounds.x + display.bounds.width * (1.0 - fill)
@@ -50,6 +51,8 @@ export class DockingService {
newBounds.y = display.bounds.y
}
this.hostApp.setAlwaysOnTop(true)
this.hostApp.unmaximize()
this.hostApp.setBounds(newBounds)
}

View File

@@ -23,10 +23,14 @@ export class HostAppService {
console.error('Unhandled exception:', err)
})
electron.ipcRenderer.on('window-shown', () => {
electron.ipcRenderer.on('host:window-shown', () => {
this.shown.emit()
})
electron.ipcRenderer.on('host:second-instance', () => {
this.secondInstance.emit()
})
this.ready.subscribe(() => {
electron.ipcRenderer.send('app:ready')
})
@@ -36,6 +40,7 @@ export class HostAppService {
quitRequested = new EventEmitter<any>()
ready = new EventEmitter<any>()
shown = new EventEmitter<any>()
secondInstance = new EventEmitter<any>()
private logger: Logger;
@@ -59,8 +64,8 @@ export class HostAppService {
this.electron.app.webContents.openDevTools()
}
setWindowCloseable(flag: boolean) {
this.electron.ipcRenderer.send('window-closeable', flag)
setCloseable(flag: boolean) {
this.electron.ipcRenderer.send('window-set-closeable', flag)
}
focusWindow() {
@@ -71,18 +76,30 @@ export class HostAppService {
this.electron.ipcRenderer.send('window-toggle-focus')
}
minimizeWindow () {
minimize () {
this.electron.ipcRenderer.send('window-minimize')
}
maximizeWindow () {
maximize () {
this.electron.ipcRenderer.send('window-maximize')
}
unmaximize () {
this.electron.ipcRenderer.send('window-unmaximize')
}
toggleMaximize () {
this.electron.ipcRenderer.send('window-toggle-maximize')
}
setBounds (bounds: Electron.Rectangle) {
this.electron.ipcRenderer.send('window-set-bounds', bounds)
}
setAlwaysOnTop (flag: boolean) {
this.electron.ipcRenderer.send('window-set-always-on-top', flag)
}
quit () {
this.logger.info('Quitting')
this.electron.app.quit()

View File

@@ -13,7 +13,7 @@ export class QuitterService {
}
quit() {
this.hostApp.setWindowCloseable(true)
this.hostApp.setCloseable(true)
this.hostApp.quit()
}
}

View File

@@ -2,7 +2,8 @@ import { Injectable, NgZone, EventEmitter } from '@angular/core'
import { Logger, LogService } from 'services/log'
const exec = require('child-process-promise').exec
import * as crypto from 'crypto'
import * as ptyjs from 'pty.js'
import * as nodePTY from 'node-pty'
import * as fs from 'fs'
export interface SessionRecoveryProvider {
@@ -42,16 +43,26 @@ export class ScreenSessionRecoveryProvider implements SessionRecoveryProvider {
getNewSessionCommand(command: string): string {
const id = crypto.randomBytes(8).toString('hex')
return `screen -U -S term-tab-${id} -- ${command}`
// TODO
let configPath = '/tmp/.termScreenConfig'
fs.writeFileSync(configPath, `
escape ^^^
vbell off
term xterm-color
bindkey "^[OH" beginning-of-line
bindkey "^[OF" end-of-line
`, 'utf-8')
return `screen -c ${configPath} -U -S term-tab-${id} -- ${command}`
}
}
export interface SessionOptions {
name?: string,
command: string,
command?: string,
shell?: string,
cwd?: string,
env?: string,
env?: any,
}
export class Session {
@@ -67,14 +78,22 @@ export class Session {
constructor (options: SessionOptions) {
this.name = options.name
console.log('Spawning', options.command)
this.pty = ptyjs.spawn('sh', ['-c', options.command], {
name: 'screen-256color',
//name: 'xterm-256color',
let binary = options.shell || 'sh'
let args = options.shell ? [] : ['-c', options.command]
let env = {
...process.env,
...options.env,
TERM: 'xterm-256color',
}
this.pty = nodePTY.spawn(binary, args, {
//name: 'screen-256color',
name: 'xterm-256color',
//name: 'xterm-color',
cols: 80,
rows: 30,
cwd: options.cwd || process.env.HOME,
env: options.env || process.env,
env: env,
})
this.open = true
@@ -156,8 +175,8 @@ export class SessionsService {
log: LogService,
) {
this.logger = log.create('sessions')
//this.recoveryProvider = new ScreenSessionRecoveryProvider()
this.recoveryProvider = new NullSessionRecoveryProvider()
this.recoveryProvider = new ScreenSessionRecoveryProvider()
//this.recoveryProvider = new NullSessionRecoveryProvider()
}
createNewSession (options: SessionOptions) : Session {