macOS touchbar support

This commit is contained in:
Eugene Pankov 2018-03-24 23:19:47 +01:00
parent 8f2d2cbe30
commit 5e07dd5442
11 changed files with 99 additions and 15 deletions

View File

@ -183,7 +183,6 @@ setupMenu = () => {
{ {
role: 'window', role: 'window',
submenu: [ submenu: [
{role: 'close'},
{role: 'minimize'}, {role: 'minimize'},
{role: 'zoom'}, {role: 'zoom'},
{type: 'separator'}, {type: 'separator'},

View File

@ -11,6 +11,7 @@ import { DockingService } from '../services/docking.service'
import { TabRecoveryService } from '../services/tabRecovery.service' import { TabRecoveryService } from '../services/tabRecovery.service'
import { ThemesService } from '../services/themes.service' import { ThemesService } from '../services/themes.service'
import { UpdaterService, Update } from '../services/updater.service' import { UpdaterService, Update } from '../services/updater.service'
import { TouchbarService } from '../services/touchbar.service'
import { SafeModeModalComponent } from './safeModeModal.component' import { SafeModeModalComponent } from './safeModeModal.component'
import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api' import { AppService, IToolbarButton, ToolbarButtonProvider } from '../api'
@ -62,6 +63,7 @@ export class AppRootComponent {
private tabRecovery: TabRecoveryService, private tabRecovery: TabRecoveryService,
private hotkeys: HotkeysService, private hotkeys: HotkeysService,
private updater: UpdaterService, private updater: UpdaterService,
private touchbar: TouchbarService,
public hostApp: HostAppService, public hostApp: HostAppService,
public config: ConfigService, public config: ConfigService,
public app: AppService, public app: AppService,
@ -121,6 +123,8 @@ export class AppRootComponent {
this.updater.check().then(update => { this.updater.check().then(update => {
this.appUpdate = update this.appUpdate = update
}) })
this.touchbar.update()
} }
onGlobalHotkey () { onGlobalHotkey () {

View File

@ -5,6 +5,7 @@ export abstract class BaseTabComponent {
private static lastTabID = 0 private static lastTabID = 0
id: number id: number
title: string title: string
titleChange$ = new Subject<string>()
customTitle: string customTitle: string
scrollable: boolean scrollable: boolean
hasActivity = false hasActivity = false
@ -23,6 +24,13 @@ export abstract class BaseTabComponent {
}) })
} }
setTitle (title: string) {
this.title = title]
if (!this.customTitle) {
this.titleChange$.next(title)
}
}
displayActivity (): void { displayActivity (): void {
this.hasActivity = true this.hasActivity = true
} }

View File

@ -69,6 +69,7 @@ export class TabHeaderComponent {
let modal = this.ngbModal.open(RenameTabModalComponent) let modal = this.ngbModal.open(RenameTabModalComponent)
modal.componentInstance.value = this.tab.customTitle || this.tab.title modal.componentInstance.value = this.tab.customTitle || this.tab.title
modal.result.then(result => { modal.result.then(result => {
this.tab.setTitle(result)
this.tab.customTitle = result this.tab.customTitle = result
}).catch(() => null) }).catch(() => null)
} }

View File

@ -14,6 +14,7 @@ import { HotkeysService, AppHotkeyProvider } from './services/hotkeys.service'
import { DockingService } from './services/docking.service' import { DockingService } from './services/docking.service'
import { TabRecoveryService } from './services/tabRecovery.service' import { TabRecoveryService } from './services/tabRecovery.service'
import { ThemesService } from './services/themes.service' import { ThemesService } from './services/themes.service'
import { TouchbarService } from './services/touchbar.service'
import { UpdaterService } from './services/updater.service' import { UpdaterService } from './services/updater.service'
import { AppRootComponent } from './components/appRoot.component' import { AppRootComponent } from './components/appRoot.component'
@ -44,6 +45,7 @@ const PROVIDERS = [
LogService, LogService,
TabRecoveryService, TabRecoveryService,
ThemesService, ThemesService,
TouchbarService,
UpdaterService, UpdaterService,
{ provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true }, { provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
{ provide: Theme, useClass: StandardTheme, multi: true }, { provide: Theme, useClass: StandardTheme, multi: true },

View File

@ -2,8 +2,8 @@ import { Subject, AsyncSubject } from 'rxjs'
import { Injectable, ComponentFactoryResolver, Injector, Optional } from '@angular/core' import { Injectable, ComponentFactoryResolver, Injector, Optional } from '@angular/core'
import { DefaultTabProvider } from '../api/defaultTabProvider' import { DefaultTabProvider } from '../api/defaultTabProvider'
import { BaseTabComponent } from '../components/baseTab.component' import { BaseTabComponent } from '../components/baseTab.component'
import { Logger, LogService } from '../services/log.service' import { Logger, LogService } from './log.service'
import { ConfigService } from '../services/config.service' import { ConfigService } from './config.service'
export declare type TabComponentType = new (...args: any[]) => BaseTabComponent export declare type TabComponentType = new (...args: any[]) => BaseTabComponent
@ -11,9 +11,12 @@ export declare type TabComponentType = new (...args: any[]) => BaseTabComponent
export class AppService { export class AppService {
tabs: BaseTabComponent[] = [] tabs: BaseTabComponent[] = []
activeTab: BaseTabComponent activeTab: BaseTabComponent
activeTabChange$ = new Subject<BaseTabComponent>()
lastTabIndex = 0 lastTabIndex = 0
logger: Logger logger: Logger
tabsChanged$ = new Subject<void>() tabsChanged$ = new Subject<void>()
tabOpened$ = new Subject<BaseTabComponent>()
tabClosed$ = new Subject<BaseTabComponent>()
ready$ = new AsyncSubject<void>() ready$ = new AsyncSubject<void>()
constructor ( constructor (
@ -35,6 +38,7 @@ export class AppService {
this.tabs.push(componentRef.instance) this.tabs.push(componentRef.instance)
this.selectTab(componentRef.instance) this.selectTab(componentRef.instance)
this.tabsChanged$.next() this.tabsChanged$.next()
this.tabOpened$.next(componentRef.instance)
return componentRef.instance return componentRef.instance
} }
@ -59,6 +63,7 @@ export class AppService {
this.activeTab.blurred$.next() this.activeTab.blurred$.next()
} }
this.activeTab = tab this.activeTab = tab
this.activeTabChange$.next(tab)
if (this.activeTab) { if (this.activeTab) {
this.activeTab.focused$.next() this.activeTab.focused$.next()
} }
@ -107,6 +112,7 @@ export class AppService {
this.selectTab(this.tabs[newIndex]) this.selectTab(this.tabs[newIndex])
} }
this.tabsChanged$.next() this.tabsChanged$.next()
this.tabClosed$.next(tab)
} }
emitReady () { emitReady () {

View File

@ -10,6 +10,7 @@ export class ElectronService {
globalShortcut: any globalShortcut: any
screen: any screen: any
remote: any remote: any
TouchBar: typeof Electron.TouchBar
private electron: any private electron: any
constructor () { constructor () {
@ -22,6 +23,7 @@ export class ElectronService {
this.clipboard = this.electron.clipboard this.clipboard = this.electron.clipboard
this.ipcRenderer = this.electron.ipcRenderer this.ipcRenderer = this.electron.ipcRenderer
this.globalShortcut = this.remote.globalShortcut this.globalShortcut = this.remote.globalShortcut
this.TouchBar = this.remote.TouchBar
} }
remoteRequire (name: string): any { remoteRequire (name: string): any {

View File

@ -0,0 +1,69 @@
import { Injectable, Inject, NgZone } from '@angular/core'
import { Subject, Subscription } from 'rxjs'
import { AppService } from './app.service'
import { ConfigService } from './config.service'
import { ElectronService } from './electron.service'
import { BaseTabComponent } from '../components/baseTab.component'
import { IToolbarButton, ToolbarButtonProvider } from '../api'
@Injectable()
export class TouchbarService {
tabSelected$ = new Subject<number>()
private titleSubscriptions = new Map<BaseTabComponent, Subscription>()
private tabsSegmentedControl: Electron.TouchBarSegmentedControl
private tabSegments: Electron.SegmentedControlSegment[] = []
constructor (
private app: AppService,
@Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[],
private config: ConfigService,
private electron: ElectronService,
private zone: NgZone,
) {
app.tabsChanged$.subscribe(() => this.update())
app.activeTabChange$.subscribe(() => this.update())
app.tabOpened$.subscribe(tab => {
let sub = tab.titleChange$.subscribe(title => {
this.tabSegments[app.tabs.indexOf(tab)].label = title
this.tabsSegmentedControl.segments = this.tabSegments
})
this.titleSubscriptions.set(tab, sub)
})
app.tabClosed$.subscribe(tab => {
this.titleSubscriptions.get(tab).unsubscribe()
this.titleSubscriptions.delete(tab)
})
}
update () {
let buttons: IToolbarButton[] = []
this.config.enabledServices(this.toolbarButtonProviders).forEach(provider => {
buttons = buttons.concat(provider.provide())
})
buttons.sort((a, b) => (a.weight || 0) - (b.weight || 0))
this.tabSegments = this.app.tabs.map(tab => ({
label: tab.title,
}))
this.tabsSegmentedControl = new this.electron.TouchBar.TouchBarSegmentedControl({
segments: this.tabSegments,
selectedIndex: this.app.tabs.indexOf(this.app.activeTab),
change: (selectedIndex) => this.zone.run(() => {
this.app.selectTab(this.app.tabs[selectedIndex])
})
})
let touchBar = new this.electron.TouchBar({
items: [
this.tabsSegmentedControl,
new this.electron.TouchBar.TouchBarSpacer({size: 'flexible'}),
new this.electron.TouchBar.TouchBarSpacer({size: 'small'}),
...buttons.map(button => new this.electron.TouchBar.TouchBarButton({
label: button.title,
// backgroundColor: '#0022cc',
click: () => this.zone.run(() => button.click()),
}))
]
})
this.electron.app.window.setTouchBar(touchBar)
}
}

View File

@ -28,7 +28,7 @@ export class SettingsTabComponent extends BaseTabComponent {
) { ) {
super() super()
this.hotkeyDescriptions = config.enabledServices(hotkeyProviders).map(x => x.hotkeys).reduce((a, b) => a.concat(b)) this.hotkeyDescriptions = config.enabledServices(hotkeyProviders).map(x => x.hotkeys).reduce((a, b) => a.concat(b))
this.title = 'Settings' this.setTitle('Settings')
this.scrollable = true this.scrollable = true
this.screens = this.docking.getScreens() this.screens = this.docking.getScreens()
this.settingsProviders = config.enabledServices(this.settingsProviders) this.settingsProviders = config.enabledServices(this.settingsProviders)

View File

@ -18,17 +18,14 @@ export class ButtonProvider extends ToolbarButtonProvider {
} }
activate () { activate () {
let modal = this.ngbModal.open(SSHModalComponent) this.ngbModal.open(SSHModalComponent)
modal.result.then(() => {
//this.terminal.openTab(shell)
})
} }
provide (): IToolbarButton[] { provide (): IToolbarButton[] {
return [{ return [{
icon: 'globe', icon: 'globe',
weight: 5, weight: 5,
title: 'SSH connections', title: 'SSH',
click: async () => { click: async () => {
this.activate() this.activate()
} }

View File

@ -58,7 +58,7 @@ export class TerminalTabComponent extends BaseTabComponent {
) { ) {
super() super()
this.decorators = this.decorators || [] this.decorators = this.decorators || []
this.title = 'Terminal' this.setTitle('Terminal')
this.resize$.first().subscribe(async (resizeEvent) => { this.resize$.first().subscribe(async (resizeEvent) => {
if (!this.session) { if (!this.session) {
this.session = this.sessions.addSession( this.session = this.sessions.addSession(
@ -211,11 +211,7 @@ export class TerminalTabComponent extends BaseTabComponent {
} }
attachHTermHandlers (hterm: any) { attachHTermHandlers (hterm: any) {
hterm.setWindowTitle = (title) => { hterm.setWindowTitle = title => this.zone.run(() => this.setTitle(title))
this.zone.run(() => {
this.title = title
})
}
const _setAlternateMode = hterm.setAlternateMode.bind(hterm) const _setAlternateMode = hterm.setAlternateMode.bind(hterm)
hterm.setAlternateMode = (state) => { hterm.setAlternateMode = (state) => {