mirror of
https://github.com/rhysd/Mstdn.git
synced 2025-02-01 05:52:11 +01:00
first main process logic
This commit is contained in:
parent
8504fdf901
commit
ed0a1682d3
169
main/app.ts
Normal file
169
main/app.ts
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import * as path from 'path';
|
||||||
|
import {app, BrowserWindow, globalShortcut, Tray} from 'electron';
|
||||||
|
import windowState = require('electron-window-state');
|
||||||
|
import * as menubar from 'menubar';
|
||||||
|
import log from './log';
|
||||||
|
import {Config} from './config'
|
||||||
|
|
||||||
|
const IS_DEBUG = process.env.NODE_ENV === 'development';
|
||||||
|
const IS_DARWIN = process.platform === 'darwin';
|
||||||
|
const APP_ICON = path.join(__dirname, '..', 'resources', 'icon.png');
|
||||||
|
const DEFAULT_WIDTH = 340;
|
||||||
|
const DEFAULT_HEIGHT = 400;
|
||||||
|
|
||||||
|
export class App {
|
||||||
|
private host: string;
|
||||||
|
|
||||||
|
constructor(private win: Electron.BrowserWindow, private config: Config) {
|
||||||
|
if (config.accounts.length === 0) {
|
||||||
|
throw new Error('No account found. Please check the config.');
|
||||||
|
}
|
||||||
|
this.host = this.config.accounts[0].host;
|
||||||
|
}
|
||||||
|
|
||||||
|
open() {
|
||||||
|
this.win.loadURL(`https://${this.host}`);
|
||||||
|
this.win.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function trayIcon(color: string) {
|
||||||
|
return path.join(__dirname, '..', 'resources', `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<Electron.BrowserWindow> {
|
||||||
|
log.debug('Setup a normal window');
|
||||||
|
return new Promise<Electron.BrowserWindow>(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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
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'});
|
||||||
|
}
|
||||||
|
resolve(win);
|
||||||
|
});
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startMenuBar(config: Config): Promise<Electron.BrowserWindow> {
|
||||||
|
log.debug('Setup a menubar window');
|
||||||
|
return new Promise<Electron.BrowserWindow>(resolve => {
|
||||||
|
const state = windowState({
|
||||||
|
defaultWidth: DEFAULT_WIDTH,
|
||||||
|
defaultHeight: DEFAULT_HEIGHT,
|
||||||
|
});
|
||||||
|
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',
|
||||||
|
showDockIcon: true,
|
||||||
|
autoHideMenuBar: true,
|
||||||
|
useContentSize: true,
|
||||||
|
show: false,
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: false,
|
||||||
|
sandbox: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
68
main/config.ts
Normal file
68
main/config.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import {app, systemPreferences} from 'electron';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import {join} from 'path';
|
||||||
|
import log from './log';
|
||||||
|
|
||||||
|
export interface Account {
|
||||||
|
host: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Config {
|
||||||
|
hot_key: string;
|
||||||
|
icon_color: string;
|
||||||
|
always_on_top: boolean;
|
||||||
|
normal_window: boolean;
|
||||||
|
zoom_factor: number;
|
||||||
|
accounts: Account[];
|
||||||
|
keymaps: {[key: string]: string};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeDefaultConfig(): Config {
|
||||||
|
const IsDarkMode = (process.platform === 'darwin') && systemPreferences.isDarkMode();
|
||||||
|
const menubarBroken = process.platform === 'win32';
|
||||||
|
|
||||||
|
return {
|
||||||
|
hot_key: 'CmdOrCtrl+Shift+S',
|
||||||
|
icon_color: IsDarkMode ? 'white' : 'black',
|
||||||
|
always_on_top: false,
|
||||||
|
normal_window: menubarBroken,
|
||||||
|
zoom_factor: 1.0,
|
||||||
|
accounts: [],
|
||||||
|
keymaps: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function loadConfig(): Promise<Config> {
|
||||||
|
return new Promise<Config>(resolve => {
|
||||||
|
const dir = app.getPath('userData');
|
||||||
|
const file = join(dir, 'config.json');
|
||||||
|
fs.readFile(file, 'utf8', (err, json) => {
|
||||||
|
if (err) {
|
||||||
|
log.info('Configuration file was not found, will create:', file);
|
||||||
|
const default_config = makeDefaultConfig();
|
||||||
|
// Note:
|
||||||
|
// If calling writeFile() directly here, it tries to create config file before Electron
|
||||||
|
// runtime creates data directory. As the result, writeFile() would fail to create a file.
|
||||||
|
if (app.isReady()) {
|
||||||
|
fs.writeFile(file, JSON.stringify(default_config, null, 2));
|
||||||
|
} else {
|
||||||
|
app.once('ready', () => fs.writeFile(file, JSON.stringify(default_config, null, 2)));
|
||||||
|
}
|
||||||
|
return resolve(default_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const config = JSON.parse(json);
|
||||||
|
if (config.hot_key && config.hot_key.startsWith('mod+')) {
|
||||||
|
config.hot_key = `CmdOrCtrl+${config.hot_key.slice(4)}`;
|
||||||
|
}
|
||||||
|
log.debug('Configuration was loaded successfully', config);
|
||||||
|
resolve(config);
|
||||||
|
} catch (e) {
|
||||||
|
log.error('Error on loading JSON file, will load default configuration:', e.message);
|
||||||
|
resolve(makeDefaultConfig());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
22
main/index.ts
Normal file
22
main/index.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import {app} from 'electron';
|
||||||
|
import log from './log';
|
||||||
|
import startApp from './app';
|
||||||
|
import loadConfig from './config';
|
||||||
|
|
||||||
|
const appReady = new Promise<void>(resolve => app.once('ready', resolve));
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason: string) => {
|
||||||
|
log.error('FATAL: Unhandled rejection! Reason:', reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('will-quit', () => {
|
||||||
|
log.debug('Application is quitting');
|
||||||
|
});
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
loadConfig(),
|
||||||
|
appReady
|
||||||
|
]).then(([config, _]) => startApp(config)).then(win => {
|
||||||
|
win.open();
|
||||||
|
log.debug('Application launched!');
|
||||||
|
});
|
9
main/log.ts
Normal file
9
main/log.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import * as log from 'loglevel';
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
log.setLevel('debug');
|
||||||
|
} else {
|
||||||
|
log.setLevel('info');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default log;
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "mstdn",
|
"name": "mstdn",
|
||||||
"productName": "Mstdn",
|
"productName": "Mstdn",
|
||||||
"version": "0.0.0",
|
"version": "0.0.1",
|
||||||
"description": "Tiny web-based mastodon client for your desktop",
|
"description": "Tiny web-based mastodon client for your desktop",
|
||||||
"main": "main/index.js",
|
"main": "main/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -35,6 +35,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/electron": "^1.4.35",
|
"@types/electron": "^1.4.35",
|
||||||
"@types/electron-window-state": "^2.0.28",
|
"@types/electron-window-state": "^2.0.28",
|
||||||
|
"@types/loglevel": "^1.4.29",
|
||||||
"@types/mousetrap": "^1.5.33",
|
"@types/mousetrap": "^1.5.33",
|
||||||
"@types/node": "^7.0.12",
|
"@types/node": "^7.0.12",
|
||||||
"electron-packager": "^8.6.0",
|
"electron-packager": "^8.6.0",
|
||||||
@ -46,7 +47,7 @@
|
|||||||
"electron": "^1.6.2",
|
"electron": "^1.6.2",
|
||||||
"electron-window-state": "^4.1.1",
|
"electron-window-state": "^4.1.1",
|
||||||
"loglevel": "^1.4.1",
|
"loglevel": "^1.4.1",
|
||||||
"menubar": "github:rhysd/menubar#rhysd-fixes",
|
"menubar": "github:rhysd/menubar#mstdn",
|
||||||
"mousetrap": "^1.6.1"
|
"mousetrap": "^1.6.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user