Skip to content

Commit 2e9904c

Browse files
authored
Merge pull request #25 from chrisknepper/feature/mac-notification-badges-and-windows-tray-support
Feature/mac notification badges and windows tray support
2 parents 1980680 + 810dc5b commit 2e9904c

File tree

3 files changed

+163
-53
lines changed

3 files changed

+163
-53
lines changed

src/app.js

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,62 @@ import './stylesheets/main.css';
33
import './helpers/context_menu.js';
44
import './helpers/external_links.js';
55

6-
const state = {
7-
loaded: false
8-
};
9-
106
import { remote, shell } from 'electron';
117
import url from 'url';
128
import jetpack from 'fs-jetpack';
9+
import { IS_MAC } from './constants';
10+
11+
const state = {
12+
loaded: false,
13+
unreadNotificationCount: 0
14+
};
1315

1416
const app = remote.app;
1517
const appDir = jetpack.cwd(app.getAppPath());
1618

1719
// TODO: Insert or update webview here instead of in the HTML file to make testing (swapping URLs) easier
1820

1921
androidMessagesWebview.addEventListener('did-start-loading', () => {
22+
2023
// Intercept request for notifications and accept it
2124
androidMessagesWebview.getWebContents().session.setPermissionRequestHandler((webContents, permission, callback) => {
2225
const url = webContents.getURL()
23-
2426

2527
if (permission === 'notifications') {
28+
/*
29+
* We always get a "notification" in dev mode when the app starts due to calling setPermissionRequestHandler,
30+
* which accepts the permission to send browser notifications on behalf of the user--this false
31+
* notification should not result in an indicator for the user to see.
32+
* TODO: Figure out a way to modify and override notifications to solve this and other issues.
33+
*/
34+
if (IS_MAC) {
35+
if (app.mainWindow && !(app.mainWindow.isFocused())) {
36+
state.unreadNotificationCount += 1;
37+
app.dock.setBadge('' + state.unreadNotificationCount);
38+
}
39+
}
40+
41+
// TODO: Provide visual indicators for Windows/Linux, possibly via mainWindow.setOverlayIcon
42+
2643
return callback(true); // Approve
2744
}
2845

29-
// if (!url.startsWith('https://my-website.com')) {
30-
// return callback(false) // Deny
31-
// }
46+
if (!url.startsWith('https://messages.android.com')) {
47+
return callback(false); // Deny
48+
}
3249
});
50+
51+
if (IS_MAC && app.mainWindow) {
52+
app.mainWindow.on('focus', () => {
53+
state.unreadNotificationCount = 0;
54+
app.dock.setBadge('');
55+
})
56+
}
3357
});
3458

3559
androidMessagesWebview.addEventListener('did-finish-load', () => { // just before onLoad
3660
console.log('finished loading');
37-
61+
3862
});
3963

