diff --git a/CHANGELOG.md b/CHANGELOG.md index c765e1cea..b43b8f0a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Web UI version numbers should always match the corresponding version of LBRY App ## [Unreleased] ### Added + * The app now closes to the system tray unless specifically requested to quit. (#374) * Added new window menu options for reloading and help. * Rewards are now marked in transaction history (#660) * diff --git a/app/main.js b/app/main.js index c11468b8f..0774c081a 100644 --- a/app/main.js +++ b/app/main.js @@ -1,4 +1,5 @@ -const {app, BrowserWindow, ipcMain} = require('electron'); +module.exports = { safeQuit } +const {app, BrowserWindow, ipcMain, Menu, Tray, globalShortcut} = require('electron'); const url = require('url'); const isDebug = process.env.NODE_ENV === 'development'; const setMenu = require('./menu/main-menu.js'); @@ -55,6 +56,13 @@ let readyToQuit = false; // sendCredits it to, it's cached in this variable. let openUri = null; +// Set this to true to minimize on clicking close +// false for normal action +let minimize = true; + +// Keep the tray also, it is getting GC'd if put in createTray() +let tray = null; + function processRequestedUri(uri) { // Windows normalizes URIs when they're passed in from other apps. On Windows, // this function tries to restore the original URI that was typed. @@ -167,10 +175,47 @@ function createWindow () { }); } + win.removeAllListeners(); + + win.on('close', function(event) { + if (minimize) { + event.preventDefault(); + win.hide(); + } + }) + win.on('closed', () => { win = null }) + win.on("hide", () => { + // Checks what to show in the tray icon menu + if (minimize) updateTray(); + }); + + win.on("show", () => { + // Checks what to show in the tray icon menu + if (minimize) updateTray(); + }); + + win.on("blur", () => { + // Checks what to show in the tray icon menu + if (minimize) updateTray(); + + // Unregisters Alt+F4 shortcut + globalShortcut.unregister('Alt+F4'); + }); + + win.on("focus", () => { + // Checks what to show in the tray icon menu + if (minimize) updateTray(); + + // Registers shortcut for closing(quitting) the app + globalShortcut.register('Alt+F4', () => safeQuit()); + + win.webContents.send('window-is-focused', null); + }); + // Menu bar win.setAutoHideMenuBar(true); win.setMenuBarVisibility(isDebug); @@ -178,14 +223,65 @@ function createWindow () { }; +function createTray () { + // Minimize to tray logic follows: + // Set the tray icon + const iconPath = path.join(app.getAppPath(), "/dist/img/fav/32x32.png"); + tray = new Tray(iconPath); + tray.setToolTip("LBRY App"); + tray.setTitle("LBRY"); + tray.on('double-click', () => { + win.show() + }) +} + +// This needs to be done as for linux the context menu doesn't update automatically(docs) +function updateTray() { + let contextMenu = Menu.buildFromTemplate(getMenuTemplate()); + if (tray) { + tray.setContextMenu(contextMenu); + } else { + console.log("How did update tray get called without a tray?"); + } +} + +function getMenuTemplate () { + return [ + getToggleItem(), + { + label: "Quit", + click: () => safeQuit(), + }, + ] + + function getToggleItem () { + if (win.isVisible() && win.isFocused()) { + return { + label: 'Hide LBRY App', + click: () => win.hide() + + } + } else { + return { + label: 'Show LBRY App', + click: () => win.show() + } + } + } +} + function handleOpenUriRequested(uri) { if (!win) { // Window not created yet, so store up requested URI for when it is openUri = processRequestedUri(uri); } else { + if (win.isMinimized()) { - win.restore(); + win.restore() + } else if (!win.isVisible()) { + win.show() } + win.focus(); win.webContents.send('open-uri-requested', processRequestedUri(uri)); } @@ -224,6 +320,15 @@ function launchDaemon() { daemonSubprocess.on('exit', handleDaemonSubprocessExited); } + +/* + * Quits by first killing the daemon, the calling quitting for real. + */ +function safeQuit() { + minimize = false; + app.quit(); +} + /* * Quits without any preparation. When a quit is requested (either through the * interface or through app.quit()), we abort the quit, try to shut down the daemon, @@ -231,7 +336,7 @@ function launchDaemon() { */ function quitNow() { readyToQuit = true; - app.quit(); + safeQuit(); } const isSecondaryInstance = app.makeSingleInstance((argv) => { @@ -240,6 +345,8 @@ const isSecondaryInstance = app.makeSingleInstance((argv) => { } else if (win) { if (win.isMinimized()) { win.restore(); + } else if (!win.isVisible()) { + win.show(); } win.focus(); } @@ -252,6 +359,14 @@ if (isSecondaryInstance) { // We're not in the original process, so quit app.on('ready', function(){ launchDaemonIfNotRunning(); + if (process.platform === "linux") { + checkLinuxTraySupport( err => { + if (!err) createTray(); + else minimize = false; + }) + } else { + createTray(); + } createWindow(); }); @@ -385,6 +500,22 @@ function upgrade(event, installerPath) { console.log('After the install is complete, please reopen the app.'); } +// Taken from webtorrent-desktop +function checkLinuxTraySupport (cb) { + // Check that we're on Ubuntu (or another debian system) and that we have + // libappindicator1. + child_process.exec('dpkg --get-selections libappindicator1', function (err, stdout) { + if (err) return cb(err) + // Unfortunately there's no cleaner way, as far as I can tell, to check + // whether a debian package is installed: + if (stdout.endsWith('\tinstall\n')) { + cb(null) + } else { + cb(new Error('debian package not installed')) + } + }) +} + ipcMain.on('upgrade', upgrade); app.setAsDefaultProtocolClient('lbry'); diff --git a/app/menu/main-menu.js b/app/menu/main-menu.js index 7b214bfca..93251992e 100644 --- a/app/menu/main-menu.js +++ b/app/menu/main-menu.js @@ -1,6 +1,18 @@ const { app, shell, Menu } = require('electron'); +const { safeQuit } = require('../main.js') + const baseTemplate = [ + { + label: 'File', + submenu: [ + { + label: 'Quit', + accelerator: "CommandOrControl+Q", + click: () => safeQuit(), + }, + ] + }, { label: 'Edit', submenu: [ diff --git a/ui/dist/img/fav/32x32.png b/ui/dist/img/fav/32x32.png new file mode 100644 index 000000000..ece10998c Binary files /dev/null and b/ui/dist/img/fav/32x32.png differ diff --git a/ui/js/main.js b/ui/js/main.js index de18d0ee1..b074f4b6d 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -37,6 +37,14 @@ ipcRenderer.on("open-menu", (event, uri) => { } }); +const dock = remote.app.dock; + +ipcRenderer.on("window-is-focused", (event, data) => { + if (!dock) return; + app.store.dispatch({ type: types.WINDOW_FOCUSED }); + dock.setBadge(""); +}); + document.addEventListener("click", event => { var target = event.target; while (target && target !== document) { @@ -52,21 +60,6 @@ document.addEventListener("click", event => { } }); -const application = remote.app; -const dock = application.dock; -const win = remote.getCurrentWindow(); - -// Tear down previous event listeners when reload -win.removeAllListeners(); - -// Clear the badge when the window is focused -win.on("focus", () => { - if (!dock) return; - - app.store.dispatch({ type: types.WINDOW_FOCUSED }); - dock.setBadge(""); -}); - const initialState = app.store.getState(); var init = function() { diff --git a/yarn.lock b/yarn.lock index 40bbafce5..3119be846 100644 --- a/yarn.lock +++ b/yarn.lock @@ -311,13 +311,13 @@ debug@2.2.0: dependencies: ms "0.7.1" -debug@2.6.0, debug@^2.3.2, debug@^2.6.0: +debug@2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" dependencies: ms "0.7.2" -debug@^2.1.3, debug@^2.2.0, debug@^2.6.8: +debug@^2.1.3, debug@^2.2.0, debug@^2.3.2, debug@^2.6.0, debug@^2.6.8: version "2.6.8" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" dependencies: