diff --git a/src/app.js b/src/app.js index 04a60ee1..f3479e8e 100644 --- a/src/app.js +++ b/src/app.js @@ -3,13 +3,15 @@ import './stylesheets/main.css'; import './helpers/context_menu.js'; import './helpers/external_links.js'; -const state = { - loaded: false -}; - import { remote, shell } from 'electron'; import url from 'url'; import jetpack from 'fs-jetpack'; +import { IS_MAC } from './constants'; + +const state = { + loaded: false, + unreadNotificationCount: 0 +}; const app = remote.app; const appDir = jetpack.cwd(app.getAppPath()); @@ -17,24 +19,46 @@ const appDir = jetpack.cwd(app.getAppPath()); // TODO: Insert or update webview here instead of in the HTML file to make testing (swapping URLs) easier androidMessagesWebview.addEventListener('did-start-loading', () => { + // Intercept request for notifications and accept it androidMessagesWebview.getWebContents().session.setPermissionRequestHandler((webContents, permission, callback) => { const url = webContents.getURL() - if (permission === 'notifications') { + /* + * We always get a "notification" in dev mode when the app starts due to calling setPermissionRequestHandler, + * which accepts the permission to send browser notifications on behalf of the user--this false + * notification should not result in an indicator for the user to see. + * TODO: Figure out a way to modify and override notifications to solve this and other issues. + */ + if (IS_MAC) { + if (app.mainWindow && !(app.mainWindow.isFocused())) { + state.unreadNotificationCount += 1; + app.dock.setBadge('' + state.unreadNotificationCount); + } + } + + // TODO: Provide visual indicators for Windows/Linux, possibly via mainWindow.setOverlayIcon + return callback(true); // Approve } - // if (!url.startsWith('https://my-website.com')) { - // return callback(false) // Deny - // } + if (!url.startsWith('https://messages.android.com')) { + return callback(false); // Deny + } }); + + if (IS_MAC && app.mainWindow) { + app.mainWindow.on('focus', () => { + state.unreadNotificationCount = 0; + app.dock.setBadge(''); + }) + } }); androidMessagesWebview.addEventListener('did-finish-load', () => { // just before onLoad console.log('finished loading'); - + }); androidMessagesWebview.addEventListener('did-stop-loading', () => { // coincident with onLoad, can fire multiple times @@ -43,7 +67,7 @@ androidMessagesWebview.addEventListener('did-stop-loading', () => { // coinciden state.loaded = true; loader.classList.add('hidden'); } - + }); androidMessagesWebview.addEventListener('dom-ready', () => { diff --git a/src/background.js b/src/background.js index 584d74dc..d96e4481 100644 --- a/src/background.js +++ b/src/background.js @@ -5,64 +5,142 @@ import path from 'path'; import url from 'url'; -import { app, Menu } from 'electron'; +import { app, Menu, Tray } from 'electron'; import { autoUpdater } from 'electron-updater'; import { baseMenuTemplate } from './menu/base_menu_template'; import { devMenuTemplate } from './menu/dev_menu_template'; import { helpMenuTemplate } from './menu/help_menu_template'; import createWindow from './helpers/window'; -import { IS_WINDOWS } from './constants'; +import { IS_MAC, IS_WINDOWS, IS_LINUX, IS_DEV } from './constants'; // Special module holding environment variables which you declared // in config/env_xxx.json file. import env from 'env'; -const setApplicationMenu = () => { - const menus = baseMenuTemplate; +let mainWindow = null; + +// Prevent multiple instances of the app which causes many problems with an app like ours +// Without this, if an instance were minimized to the tray in Windows, clicking a shortcut would launch another instance, icky +// Adapted from https://github.com/electron/electron/blob/v2.0.2/docs/api/app.md#appmakesingleinstancecallback +const isSecondInstance = app.makeSingleInstance((commandLine, workingDirectory) => { + // Someone tried to run a second instance, let's show our existing instance instead + if (mainWindow) { + if (!mainWindow.isVisible()) { + mainWindow.show(); + } + } +}); + +if (isSecondInstance) { + app.quit() +} else { + let tray; // Must declare reference to instance of Tray as a variable, not a const, or bad/weird things happen + + const setApplicationMenu = () => { + const menus = baseMenuTemplate; + if (env.name !== 'production') { + menus.push(devMenuTemplate); + } + menus.push(helpMenuTemplate); + Menu.setApplicationMenu(Menu.buildFromTemplate(menus)); + }; + + // Save userData in separate folders for each environment. + // Thanks to this you can use production and development versions of the app + // on same machine like those are two separate apps. if (env.name !== 'production') { - menus.push(devMenuTemplate); + const userDataPath = app.getPath('userData'); + app.setPath('userData', `${userDataPath} (${env.name})`); } - menus.push(helpMenuTemplate); - Menu.setApplicationMenu(Menu.buildFromTemplate(menus)); -}; - -// Save userData in separate folders for each environment. -// Thanks to this you can use production and development versions of the app -// on same machine like those are two separate apps. -if (env.name !== 'production') { - const userDataPath = app.getPath('userData'); - app.setPath('userData', `${userDataPath} (${env.name})`); -} -if (IS_WINDOWS) { - // Stupid, DUMB calls that have to be made to let notifications come through on Windows (only Windows 10?) - // See: https://github.com/electron/electron/issues/10864#issuecomment-382519150 - app.setAppUserModelId('com.knepper.android-messages-desktop'); - app.setAsDefaultProtocolClient('android-messages-desktop'); -} + if (IS_WINDOWS) { + // Stupid, DUMB calls that have to be made to let notifications come through on Windows (only Windows 10?) + // See: https://github.com/electron/electron/issues/10864#issuecomment-382519150 + app.setAppUserModelId('com.knepper.android-messages-desktop'); + app.setAsDefaultProtocolClient('android-messages-desktop'); + } -app.on('ready', () => { - setApplicationMenu(); - autoUpdater.checkForUpdatesAndNotify(); + app.on('ready', () => { + setApplicationMenu(); + autoUpdater.checkForUpdatesAndNotify(); - const mainWindow = createWindow('main', { - width: 1100, - height: 800 - }); + mainWindow = createWindow('main', { + width: 1100, + height: 800 + }); - mainWindow.loadURL( - url.format({ - pathname: path.join(__dirname, 'app.html'), - protocol: 'file:', - slashes: true - }) - ); + mainWindow.loadURL( + url.format({ + pathname: path.join(__dirname, 'app.html'), + protocol: 'file:', + slashes: true + }) + ); - if (env.name === 'development') { - mainWindow.openDevTools(); - } -}); + app.mainWindow = mainWindow; // Quick and dirty way for renderer process to access mainWindow for communication -app.on('window-all-closed', () => { - app.quit(); -}); + if (IS_MAC) { + let quitViaContext = false; + app.on('before-quit', () => { + quitViaContext = true; + }); + + mainWindow.on('close', (event) => { + if (!quitViaContext) { + event.preventDefault(); + mainWindow.hide(); + } + }); + + app.on('activate', () => { + mainWindow.show(); + }); + } + + if (IS_WINDOWS) { + mainWindow.on('close', (event) => { + app.quit(); + }); + + tray = new Tray(__dirname + '../../resources/icon.ico'); + + let contextMenu = Menu.buildFromTemplate([ + { + label: 'Show', + click: () => { + mainWindow.show(); + } + }, + { + label: 'Quit', + click: () => { + app.quit(); + } + } + ]); + + tray.setContextMenu(contextMenu); + + tray.on('double-click', (event) => { + event.preventDefault(); + mainWindow.show(); + }); + + mainWindow.on('minimize', (event) => { + event.preventDefault(); + mainWindow.hide(); + }); + } + + // TODO: Better UX for Linux...likely similar to Windows as far as tray behavior + if (IS_LINUX) { + app.on('window-all-closed', (event) => { + app.quit(); + }); + } + + if (IS_DEV) { + mainWindow.openDevTools(); + } + }); +} diff --git a/src/menu/app_menu_template.js b/src/menu/app_menu_template.js index 7cd2b8d7..5d5a6e30 100644 --- a/src/menu/app_menu_template.js +++ b/src/menu/app_menu_template.js @@ -12,6 +12,14 @@ export const appMenuTemplate = { { type: 'separator', }, + { + label: 'Hide Android Messages Desktop', + accelerator: 'Command+H', + click: () => app.mainWindow && app.mainWindow.hide() + }, + { + type: 'separator', + }, { label: 'Quit', accelerator: 'Command+Q',