diff --git a/app/lib/app.ts b/app/lib/app.ts index 27061917..dea1f1a3 100644 --- a/app/lib/app.ts +++ b/app/lib/app.ts @@ -6,7 +6,7 @@ import * as path from 'path' import * as fs from 'fs' import { Subject, throttleTime } from 'rxjs' -import { loadConfig } from './config' +import { saveConfig } from './config' import { Window, WindowOptions } from './window' import { pluginManager } from './pluginManager' import { PTYManager } from './pty' @@ -23,10 +23,10 @@ export class Application { private windows: Window[] = [] private globalHotkey$ = new Subject() private quitRequested = false - private configStore: any userPluginsPath: string - constructor () { + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + constructor (private configStore: any) { remote.initialize() this.useBuiltinGraphics() this.ptyManager.init(this) @@ -36,6 +36,10 @@ export class Application { this.configStore = config }) + ipcMain.on('app:save-config', (_event, data) => { + saveConfig(data) + }) + ipcMain.on('app:register-global-hotkey', (_event, specs) => { globalShortcut.unregisterAll() for (const spec of specs) { @@ -63,7 +67,6 @@ export class Application { } }) - this.configStore = loadConfig() if (process.platform === 'linux') { app.commandLine.appendSwitch('no-sandbox') if (((this.configStore.appearance || {}).opacity || 1) !== 1) { @@ -111,7 +114,7 @@ export class Application { } async newWindow (options?: WindowOptions): Promise { - const window = new Window(this, options) + const window = new Window(this, this.configStore, options) this.windows.push(window) if (this.windows.length === 1){ window.makeMain() diff --git a/app/lib/config.ts b/app/lib/config.ts index 3cf951ab..cd183986 100644 --- a/app/lib/config.ts +++ b/app/lib/config.ts @@ -1,26 +1,47 @@ -import * as fs from 'fs' +import * as fs from 'mz/fs' import * as path from 'path' import * as yaml from 'js-yaml' +import { v4 as uuidv4 } from 'uuid' +import * as gracefulFS from 'graceful-fs' import { app } from 'electron' +import { promisify } from 'util' -export function migrateConfig (): void { +export async function migrateConfig (): Promise { const configPath = path.join(app.getPath('userData'), 'config.yaml') const legacyConfigPath = path.join(app.getPath('userData'), '../terminus', 'config.yaml') - if (fs.existsSync(legacyConfigPath) && ( - !fs.existsSync(configPath) || - fs.statSync(configPath).mtime < fs.statSync(legacyConfigPath).mtime + if (await fs.exists(legacyConfigPath) && ( + !await fs.exists(configPath) || + (await fs.stat(configPath)).mtime < (await fs.stat(legacyConfigPath)).mtime )) { - fs.writeFileSync(configPath, fs.readFileSync(legacyConfigPath)) + await fs.writeFile(configPath, await fs.readFile(legacyConfigPath)) } } -export function loadConfig (): any { - migrateConfig() +export async function loadConfig (): Promise { + await migrateConfig() const configPath = path.join(app.getPath('userData'), 'config.yaml') - if (fs.existsSync(configPath)) { - return yaml.load(fs.readFileSync(configPath, 'utf8')) + if (await fs.exists(configPath)) { + return yaml.load(await fs.readFile(configPath, 'utf8')) } else { return {} } } + +const configPath = path.join(app.getPath('userData'), 'config.yaml') +let _configSaveInProgress = Promise.resolve() + +async function _saveConfigInternal (content: string): Promise { + const tempPath = configPath + '.new.' + uuidv4().toString() + await fs.writeFile(tempPath, content, 'utf8') + await fs.writeFile(configPath + '.backup', content, 'utf8') + await promisify(gracefulFS.rename)(tempPath, configPath) +} + +export async function saveConfig (content: string): Promise { + try { + await _configSaveInProgress + } catch { } + _configSaveInProgress = _saveConfigInternal(content) + await _configSaveInProgress +} diff --git a/app/lib/index.ts b/app/lib/index.ts index 654e52ed..092f32a4 100644 --- a/app/lib/index.ts +++ b/app/lib/index.ts @@ -3,40 +3,63 @@ import './portable' import 'source-map-support/register' import './sentry' import './lru' -import { app, ipcMain, Menu } from 'electron' +import { app, ipcMain, Menu, dialog } from 'electron' import { parseArgs } from './cli' import { Application } from './app' import electronDebug = require('electron-debug') +import { loadConfig } from './config' if (!process.env.TABBY_PLUGINS) { process.env.TABBY_PLUGINS = '' } -const application = new Application() - -ipcMain.on('app:new-window', () => { - application.newWindow() -}) - -app.on('activate', () => { - if (!application.hasWindows()) { - application.newWindow() - } else { - application.focus() - } -}) - -process.on('uncaughtException' as any, err => { - console.log(err) - application.broadcast('uncaughtException', err) -}) - -app.on('second-instance', (_event, argv, cwd) => { - application.handleSecondInstance(argv, cwd) -}) - const argv = parseArgs(process.argv, process.cwd()) +loadConfig().then(configStore => { + const application = new Application(configStore) + + ipcMain.on('app:new-window', () => { + application.newWindow() + }) + + app.on('activate', () => { + if (!application.hasWindows()) { + application.newWindow() + } else { + application.focus() + } + }) + + process.on('uncaughtException' as any, err => { + console.log(err) + application.broadcast('uncaughtException', err) + }) + + app.on('second-instance', (_event, newArgv, cwd) => { + application.handleSecondInstance(newArgv, cwd) + }) + + app.on('ready', async () => { + if (process.platform === 'darwin') { + app.dock.setMenu(Menu.buildFromTemplate([ + { + label: 'New window', + click () { + this.app.newWindow() + }, + }, + ])) + } + application.init() + + const window = await application.newWindow({ hidden: argv.hidden }) + await window.ready + window.passCliArguments(process.argv, process.cwd(), false) + }) +}).catch(err => { + dialog.showErrorBox('Could not read config', err.message) +}) + if (!app.requestSingleInstanceLock()) { app.quit() app.exit(0) @@ -49,21 +72,3 @@ if (argv.d) { devToolsMode: 'undocked', }) } - -app.on('ready', async () => { - if (process.platform === 'darwin') { - app.dock.setMenu(Menu.buildFromTemplate([ - { - label: 'New window', - click () { - this.app.newWindow() - }, - }, - ])) - } - application.init() - - const window = await application.newWindow({ hidden: argv.hidden }) - await window.ready - window.passCliArguments(process.argv, process.cwd(), false) -}) diff --git a/app/lib/window.ts b/app/lib/window.ts index c49f1c5b..01843b88 100644 --- a/app/lib/window.ts +++ b/app/lib/window.ts @@ -11,7 +11,6 @@ import { compare as compareVersions } from 'compare-versions' import type { Application } from './app' import { parseArgs } from './cli' -import { loadConfig } from './config' let DwmEnableBlurBehindWindow: any = null if (process.platform === 'win32') { @@ -42,7 +41,6 @@ export class Window { private closing = false private lastVibrancy: { enabled: boolean, type?: string } | null = null private disableVibrancyWhileDragging = false - private configStore: any private touchBarControl: any private isFluentVibrancy = false private dockHidden = false @@ -50,9 +48,8 @@ export class Window { get visible$ (): Observable { return this.visible } get closed$ (): Observable { return this.closed } - constructor (private application: Application, options?: WindowOptions) { - this.configStore = loadConfig() - + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + constructor (private application: Application, private configStore: any, options?: WindowOptions) { options = options ?? {} this.windowConfig = new ElectronConfig({ name: 'window' }) diff --git a/tabby-electron/src/services/hostApp.service.ts b/tabby-electron/src/services/hostApp.service.ts index 0f57fb74..15f30397 100644 --- a/tabby-electron/src/services/hostApp.service.ts +++ b/tabby-electron/src/services/hostApp.service.ts @@ -65,6 +65,10 @@ export class ElectronHostAppService extends HostAppService { this.electron.ipcRenderer.send('app:config-change', configStore) } + saveConfig (data: string): void { + this.electron.ipcRenderer.send('app:save-config', data) + } + emitReady (): void { this.electron.ipcRenderer.send('app:ready') } diff --git a/tabby-electron/src/services/platform.service.ts b/tabby-electron/src/services/platform.service.ts index 694ff871..8a79a1ac 100644 --- a/tabby-electron/src/services/platform.service.ts +++ b/tabby-electron/src/services/platform.service.ts @@ -1,17 +1,15 @@ import * as path from 'path' import * as fs from 'fs/promises' -import * as gracefulFS from 'graceful-fs' import * as fsSync from 'fs' import * as os from 'os' -import { v4 as uuidv4 } from 'uuid' -import { promisify } from 'util' import promiseIpc, { RendererProcessType } from 'electron-promise-ipc' import { execFile } from 'mz/child_process' import { Injectable, NgZone } from '@angular/core' -import { PlatformService, ClipboardContent, HostAppService, Platform, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileDownload, FileUploadOptions, wrapPromise, TranslateService } from 'tabby-core' +import { PlatformService, ClipboardContent, Platform, MenuItemOptions, MessageBoxOptions, MessageBoxResult, FileUpload, FileDownload, FileUploadOptions, wrapPromise, TranslateService } from 'tabby-core' import { ElectronService } from '../services/electron.service' import { ElectronHostWindow } from './hostWindow.service' import { ShellIntegrationService } from './shellIntegration.service' +import { ElectronHostAppService } from './hostApp.service' const fontManager = require('fontmanager-redux') // eslint-disable-line /* eslint-disable block-scoped-var */ @@ -27,10 +25,9 @@ try { export class ElectronPlatformService extends PlatformService { supportsWindowControls = true private configPath: string - private _configSaveInProgress = Promise.resolve() constructor ( - private hostApp: HostAppService, + private hostApp: ElectronHostAppService, private hostWindow: ElectronHostWindow, private electron: ElectronService, private zone: NgZone, @@ -112,18 +109,7 @@ export class ElectronPlatformService extends PlatformService { } async saveConfig (content: string): Promise { - try { - await this._configSaveInProgress - } catch { } - this._configSaveInProgress = this._saveConfigInternal(content) - await this._configSaveInProgress - } - - async _saveConfigInternal (content: string): Promise { - const tempPath = this.configPath + '.new.' + uuidv4().toString() - await fs.writeFile(tempPath, content, 'utf8') - await fs.writeFile(this.configPath + '.backup', content, 'utf8') - await promisify(gracefulFS.rename)(tempPath, this.configPath) + this.hostApp.saveConfig(content) } getConfigPath (): string|null {