hyper/index.js
Guillermo Rauch d8e841a3d8 Implement hterm (#28)
* remove legacy css

* hyperterm: delegate rows / cols calculation to hterm

* session: handle pty kill problems

* index: fix memory leak by removing sessions from the map upon exit

* app: remove local copy of `xterm.js`

* term: implement the `hterm` API and some needed overrides

* package: add `hterm-umd`

* hyperterm: add optimistic tab exit

* hyperterm: delegate key combination detection to the hterm <iframe> document

* term: register keyboard

* session: fix incorrect width after resizing and creating a new tab (#13)

* tabs: fix `user-select` css property

* term: fix focus issue when exiting a url

Instead of uninstalling the keyboard, we keep the
focus on the underlying terminal.

We register a new IO handler so that we intercept
all data events.

The reason we need to do this is that we can't
programmatically restore focus on the underlying
terminal unless it's in the same tick as a user
event (ie: click).

Since we were uninstalling the keyboard and
subsequently attempting to reinstall it without
such an event, pressing Ctrl+C after a url was
effectively resulting in a loss of focus and a
horrible horrible experience.

Now it's fixed :)

* text-metrics: remove module no longer used

hterm has a much better calculation technique anyways

* term: fix default bg

* term: fix nasty hterm bug that triggered an infinite copy loop

* index: add separator in `View` menu for full screen item

* term: implement cmd+K clearing and improve hterm's `wipeContents`
2016-07-03 13:35:45 -07:00

259 lines
6 KiB
JavaScript

const { app, BrowserWindow, shell, Menu } = require('electron');
const createRPC = require('./rpc');
const Session = require('./session');
const genUid = require('uid2');
const { resolve } = require('path');
const isDev = require('electron-is-dev');
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
});
let winCount = 0;
app.on('ready', () => {
function createWindow (fn) {
let win = new BrowserWindow({
width: 540,
height: 380,
titleBarStyle: 'hidden',
title: 'HyperTerm',
backgroundColor: '#000',
transparent: true,
// we only want to show when the prompt
// is ready for user input
show: process.env.HYPERTERM_DEBUG || isDev
});
winCount++;
win.loadURL(url);
const rpc = createRPC(win);
const sessions = new Map();
rpc.on('init', () => {
win.show();
});
rpc.on('new', ({ rows = 40, cols = 100 }) => {
initSession({ rows, cols }, (uid, session) => {
sessions.set(uid, session);
rpc.emit('new session', { uid });
session.on('data', (data) => {
rpc.emit('data', { uid, data });
});
session.on('title', (title) => {
rpc.emit('title', { uid, title });
});
session.on('exit', () => {
rpc.emit('exit', { uid });
sessions.delete(uid);
});
});
});
rpc.on('focus', ({ uid }) => {
sessions.get(uid).focus();
});
rpc.on('blur', ({ uid }) => {
sessions.get(uid).blur();
});
rpc.on('exit', ({ uid }) => {
sessions.get(uid).exit();
});
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);
});
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();
}
});
// the window can be closed by the browser process itself
win.on('close', () => {
rpc.destroy();
deleteSessions();
winCount--;
});
win.rpc = rpc;
}
// when opening create a new window
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 (!winCount) {
createWindow();
}
});
// set menu
Menu.setApplicationMenu(Menu.buildFromTemplate([
{
label: 'Application',
submenu: [
{
role: 'quit'
}
]
},
{
label: 'Shell',
submenu: [
{
label: 'New Window',
accelerator: 'CmdOrCtrl+N',
click (item, focusedWindow) {
createWindow();
}
},
{
label: 'New Tab',
accelerator: 'CmdOrCtrl+T',
click (item, focusedWindow) {
if (focusedWindow) {
focusedWindow.rpc.emit('new tab');
} else {
createWindow();
}
}
},
{
label: 'Close',
accelerator: 'CmdOrCtrl+W',
click (item, focusedWindow) {
if (focusedWindow) {
focusedWindow.rpc.emit('close tab');
}
}
}
]
},
{
label: 'Edit',
submenu: [
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' },
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' },
{ label: 'Select All', accelerator: 'CmdOrCtrl+A', selector: 'selectAll:' },
{ type: 'separator' },
{
label: 'Clear',
accelerator: 'CmdOrCtrl+K',
click (item, focusedWindow) {
if (focusedWindow) {
focusedWindow.rpc.emit('clear');
}
}
}
]
},
{
label: 'View',
submenu: [
{
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
click (item, focusedWindow) {
if (focusedWindow) focusedWindow.reload();
}
},
{
label: 'Toggle Developer Tools',
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
click (item, focusedWindow) {
if (focusedWindow) {
focusedWindow.webContents.toggleDevTools();
}
}
},
{
type: 'separator'
},
{
role: 'togglefullscreen'
}
]
},
{
label: 'Window',
submenu: [
{
label: 'Select Previous Tab',
accelerator: 'CmdOrCtrl+Left',
click (item, focusedWindow) {
if (focusedWindow) {
focusedWindow.rpc.emit('move left');
}
}
},
{
label: 'Select Next Tab',
accelerator: 'CmdOrCtrl+Right',
click (item, focusedWindow) {
if (focusedWindow) {
focusedWindow.rpc.emit('move right');
}
}
}
]
}
]));
});
function initSession (opts, fn) {
genUid(20, (err, uid) => {
if (err) throw err;
fn(uid, new Session(opts));
});
}