diff --git a/main/dialog.js b/main/dialog.js index bf9d476f..6fe06ab1 100644 --- a/main/dialog.js +++ b/main/dialog.js @@ -5,8 +5,10 @@ module.exports = { openTorrentAddress } -var config = require('../config') var electron = require('electron') + +var config = require('../config') +var log = require('./log') var windows = require('./windows') /** @@ -14,6 +16,7 @@ var windows = require('./windows') */ function openSeedFile () { if (!windows.main.win) return + log('openSeedFile') var opts = { title: 'Select a file for the torrent.', properties: [ 'openFile' ] @@ -22,7 +25,7 @@ function openSeedFile () { electron.dialog.showOpenDialog(windows.main.win, opts, function (selectedPaths) { resetTitle() if (!Array.isArray(selectedPaths)) return - windows.main.send('dispatch', 'showCreateTorrent', selectedPaths) + windows.main.dispatch('showCreateTorrent', selectedPaths) }) } @@ -33,6 +36,7 @@ function openSeedFile () { */ function openSeedDirectory () { if (!windows.main.win) return + log('openSeedDirectory') var opts = process.platform === 'darwin' ? { title: 'Select a file or folder for the torrent.', @@ -46,7 +50,7 @@ function openSeedDirectory () { electron.dialog.showOpenDialog(windows.main.win, opts, function (selectedPaths) { resetTitle() if (!Array.isArray(selectedPaths)) return - windows.main.send('dispatch', 'showCreateTorrent', selectedPaths) + windows.main.dispatch('showCreateTorrent', selectedPaths) }) } @@ -55,6 +59,7 @@ function openSeedDirectory () { */ function openTorrentFile () { if (!windows.main.win) return + log('openTorrentFile') var opts = { title: 'Select a .torrent file to open.', filters: [{ name: 'Torrent Files', extensions: ['torrent'] }], @@ -65,7 +70,7 @@ function openTorrentFile () { resetTitle() if (!Array.isArray(selectedPaths)) return selectedPaths.forEach(function (selectedPath) { - windows.main.send('dispatch', 'addTorrent', selectedPath) + windows.main.dispatch('addTorrent', selectedPath) }) }) } @@ -74,7 +79,8 @@ function openTorrentFile () { * Show modal dialog to open a torrent URL (magnet uri, http torrent link, etc.) */ function openTorrentAddress () { - windows.main.send('showOpenTorrentAddress') + log('openTorrentAddress') + windows.main.dispatch('openTorrentAddress') } /** @@ -82,7 +88,7 @@ function openTorrentAddress () { */ function setTitle (title) { if (process.platform === 'darwin') { - windows.main.send('dispatch', 'setTitle', title) + windows.main.dispatch('setTitle', title) } } diff --git a/main/dock.js b/main/dock.js new file mode 100644 index 00000000..2d17a296 --- /dev/null +++ b/main/dock.js @@ -0,0 +1,59 @@ +module.exports = { + downloadFinished, + init, + setBadge +} + +var electron = require('electron') + +var app = electron.app + +var dialog = require('./dialog') +var log = require('./log') + +/** + * Add a right-click menu to the dock icon. (OS X) + */ +function init () { + if (!app.dock) return + var menu = electron.Menu.buildFromTemplate(getMenuTemplate()) + app.dock.setMenu(menu) +} + +/** + * Bounce the Downloads stack if `path` is inside the Downloads folder. (OS X) + */ +function downloadFinished (path) { + if (!app.dock) return + log(`downloadFinished: ${path}`) + app.dock.downloadFinished(path) +} + +/** + * Display string in dock badging area. (OS X) + */ +function setBadge (text) { + if (!app.dock) return + log(`setBadge: ${text}`) + app.dock.setBadge(String(text)) +} + +function getMenuTemplate () { + return [ + { + label: 'Create New Torrent...', + accelerator: 'CmdOrCtrl+N', + click: () => dialog.openSeedDirectory() + }, + { + label: 'Open Torrent File...', + accelerator: 'CmdOrCtrl+O', + click: () => dialog.openTorrentFile() + }, + { + label: 'Open Torrent Address...', + accelerator: 'CmdOrCtrl+U', + click: () => dialog.openTorrentAddress() + } + ] +} diff --git a/main/index.js b/main/index.js index eebdc44d..2d2c8b93 100644 --- a/main/index.js +++ b/main/index.js @@ -9,6 +9,7 @@ var announcement = require('./announcement') var config = require('../config') var crashReporter = require('../crash-reporter') var dialog = require('./dialog') +var dock = require('./dock') var handlers = require('./handlers') var ipc = require('./ipc') var log = require('./log') @@ -61,8 +62,8 @@ function init () { app.on('ready', function () { isReady = true - windows.main.create() - windows.webtorrent.create() + windows.main.init() + windows.webtorrent.init() menu.init() // To keep app startup fast, some code is delayed. @@ -80,20 +81,21 @@ function init () { app.isQuitting = true e.preventDefault() - windows.main.send('dispatch', 'saveState') // try to save state on exit + windows.main.dispatch('saveState') // try to save state on exit ipcMain.once('savedState', () => app.quit()) setTimeout(() => app.quit(), 2000) // quit after 2 secs, at most }) app.on('activate', function () { - if (isReady) windows.main.create() + if (isReady) windows.main.show() }) } function delayedInit () { announcement.init() - tray.init() + dock.init() handlers.install() + tray.init() updater.init() } @@ -101,7 +103,7 @@ function onOpen (e, torrentId) { e.preventDefault() if (app.ipcReady) { - windows.main.send('dispatch', 'onOpen', torrentId) + windows.main.dispatch('onOpen', torrentId) // Magnet links opened from Chrome won't focus the app without a setTimeout. // The confirmation dialog Chrome shows causes Chrome to steal back the focus. // Electron issue: https://github.com/atom/electron/issues/4338 @@ -148,6 +150,6 @@ function processArgv (argv) { } }) if (paths.length > 0) { - windows.main.send('dispatch', 'onOpen', paths) + windows.main.dispatch('onOpen', paths) } } diff --git a/main/ipc.js b/main/ipc.js index c03c9cb0..b9c4c59a 100644 --- a/main/ipc.js +++ b/main/ipc.js @@ -6,9 +6,12 @@ var electron = require('electron') var app = electron.app +var dialog = require('./dialog') +var dock = require('./dock') var log = require('./log') var menu = require('./menu') var powerSaveBlocker = require('./power-save-blocker') +var shell = require('./shell') var shortcuts = require('./shortcuts') var vlc = require('./vlc') var windows = require('./windows') @@ -20,15 +23,15 @@ var messageQueueMainToWebTorrent = [] var vlcProcess function init () { - var ipcMain = electron.ipcMain + var ipc = electron.ipcMain - ipcMain.on('ipcReady', function (e) { + ipc.on('ipcReady', function (e) { windows.main.show() app.ipcReady = true app.emit('ipcReady') }) - ipcMain.on('ipcReadyWebTorrent', function (e) { + ipc.on('ipcReadyWebTorrent', function (e) { app.ipcReadyWebTorrent = true log('sending %d queued messages from the main win to the webtorrent window', messageQueueMainToWebTorrent.length) @@ -38,109 +41,111 @@ function init () { }) }) - ipcMain.on('showOpenTorrentFile', () => menu.showOpenTorrentFile()) + /** + * Dialog + */ - ipcMain.on('setBounds', function (e, bounds, maximize) { - setBounds(bounds, maximize) - }) + ipc.on('openTorrentFile', () => dialog.openTorrentFile()) - ipcMain.on('setAspectRatio', function (e, aspectRatio) { - setAspectRatio(aspectRatio) - }) + /** + * Dock + */ - ipcMain.on('setBadge', function (e, text) { - setBadge(text) - }) + ipc.on('setBadge', (e, ...args) => dock.setBadge(...args)) + ipc.on('downloadFinished', (e, ...args) => dock.downloadFinished(...args)) - ipcMain.on('setProgress', function (e, progress) { - setProgress(progress) - }) + /** + * Events + */ - ipcMain.on('toggleFullScreen', function (e, flag) { - windows.main.toggleFullScreen(flag) - }) - - ipcMain.on('setTitle', function (e, title) { - windows.main.win.setTitle(title) - }) - - ipcMain.on('openItem', function (e, path) { - ipc.on('show', (e, ...args) => windows.main.show(...args)) - log('open item: ' + path) - electron.shell.openItem(path) - }) - - ipcMain.on('showItemInFolder', function (e, path) { - log('show item in folder: ' + path) - electron.shell.showItemInFolder(path) - }) - - ipcMain.on('blockPowerSave', () => powerSaveBlocker.start()) - ipcMain.on('unblockPowerSave', () => powerSaveBlocker.stop()) - - ipcMain.on('onPlayerOpen', function () { + ipc.on('onPlayerOpen', function () { menu.onPlayerOpen() shortcuts.onPlayerOpen() }) - ipcMain.on('onPlayerClose', function () { + ipc.on('onPlayerClose', function () { menu.onPlayerClose() shortcuts.onPlayerOpen() }) - ipcMain.on('downloadFinished', function (e, filePath) { - if (app.dock) { - // Bounces the Downloads stack if the filePath is inside the Downloads folder. - app.dock.downloadFinished(filePath) - } - }) + /** + * Power Save Blocker + */ - ipcMain.on('checkForVLC', function (e) { + ipc.on('blockPowerSave', () => powerSaveBlocker.start()) + ipc.on('unblockPowerSave', () => powerSaveBlocker.stop()) + + /** + * Shell + */ + + ipc.on('openItem', (e, ...args) => shell.openItem(...args)) + ipc.on('showItemInFolder', (e, ...args) => shell.showItemInFolder(...args)) + + /** + * Windows: Main + */ + + var main = windows.main + + ipc.on('setAspectRatio', (e, ...args) => main.setAspectRatio(...args)) + ipc.on('setBounds', (e, ...args) => main.setBounds(...args)) + ipc.on('setProgress', (e, ...args) => main.setProgress(...args)) + ipc.on('setTitle', (e, ...args) => main.setTitle(...args)) + ipc.on('show', () => main.show()) + ipc.on('toggleFullScreen', (e, ...args) => main.toggleFullScreen(...args)) + + /** + * VLC + * TODO: Move most of this code to vlc.js + */ + + ipc.on('checkForVLC', function (e) { vlc.checkForVLC(function (isInstalled) { windows.main.send('checkForVLC', isInstalled) }) }) - ipcMain.on('vlcPlay', function (e, url) { + ipc.on('vlcPlay', function (e, url) { var args = ['--play-and-exit', '--video-on-top', '--no-video-title-show', '--quiet', url] - console.log('Running vlc ' + args.join(' ')) + log('Running vlc ' + args.join(' ')) vlc.spawn(args, function (err, proc) { - if (err) return windows.main.send('dispatch', 'vlcNotFound') + if (err) return windows.main.dispatch('vlcNotFound') vlcProcess = proc // If it works, close the modal after a second var closeModalTimeout = setTimeout(() => - windows.main.send('dispatch', 'exitModal'), 1000) + windows.main.dispatch('exitModal'), 1000) vlcProcess.on('close', function (code) { clearTimeout(closeModalTimeout) if (!vlcProcess) return // Killed - console.log('VLC exited with code ', code) + log('VLC exited with code ', code) if (code === 0) { - windows.main.send('dispatch', 'backToList') + windows.main.dispatch('backToList') } else { - windows.main.send('dispatch', 'vlcNotFound') + windows.main.dispatch('vlcNotFound') } vlcProcess = null }) vlcProcess.on('error', function (e) { - console.log('VLC error', e) + log('VLC error', e) }) }) }) - ipcMain.on('vlcQuit', function () { + ipc.on('vlcQuit', function () { if (!vlcProcess) return - console.log('Killing VLC, pid ' + vlcProcess.pid) + log('Killing VLC, pid ' + vlcProcess.pid) vlcProcess.kill('SIGKILL') // kill -9 vlcProcess = null }) // Capture all events - var oldEmit = ipcMain.emit - ipcMain.emit = function (name, e, ...args) { + var oldEmit = ipc.emit + ipc.emit = function (name, e, ...args) { // Relay messages between the main window and the WebTorrent hidden window if (name.startsWith('wt-') && !app.isQuitting) { if (e.sender.browserWindowOptions.title === 'webtorrent-hidden-window') { @@ -163,68 +168,6 @@ function init () { } // Emit all other events normally - oldEmit.call(ipcMain, name, e, ...args) + oldEmit.call(ipc, name, e, ...args) } } - -function setBounds (bounds, maximize) { - // Do nothing in fullscreen - if (!windows.main.win || windows.main.win.isFullScreen()) { - log('setBounds: not setting bounds because we\'re in full screen') - return - } - - // Maximize or minimize, if the second argument is present - var willBeMaximized - if (maximize === true) { - if (!windows.main.win.isMaximized()) { - log('setBounds: maximizing') - windows.main.win.maximize() - } - willBeMaximized = true - } else if (maximize === false) { - if (windows.main.win.isMaximized()) { - log('setBounds: unmaximizing') - windows.main.win.unmaximize() - } - willBeMaximized = false - } else { - willBeMaximized = windows.main.win.isMaximized() - } - - // Assuming we're not maximized or maximizing, set the window size - if (!willBeMaximized) { - log('setBounds: setting bounds to ' + JSON.stringify(bounds)) - if (bounds.x === null && bounds.y === null) { - // X and Y not specified? By default, center on current screen - var scr = electron.screen.getDisplayMatching(windows.main.win.getBounds()) - bounds.x = Math.round(scr.bounds.x + scr.bounds.width / 2 - bounds.width / 2) - bounds.y = Math.round(scr.bounds.y + scr.bounds.height / 2 - bounds.height / 2) - log('setBounds: centered to ' + JSON.stringify(bounds)) - } - windows.main.win.setBounds(bounds, true) - } else { - log('setBounds: not setting bounds because of window maximization') - } -} - -function setAspectRatio (aspectRatio) { - log('setAspectRatio %o', aspectRatio) - if (!windows.main.win) return - windows.main.win.setAspectRatio(aspectRatio) -} - -// Display string in dock badging area (OS X) -function setBadge (text) { - log('setBadge %s', text) - if (app.dock) { - app.dock.setBadge(String(text)) - } -} - -// Show progress bar. Valid range is [0, 1]. Remove when < 0; indeterminate when > 1. -function setProgress (progress) { - log('setProgress %s', progress) - if (!windows.main.win) return - windows.main.win.setProgressBar(progress) -} diff --git a/main/menu.js b/main/menu.js index 0c7c3d4c..640c4eba 100644 --- a/main/menu.js +++ b/main/menu.js @@ -2,9 +2,10 @@ module.exports = { init, onPlayerClose, onPlayerOpen, + onToggleAlwaysOnTop, onToggleFullScreen, - onWindowHide, - onWindowShow + onWindowBlur, + onWindowFocus } var electron = require('electron') @@ -13,148 +14,66 @@ var app = electron.app var config = require('../config') var dialog = require('./dialog') -var log = require('./log') +var shell = require('./shell') var windows = require('./windows') -var appMenu +var menu function init () { - appMenu = electron.Menu.buildFromTemplate(getAppMenuTemplate()) - electron.Menu.setApplicationMenu(appMenu) - - if (app.dock) { - var dockMenu = electron.Menu.buildFromTemplate(getDockMenuTemplate()) - app.dock.setMenu(dockMenu) - } -} - -// Sets whether the window should always show on top of other windows -function toggleFloatOnTop (flag) { - if (!windows.main.win) return - log('toggleFloatOnTop %s', flag) - flag = flag != null ? flag : !windows.main.isAlwaysOnTop() - windows.main.setAlwaysOnTop(flag) - getMenuItem('Float on Top').checked = flag -} - -function toggleDevTools () { - if (!windows.main.win) return - log('toggleDevTools') - windows.main.toggleDevTools() -} - -function showWebTorrentWindow () { - log('showWebTorrentWindow') - windows.webtorrent.show() - windows.webtorrent.win.webContents.openDevTools({ detach: true }) -} - -function playPause () { - if (!windows.main.win) return - windows.main.send('dispatch', 'playPause') -} - -function increaseVolume () { - if (!windows.main.win) return - windows.main.send('dispatch', 'changeVolume', 0.1) -} - -function decreaseVolume () { - if (!windows.main.win) return - windows.main.send('dispatch', 'changeVolume', -0.1) -} - -function openSubtitles () { - if (!windows.main.win) return - windows.main.send('dispatch', 'openSubtitles') -} - -function skipForward () { - if (!windows.main.win) return - windows.main.send('dispatch', 'skip', 1) -} - -function skipBack () { - if (!windows.main.win) return - windows.main.send('dispatch', 'skip', -1) -} - -function increasePlaybackRate () { - if (!windows.main.win) return - windows.main.send('dispatch', 'changePlaybackRate', 1) -} - -function decreasePlaybackRate () { - if (!windows.main.win) return - windows.main.send('dispatch', 'changePlaybackRate', -1) -} - -// Open the preferences window -function showPreferences () { - if (!windows.main.win) return - windows.main.send('dispatch', 'preferences') -} - -function escapeBack () { - if (!windows.main.win) return - windows.main.send('dispatch', 'escapeBack') -} - -function onWindowShow () { - log('onWindowShow') - getMenuItem('Full Screen').enabled = true - getMenuItem('Float on Top').enabled = true -} - -function onWindowHide () { - log('onWindowHide') - getMenuItem('Full Screen').enabled = false - getMenuItem('Float on Top').enabled = false -} - -function onPlayerOpen () { - log('onPlayerOpen') - getMenuItem('Play/Pause').enabled = true - getMenuItem('Increase Volume').enabled = true - getMenuItem('Decrease Volume').enabled = true - getMenuItem('Add Subtitles File...').enabled = true - getMenuItem('Step Forward').enabled = true - getMenuItem('Step Backward').enabled = true - getMenuItem('Increase Speed').enabled = true - getMenuItem('Decrease Speed').enabled = true + menu = electron.Menu.buildFromTemplate(getMenuTemplate()) + electron.Menu.setApplicationMenu(menu) } function onPlayerClose () { - log('onPlayerClose') getMenuItem('Play/Pause').enabled = false getMenuItem('Increase Volume').enabled = false getMenuItem('Decrease Volume').enabled = false - getMenuItem('Add Subtitles File...').enabled = false getMenuItem('Step Forward').enabled = false getMenuItem('Step Backward').enabled = false getMenuItem('Increase Speed').enabled = false getMenuItem('Decrease Speed').enabled = false + getMenuItem('Add Subtitles File...').enabled = false } -function onToggleFullScreen (isFullScreen) { - if (isFullScreen == null) { - isFullScreen = windows.main.win.isFullScreen() - } - windows.main.win.setMenuBarVisibility(!isFullScreen) - getMenuItem('Full Screen').checked = isFullScreen - windows.main.send('fullscreenChanged', isFullScreen) +function onPlayerOpen () { + getMenuItem('Play/Pause').enabled = true + getMenuItem('Increase Volume').enabled = true + getMenuItem('Decrease Volume').enabled = true + getMenuItem('Step Forward').enabled = true + getMenuItem('Step Backward').enabled = true + getMenuItem('Increase Speed').enabled = true + getMenuItem('Decrease Speed').enabled = true + getMenuItem('Add Subtitles File...').enabled = true +} + +function onToggleAlwaysOnTop (flag) { + getMenuItem('Float on Top').checked = flag +} + +function onToggleFullScreen (flag) { + getMenuItem('Full Screen').checked = flag +} + +function onWindowBlur () { + getMenuItem('Full Screen').enabled = false + getMenuItem('Float on Top').enabled = false +} + +function onWindowFocus () { + getMenuItem('Full Screen').enabled = true + getMenuItem('Float on Top').enabled = true } function getMenuItem (label) { - for (var i = 0; i < appMenu.items.length; i++) { - var menuItem = appMenu.items[i].submenu.items.find(function (item) { + for (var i = 0; i < menu.items.length; i++) { + var menuItem = menu.items[i].submenu.items.find(function (item) { return item.label === label }) if (menuItem) return menuItem } } -function getAppMenuTemplate () { +function getMenuTemplate () { var template = [ { label: 'File', @@ -217,7 +136,7 @@ function getAppMenuTemplate () { { label: 'Preferences', accelerator: 'CmdOrCtrl+,', - click: () => showPreferences() + click: () => windows.main.dispatch('preferences') } ] }, @@ -235,7 +154,7 @@ function getAppMenuTemplate () { { label: 'Float on Top', type: 'checkbox', - click: () => toggleFloatOnTop() + click: () => windows.toggleAlwaysOnTop() }, { type: 'separator' @@ -243,7 +162,7 @@ function getAppMenuTemplate () { { label: 'Go Back', accelerator: 'Esc', - click: escapeBack + click: () => windows.main.dispatch('escapeBack') }, { type: 'separator' @@ -256,14 +175,14 @@ function getAppMenuTemplate () { accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I', - click: toggleDevTools + click: () => windows.main.toggleDevTools() }, { label: 'Show WebTorrent Process', accelerator: process.platform === 'darwin' ? 'Alt+Command+P' : 'Ctrl+Shift+P', - click: showWebTorrentWindow + click: () => windows.webtorrent.toggleDevTools() } ] } @@ -275,7 +194,7 @@ function getAppMenuTemplate () { { label: 'Play/Pause', accelerator: 'Space', - click: playPause, + click: () => windows.main.dispatch('playPause'), enabled: false }, { @@ -284,13 +203,13 @@ function getAppMenuTemplate () { { label: 'Increase Volume', accelerator: 'CmdOrCtrl+Up', - click: increaseVolume, + click: () => windows.main.dispatch('changeVolume', 0.1), enabled: false }, { label: 'Decrease Volume', accelerator: 'CmdOrCtrl+Down', - click: decreaseVolume, + click: () => windows.main.dispatch('changeVolume', -0.1), enabled: false }, { @@ -299,13 +218,13 @@ function getAppMenuTemplate () { { label: 'Step Forward', accelerator: 'CmdOrCtrl+Alt+Right', - click: skipForward, + click: () => windows.main.dispatch('skip', 1), enabled: false }, { label: 'Step Backward', accelerator: 'CmdOrCtrl+Alt+Left', - click: skipBack, + click: () => windows.main.dispatch('skip', -1), enabled: false }, { @@ -314,13 +233,13 @@ function getAppMenuTemplate () { { label: 'Increase Speed', accelerator: 'CmdOrCtrl+=', - click: increasePlaybackRate, + click: () => windows.main.dispatch('changePlaybackRate', 1), enabled: false }, { label: 'Decrease Speed', accelerator: 'CmdOrCtrl+-', - click: decreasePlaybackRate, + click: () => windows.main.dispatch('changePlaybackRate', -1), enabled: false }, { @@ -328,7 +247,7 @@ function getAppMenuTemplate () { }, { label: 'Add Subtitles File...', - click: openSubtitles, + click: () => windows.main.dispatch('openSubtitles'), enabled: false } ] @@ -339,18 +258,18 @@ function getAppMenuTemplate () { submenu: [ { label: 'Learn more about ' + config.APP_NAME, - click: () => electron.shell.openExternal(config.HOME_PAGE_URL) + click: () => shell.openExternal(config.HOME_PAGE_URL) }, { label: 'Contribute on GitHub', - click: () => electron.shell.openExternal(config.GITHUB_URL) + click: () => shell.openExternal(config.GITHUB_URL) }, { type: 'separator' }, { label: 'Report an Issue...', - click: () => electron.shell.openExternal(config.GITHUB_URL_ISSUES) + click: () => shell.openExternal(config.GITHUB_URL_ISSUES) } ] } @@ -371,7 +290,7 @@ function getAppMenuTemplate () { { label: 'Preferences', accelerator: 'Cmd+,', - click: () => showPreferences() + click: () => windows.main.dispatch('preferences') }, { type: 'separator' @@ -430,7 +349,8 @@ function getAppMenuTemplate () { }) } - // In Linux and Windows it is not possible to open both folders and files + // On Windows and Linux, open dialogs do not support selecting both files and + // folders and files, so add an extra menu item so there is one for each type. if (process.platform === 'linux' || process.platform === 'win32') { // File menu (Windows, Linux) template[0].submenu.unshift({ @@ -445,12 +365,12 @@ function getAppMenuTemplate () { }, { label: 'About ' + config.APP_NAME, - click: () => windows.about.create() + click: () => windows.about.init() } ) } - // Add "File > Quit" menu item so Linux distros where the system tray icon is missing - // will have a way to quit the app. + // Add "File > Quit" menu item so Linux distros where the system tray icon is + // missing will have a way to quit the app. if (process.platform === 'linux') { // File menu (Linux) template[0].submenu.push({ @@ -461,23 +381,3 @@ function getAppMenuTemplate () { return template } - -function getDockMenuTemplate () { - return [ - { - label: 'Create New Torrent...', - accelerator: 'CmdOrCtrl+N', - click: () => dialog.openSeedDirectory() - }, - { - label: 'Open Torrent File...', - accelerator: 'CmdOrCtrl+O', - click: () => dialog.openTorrentFile() - }, - { - label: 'Open Torrent Address...', - accelerator: 'CmdOrCtrl+U', - click: () => dialog.openTorrentAddress() - } - ] -} diff --git a/main/power-save-blocker.js b/main/power-save-blocker.js index 9511ccf1..c7d76e42 100644 --- a/main/power-save-blocker.js +++ b/main/power-save-blocker.js @@ -6,20 +6,25 @@ module.exports = { var electron = require('electron') var log = require('./log') -var powerSaveBlockerId = 0 +var blockId = 0 +/** + * Block the system from entering low-power (sleep) mode or turning off the + * display. + */ function start () { - // Stop the previous power saver block, if one exists. - stop() - - powerSaveBlockerId = electron.powerSaveBlocker.start('prevent-display-sleep') - log('powerSaveBlocker.start %d', powerSaveBlockerId) + stop() // Stop the previous power saver block, if one exists. + blockId = electron.powerSaveBlocker.start('prevent-display-sleep') + log(`powerSaveBlocker.start: ${blockId}`) } +/** + * Stop blocking the system from entering low-power mode. + */ function stop () { - if (!electron.powerSaveBlocker.isStarted(powerSaveBlockerId)) { + if (!electron.powerSaveBlocker.isStarted(blockId)) { return } - electron.powerSaveBlocker.stop(powerSaveBlockerId) - log('powerSaveBlocker.stop %d', powerSaveBlockerId) + electron.powerSaveBlocker.stop(blockId) + log(`powerSaveBlocker.stop: ${blockId}`) } diff --git a/main/shell.js b/main/shell.js new file mode 100644 index 00000000..8461d3ce --- /dev/null +++ b/main/shell.js @@ -0,0 +1,32 @@ +module.exports = { + openExternal, + openItem, + showItemInFolder +} + +var electron = require('electron') +var log = require('./log') + +/** + * Open the given external protocol URL in the desktop’s default manner. + */ +function openExternal (url) { + log(`openExternal: ${url}`) + electron.shell.openExternal(url) +} + +/** + * Open the given file in the desktop’s default manner. + */ +function openItem (path) { + log(`openItem: ${path}`) + electron.shell.openItem(path) +} + +/** + * Show the given file in a file manager. If possible, select the file. + */ +function showItemInFolder (path) { + log(`showItemInFolder: ${path}`) + electron.shell.showItemInFolder(path) +} diff --git a/main/shortcuts.js b/main/shortcuts.js index cfb212e9..4254fd37 100644 --- a/main/shortcuts.js +++ b/main/shortcuts.js @@ -7,13 +7,14 @@ var electron = require('electron') var windows = require('./windows') function onPlayerOpen () { - // Register special "media key" for play/pause, available on some keyboards + // Register play/pause media key, available on some keyboards. electron.globalShortcut.register( 'MediaPlayPause', - () => windows.main.send('dispatch', 'playPause') + () => windows.main.dispatch('playPause') ) } function onPlayerClose () { + // Return the media key to the OS, so other apps can use it. electron.globalShortcut.unregister('MediaPlayPause') } diff --git a/main/squirrel-win32.js b/main/squirrel-win32.js index f817a562..84087cfa 100644 --- a/main/squirrel-win32.js +++ b/main/squirrel-win32.js @@ -12,8 +12,8 @@ var app = electron.app var handlers = require('./handlers') -var exeName = path.basename(process.execPath) -var updateDotExe = path.join(process.execPath, '..', '..', 'Update.exe') +var EXE_NAME = path.basename(process.execPath) +var UPDATE_EXE = path.join(process.execPath, '..', '..', 'Update.exe') function handleEvent (cmd) { if (cmd === '--squirrel-install') { @@ -102,12 +102,12 @@ function spawn (command, args, cb) { // Spawn Squirrel's Update.exe with the given arguments and invoke the callback when the // command completes. function spawnUpdate (args, cb) { - spawn(updateDotExe, args, cb) + spawn(UPDATE_EXE, args, cb) } // Create desktop/start menu shortcuts using the Squirrel Update.exe command line API function createShortcuts (cb) { - spawnUpdate(['--createShortcut', exeName], cb) + spawnUpdate(['--createShortcut', EXE_NAME], cb) } // Update desktop/start menu shortcuts using the Squirrel Update.exe command line API @@ -135,5 +135,5 @@ function updateShortcuts (cb) { // Remove desktop/start menu shortcuts using the Squirrel Update.exe command line API function removeShortcuts (cb) { - spawnUpdate(['--removeShortcut', exeName], cb) + spawnUpdate(['--removeShortcut', EXE_NAME], cb) } diff --git a/main/tray.js b/main/tray.js index a0cb2d1d..73cd4ae2 100644 --- a/main/tray.js +++ b/main/tray.js @@ -1,8 +1,8 @@ module.exports = { hasTray, init, - onWindowHide, - onWindowShow + onWindowBlur, + onWindowFocus } var electron = require('electron') @@ -24,8 +24,24 @@ function init () { // OS X apps generally do not have menu bar icons } +/** + * Returns true if there a tray icon is active. + */ +function hasTray () { + return !!tray +} + +function onWindowBlur () { + if (!tray) return + updateTrayMenu() +} + +function onWindowFocus () { + if (!tray) return + updateTrayMenu() +} + function initLinux () { - // Check for libappindicator1 support before creating tray icon checkLinuxTraySupport(function (supportsTray) { if (supportsTray) createTray() }) @@ -35,6 +51,9 @@ function initWin32 () { createTray() } +/** + * Check for libappindicator1 support before creating tray icon + */ function checkLinuxTraySupport (cb) { var cp = require('child_process') @@ -49,65 +68,46 @@ function checkLinuxTraySupport (cb) { }) } -function hasTray () { - return !!tray -} - function createTray () { tray = new electron.Tray(getIconPath()) // On Windows, left click opens the app, right click opens the context menu. // On Linux, any click (left or right) opens the context menu. - tray.on('click', showApp) + tray.on('click', () => windows.main.show()) // Show the tray context menu, and keep the available commands up to date updateTrayMenu() } -function onWindowHide () { - updateTrayMenu() -} - -function onWindowShow () { - updateTrayMenu() -} - function updateTrayMenu () { - if (!tray) return + var contextMenu = electron.Menu.buildFromTemplate(getMenuTemplate) + tray.setContextMenu(contextMenu) +} - var contextMenu = electron.Menu.buildFromTemplate([ +function getMenuTemplate () { + return [ getToggleItem(), { label: 'Quit', click: () => app.quit() } - ]) - tray.setContextMenu(contextMenu) + ] function getToggleItem () { if (windows.main.win.isVisible()) { return { label: 'Hide to tray', - click: hideApp + click: () => windows.main.hide() } } else { return { label: 'Show WebTorrent', - click: showApp + click: () => windows.main.show() } } } } -function showApp () { - windows.main.show() -} - -function hideApp () { - windows.main.hide() - windows.main.send('dispatch', 'backToList') -} - function getIconPath () { return process.platform === 'win32' ? config.APP_ICON + '.ico' diff --git a/main/updater.js b/main/updater.js index 71f8922b..9fe30eb6 100644 --- a/main/updater.js +++ b/main/updater.js @@ -21,27 +21,27 @@ function init () { } } -// The Electron auto-updater does not support Linux yet, so manually check for updates and -// `show the user a modal notification. +// The Electron auto-updater does not support Linux yet, so manually check for +// updates and show the user a modal notification. function initLinux () { get.concat(AUTO_UPDATE_URL, onResponse) +} - function onResponse (err, res, data) { - if (err) return log(`Update error: ${err.message}`) - if (res.statusCode === 200) { - // Update available - try { - data = JSON.parse(data) - } catch (err) { - return log(`Update error: Invalid JSON response: ${err.message}`) - } - windows.main.send('dispatch', 'updateAvailable', data.version) - } else if (res.statusCode === 204) { - // No update available - } else { - // Unexpected status code - log(`Update error: Unexpected status code: ${res.statusCode}`) +function onResponse (err, res, data) { + if (err) return log(`Update error: ${err.message}`) + if (res.statusCode === 200) { + // Update available + try { + data = JSON.parse(data) + } catch (err) { + return log(`Update error: Invalid JSON response: ${err.message}`) } + windows.main.dispatch('updateAvailable', data.version) + } else if (res.statusCode === 204) { + // No update available + } else { + // Unexpected status code + log(`Update error: Unexpected status code: ${res.statusCode}`) } } diff --git a/main/windows/about.js b/main/windows/about.js index 31e27f69..7fba55d1 100644 --- a/main/windows/about.js +++ b/main/windows/about.js @@ -1,12 +1,12 @@ var about = module.exports = { - create, + init, win: null } var config = require('../../config') var electron = require('electron') -function create () { +function init () { if (about.win) { return about.win.show() } diff --git a/main/windows/main.js b/main/windows/main.js index c60251e5..f0d7a4ed 100644 --- a/main/windows/main.js +++ b/main/windows/main.js @@ -1,8 +1,15 @@ var main = module.exports = { - create, + dispatch, hide, + init, send, + setAspectRatio, + setBounds, + setProgress, + setTitle, show, + toggleAlwaysOnTop, + toggleDevTools, toggleFullScreen, win: null } @@ -19,7 +26,7 @@ var tray = require('../tray') var HEADER_HEIGHT = 37 var TORRENT_HEIGHT = 100 -function create () { +function init () { if (main.win) { return main.win.show() } @@ -42,21 +49,30 @@ function create () { if (win.setSheetOffset) win.setSheetOffset(HEADER_HEIGHT) win.webContents.on('dom-ready', function () { - menu.onToggleFullScreen() + menu.onToggleFullScreen(main.win.isFullScreen()) }) win.on('blur', function () { - menu.onWindowHide() - tray.onWindowHide() + menu.onWindowBlur() + tray.onWindowBlur() }) win.on('focus', function () { - menu.onWindowShow() - tray.onWindowShow() + menu.onWindowFocus() + tray.onWindowFocus() }) - win.on('enter-full-screen', () => menu.onToggleFullScreen(true)) - win.on('leave-full-screen', () => menu.onToggleFullScreen(false)) + win.on('enter-full-screen', function () { + menu.onToggleFullScreen(true) + send('fullscreenChanged', true) + win.setMenuBarVisibility(false) + }) + + win.on('leave-full-screen', function () { + menu.onToggleFullScreen(false) + send('fullscreenChanged', false) + win.setMenuBarVisibility(true) + }) win.on('close', function (e) { if (process.platform !== 'darwin' && !tray.hasTray()) { @@ -64,15 +80,115 @@ function create () { } else if (!app.isQuitting) { e.preventDefault() win.hide() - win.send('dispatch', 'backToList') } }) } -function getIconPath () { - return process.platform === 'win32' - ? config.APP_ICON + '.ico' - : config.APP_ICON + '.png' +function dispatch (...args) { + send('dispatch', ...args) +} + +function hide () { + if (!main.win) return + main.win.send('dispatch', 'backToList') + main.win.hide() +} + +function send (...args) { + if (!main.win) return + main.win.send(...args) +} + +/** + * Enforce window aspect ratio. Remove with 0. (OS X) + */ +function setAspectRatio (aspectRatio) { + if (!main.win) return + main.win.setAspectRatio(aspectRatio) +} + +/** + * Change the size of the window. + * TODO: Clean this up? Seems overly complicated. + */ +function setBounds (bounds, maximize) { + // Do nothing in fullscreen + if (!main.win || main.win.isFullScreen()) { + log('setBounds: not setting bounds because we\'re in full screen') + return + } + + // Maximize or minimize, if the second argument is present + var willBeMaximized + if (maximize === true) { + if (!main.win.isMaximized()) { + log('setBounds: maximizing') + main.win.maximize() + } + willBeMaximized = true + } else if (maximize === false) { + if (main.win.isMaximized()) { + log('setBounds: unmaximizing') + main.win.unmaximize() + } + willBeMaximized = false + } else { + willBeMaximized = main.win.isMaximized() + } + + // Assuming we're not maximized or maximizing, set the window size + if (!willBeMaximized) { + log('setBounds: setting bounds to ' + JSON.stringify(bounds)) + if (bounds.x === null && bounds.y === null) { + // X and Y not specified? By default, center on current screen + var scr = electron.screen.getDisplayMatching(main.win.getBounds()) + bounds.x = Math.round(scr.bounds.x + scr.bounds.width / 2 - bounds.width / 2) + bounds.y = Math.round(scr.bounds.y + scr.bounds.height / 2 - bounds.height / 2) + log('setBounds: centered to ' + JSON.stringify(bounds)) + } + main.win.setBounds(bounds, true) + } else { + log('setBounds: not setting bounds because of window maximization') + } +} + +/** + * Set progress bar to [0, 1]. Indeterminate when > 1. Remove with < 0. + */ +function setProgress (progress) { + if (!main.win) return + main.win.setProgressBar(progress) +} + +function setTitle (title) { + if (!main.win) return + main.win.setTitle(title) +} + +function show () { + if (!main.win) return + main.win.show() +} + +// Sets whether the window should always show on top of other windows +function toggleAlwaysOnTop (flag) { + if (!main.win) return + if (flag == null) { + flag = !main.isAlwaysOnTop() + } + log(`toggleAlwaysOnTop ${flag}`) + main.setAlwaysOnTop(flag) + menu.onToggleAlwaysOnTop(flag) +} + +function toggleDevTools () { + if (!main.win) return + log('toggleDevTools') + if (main.win.webContents.isDevToolsOpened()) { + main.win.webContents.closeDevTools() + } else { + main.win.webContents.openDevTools({ detach: true }) + } } function toggleFullScreen (flag) { @@ -82,29 +198,18 @@ function toggleFullScreen (flag) { if (flag == null) flag = !main.win.isFullScreen() - log('toggleFullScreen %s', flag) + log(`toggleFullScreen ${flag}`) if (flag) { - // Fullscreen behaves oddly unless the aspect ratio is disabled. (OS X) + // Fullscreen and aspect ratio do not play well together. (OS X) main.win.setAspectRatio(0) } main.win.setFullScreen(flag) } -function send (...args) { - if (!main.win) return - main.win.send(...args) -} - -function show () { - if (!main.win) return - main.win.show() -} - -function hide () { - if (!main.win) return - main.win.hide() -} - +function getIconPath () { + return process.platform === 'win32' + ? config.APP_ICON + '.ico' + : config.APP_ICON + '.png' } diff --git a/main/windows/webtorrent.js b/main/windows/webtorrent.js index 7d6b66f4..510fa1b5 100644 --- a/main/windows/webtorrent.js +++ b/main/windows/webtorrent.js @@ -1,14 +1,17 @@ var webtorrent = module.exports = { - create, + init, send, show, + toggleDevTools, win: null } -var config = require('../../config') var electron = require('electron') -function create () { +var config = require('../../config') +var log = require('../log') + +function init () { var win = webtorrent.win = new electron.BrowserWindow({ backgroundColor: '#1E1E1E', center: true, @@ -46,3 +49,14 @@ function send (...args) { if (!webtorrent.win) return webtorrent.win.send(...args) } + +function toggleDevTools () { + if (!webtorrent.win) return + log('toggleDevTools') + if (webtorrent.win.webContents.isDevToolsOpened()) { + webtorrent.win.webContents.closeDevTools() + webtorrent.win.hide() + } else { + webtorrent.win.webContents.openDevTools({ detach: true }) + } +} diff --git a/renderer/main.js b/renderer/main.js index 3e7c4b53..bf762730 100644 --- a/renderer/main.js +++ b/renderer/main.js @@ -224,12 +224,16 @@ function dispatch (action, ...args) { if (action === 'addTorrent') { addTorrent(args[0] /* torrent */) } - if (action === 'showOpenTorrentFile') { - ipcRenderer.send('showOpenTorrentFile') /* open torrent file */ + if (action === 'openTorrentFile') { + ipcRenderer.send('openTorrentFile') /* open torrent file */ } if (action === 'showCreateTorrent') { showCreateTorrent(args[0] /* paths */) } + if (action === 'openTorrentAddress') { + state.modal = { id: 'open-torrent-address-modal' } + update() + } if (action === 'createTorrent') { createTorrent(args[0] /* options */) } @@ -511,11 +515,6 @@ function setupIpc () { ipcRenderer.on('dispatch', (e, ...args) => dispatch(...args)) - ipcRenderer.on('showOpenTorrentAddress', function (e) { - state.modal = { id: 'open-torrent-address-modal' } - update() - }) - ipcRenderer.on('fullscreenChanged', function (e, isFullScreen) { state.window.isFullScreen = isFullScreen if (!isFullScreen) { diff --git a/renderer/views/header.js b/renderer/views/header.js index 67e74114..31e36bb6 100644 --- a/renderer/views/header.js +++ b/renderer/views/header.js @@ -39,7 +39,7 @@ function Header (state) { + onclick=${dispatcher('openTorrentFile')}> add `