Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Electron's main process #951

Merged
merged 3 commits into from
Jan 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions src/main/Daemon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* eslint-disable no-console */
import path from 'path';
import { spawn, execSync } from 'child_process';

export default class Daemon {
static path = process.env.LBRY_DAEMON || path.join(__static, 'daemon/lbrynet-daemon');
subprocess;
handlers;

constructor() {
this.handlers = [];
}

launch() {
// Kill any running daemon
if (process.platform === 'win32') {
try {
execSync('taskkill /im lbrynet-daemon.exe /t /f');
} catch (error) {
console.warn(error.message);
}
} else {
try {
execSync('pkill lbrynet-daemon');
} catch (error) {
console.warn(error.message);
}
}

console.log('Launching daemon:', Daemon.path);
this.subprocess = spawn(Daemon.path);

this.subprocess.stdout.on('data', data => console.log(`Daemon: ${data}`));
this.subprocess.stderr.on('data', data => console.error(`Daemon: ${data}`));
this.subprocess.on('exit', () => this.fire('exit'));
this.subprocess.on('error', error => console.error(`Daemon: ${error}`));
}

quit() {
if (process.platform === 'win32') {
try {
execSync(`taskkill /pid ${this.subprocess.pid} /t /f`);
} catch (error) {
console.error(error.message);
}
} else {
this.subprocess.kill();
}
}

// Follows the publish/subscribe pattern

// Subscribe method
on(event, handler, context = handler) {
this.handlers.push({ event, handler: handler.bind(context) });
}

// Publish method
fire(event, args) {
this.handlers.forEach(topic => {
if (topic.event === event) topic.handler(args);
});
}
}
63 changes: 63 additions & 0 deletions src/main/Tray.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { app, Menu, Tray as ElectronTray } from 'electron';
import path from 'path';
import createWindow from './createWindow';

export default class Tray {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be a whole object? create() is only called once and it doesn't need any state, so maybe just a createTray function that takes an updateAttachedWindow callback?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went for a exported createTray() function initially. However, since it also needs to manage the state of the window, I finally went for using a class.

window;
updateAttachedWindow;
tray;

constructor(window, updateAttachedWindow) {
this.window = window;
this.updateAttachedWindow = updateAttachedWindow;
}

create() {
let iconPath;
switch (process.platform) {
case 'darwin': {
iconPath = path.join(__static, '/img/tray/mac/trayTemplate.png');
break;
}
case 'win32': {
iconPath = path.join(__static, '/img/tray/windows/tray.ico');
break;
}
default: {
iconPath = path.join(__static, '/img/tray/default/tray.png');
}
}

this.tray = new ElectronTray(iconPath);

this.tray.on('double-click', () => {
if (!this.window || this.window.isDestroyed()) {
this.window = createWindow();
this.updateAttachedWindow(this.window);
} else {
this.window.show();
this.window.focus();
}
});

this.tray.setToolTip('LBRY App');

const template = [
{
label: `Open ${app.getName()}`,
click: () => {
if (!this.window || this.window.isDestroyed()) {
this.window = createWindow();
this.updateAttachedWindow(this.window);
} else {
this.window.show();
this.window.focus();
}
},
},
{ role: 'quit' },
];
const contextMenu = Menu.buildFromTemplate(template);
this.tray.setContextMenu(contextMenu);
}
}
100 changes: 100 additions & 0 deletions src/main/createWindow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { app, BrowserWindow, dialog } from 'electron';
import setupBarMenu from './menu/setupBarMenu';
import setupContextMenu from './menu/setupContextMenu';

export default deepLinkingURIArg => {
let windowConfiguration = {
backgroundColor: '#155B4A',
minWidth: 800,
minHeight: 600,
autoHideMenuBar: true,
show: false,
};

// Disable renderer process's webSecurity on development to enable CORS.
windowConfiguration =
process.env.NODE_ENV === 'development'
? {
...windowConfiguration,
webPreferences: {
webSecurity: false,
},
}
: windowConfiguration;

const rendererURL =
process.env.NODE_ENV === 'development'
? `http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`
: `file://${__dirname}/index.html`;

let window = new BrowserWindow(windowConfiguration);

window.maximize();

window.loadURL(rendererURL);

let deepLinkingURI;
// Protocol handler for win32
if (
!deepLinkingURIArg &&
process.platform === 'win32' &&
String(process.argv[1]).startsWith('lbry')
) {
// Keep only command line / deep linked arguments
// Windows normalizes URIs when they're passed in from other apps. On Windows, this tries to
// restore the original URI that was typed.
// - If the URI has no path, Windows adds a trailing slash. LBRY URIs can't have a slash with no
// path, so we just strip it off.
// - In a URI with a claim ID, like lbry://channel#claimid, Windows interprets the hash mark as
// an anchor and converts it to lbry://channel/#claimid. We remove the slash here as well.
deepLinkingURI = process.argv[1].replace(/\/$/, '').replace('/#', '#');
} else {
deepLinkingURI = deepLinkingURIArg;
}

setupBarMenu();
setupContextMenu(window);

window.on('closed', () => {
window = null;
});

window.on('focus', () => {
window.webContents.send('window-is-focused', null);
});

window.on('unresponsive', () => {
dialog.showMessageBox(
window,
{
type: 'warning',
buttons: ['Wait', 'Quit'],
title: 'LBRY Unresponsive',
defaultId: 1,
message: 'LBRY is not responding. Would you like to quit?',
cancelId: 0,
},
buttonIndex => {
if (buttonIndex === 1) app.quit();
}
);
});

window.once('ready-to-show', () => {
window.show();
});

window.webContents.on('did-finish-load', () => {
window.webContents.send('open-uri-requested', deepLinkingURI, true);
window.webContents.session.setUserAgent(`LBRY/${app.getVersion()}`);
if (process.env.NODE_ENV === 'development') {
window.webContents.openDevTools();
}
});

window.webContents.on('crashed', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't seem like this event is actually sent on the renderer side. Did you mean to build that, or is it something we're going to add later?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

window = null;
});

return window;
};
Loading