diff --git a/main/account_switcher.ts b/main/account_switcher.ts index c09213d..a4ac1aa 100644 --- a/main/account_switcher.ts +++ b/main/account_switcher.ts @@ -1,5 +1,5 @@ import {EventEmitter} from 'events'; -import {Menu, MenuItem, session} from 'electron'; +import {Menu, MenuItem} from 'electron'; import log from './log'; import {Account} from './config'; diff --git a/main/app.ts b/main/app.ts index 2acd6be..10dcd97 100644 --- a/main/app.ts +++ b/main/app.ts @@ -1,21 +1,18 @@ import * as path from 'path'; -import {app, BrowserWindow, globalShortcut, Tray, shell, dialog, Menu} from 'electron'; -import windowState = require('electron-window-state'); -import * as menubar from 'menubar'; +import {app, Menu} from 'electron'; import log from './log'; import {Config} from './config'; -import AccountSwitcher, {partitionForAccount} from './account_switcher'; +import AccountSwitcher from './account_switcher'; import defaultMenu from './default_menu'; +import Window from './window'; -const IS_DEBUG = process.env.NODE_ENV === 'development'; const IS_DARWIN = process.platform === 'darwin'; const APP_ICON = path.join(__dirname, '..', 'resources', 'icon', 'icon.png'); -const PRELOAD_JS = path.join(__dirname, '..', 'renderer', 'preload.js'); export class App { private switcher: AccountSwitcher; - constructor(private win: Electron.BrowserWindow, private config: Config) { + constructor(private win: Window, private config: Config) { if (config.accounts.length === 0) { throw new Error('No account found. Please check the config.'); } @@ -23,191 +20,17 @@ export class App { app.dock.setIcon(APP_ICON); } Menu.setApplicationMenu(defaultMenu()); - this.switcher = new AccountSwitcher(win, this.config.accounts); + this.switcher = new AccountSwitcher(win.browser, this.config.accounts); this.switcher.on('did-switch', () => this.open()); - - if (!IS_DARWIN) { - // Users can still access menu bar with pressing Alt key. - this.win.setMenu(Menu.getApplicationMenu()); - } - - this.win.webContents.on('will-navigate', (e, url) => { - if (!url.startsWith(`https://${this.switcher.current.host}`)) { - e.preventDefault(); - shell.openExternal(url); - } - }); - this.win.webContents.on('new-window', (e, url) => { - e.preventDefault(); - shell.openExternal(url); - }); } open() { - this.win.loadURL(`https://${this.switcher.current.host}${this.switcher.current.default_page}`); - - this.win.webContents.session.setPermissionRequestHandler((contents, permission, callback) => { - if (permission !== 'geolocation' && permission !== 'media') { - // Granted - callback(true); - return; - } - - dialog.showMessageBox({ - type: 'question', - buttons: ['Accept', 'Reject'], - message: `Permission '${permission}' is requested by ${contents.getURL()}`, - detail: "Please choose one of 'Accept' or 'Reject'", - }, (buttonIndex: number) => { - const granted = buttonIndex === 0; - callback(granted); - }); - }); + const url = `https://${this.switcher.current.host}${this.switcher.current.default_page}`; + this.win.open(url); + log.debug('Open URL: ', url); } } -function trayIcon(color: string) { - return path.join(__dirname, '..', 'resources', 'icon', `tray-icon-${ - color === 'white' ? 'white' : 'black' - }@2x.png`); -} - export default function startApp(config: Config) { - return (config.normal_window ? startNormalWindow : startMenuBar)(config) - .then(win => new App(win, config)); -} - -function startNormalWindow(config: Config): Promise { - log.debug('Setup a normal window'); - return new Promise(resolve => { - const state = windowState({ - defaultWidth: 600, - defaultHeight: 800, - }); - const win = new BrowserWindow({ - width: state.width, - height: state.height, - x: state.x, - y: state.y, - icon: APP_ICON, - show: false, - useContentSize: true, - autoHideMenuBar: true, - webPreferences: { - nodeIntegration: false, - sandbox: true, - preload: PRELOAD_JS, - partition: partitionForAccount(config.accounts[0]), - }, - }); - win.once('ready-to-show', () => { - win.show(); - }); - win.once('closed', () => { - app.quit(); - }); - - if (state.isFullScreen) { - win.setFullScreen(true); - } else if (state.isMaximized) { - win.maximize(); - } - state.manage(win); - - const toggleWindow = () => { - if (win.isFocused()) { - log.debug('Toggle window: shown -> hidden'); - if (IS_DARWIN) { - app.hide(); - } else { - win.hide(); - } - } else { - log.debug('Toggle window: hidden -> shown'); - win.show(); - } - }; - - win.webContents.on('dom-ready', () => { - log.debug('Send config to renderer procress'); - win.webContents.send('mstdn:config', config); - }); - win.webContents.once('dom-ready', () => { - log.debug('Normal window application was launched'); - if (config.hot_key) { - globalShortcut.register(config.hot_key, toggleWindow); - log.debug('Hot key was set to:', config.hot_key); - } - if (IS_DEBUG) { - win.webContents.openDevTools({mode: 'detach'}); - } - }); - - const normalIcon = trayIcon(config.icon_color); - const tray = new Tray(normalIcon); - tray.on('click', toggleWindow); - tray.on('double-click', toggleWindow); - if (IS_DARWIN) { - tray.setHighlightMode('never'); - } - - resolve(win); - }); -} - -function startMenuBar(config: Config): Promise { - log.debug('Setup a menubar window'); - return new Promise(resolve => { - const state = windowState({ - defaultWidth: 350, - defaultHeight: 420, - }); - const icon = trayIcon(config.icon_color); - const mb = menubar({ - icon, - width: state.width, - height: state.height, - alwaysOnTop: IS_DEBUG || !!config.always_on_top, - tooltip: 'Mstdn', - useContentSize: true, - autoHideMenuBar: true, - show: false, - showDockIcon: true, - webPreferences: { - nodeIntegration: false, - sandbox: true, - preload: PRELOAD_JS, - partition: partitionForAccount(config.accounts[0]), - }, - }); - mb.once('ready', () => mb.showWindow()); - mb.once('after-create-window', () => { - log.debug('Menubar application was launched'); - if (config.hot_key) { - globalShortcut.register(config.hot_key, () => { - if (mb.window.isFocused()) { - log.debug('Toggle window: shown -> hidden'); - mb.hideWindow(); - } else { - log.debug('Toggle window: hidden -> shown'); - mb.showWindow(); - } - }); - log.debug('Hot key was set to:', config.hot_key); - } - if (IS_DEBUG) { - mb.window.webContents.openDevTools({mode: 'detach'}); - } - mb.window.webContents.on('dom-ready', () => { - log.debug('Send config to renderer procress'); - mb.window.webContents.send('mstdn:config', config); - }); - state.manage(mb.window); - - resolve(mb.window); - }); - mb.once('after-close', () => { - app.quit(); - }); - }); + return Window.create(config).then(win => new App(win, config)); } diff --git a/main/window.ts b/main/window.ts new file mode 100644 index 0000000..e1eb65c --- /dev/null +++ b/main/window.ts @@ -0,0 +1,214 @@ +import * as path from 'path'; +import {app, BrowserWindow, globalShortcut, Tray, shell, dialog, Menu} from 'electron'; +import windowState = require('electron-window-state'); +import * as menubar from 'menubar'; +import {Config, Account} from './config'; +import {partitionForAccount} from './account_switcher'; +import log from './log'; + +const IS_DEBUG = process.env.NODE_ENV === 'development'; +const IS_DARWIN = process.platform === 'darwin'; +const APP_ICON = path.join(__dirname, '..', 'resources', 'icon', 'icon.png'); +const PRELOAD_JS = path.join(__dirname, '..', 'renderer', 'preload.js'); + +export default class Window { + static create(config: Config) { + return (config.normal_window ? startNormalWindow : startMenuBar)(config); + } + + constructor( + public browser: Electron.BrowserWindow, + public account: Account, + public menubar: Menubar.MenubarApp | null, + ) { + if (!IS_DARWIN) { + // Users can still access menu bar with pressing Alt key. + browser.setMenu(Menu.getApplicationMenu()); + } + + browser.webContents.on('will-navigate', (e, url) => { + if (!url.startsWith(`https://${this.account.host}`)) { + e.preventDefault(); + shell.openExternal(url); + } + }); + browser.webContents.on('new-window', (e, url) => { + e.preventDefault(); + shell.openExternal(url); + }); + + browser.webContents.session.setPermissionRequestHandler((contents, permission, callback) => { + if (permission !== 'geolocation' && permission !== 'media') { + // Granted + callback(true); + return; + } + + dialog.showMessageBox({ + type: 'question', + buttons: ['Accept', 'Reject'], + message: `Permission '${permission}' is requested by ${contents.getURL()}`, + detail: "Please choose one of 'Accept' or 'Reject'", + }, (buttonIndex: number) => { + const granted = buttonIndex === 0; + callback(granted); + }); + }); + } + + open(url: string) { + this.browser.loadURL(url); + } + + isMenubar() { + return this.browser !== null; + } + + close() { + log.error('TODO: close window'); + } +} + +function trayIcon(color: string) { + return path.join(__dirname, '..', 'resources', 'icon', `tray-icon-${ + color === 'white' ? 'white' : 'black' + }@2x.png`); +} + +function startNormalWindow(config: Config): Promise { + log.debug('Setup a normal window'); + return new Promise(resolve => { + const state = windowState({ + defaultWidth: 600, + defaultHeight: 800, + }); + const account = config.accounts[0]; + const win = new BrowserWindow({ + width: state.width, + height: state.height, + x: state.x, + y: state.y, + icon: APP_ICON, + show: false, + useContentSize: true, + autoHideMenuBar: true, + webPreferences: { + nodeIntegration: false, + sandbox: true, + preload: PRELOAD_JS, + partition: partitionForAccount(account), + }, + }); + win.once('ready-to-show', () => { + win.show(); + }); + win.once('closed', () => { + app.quit(); + }); + + if (state.isFullScreen) { + win.setFullScreen(true); + } else if (state.isMaximized) { + win.maximize(); + } + state.manage(win); + + const toggleWindow = () => { + if (win.isFocused()) { + log.debug('Toggle window: shown -> hidden'); + if (IS_DARWIN) { + app.hide(); + } else { + win.hide(); + } + } else { + log.debug('Toggle window: hidden -> shown'); + win.show(); + } + }; + + win.webContents.on('dom-ready', () => { + log.debug('Send config to renderer procress'); + win.webContents.send('mstdn:config', config); + }); + win.webContents.once('dom-ready', () => { + log.debug('Normal window application was launched'); + if (config.hot_key) { + globalShortcut.register(config.hot_key, toggleWindow); + log.debug('Hot key was set to:', config.hot_key); + } + if (IS_DEBUG) { + win.webContents.openDevTools({mode: 'detach'}); + } + }); + + const normalIcon = trayIcon(config.icon_color); + const tray = new Tray(normalIcon); + tray.on('click', toggleWindow); + tray.on('double-click', toggleWindow); + if (IS_DARWIN) { + tray.setHighlightMode('never'); + } + + resolve(new Window(win, account, null)); + }); +} + +function startMenuBar(config: Config): Promise { + log.debug('Setup a menubar window'); + return new Promise(resolve => { + const state = windowState({ + defaultWidth: 350, + defaultHeight: 420, + }); + const account = config.accounts[0]; + const icon = trayIcon(config.icon_color); + const mb = menubar({ + icon, + width: state.width, + height: state.height, + alwaysOnTop: IS_DEBUG || !!config.always_on_top, + tooltip: 'Mstdn', + useContentSize: true, + autoHideMenuBar: true, + show: false, + showDockIcon: true, + webPreferences: { + nodeIntegration: false, + sandbox: true, + preload: PRELOAD_JS, + partition: partitionForAccount(account), + }, + }); + mb.once('ready', () => mb.showWindow()); + mb.once('after-create-window', () => { + log.debug('Menubar application was launched'); + if (config.hot_key) { + globalShortcut.register(config.hot_key, () => { + if (mb.window.isFocused()) { + log.debug('Toggle window: shown -> hidden'); + mb.hideWindow(); + } else { + log.debug('Toggle window: hidden -> shown'); + mb.showWindow(); + } + }); + log.debug('Hot key was set to:', config.hot_key); + } + if (IS_DEBUG) { + mb.window.webContents.openDevTools({mode: 'detach'}); + } + mb.window.webContents.on('dom-ready', () => { + log.debug('Send config to renderer procress'); + mb.window.webContents.send('mstdn:config', config); + }); + state.manage(mb.window); + + resolve(new Window(mb.window, account, mb)); + }); + mb.once('after-close', () => { + app.quit(); + }); + }); +} +