4064
androidMessagesWebview.addEventListener('did-stop-loading', () => { // coincident with onLoad, can fire multiple times
@@ -43,7 +67,7 @@ androidMessagesWebview.addEventListener('did-stop-loading', () => { // coinciden
4367
state.loaded = true;
4468
loader.classList.add('hidden');
4569
}
46-
70+
4771
});
4872

4973
androidMessagesWebview.addEventListener('dom-ready', () => {

src/background.js

Lines changed: 121 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,64 +5,142 @@
55

66
import path from 'path';
77
import url from 'url';
8-
import { app, Menu } from 'electron';
8+
import { app, Menu, Tray } from 'electron';
99
import { autoUpdater } from 'electron-updater';
1010
import { baseMenuTemplate } from './menu/base_menu_template';
1111
import { devMenuTemplate } from './menu/dev_menu_template';
1212
import { helpMenuTemplate } from './menu/help_menu_template';
1313
import createWindow from './helpers/window';
14-
import { IS_WINDOWS } from './constants';
14+
import { IS_MAC, IS_WINDOWS, IS_LINUX, IS_DEV } from './constants';
1515

1616
// Special module holding environment variables which you declared
1717
// in config/env_xxx.json file.
1818
import env from 'env';
1919

20-
const setApplicationMenu = () => {
21-
const menus = baseMenuTemplate;
20+
let mainWindow = null;
21+
22+
// Prevent multiple instances of the app which causes many problems with an app like ours
23+
// Without this, if an instance were minimized to the tray in Windows, clicking a shortcut would launch another instance, icky
24+
// Adapted from https://github.com/electron/electron/blob/v2.0.2/docs/api/app.md#appmakesingleinstancecallback
25+
const isSecondInstance = app.makeSingleInstance((commandLine, workingDirectory) => {
26+
// Someone tried to run a second instance, let's show our existing instance instead
27+
if (mainWindow) {
28+
if (!mainWindow.isVisible()) {
29+
mainWindow.show();
30+
}
31+
}
32+
});
33+
34+
if (isSecondInstance) {
35+
app.quit()
36+
} else {
37+
let tray; // Must declare reference to instance of Tray as a variable, not a const, or bad/weird things happen
38+
39+
const setApplicationMenu = () => {
40+
const menus = baseMenuTemplate;
41+
if (env.name !== 'production') {
42+
menus.push(devMenuTemplate);
43+
}
44+
menus.push(helpMenuTemplate);
45+
Menu.setApplicationMenu(Menu.buildFromTemplate(menus));
46+
};
47+
48+
// Save userData in separate folders for each environment.
49+
// Thanks to this you can use production and development versions of the app
50+
// on same machine like those are two separate apps.
2251
if (env.name !== 'production') {
23-
menus.push(devMenuTemplate);
52+
const userDataPath = app.getPath('userData');
53+
app.setPath('userData', `${userDataPath} (${env.name})`);
2454
}
25-
menus.push(helpMenuTemplate);
26-
Menu.setApplicationMenu(Menu.buildFromTemplate(menus));
27-
};
28-
29-
// Save userData in separate folders for each environment.
30-
// Thanks to this you can use production and development versions of the app
31-
// on same machine like those are two separate apps.
32-
if (env.name !== 'production') {
33-
const userDataPath = app.getPath('userData');
34-
app.setPath('userData', `${userDataPath} (${env.name})`);
35-
}
3655

37-
if (IS_WINDOWS) {
38-
// Stupid, DUMB calls that have to be made to let notifications come through on Windows (only Windows 10?)
39-
// See: https://github.com/electron/electron/issues/10864#issuecomment-382519150
40-
app.setAppUserModelId('com.knepper.android-messages-desktop');
41-
app.setAsDefaultProtocolClient('android-messages-desktop');
42-
}
56+
if (IS_WINDOWS) {
57+
// Stupid, DUMB calls that have to be made to let notifications come through on Windows (only Windows 10?)
58+
// See: https://github.com/electron/electron/issues/10864#issuecomment-382519150
59+
app.setAppUserModelId('com.knepper.android-messages-desktop');
60+
app.setAsDefaultProtocolClient('android-messages-desktop');
61+
}
4362

44-
app.on('ready', () => {
45-
setApplicationMenu();
46-
autoUpdater.checkForUpdatesAndNotify();
63+
app.on('ready', () => {
64+
setApplicationMenu();
65+
autoUpdater.checkForUpdatesAndNotify();
4766

48-
const mainWindow = createWindow('main', {
49-
width: 1100,
50-
height: 800
51-
});
67+
mainWindow = createWindow('main', {
68+
width: 1100,
69+
height: 800
70+
});
5271

53-
mainWindow.loadURL(
54-
url.format({
55-
pathname: path.join(__dirname, 'app.html'),
56-
protocol: 'file:',
57-
slashes: true
58-
})
59-
);
72+
mainWindow.loadURL(
73+
url.format({
74+
pathname: path.join(__dirname, 'app.html'),
75+
protocol: 'file:',
76+
slashes: true
77+
})
78+
);
6079

61-
if (env.name === 'development') {
62-
mainWindow.openDevTools();
63-
}
64-
});
80+
app.mainWindow = mainWindow; // Quick and dirty way for renderer process to access mainWindow for communication
6581

66-
app.on('window-all-closed', () => {
67-
app.quit();
68-
});
82+
if (IS_MAC) {
83+
let quitViaContext = false;
84+
app.on('before-quit', () => {
85+
quitViaContext = true;
86+
});
87+
88+
mainWindow.on('close', (event) => {
89+
if (!quitViaContext) {
90+
event.preventDefault();
91+
mainWindow.hide();
92+
}
93+
});
94+
95+
app.on('activate', () => {
96+
mainWindow.show();
97+
});
98+
}
99+
100+
if (IS_WINDOWS) {
101+
mainWindow.on('close', (event) => {
102+
app.quit();
103+
});
104+
105+
tray = new Tray(__dirname + '../../resources/icon.ico');
106+
107+
let contextMenu = Menu.buildFromTemplate([
108+
{
109+
label: 'Show',
110+
click: () => {
111+
mainWindow.show();
112+
}
113+
},
114+
{
115+
label: 'Quit',
116+
click: () => {
117+
app.quit();
118+
}
119+
}
120+
]);
121+
122+
tray.setContextMenu(contextMenu);
123+
124+
tray.on('double-click', (event) => {
125+
event.preventDefault();
126+
mainWindow.show();
127+
});
128+
129+
mainWindow.on('minimize', (event) => {
130+
event.preventDefault();
131+
mainWindow.hide();
132+
});
133+
}
134+
135+
// TODO: Better UX for Linux...likely similar to Windows as far as tray behavior
136+
if (IS_LINUX) {
137+
app.on('window-all-closed', (event) => {
138+
app.quit();
139+
});
140+
}
141+
142+
if (IS_DEV) {
143+
mainWindow.openDevTools();
144+
}
145+
});
146+
}

src/menu/app_menu_template.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ export const appMenuTemplate = {
1212
{
1313
type: 'separator',
1414
},
15+
{
16+
label: 'Hide Android Messages Desktop',
17+
accelerator: 'Command+H',
18+
click: () => app.mainWindow && app.mainWindow.hide()
19+
},
20+
{
21+
type: 'separator',
22+
},
1523
{
1624
label: 'Quit',
1725
accelerator: 'Command+Q',

0 commit comments

Comments
 (0)