hyper/index.js
Mike b76e004309 Dynamically change the font-smoothing pref (#205)
* Dynamically change the `font-smoothing` pref

By default, hterm defaults to `font-smoothing: 'antialiased'`, which
works really well on retina displays. On non-retina displays, however,
the type looks very thin and is hard to read.

This will look at the devicePixelRatio of the device anytime the term
prefs are set, and change between `antialiased` and
`subpixel-antialiased` dynamically.

* Refactor to add the font smoothing override into state

This also subscribes to the electron `move` event to control when this
piece of state gets updated.

* Add UI_WINDOW_MOVE action with a side effect for font smoothing
2016-07-19 11:30:57 -07:00

252 lines
5.7 KiB
JavaScript

const { app, BrowserWindow, shell, Menu } = require('electron');
const createRPC = require('./rpc');
const createMenu = require('./menu');
const Session = require('./session');
const genUid = require('uid2');
const { resolve } = require('path');
const isDev = require('electron-is-dev');
const AutoUpdater = require('./auto-updater');
const toHex = require('convert-css-color-name-to-hex');
// set up config
const config = require('./config');
config.init();
const plugins = require('./plugins');
const windowSet = new Set([]);
// expose to plugins
app.config = config;
app.plugins = plugins;
app.getWindows = () => new Set([...windowSet]); // return a clone
if (isDev) {
console.log('running in dev mode');
} else {
console.log('running in prod mode');
}
const url = 'file://' + resolve(
isDev ? __dirname : app.getAppPath(),
// in prod version, we copy over index.html and dist from 'app'
// into one dist folder to avoid unwanted files in package
isDev ? 'app' : 'build',
'index.html'
);
console.log('electron will open', url);
app.on('window-all-closed', () => {
// by subscribing to this event and nooping
// we prevent electron's default behavior
// of quitting the app when the last
// terminal is closed
});
app.on('ready', () => {
function createWindow (fn) {
const cfg = plugins.getDecoratedConfig();
const win = new BrowserWindow({
width: 540,
height: 380,
minHeight: 190,
minWidth: 370,
titleBarStyle: 'hidden-inset',
title: 'HyperTerm',
backgroundColor: toHex(cfg.backgroundColor || '#000'),
transparent: true,
icon: resolve(__dirname, 'static/icon.png'),
// we only want to show when the prompt
// is ready for user input
show: process.env.HYPERTERM_DEBUG || isDev
});
windowSet.add(win);
win.loadURL(url);
const rpc = createRPC(win);
const sessions = new Map();
// config changes
const cfgUnsubscribe = config.subscribe(() => {
win.webContents.send('config change');
});
rpc.on('init', () => {
win.show();
if (fn) fn(win);
// auto updates
if (!isDev) {
AutoUpdater(win);
} else {
console.log('ignoring auto updates during dev');
}
});
rpc.on('new', ({ rows = 40, cols = 100, cwd = process.env.HOME }) => {
const shell = cfg.shell;
initSession({ rows, cols, cwd, shell }, (uid, session) => {
sessions.set(uid, session);
rpc.emit('session add', {
uid,
shell: session.shell,
pid: session.pty.pid
});
session.on('data', (data) => {
rpc.emit('session data', { uid, data });
});
session.on('title', (title) => {
rpc.emit('session title', { uid, title });
});
session.on('exit', () => {
rpc.emit('session exit', { uid });
sessions.delete(uid);
});
});
});
// TODO: this goes away when we are able to poll
// for the title ourseleves, instead of relying
// on Session and focus/blur to subscribe
rpc.on('focus', ({ uid }) => {
const session = sessions.get(uid);
if (session) {
session.focus();
} else {
console.log('session not found by', uid);
}
});
rpc.on('blur', ({ uid }) => {
const session = sessions.get(uid);
if (session) {
session.blur();
} else {
console.log('session not found by', uid);
}
});
rpc.on('exit', ({ uid }) => {
sessions.get(uid).exit();
});
rpc.on('unmaximize', () => {
win.unmaximize();
});
rpc.on('maximize', () => {
win.maximize();
});
rpc.on('resize', ({ cols, rows }) => {
sessions.forEach((session) => {
session.resize({ cols, rows });
});
});
rpc.on('data', ({ uid, data }) => {
sessions.get(uid).write(data);
});
rpc.on('open external', ({ url }) => {
shell.openExternal(url);
});
rpc.win.on('move', () => {
rpc.emit('move');
});
const deleteSessions = () => {
sessions.forEach((session, key) => {
session.removeAllListeners();
session.destroy();
sessions.delete(key);
});
};
// we reset the rpc channel only upon
// subsequent refreshes (ie: F5)
let i = 0;
win.webContents.on('did-navigate', () => {
if (i++) {
deleteSessions();
}
});
// expose internals to extension authors
win.rpc = rpc;
win.sessions = sessions;
const load = () => {
plugins.onWindow(win);
};
// load plugins
load();
const pluginsUnsubscribe = plugins.subscribe((err) => {
if (!err) {
load();
win.webContents.send('plugins change');
}
});
// the window can be closed by the browser process itself
win.on('close', () => {
windowSet.delete(win);
rpc.destroy();
deleteSessions();
cfgUnsubscribe();
pluginsUnsubscribe();
});
}
// when opening create a new window
createWindow();
// expose to plugins
app.createWindow = createWindow;
// mac only. when the dock icon is clicked
// and we don't have any active windows open,
// we open one
app.on('activate', () => {
if (!windowSet.size) {
createWindow();
}
});
const setupMenu = () => {
const tpl = plugins.decorateMenu(createMenu({
createWindow,
updatePlugins: () => {
plugins.updatePlugins({ force: true });
}
}));
Menu.setApplicationMenu(Menu.buildFromTemplate(tpl));
};
const load = () => {
plugins.onApp(app);
setupMenu();
};
load();
plugins.subscribe(load);
});
function initSession (opts, fn) {
genUid(20, (err, uid) => {
if (err) throw err;
fn(uid, new Session(opts));
});
}