/*jshint node:true, nomen:true */ 'use strict'; var app = require('app'); var BrowserWindow = require('browser-window'); var ipc = require('ipc'); var net = require('net'); var util = require('util'); var events = require('events'); var path = require('path'); var os = require('os'); var fs = require('fs'); /** * App * * Controls the app flow */ function App() { this.windows = []; this.ready = false; this.visible = true; app.on('ready', function () { this.ready = true; }.bind(this)); app.on('window-all-closed', function () { app.quit(); }); events.EventEmitter.call(this); } util.inherits(App, events.EventEmitter); module.exports = App; // create pipe on Windows and socks on Linux-based environments App.socket = (process.platform === 'win32') ? '\\\\.\\pipe\\piano-sock' : path.join(os.tmpdir(), 'piano.sock'); /** * Add a new window * * Open a window and add it to the * window stack. Returns window instance. * * @param {options: Object} Window options (see atom-shell docs) */ App.prototype.addWindow = function (options) { var window = new BrowserWindow(options), index = this.windows.length; window.on('closed', function () { this.windows.splice(index, 1); }.bind(this)); this.windows.push(window); return window; }; /** * Fetch options * * Gather options in a litteral from * argv parameters. */ App.prototype.fetchOptions = function () { var argv, i, arg, options = {}; argv = process.argv.slice(1); for (i in argv) { if (argv.hasOwnProperty(i)) { arg = argv[i]; if (arg[0] !== '-') { options.file = arg; } } } this.emit('options', options); return options; }; /** * Chain options * * Pass current options to the instance that * is already opened, if there is one. * Otherwise, code in callback is executed, * and server is launched. * * @param {callback: function} Function to call if this is the first instance */ App.prototype.chainOptions = function (callback) { var client; client = net.connect({ path: App.socket }, function () { // if the connection is established, // an instance is already running. // Pass on parameters and close app. client.write( JSON.stringify(this.fetchOptions()), function () { client.end(); app.terminate(); } ); }.bind(this)); client.on('error', function (err) { // if an error occurred, that means no // server was created yet: this is the // first instance this.startServer(); if (this.ready) { callback(); } else { app.on('ready', callback); } }.bind(this)); }; /** * Start app server * * Start a server on App.socket path, waiting * for new instances to pass their options. */ App.prototype.startServer = function () { var server; if (process.platform !== 'win32') { // try to unlink older socket if it exists, if it doesn't, // ignore ENOENT errors try { fs.unlinkSync(App.socket); } catch (e) { if (e.code !== 'ENOENT') { throw e; } } } server = net.createServer(function (connection) { connection.on('data', function (data) { this.windows[0].focus(); this.emit('options', JSON.parse(data)); }.bind(this)); }.bind(this)); server.listen(App.socket); }; /** * Add a switch * * @param {switch: string} Switch name * @param {value: mixed} Switch value */ App.prototype.addSwitch = function (name, value) { app.commandLine.appendSwitch(name, value); }; /** * Start the app * * Check if another instance is already opened, * if so, transmit options and close instantly. * Otherwise, open main window. */ App.prototype.start = function () { this.chainOptions(function () { // enable MIDI support this.addSwitch('enable-web-midi'); // create main window var window = this.addWindow({ title: 'Piano', icon: __dirname + '/images/logos/logo32.png', 'min-width': 750, 'min-height': 400, width: 937, height: 500, show: false }); window.loadUrl('file://' + __dirname + '/index.html'); // FIXME: atom-shell currently doesn't have a // minimize/restore event, so we poll // // https://github.com/atom/atom-shell/issues/73 setInterval(function () { if (this.visible && window.isMinimized()) { this.visible = false; window.webContents.send('visible', false); } if (!this.visible && !window.isMinimized()) { this.visible = true; window.webContents.send('visible', true); } }.bind(this), 500); // start sending options this.on('options', function (options) { window.webContents.send('options', options); }); ipc.on('ready', function () { window.show(); window.focus(); window.webContents.send('visible', true); this.fetchOptions(); }.bind(this)); }.bind(this)); };